Bringing to latest changes #3
|
@ -77,12 +77,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi
|
||||||
data = method(*(self, *parameters))
|
data = method(*(self, *parameters))
|
||||||
self.plugins.send_message_to_plugin(type, data)
|
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"):
|
def save_load_session(self, action="save_session"):
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(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()
|
self.show_new_file_menu()
|
||||||
if action in ["save_session", "save_session_as", "load_session"]:
|
if action in ["save_session", "save_session_as", "load_session"]:
|
||||||
self.save_load_session(action)
|
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
|
from plugins.plugins import Plugins
|
||||||
|
|
||||||
|
|
||||||
|
class State:
|
||||||
|
wid = None
|
||||||
|
tid = None
|
||||||
|
tab = None
|
||||||
|
icon_grid = None
|
||||||
|
store = None
|
||||||
|
|
||||||
|
|
||||||
class Controller_Data:
|
class Controller_Data:
|
||||||
""" Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """
|
""" 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):
|
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.trashman = XDGTrash()
|
||||||
self.fm_controller = WindowController()
|
self.fm_controller = WindowController()
|
||||||
self.plugins = Plugins(_settings)
|
self.plugins = Plugins(_settings)
|
||||||
self.state = self.fm_controller.load_state()
|
self.state = self.fm_controller.load_state()
|
||||||
self.trashman.regenerate()
|
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.window = self.settings.get_main_window()
|
||||||
self.window1 = self.builder.get_object("window_1")
|
self.window1 = self.builder.get_object("window_1")
|
||||||
self.window2 = self.builder.get_object("window_2")
|
self.window2 = self.builder.get_object("window_2")
|
||||||
|
@ -109,6 +116,7 @@ class Controller_Data:
|
||||||
self.window.connect("delete-event", self.tear_down)
|
self.window.connect("delete-event", self.tear_down)
|
||||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
|
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
|
||||||
|
|
||||||
|
|
||||||
def get_current_state(self):
|
def get_current_state(self):
|
||||||
'''
|
'''
|
||||||
Returns the state info most useful for any given context and action intent.
|
Returns the state info most useful for any given context and action intent.
|
||||||
|
@ -117,13 +125,15 @@ class Controller_Data:
|
||||||
a (obj): self
|
a (obj): self
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
wid, tid, tab, icon_grid, store
|
state (obj): State
|
||||||
'''
|
'''
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
state = State()
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid")
|
state.wid, state.tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
store = icon_grid.get_model()
|
state.tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
return wid, tid, tab, icon_grid, store
|
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):
|
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"
|
data = f"Exec Type: {exec_type} <--> Value: {value}\n\n{trace}\n\n\n\n"
|
||||||
start_itr = self.message_buffer.get_start_iter()
|
start_itr = self.message_buffer.get_start_iter()
|
||||||
self.message_buffer.place_cursor(start_itr)
|
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):
|
def display_message(self, type, text, seconds=None):
|
||||||
self.message_buffer.insert_at_cursor(text)
|
self.message_buffer.insert_at_cursor(text)
|
||||||
|
|
|
@ -96,9 +96,9 @@ class TabMixin(WidgetMixin):
|
||||||
return notebook.get_children()[1].get_children()[0]
|
return notebook.get_children()[1].get_children()[0]
|
||||||
|
|
||||||
def refresh_tab(data=None):
|
def refresh_tab(data=None):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
tab.load_directory()
|
state.tab.load_directory()
|
||||||
self.load_store(tab, store)
|
self.load_store(state.tab, state.store)
|
||||||
|
|
||||||
def update_tab(self, tab_label, tab, store, wid, tid):
|
def update_tab(self, tab_label, tab, store, wid, tid):
|
||||||
self.load_store(tab, store)
|
self.load_store(tab, store)
|
||||||
|
@ -172,28 +172,14 @@ class TabMixin(WidgetMixin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_path_entry(self, button=None, eve=None):
|
def set_path_entry(self, button=None, eve=None):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
path = f"{tab.get_current_directory()}/{button.get_label()}"
|
path = f"{state.tab.get_current_directory()}/{button.get_label()}"
|
||||||
path_entry = self.builder.get_object("path_entry")
|
path_entry = self.builder.get_object("path_entry")
|
||||||
path_entry.set_text(path)
|
path_entry.set_text(path)
|
||||||
path_entry.grab_focus_without_selecting()
|
path_entry.grab_focus_without_selecting()
|
||||||
path_entry.set_position(-1)
|
path_entry.set_position(-1)
|
||||||
self.path_menu.popdown()
|
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):
|
def show_hide_hidden_files(self):
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
|
|
|
@ -102,10 +102,8 @@ class WidgetFileActionMixin:
|
||||||
self.load_store(tab, store)
|
self.load_store(tab, store)
|
||||||
|
|
||||||
tab_widget_label.set_label(tab.get_end_of_path())
|
tab_widget_label.set_label(tab.get_end_of_path())
|
||||||
|
state = self.get_current_state()
|
||||||
_wid, _tid, _tab, _icon_grid, _store = self.get_current_state()
|
if [wid, tid] in [state.wid, state.tid]:
|
||||||
|
|
||||||
if [wid, tid] in [_wid, _tid]:
|
|
||||||
self.set_bottom_labels(tab)
|
self.set_bottom_labels(tab)
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,47 +128,44 @@ class WidgetFileActionMixin:
|
||||||
|
|
||||||
|
|
||||||
def open_files(self):
|
def open_files(self):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
|
|
||||||
for file in uris:
|
for file in uris:
|
||||||
tab.open_file_locally(file)
|
state.tab.open_file_locally(file)
|
||||||
|
|
||||||
def open_with_files(self, appchooser_widget):
|
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()
|
app_info = appchooser_widget.get_app_info()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files)
|
||||||
|
state.tab.app_chooser_exec(app_info, uris)
|
||||||
tab.app_chooser_exec(app_info, uris)
|
|
||||||
|
|
||||||
def execute_files(self, in_terminal=False):
|
def execute_files(self, in_terminal=False):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
paths = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
current_dir = tab.get_current_directory()
|
current_dir = state.tab.get_current_directory()
|
||||||
command = None
|
command = None
|
||||||
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
command = f"exec '{path}'" if not in_terminal else f"{tab.terminal_app} -e '{path}'"
|
command = f"exec '{path}'" if not in_terminal else f"{state.tab.terminal_app} -e '{path}'"
|
||||||
tab.execute(command, start_dir=tab.get_current_directory(), use_os_system=False)
|
state.tab.execute(command, start_dir=state.tab.get_current_directory(), use_os_system=False)
|
||||||
|
|
||||||
def archive_files(self, archiver_dialogue):
|
def archive_files(self, archiver_dialogue):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
paths = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
|
|
||||||
save_target = archiver_dialogue.get_filename();
|
save_target = archiver_dialogue.get_filename();
|
||||||
sItr, eItr = self.arc_command_buffer.get_bounds()
|
sItr, eItr = self.arc_command_buffer.get_bounds()
|
||||||
pre_command = self.arc_command_buffer.get_text(sItr, eItr, False)
|
pre_command = self.arc_command_buffer.get_text(sItr, eItr, False)
|
||||||
pre_command = pre_command.replace("%o", save_target)
|
pre_command = pre_command.replace("%o", save_target)
|
||||||
pre_command = pre_command.replace("%N", ' '.join(paths))
|
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):
|
def rename_files(self):
|
||||||
rename_label = self.builder.get_object("file_to_rename_label")
|
rename_label = self.builder.get_object("file_to_rename_label")
|
||||||
rename_input = self.builder.get_object("new_rename_fname")
|
rename_input = self.builder.get_object("new_rename_fname")
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
|
|
||||||
for uri in uris:
|
for uri in uris:
|
||||||
entry = uri.split("/")[-1]
|
entry = uri.split("/")[-1]
|
||||||
|
@ -186,7 +181,7 @@ class WidgetFileActionMixin:
|
||||||
break
|
break
|
||||||
|
|
||||||
rname_to = rename_input.get_text().strip()
|
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)
|
self.handle_files([uri], "rename", target)
|
||||||
|
|
||||||
|
|
||||||
|
@ -196,13 +191,13 @@ class WidgetFileActionMixin:
|
||||||
self.selected_files.clear()
|
self.selected_files.clear()
|
||||||
|
|
||||||
def cut_files(self):
|
def cut_files(self):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
self.to_cut_files = uris
|
self.to_cut_files = uris
|
||||||
|
|
||||||
def copy_files(self):
|
def copy_files(self):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
self.to_copy_files = uris
|
self.to_copy_files = uris
|
||||||
|
|
||||||
def paste_files(self):
|
def paste_files(self):
|
||||||
|
@ -216,8 +211,8 @@ class WidgetFileActionMixin:
|
||||||
self.handle_files(self.to_cut_files, "move", target)
|
self.handle_files(self.to_cut_files, "move", target)
|
||||||
|
|
||||||
def delete_files(self):
|
def delete_files(self):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
response = None
|
response = None
|
||||||
|
|
||||||
self.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?")
|
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)
|
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
|
||||||
|
|
||||||
if type == Gio.FileType.DIRECTORY:
|
if type == Gio.FileType.DIRECTORY:
|
||||||
tab.delete_file( file.get_path() )
|
state.tab.delete_file( file.get_path() )
|
||||||
else:
|
else:
|
||||||
file.delete(cancellable=None)
|
file.delete(cancellable=None)
|
||||||
else:
|
else:
|
||||||
|
@ -239,14 +234,14 @@ class WidgetFileActionMixin:
|
||||||
|
|
||||||
|
|
||||||
def trash_files(self):
|
def trash_files(self):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
for uri in uris:
|
for uri in uris:
|
||||||
self.trashman.trash(uri, False)
|
self.trashman.trash(uri, False)
|
||||||
|
|
||||||
def restore_trash_files(self):
|
def restore_trash_files(self):
|
||||||
wid, tid, tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||||
for uri in uris:
|
for uri in uris:
|
||||||
self.trashman.restore(filename=uri.split("/")[-1], verbose=False)
|
self.trashman.restore(filename=uri.split("/")[-1], verbose=False)
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,11 @@ class WindowMixin(TabMixin):
|
||||||
self.fm_controller.create_window()
|
self.fm_controller.create_window()
|
||||||
notebook_tggl_button.set_active(True)
|
notebook_tggl_button.set_active(True)
|
||||||
|
|
||||||
for tab in tabs:
|
if tabs:
|
||||||
self.create_new_tab_notebook(None, i, tab)
|
for tab in tabs:
|
||||||
|
self.create_new_tab_notebook(None, i, tab)
|
||||||
|
else:
|
||||||
|
self.create_new_tab_notebook(None, i, None)
|
||||||
|
|
||||||
if is_hidden:
|
if is_hidden:
|
||||||
self.toggle_notebook_pane(notebook_tggl_button)
|
self.toggle_notebook_pane(notebook_tggl_button)
|
||||||
|
@ -77,8 +80,8 @@ class WindowMixin(TabMixin):
|
||||||
|
|
||||||
|
|
||||||
def set_bottom_labels(self, tab):
|
def set_bottom_labels(self, tab):
|
||||||
_wid, _tid, _tab, icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
selected_files = icon_grid.get_selected_items()
|
selected_files = state.icon_grid.get_selected_items()
|
||||||
current_directory = tab.get_current_directory()
|
current_directory = tab.get_current_directory()
|
||||||
path_file = Gio.File.new_for_path(current_directory)
|
path_file = Gio.File.new_for_path(current_directory)
|
||||||
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
|
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_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}")
|
||||||
self.bottom_path_label.set_label(tab.get_current_directory())
|
self.bottom_path_label.set_label(tab.get_current_directory())
|
||||||
if selected_files:
|
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
|
combined_size = 0
|
||||||
for uri in uris:
|
for uri in uris:
|
||||||
try:
|
try:
|
||||||
|
@ -189,17 +192,17 @@ class WindowMixin(TabMixin):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
wid, tid, tab, _icons_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
notebook = self.builder.get_object(f"window_{state.wid}")
|
||||||
tab_label = self.get_tab_label(notebook, icons_grid)
|
tab_label = self.get_tab_label(notebook, icons_grid)
|
||||||
|
|
||||||
fileName = store[item][1]
|
fileName = state.store[item][1]
|
||||||
dir = tab.get_current_directory()
|
dir = state.tab.get_current_directory()
|
||||||
file = f"{dir}/{fileName}"
|
file = f"{dir}/{fileName}"
|
||||||
|
|
||||||
if isdir(file):
|
if isdir(file):
|
||||||
tab.set_path(file)
|
state.tab.set_path(file)
|
||||||
self.update_tab(tab_label, tab, store, wid, tid)
|
self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid)
|
||||||
else:
|
else:
|
||||||
self.open_files()
|
self.open_files()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -16,13 +16,14 @@ valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
|
||||||
class KeyboardSignalsMixin:
|
class KeyboardSignalsMixin:
|
||||||
""" KeyboardSignalsMixin keyboard hooks controller. """
|
""" 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):
|
def unset_keys_and_data(self, widget=None, eve=None):
|
||||||
self.ctrl_down = False
|
self.ctrl_down = False
|
||||||
self.shift_down = False
|
self.shift_down = False
|
||||||
self.alt_down = False
|
self.alt_down = False
|
||||||
self.is_searching = 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()
|
keyname = Gdk.keyval_name(user_data.keyval).lower()
|
||||||
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
|
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
|
||||||
if "control" in keyname:
|
if "control" in keyname:
|
||||||
|
@ -32,13 +33,9 @@ class KeyboardSignalsMixin:
|
||||||
if "alt" in keyname:
|
if "alt" in keyname:
|
||||||
self.alt_down = True
|
self.alt_down = True
|
||||||
|
|
||||||
# NOTE: Yes, this should actually be mapped to some key controller setting
|
def on_global_key_release_controller(self, widget, event):
|
||||||
# file or something. Sue me.
|
"""Handler for keyboard events"""
|
||||||
def global_key_release_controller(self, eve, user_data):
|
keyname = Gdk.keyval_name(event.keyval).lower()
|
||||||
keyname = Gdk.keyval_name(user_data.keyval).lower()
|
|
||||||
if debug:
|
|
||||||
print(f"global_key_release_controller > key > {keyname}")
|
|
||||||
|
|
||||||
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
|
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
|
||||||
if "control" in keyname:
|
if "control" in keyname:
|
||||||
self.ctrl_down = False
|
self.ctrl_down = False
|
||||||
|
@ -47,68 +44,56 @@ class KeyboardSignalsMixin:
|
||||||
if "alt" in keyname:
|
if "alt" in keyname:
|
||||||
self.alt_down = False
|
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:
|
mapping = self.keybindings.lookup(event)
|
||||||
if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
|
if mapping:
|
||||||
self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released()
|
getattr(self, mapping)()
|
||||||
if keyname == "q":
|
return True
|
||||||
self.tear_down()
|
else:
|
||||||
if keyname == "slash" or keyname == "home":
|
if debug:
|
||||||
self.builder.get_object("go_home").released()
|
print(f"on_global_key_release_controller > key > {keyname}")
|
||||||
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":
|
if self.ctrl_down:
|
||||||
self.unset_keys_and_data()
|
if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
|
||||||
self.delete_files()
|
self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released()
|
||||||
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 re.fullmatch(valid_keyvalue_pat, keyname):
|
||||||
if not self.is_searching and not self.ctrl_down \
|
if not self.is_searching and not self.ctrl_down \
|
||||||
and not self.shift_down and not self.alt_down:
|
and not self.shift_down and not self.alt_down:
|
||||||
focused_obj = self.window.get_focus()
|
focused_obj = self.window.get_focus()
|
||||||
if isinstance(focused_obj, Gtk.IconView):
|
if isinstance(focused_obj, Gtk.IconView):
|
||||||
self.is_searching = True
|
self.is_searching = True
|
||||||
wid, tid, self.search_tab, self.search_icon_grid, store = self.get_current_state()
|
state = self.get_current_state()
|
||||||
self.unset_keys_and_data()
|
self.search_tab = state.tab
|
||||||
self.popup_search_files(wid, keyname)
|
self.search_icon_grid = state.icon_grid
|
||||||
return
|
|
||||||
|
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()
|
||||||
|
|
|
@ -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
|
# Application imports
|
||||||
from .logger import Logger
|
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:
|
class Settings:
|
||||||
|
@ -46,9 +79,11 @@ class Settings:
|
||||||
self._warning_color = "#ffa800"
|
self._warning_color = "#ffa800"
|
||||||
self._error_color = "#ff0000"
|
self._error_color = "#ff0000"
|
||||||
|
|
||||||
|
self.keybindings = Keybindings()
|
||||||
self.main_window = None
|
self.main_window = None
|
||||||
self.logger = Logger(self._CONFIG_PATH).get_logger()
|
self.logger = Logger(self._CONFIG_PATH).get_logger()
|
||||||
self.builder = Gtk.Builder()
|
self.builder = Gtk.Builder()
|
||||||
|
self.keybindings.configure(DEFAULTS['keybindings'])
|
||||||
self.builder.add_from_file(self._WINDOWS_GLADE)
|
self.builder.add_from_file(self._WINDOWS_GLADE)
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,6 +129,7 @@ class Settings:
|
||||||
|
|
||||||
def get_builder(self): return self.builder
|
def get_builder(self): return self.builder
|
||||||
def get_logger(self): return self.logger
|
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_main_window(self): return self.main_window
|
||||||
def get_plugins_path(self): return self._PLUGINS_PATH
|
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>
|
<child>
|
||||||
<object class="GtkButton" id="button11">
|
<object class="GtkButton" id="button11">
|
||||||
<property name="label">gtk-cancel</property>
|
<property name="label">gtk-cancel</property>
|
||||||
|
<property name="use-action-appearance">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="receives-default">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>
|
<child>
|
||||||
<object class="GtkButton" id="button12">
|
<object class="GtkButton" id="button12">
|
||||||
<property name="label">gtk-ok</property>
|
<property name="label">gtk-ok</property>
|
||||||
|
<property name="use-action-appearance">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="receives-default">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="default-height">830</property>
|
||||||
<property name="gravity">center</property>
|
<property name="gravity">center</property>
|
||||||
<signal name="focus-out-event" handler="unset_keys_and_data" swapped="no"/>
|
<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-press-event" handler="on_global_key_press_controller" swapped="no"/>
|
||||||
<signal name="key-release-event" handler="global_key_release_controller" swapped="no"/>
|
<signal name="key-release-event" handler="on_global_key_release_controller" swapped="no"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|
Loading…
Reference in New Issue