From 4aeaffdd441a9ba352136edc8612d11615a14e6d Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 6 Mar 2022 21:27:47 -0600 Subject: [PATCH] Mostly integrated keybindings setup --- .../SolarFM/solarfm/context/controller.py | 33 ++++- .../solarfm/context/controller_data.py | 30 +++-- .../context/mixins/exception_hook_mixin.py | 2 +- .../solarfm/context/mixins/ui/tab_mixin.py | 24 +--- .../mixins/ui/widget_file_action_mixin.py | 67 +++++----- .../solarfm/context/mixins/ui/window_mixin.py | 25 ++-- .../context/signals/keyboard_signals_mixin.py | 125 ++++++++---------- .../SolarFM/solarfm/utils/keybindings.py | 124 +++++++++++++++++ .../SolarFM/solarfm/utils/settings.py | 36 +++++ .../usr/share/solarfm/Main_Window.glade | 6 +- 10 files changed, 317 insertions(+), 155 deletions(-) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller.py index fc7fa7c..52285b2 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller.py @@ -77,12 +77,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi data = method(*(self, *parameters)) self.plugins.send_message_to_plugin(type, data) - def open_terminal(self, widget=None, eve=None): - wid, tid = self.fm_controller.get_active_wid_and_tid() - tab = self.get_fm_window(wid).get_tab_by_id(tid) - dir = tab.get_current_directory() - tab.execute(f"{tab.terminal_app}", dir) - def save_load_session(self, action="save_session"): wid, tid = self.fm_controller.get_active_wid_and_tid() tab = self.get_fm_window(wid).get_tab_by_id(tid) @@ -169,3 +163,30 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.show_new_file_menu() if action in ["save_session", "save_session_as", "load_session"]: self.save_load_session(action) + + + + + + + def go_home(self, widget=None, eve=None): + self.builder.get_object("go_home").released() + + def refresh_tab(self, widget=None, eve=None): + self.builder.get_object("refresh_tab").released() + + def go_up(self, widget=None, eve=None): + self.builder.get_object("go_up").released() + + def grab_focus_path_entry(self, widget=None, eve=None): + self.builder.get_object("path_entry").grab_focus() + + def tggl_top_main_menubar(self, widget=None, eve=None): + top_main_menubar = self.builder.get_object("top_main_menubar") + top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show() + + def open_terminal(self, widget=None, eve=None): + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + dir = tab.get_current_directory() + tab.execute(f"{tab.terminal_app}", dir) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py index 28a09e8..9e06eec 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py @@ -10,22 +10,29 @@ from shellfm.windows.controller import WindowController from plugins.plugins import Plugins +class State: + wid = None + tid = None + tab = None + icon_grid = None + store = None class Controller_Data: """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """ def setup_controller_data(self, _settings): + self.settings = _settings + self.builder = self.settings.get_builder() + self.logger = self.settings.get_logger() + self.keybindings = self.settings.get_keybindings() + self.trashman = XDGTrash() self.fm_controller = WindowController() self.plugins = Plugins(_settings) self.state = self.fm_controller.load_state() self.trashman.regenerate() - self.settings = _settings - self.builder = self.settings.get_builder() - self.logger = self.settings.get_logger() - self.window = self.settings.get_main_window() self.window1 = self.builder.get_object("window_1") self.window2 = self.builder.get_object("window_2") @@ -109,6 +116,7 @@ class Controller_Data: self.window.connect("delete-event", self.tear_down) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) + def get_current_state(self): ''' Returns the state info most useful for any given context and action intent. @@ -117,13 +125,15 @@ class Controller_Data: a (obj): self Returns: - wid, tid, tab, icon_grid, store + state (obj): State ''' - wid, tid = self.fm_controller.get_active_wid_and_tid() - tab = self.get_fm_window(wid).get_tab_by_id(tid) - icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid") - store = icon_grid.get_model() - return wid, tid, tab, icon_grid, store + state = State() + wid, tid = self.fm_controller.get_active_wid_and_tid() + state.wid, state.tid = self.fm_controller.get_active_wid_and_tid() + state.tab = self.get_fm_window(wid).get_tab_by_id(tid) + state.icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid") + state.store = state.icon_grid.get_model() + return state def clear_console(self): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/exception_hook_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/exception_hook_mixin.py index 08d29cb..81a0b95 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/exception_hook_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/exception_hook_mixin.py @@ -23,7 +23,7 @@ class ExceptionHookMixin: data = f"Exec Type: {exec_type} <--> Value: {value}\n\n{trace}\n\n\n\n" start_itr = self.message_buffer.get_start_iter() self.message_buffer.place_cursor(start_itr) - self.display_message(self.error, data) + self.display_message(self.error_color, data) def display_message(self, type, text, seconds=None): self.message_buffer.insert_at_cursor(text) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py index 9ce3953..910f3f2 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py @@ -96,9 +96,9 @@ class TabMixin(WidgetMixin): return notebook.get_children()[1].get_children()[0] def refresh_tab(data=None): - wid, tid, tab, icon_grid, store = self.get_current_state() - tab.load_directory() - self.load_store(tab, store) + state = self.get_current_state() + state.tab.load_directory() + self.load_store(state.tab, state.store) def update_tab(self, tab_label, tab, store, wid, tid): self.load_store(tab, store) @@ -172,28 +172,14 @@ class TabMixin(WidgetMixin): pass def set_path_entry(self, button=None, eve=None): - wid, tid, tab, icon_grid, store = self.get_current_state() - path = f"{tab.get_current_directory()}/{button.get_label()}" + state = self.get_current_state() + path = f"{state.tab.get_current_directory()}/{button.get_label()}" path_entry = self.builder.get_object("path_entry") path_entry.set_text(path) path_entry.grab_focus_without_selecting() path_entry.set_position(-1) self.path_menu.popdown() - def keyboard_close_tab(self): - wid, tid = self.fm_controller.get_active_wid_and_tid() - notebook = self.builder.get_object(f"window_{wid}") - scroll = self.builder.get_object(f"{wid}|{tid}") - page = notebook.page_num(scroll) - tab = self.get_fm_window(wid).get_tab_by_id(tid) - watcher = tab.get_dir_watcher() - watcher.cancel() - - self.get_fm_window(wid).delete_tab_by_id(tid) - notebook.remove_page(page) - self.fm_controller.save_state() - self.set_window_title() - def show_hide_hidden_files(self): wid, tid = self.fm_controller.get_active_wid_and_tid() tab = self.get_fm_window(wid).get_tab_by_id(tid) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py index 1a84305..0a2b8f3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py @@ -102,10 +102,8 @@ class WidgetFileActionMixin: self.load_store(tab, store) tab_widget_label.set_label(tab.get_end_of_path()) - - _wid, _tid, _tab, _icon_grid, _store = self.get_current_state() - - if [wid, tid] in [_wid, _tid]: + state = self.get_current_state() + if [wid, tid] in [state.wid, state.tid]: self.set_bottom_labels(tab) @@ -130,47 +128,44 @@ class WidgetFileActionMixin: def open_files(self): - wid, tid, tab, icon_grid, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) - + state = self.get_current_state() + uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) for file in uris: - tab.open_file_locally(file) + state.tab.open_file_locally(file) def open_with_files(self, appchooser_widget): - wid, tid, tab, icon_grid, store = self.get_current_state() + state = self.get_current_state() app_info = appchooser_widget.get_app_info() - uris = self.format_to_uris(store, wid, tid, self.selected_files) - - tab.app_chooser_exec(app_info, uris) + uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files) + state.tab.app_chooser_exec(app_info, uris) def execute_files(self, in_terminal=False): - wid, tid, tab, icon_grid, store = self.get_current_state() - paths = self.format_to_uris(store, wid, tid, self.selected_files, True) - current_dir = tab.get_current_directory() + state = self.get_current_state() + paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) + current_dir = state.tab.get_current_directory() command = None - for path in paths: - command = f"exec '{path}'" if not in_terminal else f"{tab.terminal_app} -e '{path}'" - tab.execute(command, start_dir=tab.get_current_directory(), use_os_system=False) + command = f"exec '{path}'" if not in_terminal else f"{state.tab.terminal_app} -e '{path}'" + state.tab.execute(command, start_dir=state.tab.get_current_directory(), use_os_system=False) def archive_files(self, archiver_dialogue): - wid, tid, tab, icon_grid, store = self.get_current_state() - paths = self.format_to_uris(store, wid, tid, self.selected_files, True) + state = self.get_current_state() + paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) save_target = archiver_dialogue.get_filename(); sItr, eItr = self.arc_command_buffer.get_bounds() pre_command = self.arc_command_buffer.get_text(sItr, eItr, False) pre_command = pre_command.replace("%o", save_target) pre_command = pre_command.replace("%N", ' '.join(paths)) - command = f"{tab.terminal_app} -e '{pre_command}'" + command = f"{state.tab.terminal_app} -e '{pre_command}'" - tab.execute(command, start_dir=None, use_os_system=True) + state.tab.execute(command, start_dir=None, use_os_system=True) def rename_files(self): rename_label = self.builder.get_object("file_to_rename_label") rename_input = self.builder.get_object("new_rename_fname") - wid, tid, tab, icon_grid, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) + state = self.get_current_state() + uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) for uri in uris: entry = uri.split("/")[-1] @@ -186,7 +181,7 @@ class WidgetFileActionMixin: break rname_to = rename_input.get_text().strip() - target = f"{tab.get_current_directory()}/{rname_to}" + target = f"{state.tab.get_current_directory()}/{rname_to}" self.handle_files([uri], "rename", target) @@ -196,13 +191,13 @@ class WidgetFileActionMixin: self.selected_files.clear() def cut_files(self): - wid, tid, tab, icon_grid, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) + state = self.get_current_state() + uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) self.to_cut_files = uris def copy_files(self): - wid, tid, tab, icon_grid, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) + state = self.get_current_state() + uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) self.to_copy_files = uris def paste_files(self): @@ -216,8 +211,8 @@ class WidgetFileActionMixin: self.handle_files(self.to_cut_files, "move", target) def delete_files(self): - wid, tid, tab, icon_grid, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) + state = self.get_current_state() + uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) response = None self.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?") @@ -231,7 +226,7 @@ class WidgetFileActionMixin: type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - tab.delete_file( file.get_path() ) + state.tab.delete_file( file.get_path() ) else: file.delete(cancellable=None) else: @@ -239,14 +234,14 @@ class WidgetFileActionMixin: def trash_files(self): - wid, tid, tab, icon_grid, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) + state = self.get_current_state() + uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) for uri in uris: self.trashman.trash(uri, False) def restore_trash_files(self): - wid, tid, tab, icon_grid, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) + state = self.get_current_state() + uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) for uri in uris: self.trashman.restore(filename=uri.split("/")[-1], verbose=False) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py index b1e3878..b94a561 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py @@ -25,8 +25,11 @@ class WindowMixin(TabMixin): self.fm_controller.create_window() notebook_tggl_button.set_active(True) - for tab in tabs: - self.create_new_tab_notebook(None, i, tab) + if tabs: + for tab in tabs: + self.create_new_tab_notebook(None, i, tab) + else: + self.create_new_tab_notebook(None, i, None) if is_hidden: self.toggle_notebook_pane(notebook_tggl_button) @@ -77,8 +80,8 @@ class WindowMixin(TabMixin): def set_bottom_labels(self, tab): - _wid, _tid, _tab, icon_grid, store = self.get_current_state() - selected_files = icon_grid.get_selected_items() + state = self.get_current_state() + selected_files = state.icon_grid.get_selected_items() current_directory = tab.get_current_directory() path_file = Gio.File.new_for_path(current_directory) mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None) @@ -96,7 +99,7 @@ class WindowMixin(TabMixin): self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}") self.bottom_path_label.set_label(tab.get_current_directory()) if selected_files: - uris = self.format_to_uris(store, _wid, _tid, selected_files, True) + uris = self.format_to_uris(state.store, state.wid, state.tid, selected_files, True) combined_size = 0 for uri in uris: try: @@ -189,17 +192,17 @@ class WindowMixin(TabMixin): return - wid, tid, tab, _icons_grid, store = self.get_current_state() - notebook = self.builder.get_object(f"window_{wid}") + state = self.get_current_state() + notebook = self.builder.get_object(f"window_{state.wid}") tab_label = self.get_tab_label(notebook, icons_grid) - fileName = store[item][1] - dir = tab.get_current_directory() + fileName = state.store[item][1] + dir = state.tab.get_current_directory() file = f"{dir}/{fileName}" if isdir(file): - tab.set_path(file) - self.update_tab(tab_label, tab, store, wid, tid) + state.tab.set_path(file) + self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid) else: self.open_files() except Exception as e: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py index 16ed902..c50b848 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py @@ -16,13 +16,14 @@ valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") class KeyboardSignalsMixin: """ KeyboardSignalsMixin keyboard hooks controller. """ + # TODO: Need to set methods that use this to somehow check the keybindings state instead. def unset_keys_and_data(self, widget=None, eve=None): self.ctrl_down = False self.shift_down = False self.alt_down = False self.is_searching = False - def global_key_press_controller(self, eve, user_data): + def on_global_key_press_controller(self, eve, user_data): keyname = Gdk.keyval_name(user_data.keyval).lower() if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: if "control" in keyname: @@ -32,13 +33,9 @@ class KeyboardSignalsMixin: if "alt" in keyname: self.alt_down = True - # NOTE: Yes, this should actually be mapped to some key controller setting - # file or something. Sue me. - def global_key_release_controller(self, eve, user_data): - keyname = Gdk.keyval_name(user_data.keyval).lower() - if debug: - print(f"global_key_release_controller > key > {keyname}") - + def on_global_key_release_controller(self, widget, event): + """Handler for keyboard events""" + keyname = Gdk.keyval_name(event.keyval).lower() if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: if "control" in keyname: self.ctrl_down = False @@ -47,68 +44,56 @@ class KeyboardSignalsMixin: if "alt" in keyname: self.alt_down = False - if self.ctrl_down and self.shift_down and keyname == "t": - self.unset_keys_and_data() - self.trash_files() - if self.ctrl_down: - if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: - self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released() - if keyname == "q": - self.tear_down() - if keyname == "slash" or keyname == "home": - self.builder.get_object("go_home").released() - if keyname == "r" or keyname == "f5": - self.builder.get_object("refresh_tab").released() - if keyname == "up" or keyname == "u": - self.builder.get_object("go_up").released() - if keyname == "l": - self.unset_keys_and_data() - self.builder.get_object("path_entry").grab_focus() - if keyname == "t": - self.builder.get_object("create_tab").released() - if keyname == "o": - self.unset_keys_and_data() - self.open_files() - if keyname == "w": - self.keyboard_close_tab() - if keyname == "h": - self.show_hide_hidden_files() - if keyname == "e": - self.unset_keys_and_data() - self.rename_files() - if keyname == "c": - self.copy_files() - self.to_cut_files.clear() - if keyname == "x": - self.to_copy_files.clear() - self.cut_files() - if keyname == "v": - self.paste_files() - if keyname == "n": - self.unset_keys_and_data() - self.show_new_file_menu() + mapping = self.keybindings.lookup(event) + if mapping: + getattr(self, mapping)() + return True + else: + if debug: + print(f"on_global_key_release_controller > key > {keyname}") - if keyname == "delete": - self.unset_keys_and_data() - self.delete_files() - if keyname == "f2": - self.unset_keys_and_data() - self.rename_files() - if keyname == "f4": - self.unset_keys_and_data() - self.open_terminal() - if keyname in ["alt_l", "alt_r"]: - top_main_menubar = self.builder.get_object("top_main_menubar") - top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show() + if self.ctrl_down: + if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: + self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released() - if re.fullmatch(valid_keyvalue_pat, keyname): - if not self.is_searching and not self.ctrl_down \ - and not self.shift_down and not self.alt_down: - focused_obj = self.window.get_focus() - if isinstance(focused_obj, Gtk.IconView): - self.is_searching = True - wid, tid, self.search_tab, self.search_icon_grid, store = self.get_current_state() - self.unset_keys_and_data() - self.popup_search_files(wid, keyname) - return + if re.fullmatch(valid_keyvalue_pat, keyname): + if not self.is_searching and not self.ctrl_down \ + and not self.shift_down and not self.alt_down: + focused_obj = self.window.get_focus() + if isinstance(focused_obj, Gtk.IconView): + self.is_searching = True + state = self.get_current_state() + self.search_tab = state.tab + self.search_icon_grid = state.icon_grid + + self.unset_keys_and_data() + self.popup_search_files(state.wid, keyname) + return True + + + + def keyboard_create_tab(self, widget=None, eve=None): + self.builder.get_object("create_tab").released() + + def keyboard_close_tab(self): + wid, tid = self.fm_controller.get_active_wid_and_tid() + notebook = self.builder.get_object(f"window_{wid}") + scroll = self.builder.get_object(f"{wid}|{tid}") + page = notebook.page_num(scroll) + tab = self.get_fm_window(wid).get_tab_by_id(tid) + watcher = tab.get_dir_watcher() + watcher.cancel() + + self.get_fm_window(wid).delete_tab_by_id(tid) + notebook.remove_page(page) + self.fm_controller.save_state() + self.set_window_title() + + def keyboard_copy_files(self, widget=None, eve=None): + self.to_cut_files.clear() + self.copy_files() + + def keyboard_cut_files(self, widget=None, eve=None): + self.to_copy_files.clear() + self.cut_files() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py new file mode 100644 index 0000000..fe192e7 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py @@ -0,0 +1,124 @@ +# Python imports +import re + +# Gtk imports +import gi +gi.require_version('Gdk', '3.0') +from gi.repository import Gdk + +# Application imports + + + +def err(log = ""): + """Print an error message""" + print(log) + + +class KeymapError(Exception): + """Custom exception for errors in keybinding configurations""" + +MODIFIER = re.compile('<([^<]+)>') +class Keybindings: + """Class to handle loading and lookup of Terminator keybindings""" + + modifiers = { + 'ctrl': Gdk.ModifierType.CONTROL_MASK, + 'control': Gdk.ModifierType.CONTROL_MASK, + 'primary': Gdk.ModifierType.CONTROL_MASK, + 'shift': Gdk.ModifierType.SHIFT_MASK, + 'alt': Gdk.ModifierType.MOD1_MASK, + 'super': Gdk.ModifierType.SUPER_MASK, + 'hyper': Gdk.ModifierType.HYPER_MASK, + 'mod2': Gdk.ModifierType.MOD2_MASK + } + + empty = {} + keys = None + _masks = None + _lookup = None + + def __init__(self): + self.keymap = Gdk.Keymap.get_default() + self.configure({}) + + def configure(self, bindings): + """Accept new bindings and reconfigure with them""" + self.keys = bindings + self.reload() + + def reload(self): + """Parse bindings and mangle into an appropriate form""" + self._lookup = {} + self._masks = 0 + + for action, bindings in list(self.keys.items()): + if not isinstance(bindings, tuple): + bindings = (bindings,) + + for binding in bindings: + if not binding or binding == "None": + continue + + try: + keyval, mask = self._parsebinding(binding) + # Does much the same, but with poorer error handling. + #keyval, mask = Gtk.accelerator_parse(binding) + except KeymapError as e: + err ("keybinding reload failed to parse binding '%s': %s" % (binding, e)) + else: + if mask & Gdk.ModifierType.SHIFT_MASK: + if keyval == Gdk.KEY_Tab: + keyval = Gdk.KEY_ISO_Left_Tab + mask &= ~Gdk.ModifierType.SHIFT_MASK + else: + keyvals = Gdk.keyval_convert_case(keyval) + if keyvals[0] != keyvals[1]: + keyval = keyvals[1] + mask &= ~Gdk.ModifierType.SHIFT_MASK + else: + keyval = Gdk.keyval_to_lower(keyval) + + self._lookup.setdefault(mask, {}) + self._lookup[mask][keyval] = action + self._masks |= mask + + def _parsebinding(self, binding): + """Parse an individual binding using Gtk's binding function""" + mask = 0 + modifiers = re.findall(MODIFIER, binding) + + if modifiers: + for modifier in modifiers: + mask |= self._lookup_modifier(modifier) + + key = re.sub(MODIFIER, '', binding) + if key == '': + raise KeymapError('No key found!') + + keyval = Gdk.keyval_from_name(key) + + if keyval == 0: + raise KeymapError("Key '%s' is unrecognised..." % key) + return (keyval, mask) + + def _lookup_modifier(self, modifier): + """Map modifier names to gtk values""" + try: + return self.modifiers[modifier.lower()] + except KeyError: + raise KeymapError("Unhandled modifier '<%s>'" % modifier) + + def lookup(self, event): + """Translate a keyboard event into a mapped key""" + try: + _found, keyval, _egp, _lvl, consumed = self.keymap.translate_keyboard_state( + event.hardware_keycode, + Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK), + event.group) + except TypeError: + err ("Keybinding lookup failed to translate keyboard event: %s" % dir(event)) + return None + + mask = (event.get_state() & ~consumed) & self._masks + return self._lookup.get(mask, self.empty).get(keyval, None) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py index df454c7..187e005 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py @@ -13,6 +13,39 @@ from gi.repository import Gdk # Application imports from .logger import Logger +from .keybindings import Keybindings + + + + +DEFAULTS = { + 'keybindings': { + 'help' : 'F1', + 'rename_files' : 'F2', + 'open_terminal' : 'F4', + 'refresh_tab' : 'F5', + 'delete_files' : 'Delete', + # 'tggl_top_main_menubar' : '', + 'trash_files' : 't', + 'tear_down' : 'q', + 'go_home' : 'slash', + 'refresh_tab' : 'r', + 'go_up' : 'Up', + 'grab_focus_path_entry' : 'l', + 'open_files' : 'o', + 'show_hide_hidden_files' : 'h', + 'rename_files' : 'e', + 'keyboard_create_tab' : 't', + 'keyboard_close_tab' : 'w', + 'keyboard_copy_files' : 'c', + 'keyboard_cut_files' : 'x', + 'paste_files' : 'v', + 'show_new_file_menu' : 'n', + }, +} + + + class Settings: @@ -46,9 +79,11 @@ class Settings: self._warning_color = "#ffa800" self._error_color = "#ff0000" + self.keybindings = Keybindings() self.main_window = None self.logger = Logger(self._CONFIG_PATH).get_logger() self.builder = Gtk.Builder() + self.keybindings.configure(DEFAULTS['keybindings']) self.builder.add_from_file(self._WINDOWS_GLADE) @@ -94,6 +129,7 @@ class Settings: def get_builder(self): return self.builder def get_logger(self): return self.logger + def get_keybindings(self): return self.keybindings def get_main_window(self): return self.main_window def get_plugins_path(self): return self._PLUGINS_PATH diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index c0e535c..d4b201c 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1182,6 +1182,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe gtk-cancel + True True True True @@ -1196,6 +1197,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe gtk-ok + True True True True @@ -1408,8 +1410,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 830 center - - + + True