|
|
@@ -5,6 +5,7 @@
|
|
|
#include "lumacs/command_system.hpp"
|
|
|
#include "lumacs/minibuffer_manager.hpp" // Include for MinibufferManager and MinibufferMode
|
|
|
#include "lumacs/gtk_renderer.hpp" // Include for GtkRenderer
|
|
|
+#include "lumacs/gtk_window_controller.hpp" // Include for GtkWindowController
|
|
|
#include <iostream>
|
|
|
#include <filesystem>
|
|
|
#include <vector>
|
|
|
@@ -201,6 +202,9 @@ private:
|
|
|
// GtkRenderer instance
|
|
|
std::unique_ptr<GtkRenderer> gtk_renderer_;
|
|
|
|
|
|
+ // GtkWindowController instances for each window
|
|
|
+ std::vector<std::shared_ptr<GtkWindowController>> window_controllers_;
|
|
|
+
|
|
|
protected:
|
|
|
void on_activate() {
|
|
|
// Create main window and associate with the application
|
|
|
@@ -253,7 +257,7 @@ protected:
|
|
|
// Cursor blinking callback
|
|
|
bool on_cursor_blink() {
|
|
|
// Safety check - don't blink if core is destroyed or no drawing area
|
|
|
- if (!core_ || !drawing_area_ || !app_) {
|
|
|
+ if (!core_ || !app_) { // drawing_area_ is now managed by controllers
|
|
|
return false; // Stop the timer
|
|
|
}
|
|
|
|
|
|
@@ -265,7 +269,17 @@ protected:
|
|
|
try {
|
|
|
cursor_visible_ = !cursor_visible_;
|
|
|
core_->check_and_clear_message(); // Check and clear messages
|
|
|
- drawing_area_->queue_draw();
|
|
|
+
|
|
|
+ // Update all window controllers and queue redraws
|
|
|
+ for (const auto& controller : window_controllers_) {
|
|
|
+ controller->set_cursor_visible(cursor_visible_);
|
|
|
+ // Each controller's drawing area will be redrawn by its own draw_func or here
|
|
|
+ // For now, GtkEditor manages top-level redraws, but this might be refactored
|
|
|
+ // if individual window redraws are sufficient
|
|
|
+ }
|
|
|
+ if (content_widget_) {
|
|
|
+ queue_redraw_all_windows(content_widget_);
|
|
|
+ }
|
|
|
} catch (...) {
|
|
|
return false; // Stop timer on any exception
|
|
|
}
|
|
|
@@ -288,6 +302,9 @@ protected:
|
|
|
// Clear the drawing area reference since we're rebuilding
|
|
|
drawing_area_ = nullptr;
|
|
|
|
|
|
+ // Clear all window controllers
|
|
|
+ window_controllers_.clear();
|
|
|
+
|
|
|
// Initialize cached active window to prevent focus jumping
|
|
|
cached_active_window_ = core_->active_window();
|
|
|
|
|
|
@@ -306,115 +323,25 @@ protected:
|
|
|
// Create a new DrawingArea for this window
|
|
|
auto drawing_area = Gtk::make_managed<Gtk::DrawingArea>();
|
|
|
|
|
|
+ // Create a GtkWindowController for this DrawingArea
|
|
|
+ auto controller = std::make_shared<GtkWindowController>(*core_, *gtk_renderer_, node->window, *drawing_area);
|
|
|
+ controller->set_cached_active_window(cached_active_window_);
|
|
|
+ controller->set_cursor_visible(cursor_visible_);
|
|
|
+ controller->connect_events();
|
|
|
+
|
|
|
+ // Store the controller to manage its lifetime and update cached_active_window_ and cursor_visible_
|
|
|
+ window_controllers_.push_back(controller);
|
|
|
+
|
|
|
// Set up drawing for this specific window
|
|
|
// Use a weak reference to the window to avoid crashes if the layout is rebuilt
|
|
|
- std::weak_ptr<Window> weak_window = node->window;
|
|
|
- drawing_area->set_draw_func([this, weak_window, drawing_area](const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
|
|
|
- if (auto window = weak_window.lock() && gtk_renderer_) {
|
|
|
- gtk_renderer_->draw_window(cr, width, height, window, drawing_area, cached_active_window_, cursor_visible_);
|
|
|
+ drawing_area->set_draw_func([this, controller_ptr = controller](const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
|
|
|
+ if (controller_ptr) {
|
|
|
+ controller_ptr->on_draw_event(cr, width, height);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
drawing_area->set_focusable(true);
|
|
|
|
|
|
- // Add input handling
|
|
|
- auto controller = Gtk::EventControllerKey::create();
|
|
|
- // Use weak reference to window for key handling
|
|
|
- std::weak_ptr<Window> weak_window_key = node->window;
|
|
|
- controller->signal_key_pressed().connect([this, weak_window_key](guint keyval, guint keycode, Gdk::ModifierType state) -> bool {
|
|
|
- // Ensure this window is active when it receives key input
|
|
|
- if (auto window = weak_window_key.lock()) {
|
|
|
- if (core_) {
|
|
|
- core_->set_active_window(window);
|
|
|
- cached_active_window_ = window; // Cache for rendering to prevent focus jumping
|
|
|
- }
|
|
|
- }
|
|
|
- return on_key_pressed(keyval, keycode, state);
|
|
|
- }, false);
|
|
|
- drawing_area->add_controller(controller);
|
|
|
-
|
|
|
- // Add click handling to set active window explicitly and move cursor
|
|
|
- // We use GestureClick instead of EventControllerFocus to avoid spurious focus changes
|
|
|
- auto click_controller = Gtk::GestureClick::create();
|
|
|
- std::weak_ptr<Window> weak_window_click = node->window;
|
|
|
- click_controller->signal_pressed().connect([this, weak_window_click, drawing_area](int /*n_press*/, double x, double y) {
|
|
|
- if (auto window = weak_window_click.lock()) {
|
|
|
- // 1. Activate Window
|
|
|
- if (core_ && core_->active_window() != window) {
|
|
|
- core_->set_active_window(window);
|
|
|
- cached_active_window_ = window; // Cache for rendering to prevent focus jumping
|
|
|
- }
|
|
|
- // IMPORTANT: Grab keyboard focus for this widget
|
|
|
- drawing_area->grab_focus();
|
|
|
-
|
|
|
- // 2. Move Cursor
|
|
|
- if (gtk_renderer_) {
|
|
|
- if (auto pos = gtk_renderer_->resolve_screen_pos(window, x, y)) {
|
|
|
- window->set_cursor(*pos);
|
|
|
- // Clear mark on simple click
|
|
|
- window->buffer().deactivate_mark();
|
|
|
- drawing_area->queue_draw();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- drawing_area->add_controller(click_controller);
|
|
|
-
|
|
|
- // Add Drag Gesture for Selection
|
|
|
- auto drag_controller = Gtk::GestureDrag::create();
|
|
|
- std::weak_ptr<Window> weak_window_drag = node->window;
|
|
|
-
|
|
|
- drag_controller->signal_drag_begin().connect([this, weak_window_drag, drawing_area](double x, double y) {
|
|
|
- if (auto window = weak_window_drag.lock()) {
|
|
|
- if (gtk_renderer_) {
|
|
|
- if (auto pos = gtk_renderer_->resolve_screen_pos(window, x, y)) {
|
|
|
- // Set mark at start of drag
|
|
|
- window->buffer().set_mark(*pos);
|
|
|
- window->set_cursor(*pos);
|
|
|
- drawing_area->queue_draw();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- drag_controller->signal_drag_update().connect([this, weak_window_drag, drawing_area, drag_controller](double dx, double dy) {
|
|
|
- if (auto window = weak_window_drag.lock()) {
|
|
|
- double start_x, start_y;
|
|
|
- if (drag_controller->get_start_point(start_x, start_y) && gtk_renderer_) {
|
|
|
- double current_x = start_x + dx;
|
|
|
- double current_y = start_y + dy;
|
|
|
-
|
|
|
- if (auto pos = gtk_renderer_->resolve_screen_pos(window, current_x, current_y)) {
|
|
|
- window->set_cursor(*pos);
|
|
|
- drawing_area->queue_draw();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- drawing_area->add_controller(drag_controller);
|
|
|
-
|
|
|
- // Add scroll handling
|
|
|
- auto scroll_controller = Gtk::EventControllerScroll::create();
|
|
|
- scroll_controller->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
|
|
|
- std::weak_ptr<Window> weak_window_scroll = node->window;
|
|
|
- scroll_controller->signal_scroll().connect([weak_window_scroll, drawing_area](double /*dx*/, double dy) -> bool {
|
|
|
- if (auto window = weak_window_scroll.lock()) {
|
|
|
- // dy is usually 1.0 or -1.0 for wheel steps
|
|
|
- // Scroll 3 lines per step
|
|
|
- int lines = static_cast<int>(dy * 3.0);
|
|
|
- if (lines != 0) {
|
|
|
- window->scroll_lines(lines);
|
|
|
- drawing_area->queue_draw();
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }, true);
|
|
|
- drawing_area->add_controller(scroll_controller);
|
|
|
-
|
|
|
- // Context menus and tooltips removed per user request (Phase A.1)
|
|
|
-
|
|
|
// Store reference for single-window compatibility
|
|
|
if (!drawing_area_) {
|
|
|
drawing_area_ = drawing_area;
|
|
|
@@ -453,186 +380,4 @@ protected:
|
|
|
return paned;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- std::string resolve_key(guint keyval, Gdk::ModifierType state) {
|
|
|
- // Handle modifier keys
|
|
|
- unsigned int state_uint = static_cast<unsigned int>(state);
|
|
|
- bool is_control = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::CONTROL_MASK)) != 0;
|
|
|
-
|
|
|
- // Convert keyval to string
|
|
|
- std::string key_name;
|
|
|
- switch (keyval) {
|
|
|
- case GDK_KEY_Return: key_name = "Return"; break;
|
|
|
- case GDK_KEY_Tab: key_name = "Tab"; break;
|
|
|
- case GDK_KEY_Escape: key_name = "Escape"; break;
|
|
|
- case GDK_KEY_BackSpace: key_name = "Backspace"; break;
|
|
|
- case GDK_KEY_Delete: key_name = "Delete"; break;
|
|
|
- case GDK_KEY_Up: key_name = "ArrowUp"; break;
|
|
|
- case GDK_KEY_Down: key_name = "ArrowDown"; break;
|
|
|
- case GDK_KEY_Left: key_name = "ArrowLeft"; break;
|
|
|
- case GDK_KEY_Right: key_name = "ArrowRight"; break;
|
|
|
- case GDK_KEY_Home: key_name = "Home"; break;
|
|
|
- case GDK_KEY_End: key_name = "End"; break;
|
|
|
- case GDK_KEY_Page_Up: key_name = "PageUp"; break;
|
|
|
- case GDK_KEY_Page_Down: key_name = "PageDown"; break;
|
|
|
- case GDK_KEY_F3: key_name = "F3"; break;
|
|
|
- case GDK_KEY_F4: key_name = "F4"; break;
|
|
|
- default:
|
|
|
- // Handle printable characters
|
|
|
- if (keyval >= GDK_KEY_a && keyval <= GDK_KEY_z) {
|
|
|
- key_name = std::string(1, static_cast<char>(keyval));
|
|
|
- if (is_control) {
|
|
|
- // Logic for Control keys if needed
|
|
|
- } else if ((state_uint & static_cast<unsigned int>(Gdk::ModifierType::SHIFT_MASK)) != 0) {
|
|
|
- key_name = std::string(1, static_cast<char>(keyval - (GDK_KEY_a - GDK_KEY_A)));
|
|
|
- }
|
|
|
- } else if (keyval >= 32 && keyval <= 126) {
|
|
|
- key_name = std::string(1, static_cast<char>(keyval));
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
- return key_name;
|
|
|
- }
|
|
|
-
|
|
|
- // Input
|
|
|
- bool on_key_pressed(guint keyval, guint /*keycode*/, Gdk::ModifierType state) {
|
|
|
- // Safety check - don't process keys if core is destroyed
|
|
|
- if (!core_) return false;
|
|
|
-
|
|
|
- // Make cursor visible immediately when typing
|
|
|
- cursor_visible_ = true;
|
|
|
-
|
|
|
- // 1. Resolve the base key name
|
|
|
- std::string key_name = resolve_key(keyval, state);
|
|
|
- if (key_name.empty()) return false;
|
|
|
-
|
|
|
-
|
|
|
- // 2. Handle Modifiers
|
|
|
- unsigned int state_uint = static_cast<unsigned int>(state);
|
|
|
- bool is_control = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::CONTROL_MASK)) != 0;
|
|
|
- bool is_alt = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::ALT_MASK)) != 0;
|
|
|
- bool is_meta = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::META_MASK)) != 0;
|
|
|
- bool is_lumacs_meta = is_alt || is_meta;
|
|
|
-
|
|
|
- // 3. Handle Minibuffer Input Logic (Command/Buffer/File modes)
|
|
|
- if (core_->minibuffer_manager().is_active()) {
|
|
|
- // Pass the key event to the MinibufferManager
|
|
|
- core_->minibuffer_manager().handle_key_event(key_name);
|
|
|
- if (content_widget_) queue_redraw_all_windows(content_widget_);
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // 4. Normal Mode Processing (Pass to Lua)
|
|
|
- if (is_control && key_name.length() == 1) key_name = "C-" + key_name;
|
|
|
- if (is_lumacs_meta) key_name = "M-" + key_name; // Use combined meta/alt
|
|
|
-
|
|
|
- KeyResult result = core_->lua_api()->process_key(key_name);
|
|
|
-
|
|
|
- // Fallback handlers for common editing keys
|
|
|
- if (result == KeyResult::Unbound) {
|
|
|
- // Return - insert newline
|
|
|
- if (key_name == "Return") {
|
|
|
- auto cursor = core_->cursor();
|
|
|
- core_->buffer().insert_newline(cursor);
|
|
|
- core_->active_window()->set_cursor({cursor.line + 1, 0});
|
|
|
- }
|
|
|
- // Backspace - delete character
|
|
|
- else if (key_name == "Backspace") {
|
|
|
- auto cursor = core_->cursor();
|
|
|
- core_->buffer().erase_char(cursor);
|
|
|
- if (cursor.column > 0) {
|
|
|
- core_->active_window()->set_cursor({cursor.line, cursor.column - 1});
|
|
|
- } else if (cursor.line > 0) {
|
|
|
- // Join with previous line
|
|
|
- const auto& prev_line = core_->buffer().line(cursor.line - 1);
|
|
|
- size_t prev_line_len = prev_line.length();
|
|
|
- core_->active_window()->set_cursor({cursor.line - 1, prev_line_len});
|
|
|
- }
|
|
|
- }
|
|
|
- // Delete - delete character forward
|
|
|
- else if (key_name == "Delete") {
|
|
|
- auto cursor = core_->cursor();
|
|
|
- core_->buffer().erase_char({cursor.line, cursor.column + 1});
|
|
|
- // No cursor movement needed for forward delete
|
|
|
- }
|
|
|
- // Arrow key navigation - use Window methods for proper scrolling
|
|
|
- else if (key_name == "ArrowUp") {
|
|
|
- core_->active_window()->move_up();
|
|
|
- }
|
|
|
- else if (key_name == "ArrowDown") {
|
|
|
- core_->active_window()->move_down();
|
|
|
- }
|
|
|
- else if (key_name == "ArrowLeft") {
|
|
|
- core_->active_window()->move_left();
|
|
|
- }
|
|
|
- else if (key_name == "ArrowRight") {
|
|
|
- core_->active_window()->move_right();
|
|
|
- }
|
|
|
- // Page navigation - scroll multiple lines
|
|
|
- else if (key_name == "PageUp") {
|
|
|
- auto window = core_->active_window();
|
|
|
- auto cursor = core_->cursor();
|
|
|
- int page_size = std::max(1, window->viewport().height - 2); // Leave 2 lines overlap
|
|
|
-
|
|
|
- // Move cursor up by page size
|
|
|
- size_t new_line = (cursor.line >= static_cast<size_t>(page_size))
|
|
|
- ? cursor.line - page_size
|
|
|
- : 0;
|
|
|
-
|
|
|
- window->set_cursor({new_line, cursor.column});
|
|
|
- }
|
|
|
- else if (key_name == "PageDown") {
|
|
|
- auto window = core_->active_window();
|
|
|
- auto cursor = core_->cursor();
|
|
|
- int page_size = std::max(1, window->viewport().height - 2); // Leave 2 lines overlap
|
|
|
-
|
|
|
- // Move cursor down by page size
|
|
|
- size_t max_line = core_->buffer().line_count() - 1;
|
|
|
- size_t new_line = std::min(cursor.line + page_size, max_line);
|
|
|
-
|
|
|
- window->set_cursor({new_line, cursor.column});
|
|
|
- }
|
|
|
- // Home/End navigation
|
|
|
- else if (key_name == "Home") {
|
|
|
- core_->active_window()->move_to_line_start();
|
|
|
- }
|
|
|
- else if (key_name == "End") {
|
|
|
- core_->active_window()->move_to_line_end();
|
|
|
- }
|
|
|
- // Insert printable characters if unbound
|
|
|
- else if (key_name.size() == 1 && key_name[0] >= 32 && key_name[0] <= 126) {
|
|
|
- auto cursor = core_->cursor();
|
|
|
- core_->buffer().insert_char(cursor, key_name[0]);
|
|
|
- core_->active_window()->set_cursor({cursor.line, cursor.column + 1});
|
|
|
-
|
|
|
- // Debug cursor position
|
|
|
- auto new_cursor = core_->cursor();
|
|
|
- std::cerr << "[DEBUG] Inserted '" << key_name[0] << "' at (" << cursor.line << "," << cursor.column
|
|
|
- << ") -> cursor now at (" << new_cursor.line << "," << new_cursor.column << ")" << std::endl;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Request redraw after processing input
|
|
|
- if (content_widget_) {
|
|
|
- queue_redraw_all_windows(content_widget_);
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-std::unique_ptr<IEditorView> create_gtk_editor() {
|
|
|
- return std::make_unique<GtkEditor>();
|
|
|
-}
|
|
|
-
|
|
|
-} // namespace lumacs
|
|
|
-
|
|
|
-#else // LUMACS_WITH_GTK not defined
|
|
|
-
|
|
|
-namespace lumacs {
|
|
|
-std::unique_ptr<IEditorView> create_gtk_editor() {
|
|
|
- std::cerr << "Error: Lumacs was built without GTK support." << std::endl;
|
|
|
- return nullptr;
|
|
|
-}
|
|
|
-} // namespace lumacs
|
|
|
-
|
|
|
-#endif
|
|
|
+};
|