1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-22 19:45:08 +00:00

LibGUI: Move visual line metadata from GTextDocument to GTextEditor

This patch decouples GTextDocument and GTextDocumentLine from the line
wrapping functionality of GTextEditor.

This should basically make it possible to have multiple GTextEditors
editing the same GTextDocument. Of course, that will require a bit more
work since there's no paint invalidation yet.
This commit is contained in:
Andreas Kling 2019-10-27 18:00:07 +01:00
parent f1c6193d6d
commit f96c683543
4 changed files with 187 additions and 82 deletions

View file

@ -1,24 +1,26 @@
#include <LibGUI/GTextDocument.h> #include <LibGUI/GTextDocument.h>
#include <ctype.h> #include <ctype.h>
GTextDocument::GTextDocument(GTextEditor& editor) GTextDocument::GTextDocument(Client* client)
: m_editor(editor)
{ {
m_lines.append(make<GTextDocumentLine>(m_editor)); if (client)
m_clients.set(client);
append_line(make<GTextDocumentLine>());
} }
void GTextDocument::set_text(const StringView& text) void GTextDocument::set_text(const StringView& text)
{ {
m_spans.clear(); m_spans.clear();
m_lines.clear(); remove_all_lines();
int start_of_current_line = 0; int start_of_current_line = 0;
auto add_line = [&](int current_position) { auto add_line = [&](int current_position) {
int line_length = current_position - start_of_current_line; int line_length = current_position - start_of_current_line;
auto line = make<GTextDocumentLine>(m_editor); auto line = make<GTextDocumentLine>();
if (line_length) if (line_length)
line->set_text(text.substring_view(start_of_current_line, current_position - start_of_current_line)); line->set_text(text.substring_view(start_of_current_line, current_position - start_of_current_line));
m_lines.append(move(line)); append_line(move(line));
start_of_current_line = current_position + 1; start_of_current_line = current_position + 1;
}; };
int i = 0; int i = 0;
@ -38,14 +40,12 @@ int GTextDocumentLine::first_non_whitespace_column() const
return length(); return length();
} }
GTextDocumentLine::GTextDocumentLine(GTextEditor& editor) GTextDocumentLine::GTextDocumentLine()
: m_editor(editor)
{ {
clear(); clear();
} }
GTextDocumentLine::GTextDocumentLine(GTextEditor& editor, const StringView& text) GTextDocumentLine::GTextDocumentLine(const StringView& text)
: m_editor(editor)
{ {
set_text(text); set_text(text);
} }
@ -111,3 +111,45 @@ void GTextDocumentLine::truncate(int length)
m_text.resize(length + 1); m_text.resize(length + 1);
m_text.last() = 0; m_text.last() = 0;
} }
void GTextDocument::append_line(NonnullOwnPtr<GTextDocumentLine> line)
{
lines().append(move(line));
for (auto* client : m_clients)
client->document_did_append_line();
}
void GTextDocument::insert_line(int line_index, NonnullOwnPtr<GTextDocumentLine> line)
{
lines().insert(line_index, move(line));
for (auto* client : m_clients)
client->document_did_insert_line(line_index);
}
void GTextDocument::remove_line(int line_index)
{
lines().remove(line_index);
for (auto* client : m_clients)
client->document_did_remove_line(line_index);
}
void GTextDocument::remove_all_lines()
{
lines().clear();
for (auto* client : m_clients)
client->document_did_remove_all_lines();
}
GTextDocument::Client::~Client()
{
}
void GTextDocument::register_client(Client& client)
{
m_clients.set(&client);
}
void GTextDocument::unregister_client(Client& client)
{
m_clients.remove(&client);
}

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <AK/HashTable.h>
#include <AK/NonnullOwnPtrVector.h> #include <AK/NonnullOwnPtrVector.h>
#include <AK/NonnullRefPtr.h> #include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h> #include <AK/RefCounted.h>
@ -18,9 +19,18 @@ struct GTextDocumentSpan {
class GTextDocument : public RefCounted<GTextDocument> { class GTextDocument : public RefCounted<GTextDocument> {
public: public:
static NonnullRefPtr<GTextDocument> create(GTextEditor& editor) class Client {
public:
virtual ~Client();
virtual void document_did_append_line() = 0;
virtual void document_did_insert_line(int) = 0;
virtual void document_did_remove_line(int) = 0;
virtual void document_did_remove_all_lines() = 0;
};
static NonnullRefPtr<GTextDocument> create(Client* client = nullptr)
{ {
return adopt(*new GTextDocument(editor)); return adopt(*new GTextDocument(client));
} }
int line_count() const { return m_lines.size(); } int line_count() const { return m_lines.size(); }
@ -37,13 +47,21 @@ public:
bool has_spans() const { return !m_spans.is_empty(); } bool has_spans() const { return !m_spans.is_empty(); }
const Vector<GTextDocumentSpan>& spans() const { return m_spans; } const Vector<GTextDocumentSpan>& spans() const { return m_spans; }
void append_line(NonnullOwnPtr<GTextDocumentLine>);
void remove_line(int line_index);
void remove_all_lines();
void insert_line(int line_index, NonnullOwnPtr<GTextDocumentLine>);
void register_client(Client&);
void unregister_client(Client&);
private: private:
explicit GTextDocument(GTextEditor&); explicit GTextDocument(Client* client);
NonnullOwnPtrVector<GTextDocumentLine> m_lines; NonnullOwnPtrVector<GTextDocumentLine> m_lines;
Vector<GTextDocumentSpan> m_spans; Vector<GTextDocumentSpan> m_spans;
GTextEditor& m_editor; HashTable<Client*> m_clients;
}; };
class GTextDocumentLine { class GTextDocumentLine {
@ -51,8 +69,8 @@ class GTextDocumentLine {
friend class GTextDocument; friend class GTextDocument;
public: public:
explicit GTextDocumentLine(GTextEditor&); explicit GTextDocumentLine();
GTextDocumentLine(GTextEditor&, const StringView&); explicit GTextDocumentLine(const StringView&);
StringView view() const { return { characters(), length() }; } StringView view() const { return { characters(), length() }; }
const char* characters() const { return m_text.data(); } const char* characters() const { return m_text.data(); }
@ -65,19 +83,9 @@ public:
void append(const char*, int); void append(const char*, int);
void truncate(int length); void truncate(int length);
void clear(); void clear();
void recompute_visual_lines();
int visual_line_containing(int column) const;
int first_non_whitespace_column() const; int first_non_whitespace_column() const;
template<typename Callback>
void for_each_visual_line(Callback) const;
private: private:
GTextEditor& m_editor;
// NOTE: This vector is null terminated. // NOTE: This vector is null terminated.
Vector<char> m_text; Vector<char> m_text;
Vector<int, 1> m_visual_line_breaks;
Rect m_visual_rect;
}; };

View file

@ -19,7 +19,8 @@ GTextEditor::GTextEditor(Type type, GWidget* parent)
: GScrollableWidget(parent) : GScrollableWidget(parent)
, m_type(type) , m_type(type)
{ {
m_document = GTextDocument::create(*this); m_document = GTextDocument::create(this);
m_document->register_client(*this);
set_frame_shape(FrameShape::Container); set_frame_shape(FrameShape::Container);
set_frame_shadow(FrameShadow::Sunken); set_frame_shadow(FrameShadow::Sunken);
set_frame_thickness(2); set_frame_thickness(2);
@ -33,6 +34,8 @@ GTextEditor::GTextEditor(Type type, GWidget* parent)
GTextEditor::~GTextEditor() GTextEditor::~GTextEditor()
{ {
if (m_document)
m_document->unregister_client(*this);
} }
void GTextEditor::create_actions() void GTextEditor::create_actions()
@ -72,9 +75,9 @@ void GTextEditor::update_content_size()
{ {
int content_width = 0; int content_width = 0;
int content_height = 0; int content_height = 0;
for (auto& line : document().lines()) { for (auto& line : m_line_visual_data) {
content_width = max(line.m_visual_rect.width(), content_width); content_width = max(line.visual_rect.width(), content_width);
content_height += line.m_visual_rect.height(); content_height += line.visual_rect.height();
} }
content_width += m_horizontal_content_padding * 2; content_width += m_horizontal_content_padding * 2;
if (is_right_text_alignment(m_text_alignment)) if (is_right_text_alignment(m_text_alignment))
@ -95,11 +98,12 @@ GTextPosition GTextEditor::text_position_at(const Point& a_position) const
if (is_line_wrapping_enabled()) { if (is_line_wrapping_enabled()) {
for (int i = 0; i < lines().size(); ++i) { for (int i = 0; i < lines().size(); ++i) {
auto& rect = lines()[i].m_visual_rect; auto& rect = m_line_visual_data[i].visual_rect;
if (position.y() >= rect.top() && position.y() <= rect.bottom()) { if (position.y() >= rect.top() && position.y() <= rect.bottom()) {
line_index = i; line_index = i;
break; break;
} else if (position.y() > rect.bottom()) }
if (position.y() > rect.bottom())
line_index = lines().size() - 1; line_index = lines().size() - 1;
} }
} else { } else {
@ -108,14 +112,12 @@ GTextPosition GTextEditor::text_position_at(const Point& a_position) const
line_index = max(0, min(line_index, line_count() - 1)); line_index = max(0, min(line_index, line_count() - 1));
auto& line = lines()[line_index];
int column_index; int column_index;
switch (m_text_alignment) { switch (m_text_alignment) {
case TextAlignment::CenterLeft: case TextAlignment::CenterLeft:
column_index = (position.x() + glyph_width() / 2) / glyph_width(); column_index = (position.x() + glyph_width() / 2) / glyph_width();
if (is_line_wrapping_enabled()) { if (is_line_wrapping_enabled()) {
line.for_each_visual_line([&](const Rect& rect, const StringView&, int start_of_line) { for_each_visual_line(line_index, [&](const Rect& rect, const StringView&, int start_of_line) {
if (rect.contains_vertically(position.y())) { if (rect.contains_vertically(position.y())) {
column_index += start_of_line; column_index += start_of_line;
return IterationDecision::Break; return IterationDecision::Break;
@ -344,19 +346,19 @@ void GTextEditor::paint_event(GPaintEvent& event)
if (selection.start().line() < line_index) if (selection.start().line() < line_index)
first_visual_line_with_selection = 0; first_visual_line_with_selection = 0;
else else
first_visual_line_with_selection = line.visual_line_containing(selection.start().column()); first_visual_line_with_selection = visual_line_containing(line_index, selection.start().column());
if (selection.end().line() > line_index) if (selection.end().line() > line_index)
last_visual_line_with_selection = line.m_visual_line_breaks.size(); last_visual_line_with_selection = m_line_visual_data[line_index].visual_line_breaks.size();
else else
last_visual_line_with_selection = line.visual_line_containing(selection.end().column()); last_visual_line_with_selection = visual_line_containing(line_index, selection.end().column());
} }
int selection_start_column_within_line = selection.start().line() == line_index ? selection.start().column() : 0; int selection_start_column_within_line = selection.start().line() == line_index ? selection.start().column() : 0;
int selection_end_column_within_line = selection.end().line() == line_index ? selection.end().column() : line.length(); int selection_end_column_within_line = selection.end().line() == line_index ? selection.end().column() : line.length();
int visual_line_index = 0; int visual_line_index = 0;
line.for_each_visual_line([&](const Rect& visual_line_rect, const StringView& visual_line_text, int start_of_visual_line) { for_each_visual_line(line_index, [&](const Rect& visual_line_rect, const StringView& visual_line_text, int start_of_visual_line) {
if (is_multi_line() && line_index == m_cursor.line()) if (is_multi_line() && line_index == m_cursor.line())
painter.fill_rect(visual_line_rect, Color(230, 230, 230)); painter.fill_rect(visual_line_rect, Color(230, 230, 230));
#ifdef DEBUG_GTEXTEDITOR #ifdef DEBUG_GTEXTEDITOR
@ -643,6 +645,7 @@ void GTextEditor::keydown_event(GKeyEvent& event)
int previous_length = previous_line.length(); int previous_length = previous_line.length();
previous_line.append(current_line().characters(), current_line().length()); previous_line.append(current_line().characters(), current_line().length());
lines().remove(m_cursor.line()); lines().remove(m_cursor.line());
m_line_visual_data.remove(m_cursor.line());
update_content_size(); update_content_size();
update(); update();
set_cursor(m_cursor.line() - 1, previous_length); set_cursor(m_cursor.line() - 1, previous_length);
@ -676,8 +679,9 @@ void GTextEditor::delete_current_line()
return delete_selection(); return delete_selection();
lines().remove(m_cursor.line()); lines().remove(m_cursor.line());
m_line_visual_data.remove(m_cursor.line());
if (lines().is_empty()) if (lines().is_empty())
lines().append(make<GTextDocumentLine>(*this)); document().append_line(make<GTextDocumentLine>());
update_content_size(); update_content_size();
update(); update();
@ -704,6 +708,7 @@ void GTextEditor::do_delete()
int previous_length = current_line().length(); int previous_length = current_line().length();
current_line().append(next_line.characters(), next_line.length()); current_line().append(next_line.characters(), next_line.length());
lines().remove(m_cursor.line() + 1); lines().remove(m_cursor.line() + 1);
m_line_visual_data.remove(m_cursor.line() + 1);
update(); update();
did_change(); did_change();
set_cursor(m_cursor.line(), previous_length); set_cursor(m_cursor.line(), previous_length);
@ -738,16 +743,16 @@ void GTextEditor::insert_at_cursor(char ch)
if (leading_spaces) if (leading_spaces)
new_line_contents = String::repeated(' ', leading_spaces); new_line_contents = String::repeated(' ', leading_spaces);
} }
lines().insert(m_cursor.line() + (at_tail ? 1 : 0), make<GTextDocumentLine>(*this, new_line_contents)); document().insert_line(m_cursor.line() + (at_tail ? 1 : 0), make<GTextDocumentLine>(new_line_contents));
update(); update();
did_change(); did_change();
set_cursor(m_cursor.line() + 1, lines()[m_cursor.line() + 1].length()); set_cursor(m_cursor.line() + 1, lines()[m_cursor.line() + 1].length());
return; return;
} }
auto new_line = make<GTextDocumentLine>(*this); auto new_line = make<GTextDocumentLine>();
new_line->append(current_line().characters() + m_cursor.column(), current_line().length() - m_cursor.column()); new_line->append(current_line().characters() + m_cursor.column(), current_line().length() - m_cursor.column());
current_line().truncate(m_cursor.column()); current_line().truncate(m_cursor.column());
lines().insert(m_cursor.line() + 1, move(new_line)); document().insert_line(m_cursor.line() + 1, move(new_line));
update(); update();
did_change(); did_change();
set_cursor(m_cursor.line() + 1, 0); set_cursor(m_cursor.line() + 1, 0);
@ -774,7 +779,7 @@ int GTextEditor::content_x_for_position(const GTextPosition& position) const
int x_offset = -1; int x_offset = -1;
switch (m_text_alignment) { switch (m_text_alignment) {
case TextAlignment::CenterLeft: case TextAlignment::CenterLeft:
line.for_each_visual_line([&](const Rect&, const StringView& view, int start_of_visual_line) { for_each_visual_line(position.line(), [&](const Rect&, const StringView& view, int start_of_visual_line) {
if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) { if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
x_offset = (position.column() - start_of_visual_line) * glyph_width(); x_offset = (position.column() - start_of_visual_line) * glyph_width();
return IterationDecision::Break; return IterationDecision::Break;
@ -806,9 +811,8 @@ Rect GTextEditor::content_rect_for_position(const GTextPosition& position) const
return rect; return rect;
} }
auto& line = lines()[position.line()];
Rect rect; Rect rect;
line.for_each_visual_line([&](const Rect& visual_line_rect, const StringView& view, int start_of_visual_line) { for_each_visual_line(position.line(), [&](const Rect& visual_line_rect, const StringView& view, int start_of_visual_line) {
if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) { if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
// NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect // NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
// *and* included in what we get from content_x_for_position(). // *and* included in what we get from content_x_for_position().
@ -865,7 +869,7 @@ Rect GTextEditor::line_content_rect(int line_index) const
return line_rect; return line_rect;
} }
if (is_line_wrapping_enabled()) if (is_line_wrapping_enabled())
return line.m_visual_rect; return m_line_visual_data[line_index].visual_rect;
return { return {
content_x_for_position({ line_index, 0 }), content_x_for_position({ line_index, 0 }),
line_index * line_height(), line_index * line_height(),
@ -929,7 +933,6 @@ void GTextEditor::timer_event(CTimerEvent&)
update_cursor(); update_cursor();
} }
bool GTextEditor::write_to_file(const StringView& path) bool GTextEditor::write_to_file(const StringView& path)
{ {
int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666); int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
@ -991,7 +994,8 @@ String GTextEditor::text() const
void GTextEditor::clear() void GTextEditor::clear()
{ {
lines().clear(); lines().clear();
lines().append(make<GTextDocumentLine>(*this)); m_line_visual_data.clear();
document().append_line(make<GTextDocumentLine>());
m_selection.clear(); m_selection.clear();
did_update_selection(); did_update_selection();
set_cursor(0, 0); set_cursor(0, 0);
@ -1027,6 +1031,7 @@ void GTextEditor::delete_selection()
// First delete all the lines in between the first and last one. // First delete all the lines in between the first and last one.
for (int i = selection.start().line() + 1; i < selection.end().line();) { for (int i = selection.start().line() + 1; i < selection.end().line();) {
lines().remove(i); lines().remove(i);
m_line_visual_data.remove(i);
selection.end().set_line(selection.end().line() - 1); selection.end().set_line(selection.end().line() - 1);
} }
@ -1056,10 +1061,12 @@ void GTextEditor::delete_selection()
builder.append(after_selection); builder.append(after_selection);
first_line.set_text(builder.to_string()); first_line.set_text(builder.to_string());
lines().remove(selection.end().line()); lines().remove(selection.end().line());
m_line_visual_data.remove(selection.end().line());
} }
if (lines().is_empty()) if (lines().is_empty()) {
lines().append(make<GTextDocumentLine>(*this)); document().append_line(make<GTextDocumentLine>());
}
m_selection.clear(); m_selection.clear();
did_update_selection(); did_update_selection();
@ -1300,19 +1307,19 @@ char GTextEditor::character_at(const GTextPosition& position) const
void GTextEditor::recompute_all_visual_lines() void GTextEditor::recompute_all_visual_lines()
{ {
int y_offset = 0; int y_offset = 0;
for (auto& line : lines()) { for (int line_index = 0; line_index < line_count(); ++line_index) {
line.recompute_visual_lines(); recompute_visual_lines(line_index);
line.m_visual_rect.set_y(y_offset); m_line_visual_data[line_index].visual_rect.set_y(y_offset);
y_offset += line.m_visual_rect.height(); y_offset += m_line_visual_data[line_index].visual_rect.height();
} }
update_content_size(); update_content_size();
} }
int GTextDocumentLine::visual_line_containing(int column) const int GTextEditor::visual_line_containing(int line_index, int column) const
{ {
int visual_line_index = 0; int visual_line_index = 0;
for_each_visual_line([&](const Rect&, const StringView& view, int start_of_visual_line) { for_each_visual_line(line_index, [&](const Rect&, const StringView& view, int start_of_visual_line) {
if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length())) if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length()))
return IterationDecision::Break; return IterationDecision::Break;
++visual_line_index; ++visual_line_index;
@ -1321,20 +1328,23 @@ int GTextDocumentLine::visual_line_containing(int column) const
return visual_line_index; return visual_line_index;
} }
void GTextDocumentLine::recompute_visual_lines() void GTextEditor::recompute_visual_lines(int line_index)
{ {
m_visual_line_breaks.clear_with_capacity(); auto& line = document().line(line_index);
auto& visual_data = m_line_visual_data[line_index];
int available_width = m_editor.visible_text_rect_in_inner_coordinates().width(); visual_data.visual_line_breaks.clear_with_capacity();
if (m_editor.is_line_wrapping_enabled()) { int available_width = visible_text_rect_in_inner_coordinates().width();
if (is_line_wrapping_enabled()) {
int line_width_so_far = 0; int line_width_so_far = 0;
for (int i = 0; i < length(); ++i) { for (int i = 0; i < line.length(); ++i) {
auto ch = characters()[i]; auto ch = line.characters()[i];
auto glyph_width = m_editor.font().glyph_width(ch); auto glyph_width = font().glyph_width(ch);
if ((line_width_so_far + glyph_width) > available_width) { if ((line_width_so_far + glyph_width) > available_width) {
m_visual_line_breaks.append(i); visual_data.visual_line_breaks.append(i);
line_width_so_far = glyph_width; line_width_so_far = glyph_width;
continue; continue;
} }
@ -1342,36 +1352,40 @@ void GTextDocumentLine::recompute_visual_lines()
} }
} }
m_visual_line_breaks.append(length()); visual_data.visual_line_breaks.append(line.length());
if (m_editor.is_line_wrapping_enabled()) if (is_line_wrapping_enabled())
m_visual_rect = { m_editor.m_horizontal_content_padding, 0, available_width, m_visual_line_breaks.size() * m_editor.line_height() }; visual_data.visual_rect = { m_horizontal_content_padding, 0, available_width, visual_data.visual_line_breaks.size() * line_height() };
else else
m_visual_rect = { m_editor.m_horizontal_content_padding, 0, m_editor.font().width(view()), m_editor.line_height() }; visual_data.visual_rect = { m_horizontal_content_padding, 0, font().width(line.view()), line_height() };
} }
template<typename Callback> template<typename Callback>
void GTextDocumentLine::for_each_visual_line(Callback callback) const void GTextEditor::for_each_visual_line(int line_index, Callback callback) const
{ {
auto editor_visible_text_rect = m_editor.visible_text_rect_in_inner_coordinates(); auto editor_visible_text_rect = visible_text_rect_in_inner_coordinates();
int start_of_line = 0; int start_of_line = 0;
int line_index = 0; int visual_line_index = 0;
for (auto visual_line_break : m_visual_line_breaks) {
auto visual_line_view = StringView(characters() + start_of_line, visual_line_break - start_of_line); auto& line = document().line(line_index);
auto& visual_data = m_line_visual_data[line_index];
for (auto visual_line_break : visual_data.visual_line_breaks) {
auto visual_line_view = StringView(line.characters() + start_of_line, visual_line_break - start_of_line);
Rect visual_line_rect { Rect visual_line_rect {
m_visual_rect.x(), visual_data.visual_rect.x(),
m_visual_rect.y() + (line_index * m_editor.line_height()), visual_data.visual_rect.y() + (visual_line_index * line_height()),
m_editor.font().width(visual_line_view), font().width(visual_line_view),
m_editor.line_height() line_height()
}; };
if (is_right_text_alignment(m_editor.text_alignment())) if (is_right_text_alignment(text_alignment()))
visual_line_rect.set_right_without_resize(editor_visible_text_rect.right()); visual_line_rect.set_right_without_resize(editor_visible_text_rect.right());
if (!m_editor.is_multi_line()) if (!is_multi_line())
visual_line_rect.center_vertically_within(editor_visible_text_rect); visual_line_rect.center_vertically_within(editor_visible_text_rect);
if (callback(visual_line_rect, visual_line_view, start_of_line) == IterationDecision::Break) if (callback(visual_line_rect, visual_line_view, start_of_line) == IterationDecision::Break)
break; break;
start_of_line = visual_line_break; start_of_line = visual_line_break;
++line_index; ++visual_line_index;
} }
} }
@ -1397,3 +1411,24 @@ void GTextEditor::did_change_font()
vertical_scrollbar().set_step(line_height()); vertical_scrollbar().set_step(line_height());
GWidget::did_change_font(); GWidget::did_change_font();
} }
void GTextEditor::document_did_append_line()
{
m_line_visual_data.append(make<LineVisualData>());
}
void GTextEditor::document_did_remove_line(int line_index)
{
m_line_visual_data.remove(line_index);
}
void GTextEditor::document_did_remove_all_lines()
{
m_line_visual_data.clear();
}
void GTextEditor::document_did_insert_line(int line_index)
{
m_line_visual_data.insert(line_index, make<LineVisualData>());
}

View file

@ -23,7 +23,9 @@ enum class ShouldWrapAtStartOfDocument {
Yes Yes
}; };
class GTextEditor : public GScrollableWidget { class GTextEditor
: public GScrollableWidget
, public GTextDocument::Client {
C_OBJECT(GTextEditor) C_OBJECT(GTextEditor)
public: public:
enum Type { enum Type {
@ -129,6 +131,11 @@ protected:
private: private:
friend class GTextDocumentLine; friend class GTextDocumentLine;
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;
void create_actions(); void create_actions();
void paint_ruler(Painter&); void paint_ruler(Painter&);
void update_content_size(); void update_content_size();
@ -160,6 +167,9 @@ private:
Rect visible_text_rect_in_inner_coordinates() const; Rect visible_text_rect_in_inner_coordinates() const;
void recompute_all_visual_lines(); void recompute_all_visual_lines();
int visual_line_containing(int line_index, int column) const;
void recompute_visual_lines(int line_index);
Type m_type { MultiLine }; Type m_type { MultiLine };
GTextPosition m_cursor; GTextPosition m_cursor;
@ -186,6 +196,16 @@ private:
NonnullRefPtrVector<GAction> m_custom_context_menu_actions; NonnullRefPtrVector<GAction> m_custom_context_menu_actions;
RefPtr<GTextDocument> m_document; RefPtr<GTextDocument> m_document;
template<typename Callback>
void for_each_visual_line(int line_index, Callback) const;
struct LineVisualData {
Vector<int, 1> visual_line_breaks;
Rect visual_rect;
};
NonnullOwnPtrVector<LineVisualData> m_line_visual_data;
}; };
inline const LogStream& operator<<(const LogStream& stream, const GTextPosition& value) inline const LogStream& operator<<(const LogStream& stream, const GTextPosition& value)