From 6164bb7043f8a40c096ee717671c83d9dc5f1fc4 Mon Sep 17 00:00:00 2001 From: Stan Janssen Date: Sun, 24 May 2026 16:44:33 +0200 Subject: [PATCH] Correct accelerator arrow keys for RTL languages This corrects accelerators that include a Left or Right arrow key when using a Right-to-Left (RTL) language, where UI elements are draw from right to left. This includes switching focus to the sidebar, which is on the right side in an RTL situation, as well as the 'drill down' and 'drill up' keys when navigating in list or Miller views, and the arrow keys for selecting the previous and next icon in icon view (the icons are also drawn right to left, with the 'next' icon being on the left of the 'previous' icon in an RTL situation. This also matches the directions of the arrow buttons in the tool bar. --- src/View/IconView.vala | 14 ++++++- src/View/ListView.vala | 51 +++++++++++++----------- src/View/Miller.vala | 88 +++++++++++++++++++++--------------------- src/View/Window.vala | 27 +++++++++---- 4 files changed, 104 insertions(+), 76 deletions(-) diff --git a/src/View/IconView.vala b/src/View/IconView.vala index f43c567d9..f855cb216 100644 --- a/src/View/IconView.vala +++ b/src/View/IconView.vala @@ -243,11 +243,21 @@ public class Files.IconView : Files.AbstractDirectoryView { /* Override native Gtk.IconView cursor handling */ protected override bool move_cursor (uint keyval, bool only_shift_pressed, bool control_pressed) { + uint prev_key; + uint next_key; + if (Gtk.StateFlags.DIR_RTL in get_style_context ().get_state ()) { + prev_key = Gdk.Key.Right; + next_key = Gdk.Key.Left; + } else { + prev_key = Gdk.Key.Left; + next_key = Gdk.Key.Right; + } + Gtk.TreePath? path = get_path_at_cursor (); if (path != null) { - if (keyval == Gdk.Key.Right) { + if (keyval == next_key) { path.next (); /* Does not check if path is valid */ - } else if (keyval == Gdk.Key.Left) { + } else if (keyval == prev_key) { path.prev (); } else if (keyval == Gdk.Key.Up) { path = up (path); diff --git a/src/View/ListView.vala b/src/View/ListView.vala index ddf08eaf4..0c754fd69 100644 --- a/src/View/ListView.vala +++ b/src/View/ListView.vala @@ -160,36 +160,41 @@ namespace Files { } protected override bool on_view_key_press_event (uint keyval, uint keycode, Gdk.ModifierType state) { + uint up_key; + uint down_key; + if (Gtk.StateFlags.DIR_RTL in get_style_context ().get_state ()) { + up_key = Gdk.Key.Right; + down_key = Gdk.Key.Left; + } else { + up_key = Gdk.Key.Left; + down_key = Gdk.Key.Right; + } + bool control_pressed = ((state & Gdk.ModifierType.CONTROL_MASK) != 0); bool shift_pressed = ((state & Gdk.ModifierType.SHIFT_MASK) != 0); if (!control_pressed && !shift_pressed) { - switch (keyval) { - case Gdk.Key.Right: - Gtk.TreePath? path = null; - tree.get_cursor (out path, null); - - if (path != null) { - tree.expand_row (path, false); - } - - return true; + if (keyval == down_key) { + Gtk.TreePath? path = null; + tree.get_cursor (out path, null); - case Gdk.Key.Left: - Gtk.TreePath? path = null; - tree.get_cursor (out path, null); + if (path != null) { + tree.expand_row (path, false); + } - if (path != null) { - if (tree.is_row_expanded (path)) { - tree.collapse_row (path); - } else if (path.up ()) { - tree.collapse_row (path); - } + return true; + } else if (keyval == up_key) { + Gtk.TreePath? path = null; + tree.get_cursor (out path, null); + + if (path != null) { + if (tree.is_row_expanded (path)) { + tree.collapse_row (path); + } else if (path.up ()) { + tree.collapse_row (path); } + } - return true; - - default: - break; + return true; } } diff --git a/src/View/Miller.vala b/src/View/Miller.vala index df2e46f68..435c8bd70 100644 --- a/src/View/Miller.vala +++ b/src/View/Miller.vala @@ -424,59 +424,61 @@ namespace Files.View { View.Slot? to_activate = null; var prefs = Files.Preferences.get_default (); - switch (keyval) { - case Gdk.Key.Left: - if (current_position > 0) { - if (prefs.show_file_preview) { - clear_file_details (); - } - - to_activate = slot_list.nth_data (current_position - 1); - } - - break; - case Gdk.Key.Right: - if (current_slot.get_selected_files () == null) { - return true; - } + uint up_key; + uint down_key; - Files.File? selected_file = current_slot.get_selected_files ().data; - if (selected_file == null) { - return true; - } + // Determine which key is the "drill down" key based on the layout direction + if (Gtk.StateFlags.DIR_RTL in scrolled_window.get_style_context ().get_state ()) { + up_key = Gdk.Key.Right; + down_key = Gdk.Key.Left; + } else { + up_key = Gdk.Key.Left; + down_key = Gdk.Key.Right; + } - GLib.File current_location = selected_file.location; - GLib.File? next_location = null; - if (current_position < slot_list.length () - 1) { //Can be assumed to limited in length - next_location = slot_list.nth_data (current_position + 1).location; + if (keyval == up_key) { + if (current_position > 0) { + if (prefs.show_file_preview) { + clear_file_details (); } - if (next_location != null && next_location.equal (current_location)) { - to_activate = slot_list.nth_data (current_position + 1); - } else if (selected_file.is_folder ()) { - add_location (current_location, current_slot); - return true; - } + to_activate = slot_list.nth_data (current_position - 1); + } - break; + } else if (keyval == down_key) { + if (current_slot.get_selected_files () == null) { + return true; + } - case Gdk.Key.BackSpace: - if (current_position > 0) { - truncate_list_after_slot (slot_list.nth_data (current_position - 1)); - } else { - ctab.go_up (); - return true; - } + Files.File? selected_file = current_slot.get_selected_files ().data; + if (selected_file == null) { + return true; + } - if (prefs.show_file_preview) { - clear_file_details (); - } + GLib.File current_location = selected_file.location; + GLib.File? next_location = null; + if (current_position < slot_list.length () - 1) { //Can be assumed to limited in length + next_location = slot_list.nth_data (current_position + 1).location; + } - break; + if (next_location != null && next_location.equal (current_location)) { + to_activate = slot_list.nth_data (current_position + 1); + } else if (selected_file.is_folder ()) { + add_location (current_location, current_slot); + return true; + } + } else if (keyval == Gdk.Key.BackSpace) { + if (current_position > 0) { + truncate_list_after_slot (slot_list.nth_data (current_position - 1)); + } else { + ctab.go_up (); + return true; + } - default: - break; + if (prefs.show_file_preview) { + clear_file_details (); + } } if (to_activate != null) { diff --git a/src/View/Window.vala b/src/View/Window.vala index 3000df84c..4b175a565 100644 --- a/src/View/Window.vala +++ b/src/View/Window.vala @@ -158,12 +158,19 @@ public class Files.View.Window : Hdy.ApplicationWindow { marlin_app.set_accels_for_action ("win.go-to::NETWORK", {"N"}); marlin_app.set_accels_for_action ("win.go-to::SERVER", {"C"}); marlin_app.set_accels_for_action ("win.go-to::UP", {"Up"}); - marlin_app.set_accels_for_action ("win.forward(1)", {"Right", "XF86Forward"}); - marlin_app.set_accels_for_action ("win.back(1)", {"Left", "XF86Back"}); marlin_app.set_accels_for_action ("win.tab::TAB", {"K"}); marlin_app.set_accels_for_action ("win.tab::WINDOW", {"N"}); - marlin_app.set_accels_for_action ("win.focus-sidebar", {"Left"}); - marlin_app.set_accels_for_action ("win.focus-current-container", {"Right"}); + + // Make sure Left and Right arrow keys are mapped logically based on layout direction + if (Gtk.StateFlags.DIR_RTL in get_style_context ().get_state ()) { + marlin_app.set_accels_for_action ("win.focus-sidebar", {"Right"}); + marlin_app.set_accels_for_action ("win.focus-current-container", {"Left"}); + marlin_app.set_accels_for_action ("win.forward(1)", {"Right", "XF86Forward"}); + } else { + marlin_app.set_accels_for_action ("win.focus-sidebar", {"Left"}); + marlin_app.set_accels_for_action ("win.focus-current-container", {"Right"}); + marlin_app.set_accels_for_action ("win.back(1)", {"Left", "XF86Back"}); + } } build_window (); @@ -190,15 +197,19 @@ public class Files.View.Window : Hdy.ApplicationWindow { private void build_window () { button_back = new View.Chrome.ButtonWithMenu ("go-previous-symbolic"); - - button_back.tooltip_markup = Granite.markup_accel_tooltip ({"Left"}, _("Previous")); button_back.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); button_forward = new View.Chrome.ButtonWithMenu ("go-next-symbolic"); - - button_forward.tooltip_markup = Granite.markup_accel_tooltip ({"Right"}, _("Next")); button_forward.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + if (Gtk.StateFlags.DIR_RTL in get_style_context ().get_state ()) { + button_back.tooltip_markup = Granite.markup_accel_tooltip ({"Right"}, _("Previous")); + button_forward.tooltip_markup = Granite.markup_accel_tooltip ({"Left"}, _("Next")); + } else { + button_back.tooltip_markup = Granite.markup_accel_tooltip ({"Left"}, _("Previous")); + button_forward.tooltip_markup = Granite.markup_accel_tooltip ({"Right"}, _("Next")); + } + view_switcher = new Chrome.ViewSwitcher ((SimpleAction)lookup_action ("view-mode")) { margin_end = 20 };