Browse Source

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.
Andreas Kling 5 years ago
parent
commit
e39b1f11f9

+ 9 - 0
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);
+}

+ 19 - 0
DevTools/HackStudio/Editor.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <LibGUI/GTextEditor.h>
+
+class Editor final : public GTextEditor {
+    C_OBJECT(Editor)
+public:
+    virtual ~Editor() override {}
+
+    Function<void()> on_focus;
+
+private:
+    virtual void focusin_event(CEvent& event) override;
+
+    Editor(GWidget* parent)
+        : GTextEditor(GTextEditor::MultiLine, parent)
+    {
+    }
+};

+ 62 - 0
DevTools/HackStudio/EditorWrapper.cpp

@@ -0,0 +1,62 @@
+#include "EditorWrapper.h"
+#include "Editor.h"
+#include <LibGUI/GAction.h>
+#include <LibGUI/GBoxLayout.h>
+#include <LibGUI/GInputBox.h>
+#include <LibGUI/GLabel.h>
+
+extern RefPtr<EditorWrapper> g_current_editor_wrapper;
+
+EditorWrapper::EditorWrapper(GWidget* parent)
+    : GWidget(parent)
+{
+    set_layout(make<GBoxLayout>(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<GBoxLayout>(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()
+{
+}

+ 24 - 0
DevTools/HackStudio/EditorWrapper.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <LibGUI/GWidget.h>
+
+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<GLabel> m_filename_label;
+    RefPtr<GLabel> m_cursor_label;
+    RefPtr<Editor> m_editor;
+};

+ 3 - 3
DevTools/HackStudio/FindInFilesWidget.cpp

@@ -5,7 +5,7 @@
 #include <LibGUI/GListView.h>
 #include <LibGUI/GListView.h>
 #include <LibGUI/GTextBox.h>
 #include <LibGUI/GTextBox.h>
 
 
-extern GTextEditor& main_editor();
+extern GTextEditor& current_editor();
 extern void open_file(const String&);
 extern void open_file(const String&);
 extern OwnPtr<Project> g_project;
 extern OwnPtr<Project> g_project;
 
 
@@ -71,8 +71,8 @@ FindInFilesWidget::FindInFilesWidget(GWidget* parent)
         int line_number = parts[1].to_int(ok);
         int line_number = parts[1].to_int(ok);
         ASSERT(ok);
         ASSERT(ok);
         open_file(parts[0]);
         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&) {
     m_button->on_click = [this](auto&) {

+ 2 - 0
DevTools/HackStudio/Makefile

@@ -7,6 +7,8 @@ OBJS = \
     FindInFilesWidget.o \
     FindInFilesWidget.o \
     ProcessStateWidget.o \
     ProcessStateWidget.o \
     CppLexer.o \
     CppLexer.o \
+    Editor.o \
+    EditorWrapper.o \
     main.o
     main.o
 
 
 APP = HackStudio
 APP = HackStudio

+ 37 - 42
DevTools/HackStudio/main.cpp

@@ -1,4 +1,6 @@
 #include "CppLexer.h"
 #include "CppLexer.h"
+#include "Editor.h"
+#include "EditorWrapper.h"
 #include "FindInFilesWidget.h"
 #include "FindInFilesWidget.h"
 #include "Project.h"
 #include "Project.h"
 #include "TerminalWrapper.h"
 #include "TerminalWrapper.h"
@@ -10,12 +12,12 @@
 #include <LibGUI/GButton.h>
 #include <LibGUI/GButton.h>
 #include <LibGUI/GFilePicker.h>
 #include <LibGUI/GFilePicker.h>
 #include <LibGUI/GInputBox.h>
 #include <LibGUI/GInputBox.h>
+#include <LibGUI/GLabel.h>
 #include <LibGUI/GListView.h>
 #include <LibGUI/GListView.h>
 #include <LibGUI/GMenu.h>
 #include <LibGUI/GMenu.h>
 #include <LibGUI/GMenuBar.h>
 #include <LibGUI/GMenuBar.h>
 #include <LibGUI/GMessageBox.h>
 #include <LibGUI/GMessageBox.h>
 #include <LibGUI/GSplitter.h>
 #include <LibGUI/GSplitter.h>
-#include <LibGUI/GStatusBar.h>
 #include <LibGUI/GTabWidget.h>
 #include <LibGUI/GTabWidget.h>
 #include <LibGUI/GTextBox.h>
 #include <LibGUI/GTextBox.h>
 #include <LibGUI/GTextEditor.h>
 #include <LibGUI/GTextEditor.h>
@@ -25,16 +27,28 @@
 #include <stdio.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <unistd.h>
 
 
+RefPtr<EditorWrapper> g_current_editor_wrapper;
+
 String g_currently_open_file;
 String g_currently_open_file;
 OwnPtr<Project> g_project;
 OwnPtr<Project> g_project;
 RefPtr<GWindow> g_window;
 RefPtr<GWindow> g_window;
 RefPtr<GListView> g_project_list_view;
 RefPtr<GListView> g_project_list_view;
-RefPtr<GTextEditor> g_text_editor;
 
 
-GTextEditor& main_editor()
+void add_new_editor(GWidget& parent)
+{
+    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()
 {
 {
-    ASSERT(g_text_editor);
-    return *g_text_editor;
+    return current_editor_wrapper().editor();
 }
 }
 
 
 static void build(TerminalWrapper&);
 static void build(TerminalWrapper&);
@@ -72,10 +86,8 @@ int main(int argc, char** argv)
     g_project_list_view->set_preferred_size(200, 0);
     g_project_list_view->set_preferred_size(200, 0);
 
 
     auto inner_splitter = GSplitter::construct(Orientation::Vertical, outer_splitter);
     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 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);
         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&) {
     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())
         if (g_currently_open_file.is_empty())
             return;
             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(new_action);
     toolbar->add_action(add_existing_file_action);
     toolbar->add_action(add_existing_file_action);
     toolbar->add_action(save_action);
     toolbar->add_action(save_action);
     toolbar->add_separator();
     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_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();
     toolbar->add_separator();
 
 
     g_project_list_view->on_activation = [&](auto& index) {
     g_project_list_view->on_activation = [&](auto& index) {
@@ -156,26 +169,6 @@ int main(int argc, char** argv)
     auto terminal_wrapper = TerminalWrapper::construct(nullptr);
     auto terminal_wrapper = TerminalWrapper::construct(nullptr);
     tab_widget->add_widget("Console", terminal_wrapper);
     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<GMenuBar>();
     auto menubar = make<GMenuBar>();
     auto app_menu = make<GMenu>("HackStudio");
     auto app_menu = make<GMenu>("HackStudio");
     app_menu->add_action(save_action);
     app_menu->add_action(save_action);
@@ -274,7 +267,7 @@ static TextStyle style_for_token_type(CppToken::Type type)
 
 
 static void rehighlight()
 static void rehighlight()
 {
 {
-    auto text = g_text_editor->text();
+    auto text = current_editor().text();
     CppLexer lexer(text);
     CppLexer lexer(text);
     auto tokens = lexer.lex();
     auto tokens = lexer.lex();
 
 
@@ -291,8 +284,8 @@ static void rehighlight()
         span.font = style.font;
         span.font = style.font;
         spans.append(span);
         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)
 void open_file(const String& filename)
@@ -303,18 +296,20 @@ void open_file(const String& filename)
         return;
         return;
     }
     }
     auto contents = file->read_all();
     auto contents = file->read_all();
-    g_text_editor->set_text(contents);
+    current_editor().set_text(contents);
 
 
     if (filename.ends_with(".cpp")) {
     if (filename.ends_with(".cpp")) {
-        g_text_editor->on_change = [] { rehighlight(); };
+        current_editor().on_change = [] { rehighlight(); };
         rehighlight();
         rehighlight();
     } else {
     } else {
-        g_text_editor->on_change = nullptr;
+        current_editor().on_change = nullptr;
     }
     }
 
 
     g_currently_open_file = filename;
     g_currently_open_file = filename;
     g_window->set_title(String::format("%s - HackStudio", g_currently_open_file.characters()));
     g_window->set_title(String::format("%s - HackStudio", g_currently_open_file.characters()));
     g_project_list_view->update();
     g_project_list_view->update();
 
 
-    g_text_editor->set_focus(true);
+    current_editor_wrapper().filename_label().set_text(filename);
+
+    current_editor().set_focus(true);
 }
 }