diff options
-rw-r--r-- | Applications/TextEditor/main.cpp | 5 | ||||
-rw-r--r-- | LibGUI/GTextEditor.cpp | 107 | ||||
-rw-r--r-- | LibGUI/GTextEditor.h | 34 |
3 files changed, 95 insertions, 51 deletions
diff --git a/Applications/TextEditor/main.cpp b/Applications/TextEditor/main.cpp index 3ee579032c..b2381a8e48 100644 --- a/Applications/TextEditor/main.cpp +++ b/Applications/TextEditor/main.cpp @@ -29,8 +29,9 @@ int main(int argc, char** argv) text_editor->on_cursor_change = [statusbar] (GTextEditor& editor) { StringBuilder builder; builder.appendf("Line: %d, Column: %d", editor.cursor().line(), editor.cursor().column()); - if (editor.selection_start().is_valid()) { - builder.appendf(" Selection: [%d,%d]-[%d,%d]", editor.selection_start().line(), editor.selection_start().column(), editor.cursor().line(), editor.cursor().column()); + auto selection = editor.normalized_selection(); + if (selection.is_valid()) { + builder.appendf(" Selection: [%d,%d]-[%d,%d]", selection.start().line(), selection.start().column(), selection.end().line(), selection.end().column()); } statusbar->set_text(builder.to_string()); }; diff --git a/LibGUI/GTextEditor.cpp b/LibGUI/GTextEditor.cpp index f9eaffbb67..334d63abb9 100644 --- a/LibGUI/GTextEditor.cpp +++ b/LibGUI/GTextEditor.cpp @@ -110,9 +110,9 @@ void GTextEditor::mousedown_event(GMouseEvent& event) if (event.button() == GMouseButton::Left) { if (event.modifiers() & Mod_Shift) { if (!has_selection()) - m_selection_start = m_cursor; + m_selection.set(m_cursor, { }); } else { - m_selection_start = { }; + m_selection.clear(); } m_in_drag_select = true; @@ -122,9 +122,12 @@ void GTextEditor::mousedown_event(GMouseEvent& event) if (!(event.modifiers() & Mod_Shift)) { if (!has_selection()) - m_selection_start = m_cursor; + m_selection.set(m_cursor, { }); } + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); + // FIXME: Only update the relevant rects. update(); return; @@ -146,6 +149,7 @@ void GTextEditor::mousemove_event(GMouseEvent& event) { if (m_in_drag_select) { set_cursor(text_position_at(event.position())); + m_selection.set_end(m_cursor); update(); return; } @@ -184,11 +188,8 @@ void GTextEditor::paint_event(GPaintEvent& event) int first_visible_line = text_position_at(event.rect().top_left()).line(); int last_visible_line = text_position_at(event.rect().bottom_right()).line(); - auto normalized_selection_start = m_selection_start; - auto normalized_selection_end = m_cursor; - if (m_cursor < m_selection_start) - swap(normalized_selection_start, normalized_selection_end); - bool has_selection = m_selection_start.is_valid(); + auto selection = normalized_selection(); + bool has_selection = selection.is_valid(); painter.set_font(Font::default_font()); for (int i = first_visible_line; i <= last_visible_line; ++i) { @@ -214,10 +215,10 @@ void GTextEditor::paint_event(GPaintEvent& event) if (i == m_cursor.line()) painter.fill_rect(line_rect, Color(230, 230, 230)); painter.draw_text(line_rect, line.characters(), line.length(), TextAlignment::CenterLeft, Color::Black); - bool line_has_selection = has_selection && i >= normalized_selection_start.line() && i <= normalized_selection_end.line(); + bool line_has_selection = has_selection && i >= selection.start().line() && i <= selection.end().line(); if (line_has_selection) { - int selection_start_column_on_line = normalized_selection_start.line() == i ? normalized_selection_start.column() : 0; - int selection_end_column_on_line = normalized_selection_end.line() == i ? normalized_selection_end.column() : line.length(); + int selection_start_column_on_line = selection.start().line() == i ? selection.start().column() : 0; + int selection_end_column_on_line = selection.end().line() == i ? selection.end().column() : line.length(); int selection_left = selection_start_column_on_line * font().glyph_width('x'); int selection_right = line_rect.left() + selection_end_column_on_line * font().glyph_width('x'); Rect selection_rect { selection_left, line_rect.y(), selection_right - selection_left, line_rect.height() }; @@ -244,13 +245,13 @@ void GTextEditor::paint_event(GPaintEvent& event) void GTextEditor::toggle_selection_if_needed_for_event(const GKeyEvent& event) { - if (event.shift() && !m_selection_start.is_valid()) { - m_selection_start = m_cursor; + if (event.shift() && !m_selection.is_valid()) { + m_selection.set(m_cursor, { }); update(); return; } - if (!event.shift() && m_selection_start.is_valid()) { - m_selection_start = { }; + if (!event.shift() && m_selection.is_valid()) { + m_selection.clear(); update(); return; } @@ -264,6 +265,8 @@ void GTextEditor::keydown_event(GKeyEvent& event) int new_column = min(m_cursor.column(), m_lines[new_line]->length()); toggle_selection_if_needed_for_event(event); set_cursor(new_line, new_column); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); } return; } @@ -273,6 +276,8 @@ void GTextEditor::keydown_event(GKeyEvent& event) int new_column = min(m_cursor.column(), m_lines[new_line]->length()); toggle_selection_if_needed_for_event(event); set_cursor(new_line, new_column); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); } return; } @@ -282,6 +287,8 @@ void GTextEditor::keydown_event(GKeyEvent& event) int new_column = min(m_cursor.column(), m_lines[new_line]->length()); toggle_selection_if_needed_for_event(event); set_cursor(new_line, new_column); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); } return; } @@ -291,6 +298,8 @@ void GTextEditor::keydown_event(GKeyEvent& event) int new_column = min(m_cursor.column(), m_lines[new_line]->length()); toggle_selection_if_needed_for_event(event); set_cursor(new_line, new_column); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); } return; } @@ -299,6 +308,8 @@ void GTextEditor::keydown_event(GKeyEvent& event) int new_column = m_cursor.column() - 1; toggle_selection_if_needed_for_event(event); set_cursor(m_cursor.line(), new_column); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); } return; } @@ -307,32 +318,44 @@ void GTextEditor::keydown_event(GKeyEvent& event) int new_column = m_cursor.column() + 1; toggle_selection_if_needed_for_event(event); set_cursor(m_cursor.line(), new_column); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); } return; } if (!event.ctrl() && event.key() == KeyCode::Key_Home) { toggle_selection_if_needed_for_event(event); set_cursor(m_cursor.line(), 0); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); return; } if (!event.ctrl() && event.key() == KeyCode::Key_End) { toggle_selection_if_needed_for_event(event); set_cursor(m_cursor.line(), current_line().length()); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); return; } if (event.ctrl() && event.key() == KeyCode::Key_Home) { toggle_selection_if_needed_for_event(event); set_cursor(0, 0); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); return; } if (event.ctrl() && event.key() == KeyCode::Key_End) { toggle_selection_if_needed_for_event(event); set_cursor(line_count() - 1, m_lines[line_count() - 1]->length()); + if (m_selection.start().is_valid()) + m_selection.set_end(m_cursor); return; } if (event.modifiers() == Mod_Ctrl && event.key() == KeyCode::Key_A) { - m_selection_start = { 0, 0 }; - set_cursor(line_count() - 1, m_lines[line_count() - 1]->length()); + GTextPosition start_of_document { 0, 0 }; + GTextPosition end_of_document { line_count() - 1, m_lines[line_count() - 1]->length() }; + m_selection.set(start_of_document, end_of_document); + set_cursor(end_of_document); update(); return; } @@ -650,19 +673,14 @@ String GTextEditor::selected_text() const if (!has_selection()) return { }; - auto normalized_selection_start = m_selection_start; - auto normalized_selection_end = m_cursor; - if (m_cursor < m_selection_start) - swap(normalized_selection_start, normalized_selection_end); - + auto selection = normalized_selection(); StringBuilder builder; - - for (int i = normalized_selection_start.line(); i <= normalized_selection_end.line(); ++i) { + for (int i = selection.start().line(); i <= selection.end().line(); ++i) { auto& line = *m_lines[i]; - int selection_start_column_on_line = normalized_selection_start.line() == i ? normalized_selection_start.column() : 0; - int selection_end_column_on_line = normalized_selection_end.line() == i ? normalized_selection_end.column() : line.length(); + int selection_start_column_on_line = selection.start().line() == i ? selection.start().column() : 0; + int selection_end_column_on_line = selection.end().line() == i ? selection.end().column() : line.length(); builder.append(line.characters() + selection_start_column_on_line, selection_end_column_on_line - selection_start_column_on_line); - if (i != normalized_selection_end.line()) + if (i != selection.end().line()) builder.append('\n'); } @@ -674,26 +692,23 @@ void GTextEditor::delete_selection() if (!has_selection()) return; - auto normalized_selection_start = m_selection_start; - auto normalized_selection_end = m_cursor; - if (m_cursor < m_selection_start) - swap(normalized_selection_start, normalized_selection_end); + auto selection = normalized_selection(); // First delete all the lines in between the first and last one. - for (int i = normalized_selection_start.line() + 1; i < normalized_selection_end.line();) { + for (int i = selection.start().line() + 1; i < selection.end().line();) { m_lines.remove(i); - normalized_selection_end.set_line(normalized_selection_end.line() - 1); + selection.end().set_line(selection.end().line() - 1); } - if (normalized_selection_start.line() == normalized_selection_end.line()) { + if (selection.start().line() == selection.end().line()) { // Delete within same line. - auto& line = *m_lines[normalized_selection_start.line()]; - bool whole_line_is_selected = normalized_selection_start.column() == 0 && normalized_selection_end.column() == line.length(); + auto& line = *m_lines[selection.start().line()]; + bool whole_line_is_selected = selection.start().column() == 0 && selection.end().column() == line.length(); if (whole_line_is_selected) { line.clear(); } else { - auto before_selection = String(line.characters(), line.length()).substring(0, normalized_selection_start.column()); - auto after_selection = String(line.characters(), line.length()).substring(normalized_selection_end.column(), line.length() - normalized_selection_end.column()); + auto before_selection = String(line.characters(), line.length()).substring(0, selection.start().column()); + auto after_selection = String(line.characters(), line.length()).substring(selection.end().column(), line.length() - selection.end().column()); StringBuilder builder(before_selection.length() + after_selection.length()); builder.append(before_selection); builder.append(after_selection); @@ -701,23 +716,23 @@ void GTextEditor::delete_selection() } } else { // Delete across a newline, merging lines. - ASSERT(normalized_selection_start.line() == normalized_selection_end.line() - 1); - auto& first_line = *m_lines[normalized_selection_start.line()]; - auto& second_line = *m_lines[normalized_selection_end.line()]; - auto before_selection = String(first_line.characters(), first_line.length()).substring(0, normalized_selection_start.column()); - auto after_selection = String(second_line.characters(), second_line.length()).substring(normalized_selection_end.column(), second_line.length() - normalized_selection_end.column()); + ASSERT(selection.start().line() == selection.end().line() - 1); + auto& first_line = *m_lines[selection.start().line()]; + auto& second_line = *m_lines[selection.end().line()]; + auto before_selection = String(first_line.characters(), first_line.length()).substring(0, selection.start().column()); + auto after_selection = String(second_line.characters(), second_line.length()).substring(selection.end().column(), second_line.length() - selection.end().column()); StringBuilder builder(before_selection.length() + after_selection.length()); builder.append(before_selection); builder.append(after_selection); first_line.set_text(builder.to_string()); - m_lines.remove(normalized_selection_end.line()); + m_lines.remove(selection.end().line()); } if (m_lines.is_empty()) m_lines.append(make<Line>()); - m_selection_start = { }; - set_cursor(normalized_selection_start); + m_selection.clear(); + set_cursor(selection.start()); update(); } diff --git a/LibGUI/GTextEditor.h b/LibGUI/GTextEditor.h index 4fd230822a..0d0bda091e 100644 --- a/LibGUI/GTextEditor.h +++ b/LibGUI/GTextEditor.h @@ -32,6 +32,34 @@ private: int m_column { -1 }; }; +class GTextRange { +public: + GTextRange() { } + GTextRange(const GTextPosition& start, const GTextPosition& end) : m_start(start) , m_end(end) { } + + bool is_valid() const { return m_start.is_valid() && m_end.is_valid(); } + void clear() { m_start = { }; m_end = { }; } + + GTextPosition& start() { return m_start; } + GTextPosition& end() { return m_end; } + const GTextPosition& start() const { return m_start; } + const GTextPosition& end() const { return m_end; } + + GTextRange normalized() const { return GTextRange(normalized_start(), normalized_end()); } + + void set_start(const GTextPosition& position) { m_start = position; } + void set_end(const GTextPosition& position) { m_end = position; } + + void set(const GTextPosition& start, const GTextPosition& end) { m_start = start; m_end = end; } + +private: + GTextPosition normalized_start() const { return m_start < m_end ? m_start : m_end; } + GTextPosition normalized_end() const { return m_start < m_end ? m_end : m_start; } + + GTextPosition m_start; + GTextPosition m_end; +}; + class GTextEditor : public GWidget { public: explicit GTextEditor(GWidget* parent); @@ -49,12 +77,12 @@ public: int line_height() const { return font().glyph_height() + m_line_spacing; } int padding() const { return 3; } GTextPosition cursor() const { return m_cursor; } - GTextPosition selection_start() const { return m_selection_start; } + GTextRange normalized_selection() const { return m_selection.normalized(); } int glyph_width() const { return font().glyph_width('x'); } bool write_to_file(const String& path); - bool has_selection() const { return m_selection_start.is_valid(); } + bool has_selection() const { return m_selection.is_valid(); } String selected_text() const; void cut(); @@ -121,5 +149,5 @@ private: bool m_cursor_state { true }; bool m_in_drag_select { false }; int m_line_spacing { 2 }; - GTextPosition m_selection_start; + GTextRange m_selection; }; |