diff --git a/Libraries/LibGUI/GTextDocument.cpp b/Libraries/LibGUI/GTextDocument.cpp index ba742b6023..12a71e2a8f 100644 --- a/Libraries/LibGUI/GTextDocument.cpp +++ b/Libraries/LibGUI/GTextDocument.cpp @@ -5,7 +5,7 @@ GTextDocument::GTextDocument(Client* client) { if (client) m_clients.set(client); - append_line(make()); + append_line(make(*this)); } void GTextDocument::set_text(const StringView& text) @@ -17,9 +17,9 @@ void GTextDocument::set_text(const StringView& text) auto add_line = [&](int current_position) { int line_length = current_position - start_of_current_line; - auto line = make(); + auto line = make(*this); if (line_length) - line->set_text(text.substring_view(start_of_current_line, current_position - start_of_current_line)); + line->set_text(*this, text.substring_view(start_of_current_line, current_position - start_of_current_line)); append_line(move(line)); start_of_current_line = current_position + 1; }; @@ -40,53 +40,56 @@ int GTextDocumentLine::first_non_whitespace_column() const return length(); } -GTextDocumentLine::GTextDocumentLine() +GTextDocumentLine::GTextDocumentLine(GTextDocument& document) { - clear(); + clear(document); } -GTextDocumentLine::GTextDocumentLine(const StringView& text) +GTextDocumentLine::GTextDocumentLine(GTextDocument& document, const StringView& text) { - set_text(text); + set_text(document, text); } -void GTextDocumentLine::clear() +void GTextDocumentLine::clear(GTextDocument& document) { m_text.clear(); m_text.append(0); + document.update_views({}); } -void GTextDocumentLine::set_text(const StringView& text) +void GTextDocumentLine::set_text(GTextDocument& document, const StringView& text) { if (text.length() == length() && !memcmp(text.characters_without_null_termination(), characters(), length())) return; if (text.is_empty()) { - clear(); + clear(document); return; } m_text.resize(text.length() + 1); memcpy(m_text.data(), text.characters_without_null_termination(), text.length() + 1); + document.update_views({}); } -void GTextDocumentLine::append(const char* characters, int length) +void GTextDocumentLine::append(GTextDocument& document, const char* characters, int length) { int old_length = m_text.size() - 1; m_text.resize(m_text.size() + length); memcpy(m_text.data() + old_length, characters, length); m_text.last() = 0; + document.update_views({}); } -void GTextDocumentLine::append(char ch) +void GTextDocumentLine::append(GTextDocument& document, char ch) { - insert(length(), ch); + insert(document, length(), ch); } -void GTextDocumentLine::prepend(char ch) +void GTextDocumentLine::prepend(GTextDocument& document, char ch) { - insert(0, ch); + insert(document, 0, ch); } -void GTextDocumentLine::insert(int index, char ch) +void GTextDocumentLine::insert(GTextDocument& document, int index, char ch) { if (index == length()) { m_text.last() = ch; @@ -94,9 +97,10 @@ void GTextDocumentLine::insert(int index, char ch) } else { m_text.insert(index, move(ch)); } + document.update_views({}); } -void GTextDocumentLine::remove(int index) +void GTextDocumentLine::remove(GTextDocument& document, int index) { if (index == length()) { m_text.take_last(); @@ -104,12 +108,14 @@ void GTextDocumentLine::remove(int index) } else { m_text.remove(index); } + document.update_views({}); } -void GTextDocumentLine::truncate(int length) +void GTextDocumentLine::truncate(GTextDocument& document, int length) { m_text.resize(length + 1); m_text.last() = 0; + document.update_views({}); } void GTextDocument::append_line(NonnullOwnPtr line) @@ -153,3 +159,9 @@ void GTextDocument::unregister_client(Client& client) { m_clients.remove(&client); } + +void GTextDocument::update_views(Badge) +{ + for (auto* client : m_clients) + client->document_did_change(); +} diff --git a/Libraries/LibGUI/GTextDocument.h b/Libraries/LibGUI/GTextDocument.h index 115af4787a..47ad556370 100644 --- a/Libraries/LibGUI/GTextDocument.h +++ b/Libraries/LibGUI/GTextDocument.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -26,6 +27,7 @@ public: virtual void document_did_insert_line(int) = 0; virtual void document_did_remove_line(int) = 0; virtual void document_did_remove_all_lines() = 0; + virtual void document_did_change() = 0; }; static NonnullRefPtr create(Client* client = nullptr) @@ -55,6 +57,8 @@ public: void register_client(Client&); void unregister_client(Client&); + void update_views(Badge); + private: explicit GTextDocument(Client* client); @@ -69,20 +73,20 @@ class GTextDocumentLine { friend class GTextDocument; public: - explicit GTextDocumentLine(); - explicit GTextDocumentLine(const StringView&); + explicit GTextDocumentLine(GTextDocument&); + explicit GTextDocumentLine(GTextDocument&, const StringView&); StringView view() const { return { characters(), length() }; } const char* characters() const { return m_text.data(); } int length() const { return m_text.size() - 1; } - void set_text(const StringView&); - void append(char); - void prepend(char); - void insert(int index, char); - void remove(int index); - void append(const char*, int); - void truncate(int length); - void clear(); + void set_text(GTextDocument&, const StringView&); + void append(GTextDocument&, char); + void prepend(GTextDocument&, char); + void insert(GTextDocument&, int index, char); + void remove(GTextDocument&, int index); + void append(GTextDocument&, const char*, int); + void truncate(GTextDocument&, int length); + void clear(GTextDocument&); int first_non_whitespace_column() const; private: diff --git a/Libraries/LibGUI/GTextEditor.cpp b/Libraries/LibGUI/GTextEditor.cpp index 20012e4b69..9b991eaff6 100644 --- a/Libraries/LibGUI/GTextEditor.cpp +++ b/Libraries/LibGUI/GTextEditor.cpp @@ -19,8 +19,7 @@ GTextEditor::GTextEditor(Type type, GWidget* parent) : GScrollableWidget(parent) , m_type(type) { - m_document = GTextDocument::create(this); - m_document->register_client(*this); + set_document(GTextDocument::create()); set_frame_shape(FrameShape::Container); set_frame_shadow(FrameShadow::Sunken); set_frame_thickness(2); @@ -632,7 +631,7 @@ void GTextEditor::keydown_event(GKeyEvent& event) // Backspace within line for (int i = 0; i < erase_count; ++i) { - current_line().remove(m_cursor.column() - 1 - i); + current_line().remove(document(), m_cursor.column() - 1 - i); } update_content_size(); set_cursor(m_cursor.line(), m_cursor.column() - erase_count); @@ -643,9 +642,8 @@ void GTextEditor::keydown_event(GKeyEvent& event) // Backspace at column 0; merge with previous line auto& previous_line = lines()[m_cursor.line() - 1]; int previous_length = previous_line.length(); - previous_line.append(current_line().characters(), current_line().length()); - lines().remove(m_cursor.line()); - m_line_visual_data.remove(m_cursor.line()); + previous_line.append(document(), current_line().characters(), current_line().length()); + document().remove_line(m_cursor.line()); update_content_size(); update(); set_cursor(m_cursor.line() - 1, previous_length); @@ -678,10 +676,9 @@ void GTextEditor::delete_current_line() if (has_selection()) return delete_selection(); - lines().remove(m_cursor.line()); - m_line_visual_data.remove(m_cursor.line()); + document().remove_line(m_cursor.line()); if (lines().is_empty()) - document().append_line(make()); + document().append_line(make(document())); update_content_size(); update(); @@ -697,7 +694,7 @@ void GTextEditor::do_delete() if (m_cursor.column() < current_line().length()) { // Delete within line - current_line().remove(m_cursor.column()); + current_line().remove(document(), m_cursor.column()); did_change(); update_cursor(); return; @@ -706,9 +703,8 @@ void GTextEditor::do_delete() // Delete at end of line; merge with next line auto& next_line = lines()[m_cursor.line() + 1]; int previous_length = current_line().length(); - current_line().append(next_line.characters(), next_line.length()); - lines().remove(m_cursor.line() + 1); - m_line_visual_data.remove(m_cursor.line() + 1); + current_line().append(document(), next_line.characters(), next_line.length()); + document().remove_line(m_cursor.line() + 1); update(); did_change(); set_cursor(m_cursor.line(), previous_length); @@ -743,15 +739,15 @@ void GTextEditor::insert_at_cursor(char ch) if (leading_spaces) new_line_contents = String::repeated(' ', leading_spaces); } - document().insert_line(m_cursor.line() + (at_tail ? 1 : 0), make(new_line_contents)); + document().insert_line(m_cursor.line() + (at_tail ? 1 : 0), make(document(), new_line_contents)); update(); did_change(); set_cursor(m_cursor.line() + 1, lines()[m_cursor.line() + 1].length()); return; } - auto new_line = make(); - new_line->append(current_line().characters() + m_cursor.column(), current_line().length() - m_cursor.column()); - current_line().truncate(m_cursor.column()); + auto new_line = make(document()); + new_line->append(document(), current_line().characters() + m_cursor.column(), current_line().length() - m_cursor.column()); + current_line().truncate(document(), m_cursor.column()); document().insert_line(m_cursor.line() + 1, move(new_line)); update(); did_change(); @@ -762,13 +758,13 @@ void GTextEditor::insert_at_cursor(char ch) int next_soft_tab_stop = ((m_cursor.column() + m_soft_tab_width) / m_soft_tab_width) * m_soft_tab_width; int spaces_to_insert = next_soft_tab_stop - m_cursor.column(); for (int i = 0; i < spaces_to_insert; ++i) { - current_line().insert(m_cursor.column(), ' '); + current_line().insert(document(), m_cursor.column(), ' '); } did_change(); set_cursor(m_cursor.line(), next_soft_tab_stop); return; } - current_line().insert(m_cursor.column(), ch); + current_line().insert(document(), m_cursor.column(), ch); did_change(); set_cursor(m_cursor.line(), m_cursor.column() + 1); } @@ -993,9 +989,8 @@ String GTextEditor::text() const void GTextEditor::clear() { - lines().clear(); - m_line_visual_data.clear(); - document().append_line(make()); + document().remove_all_lines(); + document().append_line(make(document())); m_selection.clear(); did_update_selection(); set_cursor(0, 0); @@ -1030,8 +1025,7 @@ void GTextEditor::delete_selection() // First delete all the lines in between the first and last one. for (int i = selection.start().line() + 1; i < selection.end().line();) { - lines().remove(i); - m_line_visual_data.remove(i); + document().remove_line(i); selection.end().set_line(selection.end().line() - 1); } @@ -1040,14 +1034,14 @@ void GTextEditor::delete_selection() auto& line = 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(); + line.clear(document()); } else { 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); - line.set_text(builder.to_string()); + line.set_text(document(), builder.to_string()); } } else { // Delete across a newline, merging lines. @@ -1059,13 +1053,12 @@ void GTextEditor::delete_selection() StringBuilder builder(before_selection.length() + after_selection.length()); builder.append(before_selection); builder.append(after_selection); - first_line.set_text(builder.to_string()); - lines().remove(selection.end().line()); - m_line_visual_data.remove(selection.end().line()); + first_line.set_text(document(), builder.to_string()); + document().remove_line(selection.end().line()); } if (lines().is_empty()) { - document().append_line(make()); + document().append_line(make(document())); } m_selection.clear(); @@ -1415,20 +1408,50 @@ void GTextEditor::did_change_font() void GTextEditor::document_did_append_line() { m_line_visual_data.append(make()); + recompute_all_visual_lines(); + update(); } void GTextEditor::document_did_remove_line(int line_index) { m_line_visual_data.remove(line_index); + recompute_all_visual_lines(); + update(); } void GTextEditor::document_did_remove_all_lines() { m_line_visual_data.clear(); + recompute_all_visual_lines(); + update(); } void GTextEditor::document_did_insert_line(int line_index) { m_line_visual_data.insert(line_index, make()); + recompute_all_visual_lines(); + update(); } +void GTextEditor::document_did_change() +{ + recompute_all_visual_lines(); + update(); +} + +void GTextEditor::set_document(GTextDocument& document) +{ + if (m_document.ptr() == &document) + return; + if (m_document) + m_document->unregister_client(*this); + m_document = document; + m_line_visual_data.clear(); + for (int i = 0; i < m_document->line_count(); ++i) { + m_line_visual_data.append(make()); + } + m_cursor = { 0, 0 }; + recompute_all_visual_lines(); + update(); + m_document->register_client(*this); +} diff --git a/Libraries/LibGUI/GTextEditor.h b/Libraries/LibGUI/GTextEditor.h index c064338f50..676202455a 100644 --- a/Libraries/LibGUI/GTextEditor.h +++ b/Libraries/LibGUI/GTextEditor.h @@ -37,6 +37,8 @@ public: const GTextDocument& document() const { return *m_document; } GTextDocument& document() { return *m_document; } + void set_document(GTextDocument&); + bool is_readonly() const { return m_readonly; } void set_readonly(bool); @@ -131,10 +133,12 @@ protected: private: friend class GTextDocumentLine; + // ^GTextDocument::Client virtual void document_did_append_line() override; virtual void document_did_insert_line(int) override; virtual void document_did_remove_line(int) override; virtual void document_did_remove_all_lines() override; + virtual void document_did_change() override; void create_actions(); void paint_ruler(Painter&);