Mostly integrated keybindings setup
This commit is contained in:
parent
8eccdfce7c
commit
4aeaffdd44
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
'''
|
||||
state = 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.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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -25,8 +25,11 @@ class WindowMixin(TabMixin):
|
|||
self.fm_controller.create_window()
|
||||
notebook_tggl_button.set_active(True)
|
||||
|
||||
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:
|
||||
|
|
|
@ -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,60 +44,18 @@ 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()
|
||||
|
||||
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 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()
|
||||
|
||||
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 re.fullmatch(valid_keyvalue_pat, keyname):
|
||||
if not self.is_searching and not self.ctrl_down \
|
||||
|
@ -108,7 +63,37 @@ class KeyboardSignalsMixin:
|
|||
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()
|
||||
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(wid, keyname)
|
||||
return
|
||||
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()
|
||||
|
|
|
@ -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)
|
|
@ -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' : '<Alt>',
|
||||
'trash_files' : '<Shift><Control>t',
|
||||
'tear_down' : '<Control>q',
|
||||
'go_home' : '<Control>slash',
|
||||
'refresh_tab' : '<Control>r',
|
||||
'go_up' : '<Control>Up',
|
||||
'grab_focus_path_entry' : '<Control>l',
|
||||
'open_files' : '<Control>o',
|
||||
'show_hide_hidden_files' : '<Control>h',
|
||||
'rename_files' : '<Control>e',
|
||||
'keyboard_create_tab' : '<Control>t',
|
||||
'keyboard_close_tab' : '<Control>w',
|
||||
'keyboard_copy_files' : '<Control>c',
|
||||
'keyboard_cut_files' : '<Control>x',
|
||||
'paste_files' : '<Control>v',
|
||||
'show_new_file_menu' : '<Control>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
|
||||
|
||||
|
|
|
@ -1182,6 +1182,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
|||
<child>
|
||||
<object class="GtkButton" id="button11">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="use-action-appearance">True</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
|
@ -1196,6 +1197,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
|||
<child>
|
||||
<object class="GtkButton" id="button12">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="use-action-appearance">True</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
|
@ -1408,8 +1410,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
|||
<property name="default-height">830</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="focus-out-event" handler="unset_keys_and_data" swapped="no"/>
|
||||
<signal name="key-press-event" handler="global_key_press_controller" swapped="no"/>
|
||||
<signal name="key-release-event" handler="global_key_release_controller" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_global_key_press_controller" swapped="no"/>
|
||||
<signal name="key-release-event" handler="on_global_key_release_controller" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
|
|
Loading…
Reference in New Issue