diff options
Diffstat (limited to 'Libraries')
-rw-r--r-- | Libraries/LibGUI/GTextEditor.cpp | 100 | ||||
-rw-r--r-- | Libraries/LibGUI/GTextEditor.h | 30 |
2 files changed, 116 insertions, 14 deletions
diff --git a/Libraries/LibGUI/GTextEditor.cpp b/Libraries/LibGUI/GTextEditor.cpp index e43590a186..841cef6493 100644 --- a/Libraries/LibGUI/GTextEditor.cpp +++ b/Libraries/LibGUI/GTextEditor.cpp @@ -668,21 +668,27 @@ int GTextEditor::content_x_for_position(const GTextPosition& position) const } } -Rect GTextEditor::cursor_content_rect() const +Rect GTextEditor::content_rect_for_position(const GTextPosition& position) const { - if (!m_cursor.is_valid()) + if (!position.is_valid()) return {}; ASSERT(!m_lines.is_empty()); - ASSERT(m_cursor.column() <= (current_line().length() + 1)); + ASSERT(position.column() <= (current_line().length() + 1)); - int cursor_x = content_x_for_position(m_cursor); + int x = content_x_for_position(position); if (is_single_line()) { - Rect cursor_rect { cursor_x, 0, 1, font().glyph_height() + 2 }; - cursor_rect.center_vertically_within({ {}, frame_inner_rect().size() }); - return cursor_rect; + Rect rect { x, 0, 1, font().glyph_height() + 2 }; + rect.center_vertically_within({ {}, frame_inner_rect().size() }); + return rect; } - return { cursor_x, m_cursor.line() * line_height(), 1, line_height() }; + return { x, position.line() * line_height(), 1, line_height() }; +} + + +Rect GTextEditor::cursor_content_rect() const +{ + return content_rect_for_position(m_cursor); } Rect GTextEditor::line_widget_rect(int line_index) const @@ -696,16 +702,21 @@ Rect GTextEditor::line_widget_rect(int line_index) const return rect; } -void GTextEditor::scroll_cursor_into_view() +void GTextEditor::scroll_position_into_view(const GTextPosition& position) { - auto rect = cursor_content_rect(); - if (m_cursor.column() == 0) - rect.set_x(content_x_for_position({ m_cursor.line(), 0 }) - 2); - else if (m_cursor.column() == m_lines[m_cursor.line()].length()) - rect.set_x(content_x_for_position({ m_cursor.line(), m_lines[m_cursor.line()].length() }) + 2); + auto rect = content_rect_for_position(position); + if (position.column() == 0) + rect.set_x(content_x_for_position({ position.line(), 0 }) - 2); + else if (position.column() == m_lines[position.line()].length()) + rect.set_x(content_x_for_position({ position.line(), m_lines[position.line()].length() }) + 2); scroll_into_view(rect, true, true); } +void GTextEditor::scroll_cursor_into_view() +{ + scroll_position_into_view(m_cursor); +} + Rect GTextEditor::line_content_rect(int line_index) const { auto& line = m_lines[line_index]; @@ -1079,3 +1090,64 @@ void GTextEditor::resize_event(GResizeEvent& event) GScrollableWidget::resize_event(event); update_content_size(); } + +GTextPosition GTextEditor::next_position_after(const GTextPosition& position, ShouldWrapAtEndOfDocument should_wrap) +{ + auto& line = m_lines[position.line()]; + if (position.column() == line.length()) { + if (position.line() == line_count() - 1) { + if (should_wrap == ShouldWrapAtEndOfDocument::Yes) + return { 0, 0 }; + return {}; + } + return { position.line() + 1, 0 }; + } + return { position.line(), position.column() + 1 }; +} + +GTextRange GTextEditor::find(const StringView& needle, const GTextPosition& start) +{ + if (needle.is_empty()) + return {}; + + GTextPosition position = start.is_valid() ? start : GTextPosition(0, 0); + GTextPosition original_position = position; + + GTextPosition start_of_potential_match; + int needle_index = 0; + + do { + auto ch = character_at(position); + if (ch == needle[needle_index]) { + if (needle_index == 0) + start_of_potential_match = position; + ++needle_index; + if (needle_index >= needle.length()) + return { start_of_potential_match, next_position_after(position) }; + } else { + needle_index = 0; + } + position = next_position_after(position); + } while(position.is_valid() && position != original_position); + + return {}; +} + +void GTextEditor::set_selection(const GTextRange& selection) +{ + if (m_selection == selection) + return; + m_selection = selection; + set_cursor(m_selection.end()); + scroll_position_into_view(normalized_selection().start()); + update(); +} + +char GTextEditor::character_at(const GTextPosition& position) const +{ + ASSERT(position.line() < line_count()); + auto& line = m_lines[position.line()]; + if (position.column() == line.length()) + return '\n'; + return line.characters()[position.column()]; +} diff --git a/Libraries/LibGUI/GTextEditor.h b/Libraries/LibGUI/GTextEditor.h index 2b858e8d05..be9468644b 100644 --- a/Libraries/LibGUI/GTextEditor.h +++ b/Libraries/LibGUI/GTextEditor.h @@ -11,6 +11,8 @@ class GMenu; class GScrollBar; class Painter; +enum class ShouldWrapAtEndOfDocument { No = 0, Yes }; + class GTextPosition { public: GTextPosition() {} @@ -69,6 +71,11 @@ public: m_end = end; } + bool operator==(const GTextRange& other) const + { + return m_start == other.m_start && m_end == other.m_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; } @@ -108,6 +115,7 @@ public: void set_text(const StringView&); void scroll_cursor_into_view(); + void scroll_position_into_view(const GTextPosition&); int line_count() const { return m_lines.size(); } int line_spacing() const { return m_line_spacing; } int line_height() const { return font().glyph_height() + m_line_spacing; } @@ -118,8 +126,13 @@ public: bool write_to_file(const StringView& path); + GTextRange find(const StringView&, const GTextPosition& start = {}); + GTextPosition next_position_after(const GTextPosition&, ShouldWrapAtEndOfDocument = ShouldWrapAtEndOfDocument::Yes); + bool has_selection() const { return m_selection.is_valid(); } String selected_text() const; + void set_selection(const GTextRange&); + String text() const; void clear(); @@ -192,6 +205,7 @@ private: Rect line_content_rect(int item_index) const; Rect line_widget_rect(int line_index) const; Rect cursor_content_rect() const; + Rect content_rect_for_position(const GTextPosition&) const; void update_cursor(); void set_cursor(int line, int column); void set_cursor(const GTextPosition&); @@ -207,6 +221,7 @@ private: void delete_selection(); void did_update_selection(); int content_x_for_position(const GTextPosition&) const; + char character_at(const GTextPosition&) const; Type m_type { MultiLine }; @@ -232,3 +247,18 @@ private: RefPtr<GAction> m_delete_action; CElapsedTimer m_triple_click_timer; }; + +inline const LogStream& operator<<(const LogStream& stream, const GTextPosition& value) +{ + if (!value.is_valid()) + return stream << "GTextPosition(Invalid)"; + return stream << String::format("(%d,%d)", value.line(), value.column()); +} + +inline const LogStream& operator<<(const LogStream& stream, const GTextRange& value) +{ + if (!value.is_valid()) + return stream << "GTextRange(Invalid)"; + return stream << value.start() << '-' << value.end(); +} + |