Fix copying text across tables in RichTextLabel

Previously, using `\r` instead of `\n` for line breaks in tables (e.g.,
in class references) could cause issues when copying text across tables.
This commit is contained in:
风青山 2026-02-11 00:41:39 +08:00
parent 92a90a8e6f
commit f9c31d3483
No known key found for this signature in database
GPG key ID: 8DFDAA198ED5EDF5
2 changed files with 45 additions and 22 deletions

View file

@ -6915,38 +6915,41 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p
}
}
String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const {
String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line) const {
String txt;
ERR_FAIL_NULL_V(p_frame, txt);
ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), txt);
int start = 0; // Unless the current line is the `from` line.
if (!selection.from_line_found && p_frame == selection.from_frame) {
if (p_line < selection.from_line) {
return txt; // Skip the lines with smaller line numbers in the same `from` frame.
}
selection.from_line_found = true;
start = selection.from_char;
}
const bool from_this = selection.from_line_found; // Used to detect cases starting from a sub-frame.
Line &l = p_frame->lines[p_line];
Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
int end_idx = 0;
if (it_to != nullptr) {
end_idx = it_to->index;
} else {
for (Item *it = l.from; it; it = _get_next_item(it)) {
end_idx = it->index + 1;
}
}
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
for (Item *it = l.from; !selection.to_line_found && it && it != it_to; it = _get_next_item(it)) {
if (it->type == ITEM_TABLE) {
ItemTable *table = static_cast<ItemTable *>(it);
for (Item *E : table->subitems) {
ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E);
for (int i = 0; i < (int)frame->lines.size(); i++) {
txt += _get_line_text(frame, i, p_selection);
for (int i = 0; !selection.to_line_found && i < (int)frame->lines.size(); i++) {
txt += _get_line_text(frame, i);
}
if (selection.to_line_found) {
break;
}
}
}
if ((p_selection.to_item != nullptr) && (p_selection.to_item->index < l.from->index)) {
continue;
}
if ((p_selection.from_item != nullptr) && (p_selection.from_item->index >= end_idx)) {
if (!selection.from_line_found) {
continue;
}
if (it->type == ITEM_DROPCAP) {
@ -6961,12 +6964,26 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p
txt += " ";
}
}
if ((l.from != nullptr) && (p_frame == p_selection.to_frame) && (p_selection.to_item != nullptr) && (p_selection.to_item->index >= l.from->index) && (p_selection.to_item->index < end_idx)) {
txt = txt.substr(0, p_selection.to_char);
if (!selection.from_line_found) {
return txt; // Empty.
}
if ((l.from != nullptr) && (p_frame == p_selection.from_frame) && (p_selection.from_item != nullptr) && (p_selection.from_item->index >= l.from->index) && (p_selection.from_item->index < end_idx)) {
txt = txt.substr(p_selection.from_char);
int chars = -1; // Unless the current line is the `to` line.
if (p_frame == selection.to_frame && p_line == selection.to_line) {
selection.to_line_found = true;
if (from_this ^ selection.from_line_found) {
// For cases text from child frame to parent frame is considered to be on the same line,
// even if the line numbers are different. `start` has been reset to 0.
chars = selection.to_char - selection.from_char;
} else {
chars = selection.to_char - start;
}
}
txt = txt.substr(start, chars);
return txt;
}
@ -7005,8 +7022,12 @@ String RichTextLabel::get_selected_text() const {
String txt;
int to_line = main->first_invalid_line.load();
for (int i = 0; i < to_line; i++) {
txt += _get_line_text(main, i, selection);
selection.from_line_found = false;
selection.to_line_found = false;
for (int i = 0; !selection.to_line_found && i < to_line; i++) {
txt += _get_line_text(main, i);
}
if (selection_modifier.is_valid()) {

View file

@ -625,11 +625,13 @@ private:
int from_line = 0;
Item *from_item = nullptr;
int from_char = 0;
mutable bool from_line_found = false;
ItemFrame *to_frame = nullptr;
int to_line = 0;
Item *to_item = nullptr;
int to_char = 0;
mutable bool to_line_found = false;
bool double_click = false; // Selecting whole words?
bool active = false; // anything selected? i.e. from, to, etc. valid?
@ -663,7 +665,7 @@ private:
bool _is_click_inside_selection() const;
void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr, bool p_meta = false);
String _get_line_text(ItemFrame *p_frame, int p_line, Selection p_sel) const;
String _get_line_text(ItemFrame *p_frame, int p_line) const;
bool _search_table_cell(ItemTable *p_table, List<Item *>::Element *p_cell, const String &p_string, bool p_reverse_search, int p_from_line);
bool _search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search);
bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search);