From 42be23652320cf9ff35ef2ac207d72743013d2bb Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 18 Jun 2026 19:23:50 +0100 Subject: [PATCH 1/4] Use MenuModel for extra_menu, use local actions --- src/Widgets/NavMarkGutterRenderer.vala | 3 +- src/Widgets/SourceView.vala | 113 ++++++++++++++----------- 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src/Widgets/NavMarkGutterRenderer.vala b/src/Widgets/NavMarkGutterRenderer.vala index 501a47192..9ceb9a88b 100644 --- a/src/Widgets/NavMarkGutterRenderer.vala +++ b/src/Widgets/NavMarkGutterRenderer.vala @@ -9,7 +9,6 @@ public class Scratch.Widgets.NavMarkGutterRenderer : Gtk.SourceGutterRendererPix public Gtk.TextBuffer buffer { get; construct; } public bool has_marks { get { - purge_and_sort_mark_list (); return mark_list.size > 0; } } @@ -101,6 +100,8 @@ public class Scratch.Widgets.NavMarkGutterRenderer : Gtk.SourceGutterRendererPix } sorted_line_list.sort (); + + notify_property ("has-marks"); } public void delete_mark_at_line (int line) { diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index 23188acc8..3de368401 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -29,6 +29,7 @@ namespace Scratch.Widgets { public GLib.File location { get; set; } public FolderManager.ProjectFolderItem project { get; set; default = null; } + public SimpleActionGroup actions { get; construct; } private string font; private uint selection_changed_timer = 0; @@ -55,6 +56,7 @@ namespace Scratch.Widgets { set { ((Gtk.SourceBuffer) buffer).language = value; } + get { return ((Gtk.SourceBuffer) buffer).language; } @@ -192,7 +194,67 @@ namespace Scratch.Widgets { } }); - populate_popup.connect_after (on_context_menu); + // Actions and menumodel for additional context menu items + var sort_action = new SimpleAction ("sort-lines", null); + var mark_action = new SimpleAction ("mark", null); + var next_mark_action = new SimpleAction ("next-mark", null); + var prev_mark_action = new SimpleAction ("prev-mark", null); + var toggle_comment_action = new SimpleAction ("toggle-comment", null); + + actions = new SimpleActionGroup (); + actions.add_action (sort_action); + actions.add_action (mark_action); + actions.add_action (next_mark_action); + actions.add_action (prev_mark_action); + actions.add_action (toggle_comment_action); + + insert_action_group ("sourceview", actions); + sort_action.activate.connect (sort_selected_lines); + mark_action.activate.connect (add_mark_at_cursor); + next_mark_action.activate.connect (goto_next_mark); + prev_mark_action.activate.connect (goto_previous_mark); + toggle_comment_action.activate.connect (() => { + CommentToggler.toggle_comment (buffer as Gtk.SourceBuffer); + }); + + var extra_menu = new Menu (); + extra_menu.append (_("Sort Lines"), "sort-lines"); + extra_menu.append (_("Mark Line"), "mark"); + extra_menu.append (_("Previous Mark"), "next-mark"); + extra_menu.append (_("Next Mark"), "prev-mark"); + extra_menu.append (_("Toggle Comment"), "toggle-comment"); + + // enable/disable action depending on changes to language, selection, marks in document + buffer.notify["has-selection"].connect (() => { + sort_action.set_enabled (buffer.has_selection); + }); + buffer.notify["language"].connect (() => { + toggle_comment_action.set_enabled (CommentToggler.language_has_comments (((Gtk.SourceBuffer)buffer).language)); + }); + buffer.notify_property ("has-selection"); + buffer.notify_property ("has-language"); + + navmark_gutter_renderer.notify["has-marks"].connect (() => { + next_mark_action.set_enabled (navmark_gutter_renderer.has_marks); + prev_mark_action.set_enabled (next_mark_action.get_enabled ()); + }); + navmark_gutter_renderer.notify_property ("has-marks"); + + // For Gtk3 we need to convert extra_menu to additional Gtk.MenuItems. This is omitted in Gtk4 + populate_popup.connect_after ((menu) => { + scroll_mark_onscreen (buffer.get_mark ("insert")); //TODO Check if still needed in Gtk4 + for (int i = 0; i < extra_menu.get_n_items (); i++) { + var name = extra_menu.get_item_attribute_value (i, "label", VariantType.STRING).get_string (); + var action = extra_menu.get_item_attribute_value (i, "action", VariantType.STRING).get_string (); + // warning ("adding menuitem name %s, action_name %s", name, action); + menu.add ( + new Gtk.MenuItem.with_label (name) { + action_name = "sourceview." + action + } + ); + } + menu.show_all (); + }); size_allocate.connect ((allocation) => { // Throttle for performance @@ -588,55 +650,6 @@ namespace Scratch.Widgets { } } - private void on_context_menu (Gtk.Menu menu) { - scroll_mark_onscreen (buffer.get_mark ("insert")); - - var sort_item = new Gtk.MenuItem.with_label (_("Sort Lines")) { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_SORT_LINES - }; - - var add_edit_item = new Gtk.MenuItem.with_label (_("Mark Line")) { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_ADD_MARK - }; - - var previous_edit_item = new Gtk.MenuItem.with_label (_("Previous Mark")) { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_PREVIOUS_MARK - }; - - var next_edit_item = new Gtk.MenuItem.with_label (_("Next Mark")) { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_NEXT_MARK - }; - - menu.add (sort_item); - menu.add (add_edit_item); - menu.add (previous_edit_item); - menu.add (next_edit_item); - - if (buffer is Gtk.SourceBuffer) { - var comment_item = new Gtk.MenuItem.with_label (_("Toggle Comment")) { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_TOGGLE_COMMENT - }; - - var can_comment = CommentToggler.language_has_comments (((Gtk.SourceBuffer) buffer).get_language ()); - if (!can_comment) { - comment_item.action_name = ""; - } - - menu.add (comment_item); - } - - menu.show_all (); - - if (!(get_selected_line_count () > 1)) { - sort_item.action_name = ""; - } - - if (!navmark_gutter_renderer.has_marks) { - previous_edit_item.action_name = ""; - next_edit_item.action_name = ""; - } - } - private static int calculate_bottom_margin (int height_in_px) { const int LINES_TO_KEEP = 3; const double PT_TO_PX = 1.6667; // Normally 1.3333, but this accounts for line-height From 79cec894cd9939b7b33b4d67e2382d4a746e7136 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 18 Jun 2026 19:53:51 +0100 Subject: [PATCH 2/4] Fix loss of possible loss of selection on focus out --- src/Services/Document.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Services/Document.vala b/src/Services/Document.vala index 7ac1b5100..d894b5de7 100644 --- a/src/Services/Document.vala +++ b/src/Services/Document.vala @@ -1315,7 +1315,7 @@ namespace Scratch.Services { /* Pull the buffer into an array and then work out which parts are to be deleted. * Do not strip line currently being edited unless forced */ private void strip_trailing_spaces () { - if (!loaded || source_view.language == null) { + if (!loaded || source_view.language == null || source_view.buffer.has_selection) { return; } From 317e75ed9860fb830719c7b99604fae0452704c6 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 18 Jun 2026 20:02:52 +0100 Subject: [PATCH 3/4] Fix incorrect property name --- src/Widgets/SourceView.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index 3de368401..f6dc324cf 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -232,7 +232,7 @@ namespace Scratch.Widgets { toggle_comment_action.set_enabled (CommentToggler.language_has_comments (((Gtk.SourceBuffer)buffer).language)); }); buffer.notify_property ("has-selection"); - buffer.notify_property ("has-language"); + buffer.notify_property ("language"); navmark_gutter_renderer.notify["has-marks"].connect (() => { next_mark_action.set_enabled (navmark_gutter_renderer.has_marks); From 1f22d69324960539d6fcabfd902759097e25928c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 19 Jun 2026 09:36:29 +0100 Subject: [PATCH 4/4] Move context menu action accelerators to SourceView --- src/MainWindow.vala | 64 ------------------------------------- src/Widgets/SourceView.vala | 37 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 64 deletions(-) diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 78b9b1d1e..d823fc836 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -79,14 +79,10 @@ namespace Scratch { public const string ACTION_OPEN_PROJECT = "action-open-project"; public const string ACTION_COLLAPSE_ALL_FOLDERS = "action-collapse-all-folders"; public const string ACTION_GO_TO = "action-go-to"; - public const string ACTION_SORT_LINES = "action-sort-lines"; public const string ACTION_NEW_TAB = "action-new-tab"; public const string ACTION_NEW_FROM_CLIPBOARD = "action-new-from-clipboard"; public const string ACTION_DUPLICATE_TAB = "action-duplicate-tab"; public const string ACTION_PREFERENCES = "preferences"; - public const string ACTION_ADD_MARK = "action_add_mark"; - public const string ACTION_PREVIOUS_MARK = "action_previous_mark"; - public const string ACTION_NEXT_MARK = "action_next_mark"; public const string ACTION_UNDO = "action-undo"; public const string ACTION_REDO = "action-redo"; @@ -104,7 +100,6 @@ namespace Scratch { public const string ACTION_ZOOM_DEFAULT = "action-zoom-default"; public const string ACTION_ZOOM_IN = "action-zoom-in"; public const string ACTION_ZOOM_OUT = "action-zoom-out"; - public const string ACTION_TOGGLE_COMMENT = "action-toggle-comment"; public const string ACTION_TOGGLE_SHOW_FIND = "action-toggle_show-find"; public const string ACTION_TOGGLE_SIDEBAR = "action-toggle-sidebar"; public const string ACTION_FOCUS_SIDEBAR = "action-focus-sidebar"; @@ -151,7 +146,6 @@ namespace Scratch { { ACTION_TOGGLE_SHOW_FIND, action_toggle_show_find, null, "false" }, { ACTION_TEMPLATES, action_templates }, { ACTION_GO_TO, action_go_to }, - { ACTION_SORT_LINES, action_sort_lines }, { ACTION_NEW_TAB, action_new_tab }, { ACTION_NEW_FROM_CLIPBOARD, action_new_tab_from_clipboard }, { ACTION_DUPLICATE_TAB, action_duplicate_tab }, @@ -168,7 +162,6 @@ namespace Scratch { { ACTION_ZOOM_DEFAULT, action_set_default_zoom }, { ACTION_ZOOM_IN, action_zoom_in }, { ACTION_ZOOM_OUT, action_zoom_out}, - { ACTION_TOGGLE_COMMENT, action_toggle_comment }, { ACTION_TOGGLE_SIDEBAR, action_toggle_sidebar, null, "true" }, { ACTION_FOCUS_SIDEBAR, action_focus_sidebar }, { ACTION_FOCUS_DOCUMENT, action_focus_document }, @@ -180,9 +173,6 @@ namespace Scratch { { ACTION_PREVIOUS_TAB, action_previous_tab }, { ACTION_CLEAR_LINES, action_clear_lines }, { ACTION_BRANCH_ACTIONS, action_branch_actions, "s" }, - { ACTION_ADD_MARK, action_add_mark}, - { ACTION_PREVIOUS_MARK, action_previous_mark}, - { ACTION_NEXT_MARK, action_next_mark}, { ACTION_CLOSE_TAB, action_close_tab, "s" }, { ACTION_CLOSE_TABS_TO_RIGHT, action_close_tabs_to_right }, { ACTION_CLOSE_OTHER_TABS, action_close_other_tabs }, @@ -220,7 +210,6 @@ namespace Scratch { action_accelerators.set (ACTION_SAVE, "s"); action_accelerators.set (ACTION_SAVE_AS, "s"); action_accelerators.set (ACTION_GO_TO, "i"); - action_accelerators.set (ACTION_SORT_LINES, "F5"); action_accelerators.set (ACTION_NEW_TAB, "n"); action_accelerators.set (ACTION_DUPLICATE_TAB, "k" ); action_accelerators.set (ACTION_UNDO, "z"); @@ -239,8 +228,6 @@ namespace Scratch { action_accelerators.set (ACTION_ZOOM_IN, "KP_Add"); action_accelerators.set (ACTION_ZOOM_OUT, "minus"); action_accelerators.set (ACTION_ZOOM_OUT, "KP_Subtract"); - action_accelerators.set (ACTION_TOGGLE_COMMENT, "m"); - action_accelerators.set (ACTION_TOGGLE_COMMENT, "slash"); action_accelerators.set (ACTION_TOGGLE_SIDEBAR, "F9"); // GNOME action_accelerators.set (ACTION_TOGGLE_SIDEBAR, "backslash"); // Atom action_accelerators.set (ACTION_FOCUS_SIDEBAR, "Left"); @@ -255,9 +242,6 @@ namespace Scratch { action_accelerators.set (ACTION_PREVIOUS_TAB, "Page_Up"); action_accelerators.set (ACTION_CLEAR_LINES, "K"); //Geany action_accelerators.set (ACTION_BRANCH_ACTIONS + "::", "B"); - action_accelerators.set (ACTION_ADD_MARK, "equal"); - action_accelerators.set (ACTION_PREVIOUS_MARK, "Left"); - action_accelerators.set (ACTION_NEXT_MARK, "Right"); action_accelerators.set (ACTION_HIDE_PROJECT_DOCS + "::", "h"); action_accelerators.set (ACTION_MOVE_TAB_TO_NEW_WINDOW, "n"); action_accelerators.set (ACTION_RESTORE_PROJECT_DOCS + "::", "r"); @@ -1405,27 +1389,6 @@ namespace Scratch { buffer.insert (ref start, selected.up (), -1); } - private void action_toggle_comment () { - var doc = get_focused_document (); - if (doc == null) { - return; - } - - var buffer = doc.source_view.buffer; - if (buffer is Gtk.SourceBuffer) { - CommentToggler.toggle_comment (buffer as Gtk.SourceBuffer); - } - } - - private void action_sort_lines () { - var doc = get_focused_document (); - if (doc == null) { - return; - } - - doc.source_view.sort_selected_lines (); - } - private void action_toggle_sidebar (SimpleAction action) { if (sidebar == null) { return; @@ -1526,33 +1489,6 @@ namespace Scratch { folder_manager_view.branch_actions (get_target_path_for_actions (param)); } - private void action_previous_mark () { - var doc = get_focused_document (); - if (doc == null) { - return; - } - - doc.source_view.goto_previous_mark (); - } - - private void action_next_mark () { - var doc = get_focused_document (); - if (doc == null) { - return; - } - - doc.source_view.goto_next_mark (); - } - - private void action_add_mark () { - var doc = get_focused_document (); - if (doc == null) { - return; - } - - doc.source_view.add_mark_at_cursor (); - } - private void action_move_tab_to_new_window () { document_view.transfer_tab_to_new_window (); } diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index f6dc324cf..48348ec21 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -39,6 +39,7 @@ namespace Scratch.Widgets { private string selected_text = ""; private GitGutterRenderer git_diff_gutter_renderer; private NavMarkGutterRenderer navmark_gutter_renderer; + private Gtk.EventControllerKey key_controller; private const uint THROTTLE_MS = 400; private double total_delta = 0; @@ -256,6 +257,42 @@ namespace Scratch.Widgets { menu.show_all (); }); + // Handle context menu shortcuts here. + // In Gtk3 we use a EventControllerKey but after porting to Gtk4 we can replace with Gtk.Shortcuts + key_controller = new Gtk.EventControllerKey (application.get_active_window ()) { + propagation_phase = CAPTURE + }; + key_controller.key_pressed.connect ((kv, kc, state) => { + if (!this.is_focus || !Gtk.accelerator_valid (kv, state)) { + return false; + } + + var mods = (state & Gtk.accelerator_get_default_mod_mask ()); + var accel = Gtk.accelerator_name (kv, mods); + switch (accel) { + case "F5": + sort_selected_lines (); + return true; + case "equal": + add_mark_at_cursor (); + return true; + case "Left": + goto_next_mark (); + return true; + case "Right": + goto_previous_mark (); + return true; + case "m": + case "slash": + CommentToggler.toggle_comment (buffer as Gtk.SourceBuffer); + return true; + default: + break; + } + + return false; + }); + size_allocate.connect ((allocation) => { // Throttle for performance if (size_allocate_timer == 0) {