diff --git a/Applications/Browser/Tab.cpp b/Applications/Browser/Tab.cpp index 0c87334bac..945eb2bcfb 100644 --- a/Applications/Browser/Tab.cpp +++ b/Applications/Browser/Tab.cpp @@ -261,7 +261,7 @@ Tab::Tab(Type type) auto window = GUI::Window::construct(); auto& editor = window->set_main_widget(); editor.set_text(source); - editor.set_readonly(true); + editor.set_mode(GUI::TextEditor::ReadOnly); editor.set_ruler_visible(true); window->set_rect(150, 150, 640, 480); window->set_title(url); diff --git a/Applications/SystemMonitor/ProcessStacksWidget.cpp b/Applications/SystemMonitor/ProcessStacksWidget.cpp index 930fd354f7..dce6cd6a7b 100644 --- a/Applications/SystemMonitor/ProcessStacksWidget.cpp +++ b/Applications/SystemMonitor/ProcessStacksWidget.cpp @@ -35,7 +35,7 @@ ProcessStacksWidget::ProcessStacksWidget() set_layout(); layout()->set_margins({ 4, 4, 4, 4 }); m_stacks_editor = add(); - m_stacks_editor->set_readonly(true); + m_stacks_editor->set_mode(GUI::TextEditor::ReadOnly); m_timer = add(1000, [this] { refresh(); }); } diff --git a/DevTools/HackStudio/main.cpp b/DevTools/HackStudio/main.cpp index 516e268df6..5ed3f1717d 100644 --- a/DevTools/HackStudio/main.cpp +++ b/DevTools/HackStudio/main.cpp @@ -734,11 +734,11 @@ void open_file(const String& filename) auto project_file = g_project->get_file(filename); if (project_file) { current_editor().set_document(const_cast(project_file->document())); - current_editor().set_readonly(false); + current_editor().set_mode(GUI::TextEditor::Editable); } else { auto external_file = ProjectFile::construct_with_name(filename); current_editor().set_document(const_cast(external_file->document())); - current_editor().set_readonly(true); + current_editor().set_mode(GUI::TextEditor::ReadOnly); } if (filename.ends_with(".cpp") || filename.ends_with(".h")) diff --git a/Libraries/LibGUI/ComboBox.cpp b/Libraries/LibGUI/ComboBox.cpp index 7516760261..42a0bdf61e 100644 --- a/Libraries/LibGUI/ComboBox.cpp +++ b/Libraries/LibGUI/ComboBox.cpp @@ -177,7 +177,7 @@ void ComboBox::set_only_allow_values_from_model(bool b) if (m_only_allow_values_from_model == b) return; m_only_allow_values_from_model = b; - m_editor->set_readonly(m_only_allow_values_from_model); + m_editor->set_mode(m_only_allow_values_from_model ? TextEditor::DisplayOnly : TextEditor::Editable); } Model* ComboBox::model() diff --git a/Libraries/LibGUI/TextEditor.cpp b/Libraries/LibGUI/TextEditor.cpp index 7eb199c732..7533f9a4e1 100644 --- a/Libraries/LibGUI/TextEditor.cpp +++ b/Libraries/LibGUI/TextEditor.cpp @@ -201,6 +201,9 @@ void TextEditor::doubleclick_event(MouseEvent& event) if (event.button() != MouseButton::Left) return; + if (is_displayonly()) + return; + // NOTE: This ensures that spans are updated before we look at them. flush_pending_change_notification_if_needed(); @@ -236,6 +239,12 @@ void TextEditor::mousedown_event(MouseEvent& event) return; } + if (on_mousedown) + on_mousedown(); + + if (is_displayonly()) + return; + if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) { m_triple_click_timer = Core::ElapsedTimer(); @@ -364,6 +373,19 @@ void TextEditor::paint_event(PaintEvent& event) painter.add_clip_rect(event.rect()); painter.fill_rect(event.rect(), widget_background_color); + if (is_displayonly() && (is_focused() || has_visible_list())) { + widget_background_color = palette().selection(); + Gfx::IntRect display_rect { + widget_inner_rect().x() + 1, + widget_inner_rect().y() + 1, + widget_inner_rect().width() - button_padding(), + widget_inner_rect().height() - 2 + }; + painter.add_clip_rect(display_rect); + painter.add_clip_rect(event.rect()); + painter.fill_rect(event.rect(), widget_background_color); + } + painter.translate(frame_thickness(), frame_thickness()); auto ruler_rect = ruler_rect_in_inner_coordinates(); @@ -438,6 +460,8 @@ void TextEditor::paint_event(PaintEvent& event) if (!document().has_spans()) { // Fast-path for plain text auto color = palette().color(is_enabled() ? foreground_role() : Gfx::ColorRole::DisabledText); + if (is_displayonly() && (is_focused() || has_visible_list())) + color = palette().color(is_enabled() ? Gfx::ColorRole::SelectionText : Gfx::ColorRole::DisabledText); painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, color); } else { Gfx::IntRect character_rect = { visual_line_rect.location(), { 0, line_height() } }; @@ -521,7 +545,7 @@ void TextEditor::paint_event(PaintEvent& event) painter.draw_scaled_bitmap(icon_rect, *m_icon, m_icon->rect()); } - if (is_focused() && m_cursor_state) + if (is_focused() && m_cursor_state && !is_displayonly()) painter.fill_rect(cursor_content_rect(), palette().text_cursor()); } @@ -1269,7 +1293,8 @@ void TextEditor::undefer_reflow() void TextEditor::enter_event(Core::Event&) { ASSERT(window()); - window()->set_override_cursor(StandardCursor::IBeam); + if (!is_displayonly()) + window()->set_override_cursor(StandardCursor::IBeam); m_automatic_selection_scroll_timer->stop(); } @@ -1302,15 +1327,42 @@ void TextEditor::did_change() }); } } - -void TextEditor::set_readonly(bool readonly) +void TextEditor::set_mode(const Mode mode) { - if (m_readonly == readonly) + if (m_mode == mode) return; - m_readonly = readonly; - m_cut_action->set_enabled(!is_readonly() && has_selection()); - m_delete_action->set_enabled(!is_readonly()); - m_paste_action->set_enabled(!is_readonly()); + m_mode = mode; + switch (mode) { + case Editable: + m_cut_action->set_enabled(true && has_selection()); + m_delete_action->set_enabled(true); + m_paste_action->set_enabled(true); + set_accepts_emoji_input(true); + break; + case ReadOnly: + case DisplayOnly: + m_cut_action->set_enabled(false && has_selection()); + m_delete_action->set_enabled(false); + m_paste_action->set_enabled(false); + set_accepts_emoji_input(false); + break; + default: + ASSERT_NOT_REACHED(); + } +} + +void TextEditor::set_has_open_button(bool has_button) +{ + if (m_has_open_button == has_button) + return; + m_has_open_button = has_button; +} + +void TextEditor::set_has_visible_list(bool visible) +{ + if (m_has_visible_list == visible) + return; + m_has_visible_list = visible; } void TextEditor::did_update_selection() @@ -1327,6 +1379,9 @@ void TextEditor::did_update_selection() void TextEditor::context_menu_event(ContextMenuEvent& event) { + if (is_displayonly()) + return; + if (!m_context_menu) { m_context_menu = Menu::construct(); m_context_menu->add_action(undo_action()); diff --git a/Libraries/LibGUI/TextEditor.h b/Libraries/LibGUI/TextEditor.h index 7d505a6b13..a3695090e5 100644 --- a/Libraries/LibGUI/TextEditor.h +++ b/Libraries/LibGUI/TextEditor.h @@ -46,6 +46,13 @@ public: MultiLine, SingleLine }; + + enum Mode { + Editable, + ReadOnly, + DisplayOnly + }; + virtual ~TextEditor() override; const TextDocument& document() const { return *m_document; } @@ -53,8 +60,10 @@ public: void set_document(TextDocument&); - bool is_readonly() const { return m_readonly; } - void set_readonly(bool); + bool has_visible_list() const { return m_has_visible_list; } + void set_has_visible_list(bool); + bool has_open_button() const { return m_has_open_button; } + void set_has_open_button(bool); virtual bool is_automatic_indentation_enabled() const final { return m_automatic_indentation_enabled; } void set_automatic_indentation_enabled(bool enabled) { m_automatic_indentation_enabled = enabled; } @@ -71,6 +80,12 @@ public: bool is_single_line() const { return m_type == SingleLine; } bool is_multi_line() const { return m_type == MultiLine; } + Mode mode() const { return m_mode; } + bool is_editable() const { return m_mode == Editable; } + bool is_readonly() const { return m_mode == ReadOnly; } + bool is_displayonly() const { return m_mode == DisplayOnly; } + void set_mode(const Mode); + bool is_ruler_visible() const { return m_ruler_visible; } void set_ruler_visible(bool b) { m_ruler_visible = b; } @@ -153,7 +168,7 @@ protected: virtual void context_menu_event(ContextMenuEvent&) override; virtual void resize_event(ResizeEvent&) override; virtual void theme_change_event(ThemeChangeEvent&) override; - virtual void cursor_did_change() {} + virtual void cursor_did_change() { } Gfx::IntRect ruler_content_rect(size_t line) const; TextPosition text_position_at(const Gfx::IntPoint&) const; @@ -182,6 +197,7 @@ private: int icon_size() const { return 16; } int icon_padding() const { return 2; } + int button_padding() const { return m_has_open_button ? 17 : 2; } class ReflowDeferrer { public: @@ -194,6 +210,7 @@ private: { m_editor.undefer_reflow(); } + private: TextEditor& m_editor; }; @@ -238,6 +255,7 @@ private: } Type m_type { MultiLine }; + Mode m_mode { Editable }; TextPosition m_cursor; Gfx::TextAlignment m_text_alignment { Gfx::TextAlignment::CenterLeft }; @@ -247,7 +265,8 @@ private: bool m_has_pending_change_notification { false }; bool m_automatic_indentation_enabled { false }; bool m_line_wrapping_enabled { false }; - bool m_readonly { false }; + bool m_has_visible_list { false }; + bool m_has_open_button { false }; int m_line_spacing { 4 }; size_t m_soft_tab_width { 4 }; int m_horizontal_content_padding { 3 };