From e39b1f11f96d7e6865cb9187a8f5a72ea320a6b3 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 27 Oct 2019 12:55:10 +0100 Subject: [PATCH] HackStudio: Support multiple editors on screen This patch adds Editor (subclass of GTextEditor) and EditorWrapper. An EditorWrapper is a composite widget that adds a little statusbar above an Editor widget. The statusbar is used for showing the filename and the current cursor position. More things can definitely be added. To get to the currently active editor, call current_editor(). You can also get to the current editor's wrapper by calling.. current_editor_wrapper(). Which editor is current is determined by which was was last focused by the user. --- DevTools/HackStudio/Editor.cpp | 9 +++ DevTools/HackStudio/Editor.h | 19 ++++++ DevTools/HackStudio/EditorWrapper.cpp | 62 ++++++++++++++++++ DevTools/HackStudio/EditorWrapper.h | 24 +++++++ DevTools/HackStudio/FindInFilesWidget.cpp | 6 +- DevTools/HackStudio/Makefile | 2 + DevTools/HackStudio/main.cpp | 79 +++++++++++------------ 7 files changed, 156 insertions(+), 45 deletions(-) create mode 100644 DevTools/HackStudio/Editor.cpp create mode 100644 DevTools/HackStudio/Editor.h create mode 100644 DevTools/HackStudio/EditorWrapper.cpp create mode 100644 DevTools/HackStudio/EditorWrapper.h diff --git a/DevTools/HackStudio/Editor.cpp b/DevTools/HackStudio/Editor.cpp new file mode 100644 index 0000000000..073658fecf --- /dev/null +++ b/DevTools/HackStudio/Editor.cpp @@ -0,0 +1,9 @@ +#include "Editor.h" +#include "EditorWrapper.h" + +void Editor::focusin_event(CEvent& event) +{ + if (on_focus) + on_focus(); + GTextEditor::focusin_event(event); +} diff --git a/DevTools/HackStudio/Editor.h b/DevTools/HackStudio/Editor.h new file mode 100644 index 0000000000..349984eaab --- /dev/null +++ b/DevTools/HackStudio/Editor.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class Editor final : public GTextEditor { + C_OBJECT(Editor) +public: + virtual ~Editor() override {} + + Function on_focus; + +private: + virtual void focusin_event(CEvent& event) override; + + Editor(GWidget* parent) + : GTextEditor(GTextEditor::MultiLine, parent) + { + } +}; diff --git a/DevTools/HackStudio/EditorWrapper.cpp b/DevTools/HackStudio/EditorWrapper.cpp new file mode 100644 index 0000000000..4192e83f15 --- /dev/null +++ b/DevTools/HackStudio/EditorWrapper.cpp @@ -0,0 +1,62 @@ +#include "EditorWrapper.h" +#include "Editor.h" +#include +#include +#include +#include + +extern RefPtr g_current_editor_wrapper; + +EditorWrapper::EditorWrapper(GWidget* parent) + : GWidget(parent) +{ + set_layout(make(Orientation::Vertical)); + + auto label_wrapper = GWidget::construct(this); + label_wrapper->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); + label_wrapper->set_preferred_size(0, 16); + label_wrapper->set_fill_with_background_color(true); + label_wrapper->set_layout(make(Orientation::Horizontal)); + + m_filename_label = GLabel::construct("(Untitled)", label_wrapper); + m_filename_label->set_font(Font::default_bold_font()); + m_filename_label->set_text_alignment(TextAlignment::CenterLeft); + m_filename_label->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); + m_filename_label->set_preferred_size(0, 16); + + m_cursor_label = GLabel::construct("(Cursor)", label_wrapper); + m_cursor_label->set_text_alignment(TextAlignment::CenterRight); + m_cursor_label->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); + m_cursor_label->set_preferred_size(0, 16); + + m_editor = Editor::construct(this); + m_editor->set_ruler_visible(true); + m_editor->set_line_wrapping_enabled(true); + m_editor->set_automatic_indentation_enabled(true); + + m_editor->on_cursor_change = [this] { + m_cursor_label->set_text(String::format("Line: %d, Column: %d", m_editor->cursor().line() + 1, m_editor->cursor().column())); + }; + + m_editor->on_focus = [this] { + g_current_editor_wrapper = this; + }; + + m_editor->add_custom_context_menu_action(GAction::create( + "Go to line...", { Mod_Ctrl, Key_L }, GraphicsBitmap::load_from_file("/res/icons/16x16/go-forward.png"), [this](auto&) { + auto input_box = GInputBox::construct("Line:", "Go to line", window()); + auto result = input_box->exec(); + if (result == GInputBox::ExecOK) { + bool ok; + auto line_number = input_box->text_value().to_uint(ok); + if (ok) { + m_editor->set_cursor(line_number - 1, 0); + } + } + }, + m_editor)); +} + +EditorWrapper::~EditorWrapper() +{ +} diff --git a/DevTools/HackStudio/EditorWrapper.h b/DevTools/HackStudio/EditorWrapper.h new file mode 100644 index 0000000000..b0b932525f --- /dev/null +++ b/DevTools/HackStudio/EditorWrapper.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class GLabel; +class Editor; + +class EditorWrapper : public GWidget { + C_OBJECT(EditorWrapper) +public: + virtual ~EditorWrapper() override; + + Editor& editor() { return *m_editor; } + const Editor& editor() const { return *m_editor; } + + GLabel& filename_label() { return *m_filename_label; } + +private: + explicit EditorWrapper(GWidget* parent = nullptr); + + RefPtr m_filename_label; + RefPtr m_cursor_label; + RefPtr m_editor; +}; diff --git a/DevTools/HackStudio/FindInFilesWidget.cpp b/DevTools/HackStudio/FindInFilesWidget.cpp index 666f84fc51..ec89f8866e 100644 --- a/DevTools/HackStudio/FindInFilesWidget.cpp +++ b/DevTools/HackStudio/FindInFilesWidget.cpp @@ -5,7 +5,7 @@ #include #include -extern GTextEditor& main_editor(); +extern GTextEditor& current_editor(); extern void open_file(const String&); extern OwnPtr g_project; @@ -71,8 +71,8 @@ FindInFilesWidget::FindInFilesWidget(GWidget* parent) int line_number = parts[1].to_int(ok); ASSERT(ok); open_file(parts[0]); - main_editor().set_cursor(line_number - 1, 0); - main_editor().set_focus(true); + current_editor().set_cursor(line_number - 1, 0); + current_editor().set_focus(true); }; m_button->on_click = [this](auto&) { diff --git a/DevTools/HackStudio/Makefile b/DevTools/HackStudio/Makefile index fa6e23fe76..f5e17f115b 100644 --- a/DevTools/HackStudio/Makefile +++ b/DevTools/HackStudio/Makefile @@ -7,6 +7,8 @@ OBJS = \ FindInFilesWidget.o \ ProcessStateWidget.o \ CppLexer.o \ + Editor.o \ + EditorWrapper.o \ main.o APP = HackStudio diff --git a/DevTools/HackStudio/main.cpp b/DevTools/HackStudio/main.cpp index c1b78fd77c..9ab890e34d 100644 --- a/DevTools/HackStudio/main.cpp +++ b/DevTools/HackStudio/main.cpp @@ -1,4 +1,6 @@ #include "CppLexer.h" +#include "Editor.h" +#include "EditorWrapper.h" #include "FindInFilesWidget.h" #include "Project.h" #include "TerminalWrapper.h" @@ -10,12 +12,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -25,16 +27,28 @@ #include #include +RefPtr g_current_editor_wrapper; + String g_currently_open_file; OwnPtr g_project; RefPtr g_window; RefPtr g_project_list_view; -RefPtr g_text_editor; -GTextEditor& main_editor() +void add_new_editor(GWidget& parent) { - ASSERT(g_text_editor); - return *g_text_editor; + auto wrapper = EditorWrapper::construct(&parent); + g_current_editor_wrapper = wrapper; +} + +EditorWrapper& current_editor_wrapper() +{ + ASSERT(g_current_editor_wrapper); + return *g_current_editor_wrapper; +} + +Editor& current_editor() +{ + return current_editor_wrapper().editor(); } static void build(TerminalWrapper&); @@ -72,10 +86,8 @@ int main(int argc, char** argv) g_project_list_view->set_preferred_size(200, 0); auto inner_splitter = GSplitter::construct(Orientation::Vertical, outer_splitter); - g_text_editor = GTextEditor::construct(GTextEditor::MultiLine, inner_splitter); - g_text_editor->set_ruler_visible(true); - g_text_editor->set_line_wrapping_enabled(true); - g_text_editor->set_automatic_indentation_enabled(true); + add_new_editor(inner_splitter); + add_new_editor(inner_splitter); auto new_action = GAction::create("Add new file to project...", { Mod_Ctrl, Key_N }, GraphicsBitmap::load_from_file("/res/icons/16x16/new.png"), [&](const GAction&) { auto input_box = GInputBox::construct("Enter name of new file:", "Add new file to project", g_window); @@ -110,19 +122,20 @@ int main(int argc, char** argv) auto save_action = GAction::create("Save", { Mod_Ctrl, Key_S }, GraphicsBitmap::load_from_file("/res/icons/16x16/save.png"), [&](auto&) { if (g_currently_open_file.is_empty()) return; - g_text_editor->write_to_file(g_currently_open_file); + current_editor().write_to_file(g_currently_open_file); }); toolbar->add_action(new_action); toolbar->add_action(add_existing_file_action); toolbar->add_action(save_action); toolbar->add_separator(); - toolbar->add_action(g_text_editor->cut_action()); - toolbar->add_action(g_text_editor->copy_action()); - toolbar->add_action(g_text_editor->paste_action()); + + toolbar->add_action(GCommonActions::make_cut_action([&](auto&) { current_editor().cut_action().activate(); })); + toolbar->add_action(GCommonActions::make_copy_action([&](auto&) { current_editor().copy_action().activate(); })); + toolbar->add_action(GCommonActions::make_paste_action([&](auto&) { current_editor().paste_action().activate(); })); toolbar->add_separator(); - toolbar->add_action(g_text_editor->undo_action()); - toolbar->add_action(g_text_editor->redo_action()); + toolbar->add_action(GCommonActions::make_undo_action([&](auto&) { current_editor().undo_action().activate(); })); + toolbar->add_action(GCommonActions::make_redo_action([&](auto&) { current_editor().redo_action().activate(); })); toolbar->add_separator(); g_project_list_view->on_activation = [&](auto& index) { @@ -156,26 +169,6 @@ int main(int argc, char** argv) auto terminal_wrapper = TerminalWrapper::construct(nullptr); tab_widget->add_widget("Console", terminal_wrapper); - auto statusbar = GStatusBar::construct(widget); - - g_text_editor->on_cursor_change = [&] { - statusbar->set_text(String::format("Line: %d, Column: %d", g_text_editor->cursor().line() + 1, g_text_editor->cursor().column())); - }; - - g_text_editor->add_custom_context_menu_action(GAction::create( - "Go to line...", { Mod_Ctrl, Key_L }, GraphicsBitmap::load_from_file("/res/icons/16x16/go-forward.png"), [&](auto&) { - auto input_box = GInputBox::construct("Line:", "Go to line", g_window); - auto result = input_box->exec(); - if (result == GInputBox::ExecOK) { - bool ok; - auto line_number = input_box->text_value().to_uint(ok); - if (ok) { - g_text_editor->set_cursor(line_number - 1, 0); - } - } - }, - g_text_editor)); - auto menubar = make(); auto app_menu = make("HackStudio"); app_menu->add_action(save_action); @@ -274,7 +267,7 @@ static TextStyle style_for_token_type(CppToken::Type type) static void rehighlight() { - auto text = g_text_editor->text(); + auto text = current_editor().text(); CppLexer lexer(text); auto tokens = lexer.lex(); @@ -291,8 +284,8 @@ static void rehighlight() span.font = style.font; spans.append(span); } - g_text_editor->set_spans(spans); - g_text_editor->update(); + current_editor().set_spans(spans); + current_editor().update(); } void open_file(const String& filename) @@ -303,18 +296,20 @@ void open_file(const String& filename) return; } auto contents = file->read_all(); - g_text_editor->set_text(contents); + current_editor().set_text(contents); if (filename.ends_with(".cpp")) { - g_text_editor->on_change = [] { rehighlight(); }; + current_editor().on_change = [] { rehighlight(); }; rehighlight(); } else { - g_text_editor->on_change = nullptr; + current_editor().on_change = nullptr; } g_currently_open_file = filename; g_window->set_title(String::format("%s - HackStudio", g_currently_open_file.characters())); g_project_list_view->update(); - g_text_editor->set_focus(true); + current_editor_wrapper().filename_label().set_text(filename); + + current_editor().set_focus(true); }