From a863dbc586d97f4fd6215c7253fb100ef489e518 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 31 Jan 2022 00:13:43 -0600 Subject: [PATCH 01/41] Plugin work on socket/plug --- plugins/example/__main__.py | 23 +++-- .../SolarFM/solarfm/controller/Controller.py | 1 - .../solarfm/controller/IPCServerMixin.py | 2 +- .../controller/mixins/ShowHideMixin.py | 6 ++ .../controller/mixins/ui/WidgetMixin.py | 2 - .../SolarFM/solarfm/plugins/Plugins.py | 17 ++-- .../usr/share/solarfm/Main_Window.glade | 84 ++++++++++++------- 7 files changed, 84 insertions(+), 51 deletions(-) diff --git a/plugins/example/__main__.py b/plugins/example/__main__.py index 004f660..b5d57b9 100644 --- a/plugins/example/__main__.py +++ b/plugins/example/__main__.py @@ -20,18 +20,17 @@ class Main: self._socket_id = socket_id self._event_system = event_system self._gtk_plug = Gtk.Plug.new(self._socket_id) - self.start_loop() - @threaded - def start_loop(self): - i = 0 - cycles = 5 - alive = True - while alive: - if i == cycles: - alive = False + button = Gtk.Button(label="Click Me!") + button.connect("button-release-event", self._do_action) + self._gtk_plug.add(button) + self._gtk_plug.show_all() - self._event_system.push_gui_event(["some_type", "display_message", ("warning", str(i), None)]) - i += 1 - time.sleep(1) + def _do_action(self, widget=None, eve=None): + message = "Hello, World!" + self._event_system.push_gui_event(["some_type", "display_message", ("warning", message, None)]) + + + def get_socket_id(self): + return self._socket_id diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index ecedf6b..5b48748 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -62,7 +62,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data except Exception as e: print(repr(e)) - def custom_except_hook(self, exctype, value, _traceback): trace = ''.join(traceback.format_tb(_traceback)) data = f"Exectype: {exctype} <--> Value: {value}\n\n{trace}\n\n\n\n" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py index a689101..7410fb8 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py @@ -34,7 +34,7 @@ class IPCServerMixin: if "FILE|" in msg: file = msg.split("FILE|")[1].strip() if file: - event_system.push_gui_event([None, "handle_file_from_ipc", file]) + event_system.push_gui_event([None, "handle_file_from_ipc", (file,)]) conn.close() break diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py index 0f896fc..98ca4c6 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py @@ -96,6 +96,12 @@ class ShowHideMixin: dialog.response(Gtk.ResponseType.OK) + def show_plugins_popup(self, widget=None, eve=None): + self.builder.get_object("plugin_list").popup() + + def hide_plugins_popup(self, widget=None, eve=None): + self.builder.get_object("plugin_list").hide() + def show_context_menu(self, widget=None, eve=None): self.builder.get_object("context_menu").run() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py index 349cba7..b2f0648 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py @@ -133,8 +133,6 @@ class WidgetMixin: grid.connect("button_release_event", self.grid_icon_single_click) grid.connect("item-activated", self.grid_icon_double_click) - # grid.connect("toggle-cursor-item", self.grid_cursor_toggled) - # grid.connect("notify", self.grid_cursor_toggled) grid.connect("selection-changed", self.grid_set_selected_items) grid.connect("drag-data-get", self.grid_on_drag_set) grid.connect("drag-data-received", self.grid_on_drag_data_received) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py index 6458eb1..d6bc67c 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py @@ -16,15 +16,18 @@ class Plugins: """docstring for Plugins""" def __init__(self, settings): self._settings = settings + self._plugin_list_widget = self._settings.get_builder().get_object("plugin_list") + self._plugin_list_socket = self._settings.get_builder().get_object("plugin_socket") self._plugins_path = self._settings.get_plugins_path() - self.gtk_socket = Gtk.Socket().new() + self._gtk_socket = Gtk.Socket().new() self._plugins_dir_watcher = None - self.gtk_socket_id = None self._plugin_collection = [] - self._settings.get_main_window().add(self.gtk_socket) - self.gtk_socket.show() - self.gtk_socket_id = self.gtk_socket.get_id() + self._plugin_list_socket.add(self._gtk_socket) + + # NOTE: Must get ID after adding socket to window. Else issues.... + self._gtk_socket_id = self._gtk_socket.get_id() + self._plugin_list_widget.show_all() def launch_plugins(self): @@ -49,10 +52,10 @@ class Plugins: if isdir(path): spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) module = importlib.util.module_from_spec(spec) - self._plugin_collection.append([file, module]) spec.loader.exec_module(module) - module.Main(self.gtk_socket_id, event_system) + plugin = module.Main(self._gtk_socket_id, event_system) + self._plugin_collection.append([file, plugin]) def reload_plugins(self, file=None): print(f"Reloading plugins...") diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index e760dc6..d15ac42 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1124,7 +1124,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - + True False gtk-justify-center @@ -1386,6 +1386,29 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False + + + True + False + Debug + + + True + False + + + Show Errors + True + False + image3 + False + + + + + + + gtk-quit @@ -1482,29 +1505,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - - - True - False - Debug - - - True - False - - - Show Errors - True - False - image1 - False - - - - - - - True @@ -1518,6 +1518,20 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False 5 start + + + Plugins + True + True + True + + + + True + True + 0 + + tggl_notebook_1 @@ -1531,7 +1545,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 0 + 1 @@ -1547,7 +1561,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 1 + 2 @@ -1563,7 +1577,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 2 + 3 @@ -1579,7 +1593,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 3 + 4 @@ -2043,6 +2057,20 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + False + plugins_buttoin + + + True + False + vertical + + + + + + False 5 -- 2.27.0 From 95c6f796273c69ebab2ef80a7e16fa103b226aaf Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 1 Feb 2022 01:43:09 -0600 Subject: [PATCH 02/41] further plugin work, refactoring --- plugins/example/__main__.py | 24 +++++++-- .../SolarFM/solarfm/__builtins__.py | 26 ++++----- .../SolarFM/solarfm/controller/Controller.py | 13 ++++- .../SolarFM/solarfm/plugins/Plugins.py | 53 ++++++++++++++----- .../shellfm/windows/WindowController.py | 2 +- 5 files changed, 84 insertions(+), 34 deletions(-) diff --git a/plugins/example/__main__.py b/plugins/example/__main__.py index b5d57b9..24d98a9 100644 --- a/plugins/example/__main__.py +++ b/plugins/example/__main__.py @@ -1,5 +1,5 @@ # Python imports -import sys, traceback, threading, inspect, os, time +import sys, threading, subprocess, time # Gtk imports import gi @@ -11,26 +11,42 @@ from gi.repository import Gtk def threaded(fn): def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() return wrapper class Main: def __init__(self, socket_id, event_system): - self._socket_id = socket_id + self._plugin_name = "Example Plugin" self._event_system = event_system + self._socket_id = socket_id self._gtk_plug = Gtk.Plug.new(self._socket_id) + button = Gtk.Button(label="Click Me!") + self._message = None + self._time_out = 5 - button = Gtk.Button(label="Click Me!") button.connect("button-release-event", self._do_action) self._gtk_plug.add(button) self._gtk_plug.show_all() + @threaded def _do_action(self, widget=None, eve=None): message = "Hello, World!" self._event_system.push_gui_event(["some_type", "display_message", ("warning", message, None)]) + def set_message(self, data): + self._message = data + + def get_plugin_name(self): + return self._plugin_name + def get_socket_id(self): return self._socket_id + + def _run_timeout(self): + timeout = 0 + while not self._message and timeout < self._time_out: + time.sleep(1) + timeout += 1 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 42391a5..519135a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -13,13 +13,13 @@ class Builtins(IPCServerMixin): """Docstring for __builtins__ extender""" def __init__(self): - # NOTE: The format used is list of [type, target, data] Where: + # NOTE: The format used is list of [type, target, (data,)] Where: # type is useful context for control flow, # target is the method to call, # data is the method parameters to give # Where data may be any kind of data self._gui_events = [] - self._fm_events = [] + self._module_events = [] self.is_ipc_alive = False self.ipc_authkey = b'solarfm-ipc' self.ipc_address = '127.0.0.1' @@ -33,9 +33,9 @@ class Builtins(IPCServerMixin): return self._gui_events.pop(0) return None - def _pop_fm_event(self): - if len(self._fm_events) > 0: - return self._fm_events.pop(0) + def _pop_module_event(self): + if len(self._module_events) > 0: + return self._module_events.pop(0) return None @@ -44,26 +44,26 @@ class Builtins(IPCServerMixin): self._gui_events.append(event) return None - raise Exception("Invald event format! Please do: [type, target, data]") + raise Exception("Invald event format! Please do: [type, target, (data,)]") - def push_fm_event(self, event): + def push_module_event(self, event): if len(event) == 3: - self._fm_events.append(event) + self._module_events.append(event) return None - raise Exception("Invald event format! Please do: [type, target, data]") + raise Exception("Invald event format! Please do: [type, target, (data,)]") def read_gui_event(self): return self._gui_events[0] - def read_fm_event(self): - return self._fm_events[0] + def read_module_event(self): + return self._module_events[0] def consume_gui_event(self): return self._pop_gui_event() - def consume_fm_event(self): - return self._pop_fm_event() + def consume_module_event(self): + return self._pop_module_event() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index 5b48748..56f3949 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -57,11 +57,20 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data if event: try: type, target, data = event - method = getattr(self.__class__, target) - GLib.idle_add(method, *(self, *data,)) + if type: + method = getattr(self.__class__, "handle_gui_event_and_set_message") + GLib.idle_add(method, *(self, type, target, data)) + else: + method = getattr(self.__class__, target) + GLib.idle_add(method, *(self, *data,)) except Exception as e: print(repr(e)) + def handle_gui_event_and_set_message(self, type, target, parameters): + method = getattr(self.__class__, f"{target}") + data = method(*(self, *parameters)) + self.plugins.set_message_on_plugin(type, data) + def custom_except_hook(self, exctype, value, _traceback): trace = ''.join(traceback.format_tb(_traceback)) data = f"Exectype: {exctype} <--> Value: {value}\n\n{trace}\n\n\n\n" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py index d6bc67c..02acf7a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py @@ -10,6 +10,12 @@ from gi.repository import Gtk, Gio # Application imports +class Plugin: + name = None + module = None + gtk_socket_id = None + gtk_socket = None + reference = None class Plugins: @@ -19,16 +25,9 @@ class Plugins: self._plugin_list_widget = self._settings.get_builder().get_object("plugin_list") self._plugin_list_socket = self._settings.get_builder().get_object("plugin_socket") self._plugins_path = self._settings.get_plugins_path() - self._gtk_socket = Gtk.Socket().new() self._plugins_dir_watcher = None self._plugin_collection = [] - self._plugin_list_socket.add(self._gtk_socket) - - # NOTE: Must get ID after adding socket to window. Else issues.... - self._gtk_socket_id = self._gtk_socket.get_id() - self._plugin_list_widget.show_all() - def launch_plugins(self): self._set_plugins_watcher() @@ -45,17 +44,36 @@ class Plugins: Gio.FileMonitorEvent.MOVED_OUT]: self.reload_plugins(file) + # @threaded def load_plugins(self, file=None): print(f"Loading plugins...") for file in os.listdir(self._plugins_path): - path = join(self._plugins_path, file) - if isdir(path): - spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) - module = importlib.util.module_from_spec(spec) + try: + path = join(self._plugins_path, file) + if isdir(path): + gtk_socket = Gtk.Socket().new() + self._plugin_list_socket.add(gtk_socket) + # NOTE: Must get ID after adding socket to window. Else issues.... + gtk_socket_id = gtk_socket.get_id() + + spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + ref = module.Main(gtk_socket_id, event_system) + plugin = Plugin() + plugin.name = ref.get_plugin_name() + plugin.module = path + plugin.gtk_socket_id = gtk_socket_id + plugin.gtk_socket = gtk_socket + plugin.reference = ref + + self._plugin_collection.append(plugin) + gtk_socket.show_all() + except Exception as e: + print("Malformed plugin! Not loading!") + print(repr(e)) - spec.loader.exec_module(module) - plugin = module.Main(self._gtk_socket_id, event_system) - self._plugin_collection.append([file, plugin]) def reload_plugins(self, file=None): print(f"Reloading plugins...") @@ -64,3 +82,10 @@ class Plugins: # for dir in self._plugin_collection: # if not os.path.isdir(os.path.join(self._plugins_path, dir)): # to_unload.append(dir) + + def set_message_on_plugin(self, type, data): + print("Trying to send message to plugin...") + for plugin in self._plugin_collection: + if type in plugin.name: + print('Found plugin; posting message...') + plugin.reference.set_message(data) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py index 7f068e7..cbcfbcd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py @@ -32,7 +32,7 @@ class WindowController: def fm_event_observer(self): while True: time.sleep(event_sleep_time) - event = event_system.consume_fm_event() + event = event_system.consume_module_event() if event: print(event) -- 2.27.0 From ca855712b195aa0cc5e06601c1990a2da9ac322c Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 1 Feb 2022 21:08:02 -0600 Subject: [PATCH 03/41] Moved custom exception hook; added some doc strings --- .../SolarFM/solarfm/__builtins__.py | 2 +- .../solarfm-0.0.1/SolarFM/solarfm/__init__.py | 2 + .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 2 + .../SolarFM/solarfm/controller/Controller.py | 62 ++----------------- .../solarfm/controller/Controller_Data.py | 54 ++++++++++++++-- .../solarfm/controller/IPCServerMixin.py | 1 + .../controller/mixins/ExceptionHookMixin.py | 62 +++++++++++++++++++ .../solarfm/controller/mixins/__init__.py | 1 + 8 files changed, 123 insertions(+), 63 deletions(-) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 519135a..3dcbd14 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -10,7 +10,7 @@ from controller import IPCServerMixin class Builtins(IPCServerMixin): - """Docstring for __builtins__ extender""" + """ Inheret IPCServerMixin. Create an pub/sub systems. """ def __init__(self): # NOTE: The format used is list of [type, target, (data,)] Where: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py index cb48f8c..34d1fbd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py @@ -12,6 +12,8 @@ from __builtins__ import Builtins class Main(Builtins): + ''' Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes.''' + def __init__(self, args, unknownargs): if not debug: event_system.create_ipc_server() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index 66870d2..727a85f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -19,6 +19,8 @@ from __init__ import Main if __name__ == "__main__": + ''' Set process title, get arguments, and create GTK main thread. ''' + try: # import web_pdb # web_pdb.set_trace() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index 56f3949..3cfb511 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -1,5 +1,5 @@ # Python imports -import sys, traceback, threading, inspect, os, time +import traceback, threading, inspect, os, time # Lib imports import gi @@ -7,7 +7,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib # Application imports -from .mixins import UIMixin +from .mixins import ExceptionHookMixin, UIMixin from .signals import IPCSignalsMixin, KeyboardSignalsMixin from . import Controller_Data @@ -20,9 +20,9 @@ def threaded(fn): -class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data): +class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): + ''' Controller coordinates the mixins and is somewhat the root hub of it all. ''' def __init__(self, args, unknownargs, _settings): - sys.excepthook = self.custom_except_hook self.setup_controller_data(_settings) self.window.show() self.generate_windows(self.state) @@ -71,60 +71,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data data = method(*(self, *parameters)) self.plugins.set_message_on_plugin(type, data) - def custom_except_hook(self, exctype, value, _traceback): - trace = ''.join(traceback.format_tb(_traceback)) - data = f"Exectype: {exctype} <--> 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) - - def display_message(self, type, text, seconds=None): - self.message_buffer.insert_at_cursor(text) - self.message_widget.popup() - if seconds: - self.hide_message_timeout(seconds) - - @threaded - def hide_message_timeout(self, seconds=3): - time.sleep(seconds) - GLib.idle_add(self.message_widget.popdown) - - def save_debug_alerts(self, widget=None, eve=None): - start_itr, end_itr = self.message_buffer.get_bounds() - save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \ - action = Gtk.FileChooserAction.SAVE, \ - buttons = (Gtk.STOCK_CANCEL, \ - Gtk.ResponseType.CANCEL, \ - Gtk.STOCK_SAVE, \ - Gtk.ResponseType.OK)) - - text = self.message_buffer.get_text(start_itr, end_itr, False) - resp = save_location_prompt.run() - if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT): - pass - elif resp == Gtk.ResponseType.OK: - target = save_location_prompt.get_filename(); - with open(target, "w") as f: - f.write(text) - - save_location_prompt.destroy() - - - def set_arc_buffer_text(self, widget=None, eve=None): - id = widget.get_active_id() - self.arc_command_buffer.set_text(self.arc_commands[int(id)]) - - - def clear_children(self, widget): - for child in widget.get_children(): - widget.remove(child) - - def get_current_state(self): - wid, tid = self.window_controller.get_active_data() - view = self.get_fm_window(wid).get_view_by_id(tid) - iconview = self.builder.get_object(f"{wid}|{tid}|iconview") - store = iconview.get_model() - return wid, tid, view, iconview, store def do_action_from_menu_controls(self, widget, eventbutton): action = widget.get_name() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py index fe97040..7453a63 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py @@ -1,5 +1,5 @@ # Python imports -import signal +import sys, os, signal # Lib imports from gi.repository import GLib @@ -13,8 +13,7 @@ from plugins import Plugins class Controller_Data: - def has_method(self, o, name): - return callable(getattr(o, name, None)) + ''' 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.trashman = XDGTrash() @@ -104,6 +103,53 @@ class Controller_Data: self.warning = "#ffa800" self.error = "#ff0000" - + sys.excepthook = self.custom_except_hook 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. + + Parameters: + a (obj): self + + Returns: + wid, tid, view, iconview, store + ''' + wid, tid = self.window_controller.get_active_data() + view = self.get_fm_window(wid).get_view_by_id(tid) + iconview = self.builder.get_object(f"{wid}|{tid}|iconview") + store = iconview.get_model() + return wid, tid, view, iconview, store + + + def clear_console(self): + ''' Clears the terminal screen. ''' + os.system('cls' if os.name == 'nt' else 'clear') + + def call_method(self, _method_name, data = None): + ''' + Calls a method from scope of class. + + Parameters: + a (obj): self + b (str): method name to be called + c (*): Data (if any) to be passed to the method. + Note: It must be structured according to the given methods requirements. + + Returns: + Return data is that which the calling method gives. + ''' + method_name = str(_method_name) + method = getattr(self, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}") + return method(data) if data else method() + + def has_method(self, obj, name): + ''' Checks if a given method exists. ''' + return callable(getattr(obj, name, None)) + + def clear_children(self, widget): + ''' Clear children of a gtk widget. ''' + for child in widget.get_children(): + widget.remove(child) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py index 7410fb8..b273c9b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py @@ -16,6 +16,7 @@ def threaded(fn): class IPCServerMixin: + ''' Create a listener so that other SolarFM instances send requests back to existing instance. ''' @threaded def create_ipc_server(self): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py new file mode 100644 index 0000000..fecab38 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py @@ -0,0 +1,62 @@ +# Python imports +import traceback, threading, time + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib + +# Application imports + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class ExceptionHookMixin: + ''' ExceptionHookMixin custom exception hook to reroute to a Gtk text area. ''' + + def custom_except_hook(self, exctype, value, _traceback): + trace = ''.join(traceback.format_tb(_traceback)) + data = f"Exectype: {exctype} <--> 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) + + def display_message(self, type, text, seconds=None): + self.message_buffer.insert_at_cursor(text) + self.message_widget.popup() + if seconds: + self.hide_message_timeout(seconds) + + @threaded + def hide_message_timeout(self, seconds=3): + time.sleep(seconds) + GLib.idle_add(self.message_widget.popdown) + + def save_debug_alerts(self, widget=None, eve=None): + start_itr, end_itr = self.message_buffer.get_bounds() + save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \ + action = Gtk.FileChooserAction.SAVE, \ + buttons = (Gtk.STOCK_CANCEL, \ + Gtk.ResponseType.CANCEL, \ + Gtk.STOCK_SAVE, \ + Gtk.ResponseType.OK)) + + text = self.message_buffer.get_text(start_itr, end_itr, False) + resp = save_location_prompt.run() + if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT): + pass + elif resp == Gtk.ResponseType.OK: + target = save_location_prompt.get_filename(); + with open(target, "w") as f: + f.write(text) + + save_location_prompt.destroy() + + + def set_arc_buffer_text(self, widget=None, eve=None): + id = widget.get_active_id() + self.arc_command_buffer.set_text(self.arc_commands[int(id)]) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py index d4205b8..ec27013 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py @@ -1,2 +1,3 @@ from .ShowHideMixin import ShowHideMixin +from .ExceptionHookMixin import ExceptionHookMixin from .UIMixin import UIMixin -- 2.27.0 From 7534bf141ef22492822ea08f63a3192e7bbd894b Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 1 Feb 2022 23:29:42 -0600 Subject: [PATCH 04/41] Added initial save/load logic --- .../SolarFM/solarfm/controller/Controller.py | 32 ++++++++++++++++++- .../solarfm/controller/Controller_Data.py | 3 +- .../shellfm/windows/WindowController.py | 16 +++++++--- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index 3cfb511..fa7704e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -72,6 +72,34 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.plugins.set_message_on_plugin(type, data) + def save_load_session(self, action="save_session"): + wid, tid = self.window_controller.get_active_data() + view = self.get_fm_window(wid).get_view_by_id(tid) + save_load_dialog = self.builder.get_object("save_load_dialog") + save_load_dialog.set_current_folder(view.get_current_directory()) + save_load_dialog.set_current_name("session.json") + + if action == "save_session": + save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) + elif action == "load_session": + save_load_dialog.set_action(Gtk.FileChooserAction.OPEN) + + + response = save_load_dialog.run() + if response == Gtk.ResponseType.OK: + path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" + + if action == "save_session": + self.window_controller.save_state(path) + elif action == "load_session": + session_json = self.window_controller.load_state(path) + print(session_json) + if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): + pass + + save_load_dialog.hide() + + def do_action_from_menu_controls(self, widget, eventbutton): action = widget.get_name() self.ctrlDown = True @@ -104,7 +132,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi if action == "trash": self.trash_files() if action == "go_to_trash": - self.builder.get_object("path_entry").set_text(self.trash_files_path) + self.path_entry.set_text(self.trash_files_path) if action == "restore_from_trash": self.restore_trash_files() if action == "empty_trash": @@ -112,5 +140,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi if action == "create": self.create_files() + if action in ["save_session", "load_session"]: + self.save_load_session(action) self.ctrlDown = False diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py index 7453a63..c66d0eb 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py @@ -36,13 +36,14 @@ class Controller_Data: self.message_buffer = self.builder.get_object("message_buffer") self.arc_command_buffer = self.builder.get_object("arc_command_buffer") + self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn") self.warning_alert = self.builder.get_object("warning_alert") self.edit_file_menu = self.builder.get_object("edit_file_menu") self.file_exists_dialog = self.builder.get_object("file_exists_dialog") self.exists_file_label = self.builder.get_object("exists_file_label") self.exists_file_field = self.builder.get_object("exists_file_field") self.path_menu = self.builder.get_object("path_menu") - self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn") + self.path_entry = self.builder.get_object("path_entry") self.bottom_size_label = self.builder.get_object("bottom_size_label") self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py index cbcfbcd..eab1466 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py @@ -151,7 +151,10 @@ class WindowController: - def save_state(self): + def save_state(self, session_file = None): + if not session_file: + session_file = self.session_file + windows = [] for window in self.windows: views = [] @@ -172,10 +175,13 @@ class WindowController: ] ) - with open(self.session_file, 'w') as outfile: + with open(session_file, 'w') as outfile: json.dump(windows, outfile, separators=(',', ':'), indent=4) - def load_state(self): - if path.isfile(self.session_file): - with open(self.session_file) as infile: + def load_state(self, session_file = None): + if not session_file: + session_file = self.session_file + + if path.isfile(session_file): + with open(session_file) as infile: return json.load(infile) -- 2.27.0 From 52aa14dcb404a04ffb8f584ba0b6bdb5dbfb6717 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 1 Feb 2022 23:31:49 -0600 Subject: [PATCH 05/41] Added initial save/load logic --- .../usr/share/solarfm/Main_Window.glade | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index d15ac42..f6905d3 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1124,6 +1124,16 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + True + False + gtk-save-as + + + True + False + gtk-file + True False @@ -1146,6 +1156,64 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-edit + + False + dialog + True + + + False + vertical + 2 + + + False + end + + + gtk-cancel + True + True + True + True + + + True + True + 0 + + + + + gtk-ok + True + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + + + + + button11 + button12 + + True False @@ -1386,6 +1454,30 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False + + + Save Session + save_session + True + False + New File/Folder... + image1 + False + + + + + + Load Session + load_session + True + False + New File/Folder... + image2 + False + + + True -- 2.27.0 From 67c13d264a15a6dda205da2d86c1e3afdcd61e5f Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 4 Feb 2022 17:46:03 -0600 Subject: [PATCH 06/41] Fleshed out session load --- .../SolarFM/solarfm/controller/Controller.py | 28 ++++++++--- .../controller/mixins/ui/WindowMixin.py | 6 +-- .../shellfm/windows/WindowController.py | 47 +++++++++++-------- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index fa7704e..14b62fc 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -1,5 +1,5 @@ # Python imports -import traceback, threading, inspect, os, time +import traceback, threading, inspect, os, gc, time # Lib imports import gi @@ -76,30 +76,44 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi wid, tid = self.window_controller.get_active_data() view = self.get_fm_window(wid).get_view_by_id(tid) save_load_dialog = self.builder.get_object("save_load_dialog") - save_load_dialog.set_current_folder(view.get_current_directory()) - save_load_dialog.set_current_name("session.json") if action == "save_session": save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) elif action == "load_session": save_load_dialog.set_action(Gtk.FileChooserAction.OPEN) - + save_load_dialog.set_current_folder(view.get_current_directory()) + save_load_dialog.set_current_name("session.json") response = save_load_dialog.run() if response == Gtk.ResponseType.OK: - path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" - if action == "save_session": + path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" self.window_controller.save_state(path) elif action == "load_session": + path = f"{save_load_dialog.get_file().get_path()}" session_json = self.window_controller.load_state(path) - print(session_json) + self.load_session(session_json) if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): pass save_load_dialog.hide() + def load_session(self, session_json): + if debug: + print(f"Session Data: {session_json}") + + self.ctrlDown = False + self.shiftDown = False + self.altDown = False + for notebook in self.notebooks: + self.clear_children(notebook) + + self.window_controller.unload_views_and_windows() + self.generate_windows(session_json) + gc.collect() + + def do_action_from_menu_controls(self, widget, eventbutton): action = widget.get_name() self.ctrlDown = True diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py index 17e4be3..8f3d98d 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py @@ -16,9 +16,9 @@ from . import TabMixin, WidgetMixin class WindowMixin(TabMixin): """docstring for WindowMixin""" - def generate_windows(self, data = None): - if data: - for j, value in enumerate(data): + def generate_windows(self, session_json = None): + if session_json: + for j, value in enumerate(session_json): i = j + 1 isHidden = True if value[0]["window"]["isHidden"] == "True" else False object = self.builder.get_object(f"tggl_notebook_{i}") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py index eab1466..33e86e7 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py @@ -151,32 +151,41 @@ class WindowController: + def unload_views_and_windows(self): + for window in self.windows: + window.views.clear() + + self.windows.clear() + def save_state(self, session_file = None): if not session_file: session_file = self.session_file - windows = [] - for window in self.windows: - views = [] - for view in window.views: - views.append(view.get_current_directory()) + if len(self.windows) > 0: + windows = [] + for window in self.windows: + views = [] + for view in window.views: + views.append(view.get_current_directory()) - windows.append( - [ - { - 'window':{ - "ID": window.id, - "Name": window.name, - "Nickname": window.nickname, - "isHidden": f"{window.isHidden}", - 'views': views + windows.append( + [ + { + 'window':{ + "ID": window.id, + "Name": window.name, + "Nickname": window.nickname, + "isHidden": f"{window.isHidden}", + 'views': views + } } - } - ] - ) + ] + ) - with open(session_file, 'w') as outfile: - json.dump(windows, outfile, separators=(',', ':'), indent=4) + with open(session_file, 'w') as outfile: + json.dump(windows, outfile, separators=(',', ':'), indent=4) + else: + raise Exception("Window dara corrupted! Can not save session!") def load_state(self, session_file = None): if not session_file: -- 2.27.0 From f77becc21c6b304db320a9767b119fad2cc4bbb4 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 4 Feb 2022 18:14:11 -0600 Subject: [PATCH 07/41] UI changes, added save_as --- .../SolarFM/solarfm/controller/Controller.py | 7 +- .../usr/share/solarfm/Main_Window.glade | 114 +++++++++++------- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index 14b62fc..008e308 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -78,9 +78,14 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi save_load_dialog = self.builder.get_object("save_load_dialog") if action == "save_session": + self.window_controller.save_state() + return + elif action == "save_session_as": save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) elif action == "load_session": save_load_dialog.set_action(Gtk.FileChooserAction.OPEN) + else: + raise Exception(f"Unknown action given: {action}") save_load_dialog.set_current_folder(view.get_current_directory()) save_load_dialog.set_current_name("session.json") @@ -154,7 +159,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi if action == "create": self.create_files() - if action in ["save_session", "load_session"]: + if action in ["save_session", "save_session_as", "load_session"]: self.save_load_session(action) self.ctrlDown = False diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index f6905d3..e55b351 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1139,6 +1139,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-justify-center + + True + False + gtk-save + True @@ -1455,27 +1460,52 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - - Save Session - save_session + True False - New File/Folder... - image1 - False - - - - - - Load Session - load_session - True - False - New File/Folder... - image2 - False - + Session + + + True + False + + + Save Session + save_session + True + False + New File/Folder... + image4 + False + + + + + + Save Session As + save_session_as + True + False + New File/Folder... + image1 + False + + + + + + Load Session + load_session + True + False + New File/Folder... + image2 + False + + + + + @@ -2458,25 +2488,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 8 - - - gtk-delete - delete - True - True - True - Delete... - 20 - True - True - - - - False - True - 9 - - Restore From Trash @@ -2491,7 +2502,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 10 + 9 @@ -2502,13 +2513,12 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True Empty Trash... - 20 False True - 11 + 10 @@ -2519,6 +2529,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True Move to Trash... + 20 trash_img True @@ -2526,7 +2537,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 12 + 11 @@ -2541,6 +2552,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True + + False + True + 12 + + + + + gtk-delete + delete + True + True + True + Delete... + 20 + True + True + + False True -- 2.27.0 From eafc8613e67645c9fb0d1651471d9d05f201fb8e Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 4 Feb 2022 18:25:41 -0600 Subject: [PATCH 08/41] Bug fixes --- .../SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py | 2 +- src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/trash.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py index b397027..17c3d0d 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py @@ -89,7 +89,7 @@ class KeyboardSignalsMixin: if self.ctrlDown and keyname == "h": self.show_hide_hidden_files() if (self.ctrlDown and keyname == "e"): - self.edit_files() + self.rename_files() if self.ctrlDown and keyname == "c": self.to_cut_files.clear() self.copy_files() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/trash.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/trash.py index be29701..4210f9c 100755 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/trash.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/trash.py @@ -21,7 +21,7 @@ class Trash(object): if os.path.isfile(item): size = size + os.path.getsize(item) elif os.path.isdir(item): - size = size + size_dir(item) + size = size + self.size_dir(item) return size -- 2.27.0 From 6eed25efd6002c1afaeb02f3cf159e4a54baeb2e Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 11 Feb 2022 00:58:49 -0600 Subject: [PATCH 09/41] Shellfm update, refactors to support update --- README.md | 4 +- plugins/{example => template}/__main__.py | 2 +- plugins/youtube_download/__main__.py | 58 +++++ plugins/youtube_download/download.sh | 17 ++ .../solarfm-0.0.1/SolarFM/solarfm/__init__.py | 2 +- .../SolarFM/solarfm/controller/Controller.py | 6 +- .../solarfm/controller/Controller_Data.py | 4 +- .../controller/mixins/ShowHideMixin.py | 2 +- .../solarfm/controller/mixins/ui/PaneMixin.py | 2 +- .../solarfm/controller/mixins/ui/TabMixin.py | 26 +-- .../mixins/ui/WidgetFileActionMixin.py | 10 +- .../controller/mixins/ui/WidgetMixin.py | 16 +- .../controller/mixins/ui/WindowMixin.py | 14 +- .../controller/signals/IPCSignalsMixin.py | 2 +- .../signals/KeyboardSignalsMixin.py | 2 +- .../SolarFM/solarfm/plugins/Plugins.py | 11 +- .../SolarFM/solarfm/shellfm/__init__.py | 1 - .../SolarFM/solarfm/shellfm/windows/Window.py | 66 ------ .../shellfm/windows/WindowController.py | 196 ----------------- .../solarfm/shellfm/windows/__init__.py | 2 - .../solarfm/shellfm/windows/controller.py | 185 ++++++++++++++++ .../solarfm/shellfm/windows/view/__init__.py | 5 - .../shellfm/windows/view/icons/__init__.py | 4 - .../windows/view/icons/mixins/__init__.py | 4 - .../shellfm/windows/view/utils/__init__.py | 3 - .../solarfm/shellfm/windows/views/__init__.py | 0 .../shellfm/windows/views/icons/__init__.py | 0 .../icons/Icon.py => views/icons/icon.py} | 5 +- .../windows/views/icons/mixins/__init__.py | 0 .../icons/mixins/desktopiconmixin.py} | 0 .../icons/mixins/videoiconmixin.py} | 0 .../icons/mixins/xdg/BaseDirectory.py | 0 .../icons/mixins/xdg/Config.py | 0 .../icons/mixins/xdg/DesktopEntry.py | 0 .../icons/mixins/xdg/Exceptions.py | 0 .../icons/mixins/xdg/IconTheme.py | 0 .../icons/mixins/xdg/IniFile.py | 0 .../icons/mixins/xdg/Locale.py | 0 .../{view => views}/icons/mixins/xdg/Menu.py | 0 .../icons/mixins/xdg/MenuEditor.py | 0 .../{view => views}/icons/mixins/xdg/Mime.py | 0 .../icons/mixins/xdg/RecentFiles.py | 0 .../icons/mixins/xdg/__init__.py | 0 .../{view => views}/icons/mixins/xdg/util.py | 0 .../windows/{view/Path.py => views/path.py} | 0 .../shellfm/windows/views/utils/__init__.py | 0 .../utils/filehandler.py} | 0 .../Launcher.py => views/utils/launcher.py} | 0 .../Settings.py => views/utils/settings.py} | 0 .../windows/{view/View.py => views/view.py} | 207 ++++++++++-------- .../SolarFM/solarfm/shellfm/windows/window.py | 89 ++++++++ .../usr/share/solarfm/Main_Window.glade | 52 ++--- 52 files changed, 547 insertions(+), 450 deletions(-) rename plugins/{example => template}/__main__.py (94%) create mode 100644 plugins/youtube_download/__main__.py create mode 100755 plugins/youtube_download/download.sh delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/Window.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/__init__.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/__init__.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/__init__.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/__init__.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view/icons/Icon.py => views/icons/icon.py} (94%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/__init__.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view/icons/mixins/DesktopIconMixin.py => views/icons/mixins/desktopiconmixin.py} (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view/icons/mixins/VideoIconMixin.py => views/icons/mixins/videoiconmixin.py} (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/BaseDirectory.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/Config.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/DesktopEntry.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/Exceptions.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/IconTheme.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/IniFile.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/Locale.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/Menu.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/MenuEditor.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/Mime.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/RecentFiles.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view => views}/icons/mixins/xdg/util.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view/Path.py => views/path.py} (100%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/__init__.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view/utils/FileHandler.py => views/utils/filehandler.py} (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view/utils/Launcher.py => views/utils/launcher.py} (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view/utils/Settings.py => views/utils/settings.py} (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{view/View.py => views/view.py} (51%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py diff --git a/README.md b/README.md index d95b54d..3553c5a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# SolarFM - # SolarFM SolarFM is a Gtk+ Python file manager. @@ -14,6 +12,8 @@ sudo apt-get install python3.8 wget python3-setproctitle python3-gi ffmpegthumbn # TODO
  • Add simpleish plugin system to run bash/python scripts.
  • +
  • Add simpleish search plugin to do recursive search and show.
  • +
  • Add simpleish bulk-renamer.
# Images diff --git a/plugins/example/__main__.py b/plugins/template/__main__.py similarity index 94% rename from plugins/example/__main__.py rename to plugins/template/__main__.py index 24d98a9..63cf7cc 100644 --- a/plugins/example/__main__.py +++ b/plugins/template/__main__.py @@ -21,7 +21,7 @@ class Main: self._event_system = event_system self._socket_id = socket_id self._gtk_plug = Gtk.Plug.new(self._socket_id) - button = Gtk.Button(label="Click Me!") + button = Gtk.Button(label=label=self._plugin_name) self._message = None self._time_out = 5 diff --git a/plugins/youtube_download/__main__.py b/plugins/youtube_download/__main__.py new file mode 100644 index 0000000..c43a38d --- /dev/null +++ b/plugins/youtube_download/__main__.py @@ -0,0 +1,58 @@ +# Python imports +import os, sys, threading, subprocess, time + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + + +class Main: + def __init__(self, socket_id, event_system): + self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + self._plugin_name = "Youtube Download" + self._event_system = event_system + self._socket_id = socket_id + self._gtk_plug = Gtk.Plug.new(self._socket_id) + button = Gtk.Button(label=self._plugin_name) + self._message = None + self._time_out = 5 + + button.connect("button-release-event", self._do_download) + self._gtk_plug.add(button) + self._gtk_plug.show_all() + + + @threaded + def _do_download(self, widget=None, eve=None): + self._event_system.push_gui_event([self._plugin_name, "get_current_state", ()]) + self._run_timeout() + + if self._message: + wid, tid, view, iconview, store = self._message + subprocess.Popen([f'{self.SCRIPT_PTH}/download.sh' , view.get_current_directory()]) + self._message = None + + + def set_message(self, data): + self._message = data + + def get_plugin_name(self): + return self._plugin_name + + def get_socket_id(self): + return self._socket_id + + def _run_timeout(self): + timeout = 0 + while not self._message and timeout < self._time_out: + time.sleep(1) + timeout += 1 diff --git a/plugins/youtube_download/download.sh b/plugins/youtube_download/download.sh new file mode 100755 index 0000000..073b354 --- /dev/null +++ b/plugins/youtube_download/download.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# . CONFIG.sh + +# set -o xtrace ## To debug scripts +# set -o errexit ## To exit on error +# set -o errunset ## To exit if a variable is referenced but not set + + +function main() { + cd "$(dirname "")" + echo "Working Dir: " $(pwd) + + LINK=`xclip -selection clipboard -o` + yt-dlp --write-sub --embed-sub --sub-langs en -o "${1}/%(title)s.%(ext)s" "${LINK}" +} +main "$@"; diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py index 34d1fbd..de673d5 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py @@ -19,7 +19,7 @@ class Main(Builtins): event_system.create_ipc_server() time.sleep(0.2) - if not trace_debug: + if not trace_debug and not debug: if not event_system.is_ipc_alive: if unknownargs: for arg in unknownargs: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index 008e308..325c860 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -25,9 +25,13 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def __init__(self, args, unknownargs, _settings): self.setup_controller_data(_settings) self.window.show() + self.generate_windows(self.state) self.plugins.launch_plugins() + if debug: + self.window.set_interactive_debugging(True) + if not trace_debug: self.gui_event_observer() @@ -73,7 +77,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def save_load_session(self, action="save_session"): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) save_load_dialog = self.builder.get_object("save_load_dialog") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py index c66d0eb..c0e5726 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py @@ -6,7 +6,7 @@ from gi.repository import GLib # Application imports from trasher.xdgtrash import XDGTrash -from shellfm import WindowController +from shellfm.windows.controller import WindowController from plugins import Plugins @@ -118,7 +118,7 @@ class Controller_Data: Returns: wid, tid, view, iconview, store ''' - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) iconview = self.builder.get_object(f"{wid}|{tid}|iconview") store = iconview.get_model() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py index 98ca4c6..d205511 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py @@ -56,7 +56,7 @@ class ShowHideMixin: def show_archiver_dialogue(self, widget=None, eve=None): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) archiver_dialogue = self.builder.get_object("archiver_dialogue") archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py index 235736d..accd2c4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py @@ -61,5 +61,5 @@ class PaneMixin: def _save_state(self, state, pane_index): window = self.window_controller.get_window_by_index(pane_index - 1) - window.isHidden = state + window.set_is_hidden(state) self.window_controller.save_state() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py index d6e57fb..ba0d6bd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py @@ -30,7 +30,7 @@ class TabMixin(WidgetMixin): # scroll, store = self.create_grid_treeview_widget(view, wid) index = notebook.append_page(scroll, tab) - self.window_controller.set_active_data(wid, view.get_tab_id()) + self.window_controller.set__wid_and_tid(wid, view.get_id()) path_entry.set_text(view.get_current_directory()) notebook.show_all() notebook.set_current_page(index) @@ -47,7 +47,7 @@ class TabMixin(WidgetMixin): def close_tab(self, button, eve=None): notebook = button.get_parent().get_parent() - tid = self.get_tab_id_from_tab_box(button.get_parent()) + tid = self.get_id_from_tab_box(button.get_parent()) wid = int(notebook.get_name()[-1]) scroll = self.builder.get_object(f"{wid}|{tid}") page = notebook.page_num(scroll) @@ -65,12 +65,12 @@ class TabMixin(WidgetMixin): window = self.get_fm_window(wid) view = None - for i, view in enumerate(window.views): - if view.id == tid: + for i, view in enumerate(window.get_all_views()): + if view.get_id() == tid: _view = window.get_view_by_id(tid) watcher = _view.get_dir_watcher() watcher.cancel() - window.views.insert(new_index, window.views.pop(i)) + window.get_all_views().insert(new_index, window.get_all_views().pop(i)) view = window.get_view_by_id(tid) self.set_file_watcher(view) @@ -79,11 +79,11 @@ class TabMixin(WidgetMixin): def on_tab_switch_update(self, notebook, content=None, index=None): self.selected_files.clear() wid, tid = content.get_children()[0].get_name().split("|") - self.window_controller.set_active_data(wid, tid) + self.window_controller.set__wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() - def get_tab_id_from_tab_box(self, tab_box): + def get_id_from_tab_box(self, tab_box): tid = tab_box.get_children()[2] return tid.get_text() @@ -114,7 +114,7 @@ class TabMixin(WidgetMixin): def do_action_from_bar_controls(self, widget, eve=None): action = widget.get_name() - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") view = self.get_fm_window(wid).get_view_by_id(tid) @@ -138,11 +138,11 @@ class TabMixin(WidgetMixin): if isinstance(focused_obj, Gtk.Entry): button_box = self.path_menu.get_children()[0].get_children()[0].get_children()[0] query = widget.get_text().replace(dir, "") - files = view.files + view.hidden + files = view.get_files() + view.get_hidden() self.clear_children(button_box) show_path_menu = False - for file in files: + for file, hash in files: if os.path.isdir(f"{dir}{file}"): if query.lower() in file.lower(): button = Gtk.Button(label=file) @@ -183,7 +183,7 @@ class TabMixin(WidgetMixin): self.path_menu.popdown() def keyboard_close_tab(self): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_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) @@ -198,8 +198,8 @@ class TabMixin(WidgetMixin): # File control events def show_hide_hidden_files(self): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) - view.hide_hidden = not view.hide_hidden + view.set_is_hidden(not view.is_hidden()) view.load_directory() self.builder.get_object("refresh_view").released() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py index 1969125..161cf11 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py @@ -52,7 +52,7 @@ class WidgetFileActionMixin: .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) wid = view.get_wid() - tid = view.get_tab_id() + tid = view.get_id() dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",)) view.set_dir_watcher(dir_watcher) @@ -174,7 +174,7 @@ class WidgetFileActionMixin: self.to_copy_files = uris def paste_files(self): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) target = f"{view.get_current_directory()}" @@ -227,7 +227,7 @@ class WidgetFileActionMixin: file_name = fname_field.get_text().strip() type = self.builder.get_object("context_menu_type_toggle").get_state() - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) target = f"{view.get_current_directory()}" @@ -294,7 +294,7 @@ class WidgetFileActionMixin: type = _file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) view.delete_file( _file.get_path() ) else: @@ -321,7 +321,7 @@ class WidgetFileActionMixin: type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) fPath = file.get_path() tPath = target.get_path() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py index b2f0648..17c40cb 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py @@ -100,7 +100,7 @@ class WidgetMixin: label.set_label(f"{view.get_end_of_path()}") label.set_width_chars(len(view.get_end_of_path())) label.set_xalign(0.0) - tid.set_label(f"{view.id}") + tid.set_label(f"{view.get_id()}") close.add(icon) tab.add(label) @@ -148,10 +148,10 @@ class WidgetMixin: grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{view.id}") - scroll.set_name(f"{wid}|{view.id}") - self.builder.expose_object(f"{wid}|{view.id}|iconview", grid) - self.builder.expose_object(f"{wid}|{view.id}", scroll) + grid.set_name(f"{wid}|{view.get_id()}") + scroll.set_name(f"{wid}|{view.get_id()}") + self.builder.expose_object(f"{wid}|{view.get_id()}|iconview", grid) + self.builder.expose_object(f"{wid}|{view.get_id()}", scroll) return scroll, store def create_grid_treeview_widget(self, view, wid): @@ -197,10 +197,10 @@ class WidgetMixin: grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{view.id}") - scroll.set_name(f"{wid}|{view.id}") + grid.set_name(f"{wid}|{view.get_id()}") + scroll.set_name(f"{wid}|{view.get_id()}") grid.columns_autosize() - self.builder.expose_object(f"{wid}|{view.id}", scroll) + self.builder.expose_object(f"{wid}|{view.get_id()}", scroll) return scroll, store diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py index 8f3d98d..f4d7392 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py @@ -113,7 +113,7 @@ class WindowMixin(TabMixin): formatted_size = self.sizeof_fmt(combined_size) - if view.hide_hidden: + if view.get_hidden(): self.bottom_path_label.set_label(f" {len(uris)} / {view.get_files_count()} ({formatted_size})") else: self.bottom_path_label.set_label(f" {len(uris)} / {view.get_not_hidden_count()} ({formatted_size})") @@ -121,7 +121,7 @@ class WindowMixin(TabMixin): return # If nothing selected - if view.hide_hidden: + if view.get_hidden(): if view.get_hidden_count() > 0: self.bottom_file_count_label.set_label(f"{view.get_not_hidden_count()} visible ({view.get_hidden_count()} hidden)") else: @@ -132,7 +132,7 @@ class WindowMixin(TabMixin): def set_window_title(self): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") view = self.get_fm_window(wid).get_view_by_id(tid) dir = view.get_current_directory() @@ -164,7 +164,7 @@ class WindowMixin(TabMixin): try: self.path_menu.popdown() wid, tid = iconview.get_name().split("|") - self.window_controller.set_active_data(wid, tid) + self.window_controller.set__wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() @@ -221,7 +221,7 @@ class WindowMixin(TabMixin): data.set_text(uris_text, -1) def grid_on_drag_motion(self, iconview, drag_context, x, y, data): - current = '|'.join(self.window_controller.get_active_data()) + current = '|'.join(self.window_controller.get_active_wid_and_tid()) target = iconview.get_name() wid, tid = target.split("|") store = iconview.get_model() @@ -232,12 +232,12 @@ class WindowMixin(TabMixin): self.override_drop_dest = uri if isdir(uri) else None if target not in current: - self.window_controller.set_active_data(wid, tid) + self.window_controller.set__wid_and_tid(wid, tid) def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): if info == 80: - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") view = self.get_fm_window(wid).get_view_by_id(tid) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/IPCSignalsMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/IPCSignalsMixin.py index 74c1ea5..8a2d4d3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/IPCSignalsMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/IPCSignalsMixin.py @@ -11,7 +11,7 @@ class IPCSignalsMixin: print(message) def handle_file_from_ipc(self, path): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") if notebook.is_visible(): self.create_tab(wid, path) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py index 17c3d0d..89fe462 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py @@ -114,7 +114,7 @@ class KeyboardSignalsMixin: if keyname == "f2": self.rename_files() if keyname == "f4": - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) dir = view.get_current_directory() view.execute(f"{view.terminal_app}", dir) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py index 02acf7a..7bb9403 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py @@ -1,5 +1,5 @@ # Python imports -import os, importlib +import os, sys, importlib, traceback from os.path import join, isdir # Lib imports @@ -47,15 +47,20 @@ class Plugins: # @threaded def load_plugins(self, file=None): print(f"Loading plugins...") + parent_path = os.getcwd() + for file in os.listdir(self._plugins_path): try: path = join(self._plugins_path, file) if isdir(path): + os.chdir(path) + gtk_socket = Gtk.Socket().new() self._plugin_list_socket.add(gtk_socket) # NOTE: Must get ID after adding socket to window. Else issues.... gtk_socket_id = gtk_socket.get_id() + sys.path.insert(0, path) spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) @@ -72,7 +77,9 @@ class Plugins: gtk_socket.show_all() except Exception as e: print("Malformed plugin! Not loading!") - print(repr(e)) + traceback.print_exc() + + os.chdir(parent_path) def reload_plugins(self, file=None): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py index 0c8b591..e69de29 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py @@ -1 +0,0 @@ -from .windows import WindowController diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/Window.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/Window.py deleted file mode 100644 index 78c5241..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/Window.py +++ /dev/null @@ -1,66 +0,0 @@ -# Python imports -from random import randint - - -# Lib imports - - -# Application imports -from .view import View - - -class Window: - def __init__(self): - self.id_length = 10 - self.id = "" - self.name = "" - self.nickname = "" - self.isHidden = False - self.views = [] - - self.generate_id() - - - def random_with_N_digits(self, n): - range_start = 10**(n-1) - range_end = (10**n)-1 - return randint(range_start, range_end) - - def generate_id(self): - self.id = str(self.random_with_N_digits(self.id_length)) - - def get_window_id(self): - return self.id - - def create_view(self): - view = View() - self.views.append(view) - return view - - def pop_view(self): - self.views.pop() - - def delete_view_by_id(self, vid): - for view in self.views: - if view.id == vid: - self.views.remove(view) - break - - - def get_view_by_id(self, vid): - for view in self.views: - if view.id == vid: - return view - - def get_view_by_index(self, index): - return self.views[index] - - def get_views_count(self): - return len(self.views) - - def get_all_views(self): - return self.views - - def list_files_from_views(self): - for view in self.views: - print(view.files) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py deleted file mode 100644 index 33e86e7..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/WindowController.py +++ /dev/null @@ -1,196 +0,0 @@ -# Python imports -import threading, subprocess, time, json -from os import path - -# Lib imports - -# Application imports -from . import Window - - -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - -class WindowController: - def __init__(self): - USER_HOME = path.expanduser('~') - CONFIG_PATH = USER_HOME + "/.config/solarfm" - self.session_file = CONFIG_PATH + "/session.json" - - self._event_sleep_time = 1 - self.active_window_id = "" - self.active_tab_id = "" - self.windows = [] - - if not trace_debug: - self.fm_event_observer() - - @threaded - def fm_event_observer(self): - while True: - time.sleep(event_sleep_time) - event = event_system.consume_module_event() - if event: - print(event) - - def set_active_data(self, wid, tid): - self.active_window_id = str(wid) - self.active_tab_id = str(tid) - - def get_active_data(self): - return self.active_window_id, self.active_tab_id - - def create_window(self): - window = Window() - window.name = "window_" + window.id - window.nickname = "window_" + str(len(self.windows) + 1) - - self.windows.append(window) - return window - - - def add_view_for_window(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.create_view() - - def add_view_for_window_by_name(self, name): - for window in self.windows: - if window.name == name: - return window.create_view() - - def add_view_for_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - return window.create_view() - - def pop_window(self): - self.windows.pop() - - def delete_window_by_id(self, win_id): - for window in self.windows: - if window.id == win_id: - self.windows.remove(window) - break - - def delete_window_by_name(self, name): - for window in self.windows: - if window.name == name: - self.windows.remove(window) - break - - def delete_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - self.windows.remove(window) - break - - def get_window_by_id(self, win_id): - for window in self.windows: - if window.id == win_id: - return window - - raise(f"No Window by ID {win_id} found!") - - def get_window_by_name(self, name): - for window in self.windows: - if window.name == name: - return window - - raise(f"No Window by Name {name} found!") - - def get_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - return window - - raise(f"No Window by Nickname {nickname} found!") - - def get_window_by_index(self, index): - return self.windows[index] - - def get_all_windows(self): - return self.windows - - def set_window_nickname(self, win_id = None, nickname = ""): - for window in self.windows: - if window.id == win_id: - window.nickname = nickname - - def list_windows(self): - print("\n[ ---- Windows ---- ]\n") - for window in self.windows: - print(f"\nID: {window.id}") - print(f"Name: {window.name}") - print(f"Nickname: {window.nickname}") - print(f"Is Hidden: {window.isHidden}") - print(f"View Count: {window.get_views_count()}") - print("\n-------------------------\n") - - - - def list_files_from_views_of_window(self, win_id): - for window in self.windows: - if window.id == win_id: - window.list_files_from_views() - break - - def get_views_count(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.get_views_count() - - def get_views_from_window(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.get_all_views() - - - - - def unload_views_and_windows(self): - for window in self.windows: - window.views.clear() - - self.windows.clear() - - def save_state(self, session_file = None): - if not session_file: - session_file = self.session_file - - if len(self.windows) > 0: - windows = [] - for window in self.windows: - views = [] - for view in window.views: - views.append(view.get_current_directory()) - - windows.append( - [ - { - 'window':{ - "ID": window.id, - "Name": window.name, - "Nickname": window.nickname, - "isHidden": f"{window.isHidden}", - 'views': views - } - } - ] - ) - - with open(session_file, 'w') as outfile: - json.dump(windows, outfile, separators=(',', ':'), indent=4) - else: - raise Exception("Window dara corrupted! Can not save session!") - - def load_state(self, session_file = None): - if not session_file: - session_file = self.session_file - - if path.isfile(session_file): - with open(session_file) as infile: - return json.load(infile) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/__init__.py index cd9f6ce..e69de29 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/__init__.py @@ -1,2 +0,0 @@ -from .Window import Window -from .WindowController import WindowController diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py new file mode 100644 index 0000000..449cfc7 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py @@ -0,0 +1,185 @@ +# Python imports +import threading, subprocess, time, json +from os import path + +# Lib imports + +# Application imports +from .window import Window + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class WindowController: + def __init__(self): + USER_HOME = path.expanduser('~') + CONFIG_PATH = USER_HOME + "/.config/solarfm" + self._session_file = CONFIG_PATH + "/session.json" + + self._event_sleep_time = 1 + self._active_window_id = "" + self._active_tab_id = "" + self._windows = [] + + + def set__wid_and_tid(self, wid, tid): + self._active_window_id = str(wid) + self._active_tab_id = str(tid) + + def get_active_wid_and_tid(self): + return self._active_window_id, self._active_tab_id + + def create_window(self): + window = Window() + window.set_nickname(f"window_{str(len(self._windows) + 1)}") + self._windows.append(window) + return window + + + def add_view_for_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.create_view() + + def add_view_for_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + return window.create_view() + + def add_view_for_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + return window.create_view() + + def pop_window(self): + self._windows.pop() + + def delete_window_by_id(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + self._windows.remove(window) + break + + def delete_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + self._windows.remove(window) + break + + def delete_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + self._windows.remove(window) + break + + def get_window_by_id(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window + + raise(f"No Window by ID {win_id} found!") + + def get_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + return window + + raise(f"No Window by Name {name} found!") + + def get_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + return window + + raise(f"No Window by Nickname {nickname} found!") + + def get_window_by_index(self, index): + return self._windows[index] + + def get_all_windows(self): + return self._windows + + + def set_window_nickname(self, win_id = None, nickname = ""): + for window in self._windows: + if window.get_id() == win_id: + window.set_nickname(nickname) + + def list_windows(self): + print("\n[ ---- Windows ---- ]\n") + for window in self._windows: + print(f"\nID: {window.get_id()}") + print(f"Name: {window.get_name()}") + print(f"Nickname: {window.get_nickname()}") + print(f"Is Hidden: {window.is_hidden()}") + print(f"View Count: {window.get_views_count()}") + print("\n-------------------------\n") + + + + def list_files_from_views_of_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + window.list_files_from_views() + break + + def get_views_count(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_views_count() + + def get_views_from_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_all_views() + + + + + def unload_views_and_windows(self): + for window in self._windows: + window.get_all_views().clear() + + self._windows.clear() + + def save_state(self, session_file = None): + if not session_file: + session_file = self._session_file + + if len(self._windows) > 0: + windows = [] + for window in self._windows: + views = [] + for view in window.get_all_views(): + views.append(view.get_current_directory()) + + windows.append( + [ + { + 'window':{ + "ID": window.get_id(), + "Name": window.get_name(), + "Nickname": window.get_nickname(), + "isHidden": f"{window.is_hidden()}", + 'views': views + } + } + ] + ) + + with open(session_file, 'w') as outfile: + json.dump(windows, outfile, separators=(',', ':'), indent=4) + else: + raise Exception("Window data corrupted! Can not save session!") + + def load_state(self, session_file = None): + if not session_file: + session_file = self._session_file + + if path.isfile(session_file): + with open(session_file) as infile: + return json.load(infile) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/__init__.py deleted file mode 100644 index 07d9ad7..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .utils import * -from .icons import * - -from .Path import Path -from .View import View diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/__init__.py deleted file mode 100644 index b946d44..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .mixins import DesktopIconMixin -from .mixins import VideoIconMixin - -from .Icon import Icon diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/__init__.py deleted file mode 100644 index 54bfe39..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import xdg - -from .VideoIconMixin import VideoIconMixin -from .DesktopIconMixin import DesktopIconMixin diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/__init__.py deleted file mode 100644 index 3efd664..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .Settings import Settings -from .Launcher import Launcher -from .FileHandler import FileHandler diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/Icon.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/icon.py similarity index 94% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/Icon.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/icon.py index 3c14d2f..6afa0e5 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/Icon.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/icon.py @@ -3,10 +3,13 @@ import os, subprocess, threading, hashlib from os.path import isfile # Gtk imports +import gi +gi.require_version('GdkPixbuf', '2.0') from gi.repository import GdkPixbuf # Application imports -from .mixins import * +from .mixins.desktopiconmixin import DesktopIconMixin +from .mixins.videoiconmixin import VideoIconMixin def threaded(fn): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/desktopiconmixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/DesktopIconMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/desktopiconmixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/videoiconmixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/VideoIconMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/videoiconmixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Config.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Config.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Config.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Config.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Exceptions.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Exceptions.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Exceptions.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/IconTheme.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/IconTheme.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/IconTheme.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/IniFile.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/IniFile.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/IniFile.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Locale.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Locale.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Locale.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Locale.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Menu.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Menu.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Menu.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Menu.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Mime.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Mime.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/Mime.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Mime.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/util.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/util.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/mixins/xdg/util.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/util.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/Path.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/path.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/Path.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/path.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/FileHandler.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/filehandler.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/FileHandler.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/filehandler.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/Launcher.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/launcher.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/Launcher.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/launcher.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/Settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/settings.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/Settings.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/settings.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/View.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py similarity index 51% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/View.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py index 594dee1..7d97c45 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/View.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py @@ -1,6 +1,5 @@ # Python imports -import hashlib -import os +import os, hashlib, re from os import listdir from os.path import isdir, isfile, join @@ -11,64 +10,43 @@ from random import randint # Application imports -from .utils import Settings, Launcher, FileHandler -from .icons import Icon -from . import Path +from .utils.settings import Settings +from .utils.launcher import Launcher +from .utils.filehandler import FileHandler + +from .icons.icon import Icon +from .path import Path class View(Settings, FileHandler, Launcher, Icon, Path): def __init__(self): - self. logger = None - self.id_length = 10 + self.logger = None + self._id_length = 10 - self.id = "" - self.wid = None - self.dir_watcher = None - self.hide_hidden = self.HIDE_HIDDEN_FILES - self.files = [] - self.dirs = [] - self.vids = [] - self.images = [] - self.desktop = [] - self.ungrouped = [] - self.hidden = [] + self._id = "" + self._wid = None + self._dir_watcher = None + self._hide_hidden = self.HIDE_HIDDEN_FILES + self._files = [] + self._dirs = [] + self._vids = [] + self._images = [] + self._desktop = [] + self._ungrouped = [] + self._hidden = [] - self.generate_id() + self._generate_id() self.set_to_home() - - def random_with_N_digits(self, n): - range_start = 10**(n-1) - range_end = (10**n)-1 - return randint(range_start, range_end) - - def generate_id(self): - self.id = str(self.random_with_N_digits(self.id_length)) - - def get_tab_id(self): - return self.id - - def set_wid(self, _wid): - self.wid = _wid - - def get_wid(self): - return self.wid - - def set_dir_watcher(self, watcher): - self.dir_watcher = watcher - - def get_dir_watcher(self): - return self.dir_watcher - def load_directory(self): - path = self.get_path() - self.dirs = [] - self.vids = [] - self.images = [] - self.desktop = [] - self.ungrouped = [] - self.hidden = [] - self.files = [] + path = self.get_path() + self._dirs = [] + self._vids = [] + self._images = [] + self._desktop = [] + self._ungrouped = [] + self._hidden = [] + self._files = [] if not isdir(path): self.set_to_home() @@ -76,40 +54,31 @@ class View(Settings, FileHandler, Launcher, Icon, Path): for f in listdir(path): file = join(path, f) - if self.hide_hidden: + if self._hide_hidden: if f.startswith('.'): - self.hidden.append(f) + self._hidden.append(f) continue if isfile(file): lowerName = file.lower() if lowerName.endswith(self.fvideos): - self.vids.append(f) + self._vids.append(f) elif lowerName.endswith(self.fimages): - self.images.append(f) + self._images.append(f) elif lowerName.endswith((".desktop",)): - self.desktop.append(f) + self._desktop.append(f) else: - self.ungrouped.append(f) + self._ungrouped.append(f) else: - self.dirs.append(f) + self._dirs.append(f) - self.dirs.sort() - self.vids.sort() - self.images.sort() - self.desktop.sort() - self.ungrouped.sort() + self._dirs.sort(key=self._natural_keys) + self._vids.sort(key=self._natural_keys) + self._images.sort(key=self._natural_keys) + self._desktop.sort(key=self._natural_keys) + self._ungrouped.sort(key=self._natural_keys) - self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped - - def hash_text(self, text): - return hashlib.sha256(str.encode(text)).hexdigest()[:18] - - def hash_set(self, arry): - data = [] - for arr in arry: - data.append([arr, self.hash_text(arr)]) - return data + self._files = self._dirs + self._vids + self._images + self._desktop + self._ungrouped def is_folder_locked(self, hash): if self.lock_folder: @@ -129,18 +98,18 @@ class View(Settings, FileHandler, Launcher, Icon, Path): def get_not_hidden_count(self): - return len(self.files) + \ - len(self.dirs) + \ - len(self.vids) + \ - len(self.images) + \ - len(self.desktop) + \ - len(self.ungrouped) + return len(self._files) + \ + len(self._dirs) + \ + len(self._vids) + \ + len(self._images) + \ + len(self._desktop) + \ + len(self._ungrouped) def get_hidden_count(self): - return len(self.hidden) + return len(self._hidden) def get_files_count(self): - return len(self.files) + return len(self._files) def get_path_part_from_hash(self, hash): files = self.get_files() @@ -154,13 +123,13 @@ class View(Settings, FileHandler, Launcher, Icon, Path): return file def get_files_formatted(self): - files = self.hash_set(self.files), - dirs = self.hash_set(self.dirs), + files = self._hash_set(self._files), + dirs = self._hash_set(self._dirs), videos = self.get_videos(), - images = self.hash_set(self.images), - desktops = self.hash_set(self.desktop), - ungrouped = self.hash_set(self.ungrouped) - hidden = self.hash_set(self.hidden) + images = self._hash_set(self._images), + desktops = self._hash_set(self._desktop), + ungrouped = self._hash_set(self._ungrouped) + hidden = self._hash_set(self._hidden) return { 'path_head': self.get_path(), @@ -178,7 +147,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path): def get_pixbuf_icon_str_combo(self): data = [] dir = self.get_current_directory() - for file in self.files: + for file in self._files: icon = self.create_icon(dir, file).get_pixbuf() data.append([icon, file]) @@ -188,7 +157,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path): def get_gtk_icon_str_combo(self): data = [] dir = self.get_current_directory() - for file in self.files: + for file in self._files: icon = self.create_icon(dir, file) data.append([icon, file[0]]) @@ -207,23 +176,69 @@ class View(Settings, FileHandler, Launcher, Icon, Path): size = len(parts) return parts[size - 1] + + + def is_hiding_hidden(self): + return self._hide_hidden + def get_dot_dots(self): - return self.hash_set(['.', '..']) + return self._hash_set(['.', '..']) def get_files(self): - return self.hash_set(self.files) + return self._hash_set(self._files) def get_dirs(self): - return self.hash_set(self.dirs) + return self._hash_set(self._dirs) def get_videos(self): - return self.hash_set(self.vids) + return self._hash_set(self._vids) def get_images(self): - return self.hash_set(self.images) + return self._hash_set(self._images) def get_desktops(self): - return self.hash_set(self.desktop) + return self._hash_set(self._desktop) def get_ungrouped(self): - return self.hash_set(self.ungrouped) + return self._hash_set(self._ungrouped) + + def get_hidden(self): + return self._hash_set(self._hidden) + + def get_id(self): + return self._id + + def set_wid(self, _wid): + self._wid = _wid + + def get_wid(self): + return self._wid + + def set_dir_watcher(self, watcher): + self._dir_watcher = watcher + + def get_dir_watcher(self): + return self._dir_watcher + + def _atoi(self, text): + return int(text) if text.isdigit() else text + + def _natural_keys(self, text): + return [ self._atoi(c) for c in re.split('(\d+)',text) ] + + def _hash_text(self, text): + return hashlib.sha256(str.encode(text)).hexdigest()[:18] + + def _hash_set(self, arry): + data = [] + for arr in arry: + data.append([arr, self._hash_text(arr)]) + return data + + def _random_with_N_digits(self, n): + range_start = 10**(n-1) + range_end = (10**n)-1 + return randint(range_start, range_end) + + def _generate_id(self): + self._id = str(self._random_with_N_digits(self._id_length)) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py new file mode 100644 index 0000000..58d8414 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py @@ -0,0 +1,89 @@ +# Python imports +from random import randint + + +# Lib imports + + +# Application imports +from .views.view import View + + +class Window: + def __init__(self): + self._id_length = 10 + self._id = "" + self._name = "" + self._nickname = "" + self._isHidden = False + self._views = [] + + self._generate_id() + self._set_name() + + + def create_view(self): + view = View() + self._views.append(view) + return view + + def pop_view(self): + self._views.pop() + + def delete_view_by_id(self, vid): + for view in self._views: + if view.get_id() == vid: + self._views.remove(view) + break + + + def get_view_by_id(self, vid): + for view in self._views: + if view.get_id() == vid: + return view + + def get_view_by_index(self, index): + return self._views[index] + + def get_views_count(self): + return len(self._views) + + def get_all_views(self): + return self._views + + def list_files_from_views(self): + for view in self._views: + print(view.get_files()) + + + def get_id(self): + return self._id + + def get_name(self): + return self._name + + def get_nickname(self): + return self._nickname + + def is_hidden(self): + return self._isHidden + + + + + def set_nickname(self, nickname): + self._nickname = f"{nickname}" + + def set_is_hidden(self, state): + self._isHidden = f"{state}" + + def _set_name(self): + self._name = "window_" + self.get_id() + + def _random_with_N_digits(self, n): + range_start = 10**(n-1) + range_end = (10**n)-1 + return randint(range_start, range_end) + + def _generate_id(self): + self._id = str(self._random_with_N_digits(self._id_length)) diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index e55b351..9adfb86 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -643,6 +643,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False end + + + gtk-cancel + True + True + True + True + True + + + True + True + 0 + + Create @@ -657,21 +672,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 0 - - - - - gtk-cancel - True - True - True - True - True - - - True - True 1 @@ -783,8 +783,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - button10 button9 + button10 @@ -1246,14 +1246,13 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False end - - Skip - skip_renames + + gtk-cancel + cancel_renames True True True - skip_img - True + True True @@ -1262,13 +1261,14 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - - gtk-cancel - cancel_renames + + Skip + skip_renames True True True - True + skip_img + True True @@ -1366,8 +1366,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - button1 button2 + button1 -- 2.27.0 From 2622600e924fe061027180c4c8158aab47b1fed7 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 14 Feb 2022 21:12:07 -0600 Subject: [PATCH 10/41] Adding signals, resolving broken signals --- .../SolarFM/solarfm/controller/Controller.py | 10 ++-- .../mixins/ui/WidgetFileActionMixin.py | 4 +- .../controller/mixins/ui/WindowMixin.py | 2 + .../signals/KeyboardSignalsMixin.py | 16 ++++-- .../usr/share/solarfm/Main_Window.glade | 54 +++++++++++++++---- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index 325c860..f87902a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -75,6 +75,11 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi data = method(*(self, *parameters)) self.plugins.set_message_on_plugin(type, data) + def open_terminal(self, widget=None, eve=None): + wid, tid = self.window_controller.get_active_wid_and_tid() + view = self.get_fm_window(wid).get_view_by_id(tid) + dir = view.get_current_directory() + view.execute(f"{view.terminal_app}", dir) def save_load_session(self, action="save_session"): wid, tid = self.window_controller.get_active_wid_and_tid() @@ -107,7 +112,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi save_load_dialog.hide() - def load_session(self, session_json): if debug: print(f"Session Data: {session_json}") @@ -162,8 +166,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.empty_trash() if action == "create": - self.create_files() + self.show_new_file_menu() if action in ["save_session", "save_session_as", "load_session"]: self.save_load_session(action) - - self.ctrlDown = False diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py index 161cf11..8b7d275 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py @@ -87,8 +87,8 @@ class WidgetFileActionMixin: def do_file_search(self, widget, eve=None): query = widget.get_text() self.search_iconview.unselect_all() - for i, file in enumerate(self.search_view.files): - if query and query in file.lower(): + for i, file in enumerate(self.search_view.get_files()): + if query and query in file[0].lower(): path = Gtk.TreePath().new_from_indices([i]) self.search_iconview.select_path(path) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py index f4d7392..c4ee00c 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py @@ -182,9 +182,11 @@ class WindowMixin(TabMixin): def grid_icon_double_click(self, iconview, item, data=None): try: if self.ctrlDown and self.shiftDown: + self.unset_keys_and_data() self.execute_files(in_terminal=True) return elif self.ctrlDown: + self.unset_keys_and_data() self.execute_files() return diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py index 89fe462..ee27648 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py @@ -47,6 +47,7 @@ class KeyboardSignalsMixin: if self.ctrlDown and self.shiftDown and keyname == "t": + self.unset_keys_and_data() self.trash_files() @@ -57,6 +58,7 @@ class KeyboardSignalsMixin: if isinstance(focused_obj, Gtk.IconView): self.is_searching = True wid, tid, self.search_view, self.search_iconview, store = self.get_current_state() + self.unset_keys_and_data() self.popup_search_files(wid, keyname) return @@ -79,26 +81,30 @@ class KeyboardSignalsMixin: if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"): self.builder.get_object("go_up").released() if self.ctrlDown and keyname == "l": + self.unset_keys_and_data() self.builder.get_object("path_entry").grab_focus() if self.ctrlDown and keyname == "t": self.builder.get_object("create_tab").released() if self.ctrlDown and keyname == "o": + self.unset_keys_and_data() self.open_files() if self.ctrlDown and keyname == "w": self.keyboard_close_tab() if self.ctrlDown and keyname == "h": self.show_hide_hidden_files() if (self.ctrlDown and keyname == "e"): + self.unset_keys_and_data() self.rename_files() if self.ctrlDown and keyname == "c": - self.to_cut_files.clear() self.copy_files() + self.to_cut_files.clear() if self.ctrlDown and keyname == "x": self.to_copy_files.clear() self.cut_files() if self.ctrlDown and keyname == "v": self.paste_files() if self.ctrlDown and keyname == "n": + self.unset_keys_and_data() self.show_new_file_menu() @@ -110,11 +116,11 @@ class KeyboardSignalsMixin: else: top_main_menubar.show() 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": - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - dir = view.get_current_directory() - view.execute(f"{view.terminal_app}", dir) + self.unset_keys_and_data() + self.open_terminal() diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 9adfb86..07da0ac 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1144,6 +1144,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-save + + True + False + gtk-execute + True @@ -1459,6 +1464,16 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False + + + Terminal + True + False + image5 + False + + + True @@ -2397,6 +2412,24 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 3 + + + gtk-new + create + True + True + True + New File/Folder... + 20 + True + + + + False + True + 4 + + Rename @@ -2405,7 +2438,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True Rename... - 20 rename_img2 True @@ -2413,7 +2445,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 4 + 5 @@ -2431,7 +2463,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 5 + 6 @@ -2449,7 +2481,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 6 + 7 @@ -2467,7 +2499,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 7 + 8 @@ -2485,7 +2517,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 8 + 9 @@ -2502,7 +2534,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 9 + 10 @@ -2518,7 +2550,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 10 + 11 @@ -2537,7 +2569,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 11 + 12 @@ -2555,7 +2587,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 12 + 13 @@ -2574,7 +2606,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 13 + 14 -- 2.27.0 From 07714c9cd43dbf5e23a6c35f22e5a489ad9acdd3 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 15 Feb 2022 17:52:07 -0600 Subject: [PATCH 11/41] Fixing hide hidden toggle --- .../SolarFM/solarfm/controller/mixins/ui/TabMixin.py | 2 +- .../solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py index ba0d6bd..365fc72 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py @@ -200,6 +200,6 @@ class TabMixin(WidgetMixin): def show_hide_hidden_files(self): wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) - view.set_is_hidden(not view.is_hidden()) + view.set_hiding_hidden(not view.is_hiding_hidden()) view.load_directory() self.builder.get_object("refresh_view").released() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py index 7d97c45..a913d67 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py @@ -177,6 +177,8 @@ class View(Settings, FileHandler, Launcher, Icon, Path): return parts[size - 1] + def set_hiding_hidden(self, state): + self._hide_hidden = state def is_hiding_hidden(self): return self._hide_hidden -- 2.27.0 From 918eec10534a19625d9bfc449c0b1f2a3afba694 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 17 Feb 2022 10:13:27 -0600 Subject: [PATCH 12/41] Small logic fixes --- .../solarfm/controller/Controller_Data.py | 1 + .../mixins/ui/WidgetFileActionMixin.py | 59 +++++++++++++++---- .../controller/mixins/ui/WindowMixin.py | 2 +- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py index c0e5726..c737fb3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py @@ -82,6 +82,7 @@ class Controller_Data: self.selected_files = [] self.to_copy_files = [] self.to_cut_files = [] + self.soft_update_lock = {} self.single_click_open = False self.is_pane1_hidden = False diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py index 8b7d275..0e98313 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py @@ -1,5 +1,5 @@ # Python imports -import os +import os, time, threading # Lib imports import gi @@ -9,6 +9,10 @@ from gi.repository import Gtk, GObject, GLib, Gio # Application imports +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper class WidgetFileActionMixin: @@ -56,25 +60,56 @@ class WidgetFileActionMixin: dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",)) view.set_dir_watcher(dir_watcher) + # NOTE: Too lazy to impliment a proper update handler and so just regen store and update view. + # Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None): if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, - Gio.FileMonitorEvent.MOVED_OUT]: + Gio.FileMonitorEvent.MOVED_OUT, Gio.FileMonitorEvent.CHANGES_DONE_HINT]: if debug: print(eve_type) - wid, tid = data[0].split("|") - notebook = self.builder.get_object(f"window_{wid}") - view = self.get_fm_window(wid).get_view_by_id(tid) - iconview = self.builder.get_object(f"{wid}|{tid}|iconview") - store = iconview.get_model() - _store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") + if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: + self.update_on_end_soft_lock(data[0]) + elif data[0] in self.soft_update_lock.keys(): + self.soft_update_lock[data[0]]["last_update_time"] = time.time() + else: + self.soft_lock_countdown(data[0]) - view.load_directory() - self.load_store(view, store) - tab_label.set_label(view.get_end_of_path()) - self.set_bottom_labels(view) + @threaded + def soft_lock_countdown(self, tab): + self.soft_update_lock[tab] = { "last_update_time": time.time()} + lock = True + while lock: + time.sleep(0.6) + last_update_time = self.soft_update_lock[tab]["last_update_time"] + current_time = time.time() + if (current_time - last_update_time) > 0.6: + lock = False + + + self.soft_update_lock.pop(tab, None) + GLib.idle_add(self.update_on_end_soft_lock, *(tab,)) + + + def update_on_end_soft_lock(self, tab): + wid, tid = tab.split("|") + notebook = self.builder.get_object(f"window_{wid}") + view = self.get_fm_window(wid).get_view_by_id(tid) + iconview = self.builder.get_object(f"{wid}|{tid}|iconview") + store = iconview.get_model() + _store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") + + view.load_directory() + self.load_store(view, store) + + tab_label.set_label(view.get_end_of_path()) + + _wid, _tid, _view, _iconview, _store = self.get_current_state() + + if [wid, tid] in [_wid, _tid]: + self.set_bottom_labels(view) def popup_search_files(self, wid, keyname): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py index c4ee00c..1f1b29b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py @@ -146,7 +146,7 @@ class WindowMixin(TabMixin): ctx.remove_class("notebook-unselected-focus") ctx.add_class("notebook-selected-focus") - self.window.set_title("SolarFM ~ " + dir) + self.window.set_title(f"SolarFM ~ {dir}") self.set_bottom_labels(view) def set_path_text(self, wid, tid): -- 2.27.0 From f79aa4e852816a3523aefa3a1cbe49d699c4784f Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 19 Feb 2022 22:46:54 -0600 Subject: [PATCH 13/41] template fix, logic fix, file monitor change --- plugins/template/__main__.py | 2 +- .../SolarFM/solarfm/controller/Controller.py | 3 +-- .../mixins/ui/WidgetFileActionMixin.py | 2 +- .../solarfm-0.0.1/SolarFM/solarfm/solarfm.zip | Bin 0 -> 130385 bytes 4 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/solarfm.zip diff --git a/plugins/template/__main__.py b/plugins/template/__main__.py index 63cf7cc..3ae0454 100644 --- a/plugins/template/__main__.py +++ b/plugins/template/__main__.py @@ -21,7 +21,7 @@ class Main: self._event_system = event_system self._socket_id = socket_id self._gtk_plug = Gtk.Plug.new(self._socket_id) - button = Gtk.Button(label=label=self._plugin_name) + button = Gtk.Button(label=self._plugin_name) self._message = None self._time_out = 5 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index f87902a..dee9397 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -128,8 +128,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def do_action_from_menu_controls(self, widget, eventbutton): - action = widget.get_name() - self.ctrlDown = True + action = widget.get_name() self.hide_context_menu() self.hide_new_file_menu() self.hide_edit_file_menu() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py index 0e98313..dd8e274 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py @@ -65,7 +65,7 @@ class WidgetFileActionMixin: def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None): if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, - Gio.FileMonitorEvent.MOVED_OUT, Gio.FileMonitorEvent.CHANGES_DONE_HINT]: + Gio.FileMonitorEvent.MOVED_OUT]: if debug: print(eve_type) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/solarfm.zip b/src/versions/solarfm-0.0.1/SolarFM/solarfm/solarfm.zip new file mode 100644 index 0000000000000000000000000000000000000000..be50e3c1bc50ca7b366a2df229a63f744a557739 GIT binary patch literal 130385 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 MJuq2r-?Pd80}_$>u>b%7 literal 0 HcmV?d00001 -- 2.27.0 From 7b4bbd7c2be446a41403ac60e02c8be19d7461d5 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 19 Feb 2022 23:15:02 -0600 Subject: [PATCH 14/41] Cleaning up unused imports --- .gitignore | 2 ++ .../solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py | 2 +- .../solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py | 2 +- .../SolarFM/solarfm/controller/mixins/ShowHideMixin.py | 2 +- .../SolarFM/solarfm/controller/mixins/ui/TabMixin.py | 3 +-- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index f8b73e7..bf657bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea/ + # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index dee9397..b456000 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -1,5 +1,5 @@ # Python imports -import traceback, threading, inspect, os, gc, time +import os, gc, threading, time # Lib imports import gi diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py index b273c9b..fabc763 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py @@ -1,5 +1,5 @@ # Python imports -import threading, socket, time +import threading, time from multiprocessing.connection import Listener, Client # Lib imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py index d205511..d73d098 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py @@ -4,7 +4,7 @@ import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk, Gio +from gi.repository import Gtk, Gdk # Application imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py index 365fc72..41cc2c8 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py @@ -4,8 +4,7 @@ import os # Lib imports import gi gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk +from gi.repository import Gtk # Application imports from . import WidgetMixin -- 2.27.0 From 312a782a8705fe030bd18230dfc11e90796301f8 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 20 Feb 2022 00:28:34 -0600 Subject: [PATCH 15/41] Slight Pep8 fixes, import cleanup --- .gitignore | 1 + .../SolarFM/solarfm/controller/Controller.py | 2 +- .../solarfm/controller/IPCServerMixin.py | 2 +- .../controller/mixins/ExceptionHookMixin.py | 8 ++++---- .../controller/mixins/ui/WindowMixin.py | 4 ++-- .../solarfm/shellfm/windows/controller.py | 2 +- .../views/icons/mixins/desktopiconmixin.py | 3 --- .../shellfm/windows/views/utils/filehandler.py | 2 +- .../solarfm/shellfm/windows/views/view.py | 2 +- .../solarfm-0.0.1/SolarFM/solarfm/solarfm.zip | Bin 130385 -> 0 bytes 10 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/solarfm.zip diff --git a/.gitignore b/.gitignore index bf657bb..a7059ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ +*.zip # ---> Python # Byte-compiled / optimized / DLL files diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py index b456000..3f8cf71 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py @@ -127,7 +127,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi gc.collect() - def do_action_from_menu_controls(self, widget, eventbutton): + def do_action_from_menu_controls(self, widget, event_button): action = widget.get_name() self.hide_context_menu() self.hide_new_file_menu() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py index fabc763..575f62c 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py @@ -48,7 +48,7 @@ class IPCServerMixin: conn.close() break - # NOTE: Not perfect but insures we don't lockup the connection for too long. + # NOTE: Not perfect but insures we don't lock up the connection for too long. end_time = time.time() if (end - start) > self.ipc_timeout: conn.close() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py index fecab38..0efee11 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py @@ -18,9 +18,9 @@ def threaded(fn): class ExceptionHookMixin: ''' ExceptionHookMixin custom exception hook to reroute to a Gtk text area. ''' - def custom_except_hook(self, exctype, value, _traceback): + def custom_except_hook(self, exec_type, value, _traceback): trace = ''.join(traceback.format_tb(_traceback)) - data = f"Exectype: {exctype} <--> 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() self.message_buffer.place_cursor(start_itr) self.display_message(self.error, data) @@ -58,5 +58,5 @@ class ExceptionHookMixin: def set_arc_buffer_text(self, widget=None, eve=None): - id = widget.get_active_id() - self.arc_command_buffer.set_text(self.arc_commands[int(id)]) + sid = widget.get_active_id() + self.arc_command_buffer.set_text(self.arc_commands[int(sid)]) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py index 1f1b29b..89167cb 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py @@ -79,8 +79,8 @@ class WindowMixin(TabMixin): def set_bottom_labels(self, view): - _wid, _tid, _view, iconview, store = self.get_current_state() - selected_files = iconview.get_selected_items() + _wid, _tid, _view, icon_view, store = self.get_current_state() + selected_files = icon_view.get_selected_items() current_directory = view.get_current_directory() path_file = Gio.File.new_for_path(current_directory) mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py index 449cfc7..67457b1 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py @@ -1,5 +1,5 @@ # Python imports -import threading, subprocess, time, json +import threading, json from os import path # Lib imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/desktopiconmixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/desktopiconmixin.py index 2d3c30b..9f5ed2e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/desktopiconmixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/desktopiconmixin.py @@ -3,9 +3,6 @@ import os, subprocess, hashlib from os.path import isfile # Gtk imports -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk # Application imports from .xdg.DesktopEntry import DesktopEntry diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/filehandler.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/filehandler.py index d0f7396..105782a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/filehandler.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/filehandler.py @@ -1,5 +1,5 @@ # Python imports -import os, shutil, subprocess, threading +import os, shutil # Lib imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py index a913d67..5676659 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py @@ -1,5 +1,5 @@ # Python imports -import os, hashlib, re +import hashlib, re from os import listdir from os.path import isdir, isfile, join diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/solarfm.zip b/src/versions/solarfm-0.0.1/SolarFM/solarfm/solarfm.zip deleted file mode 100644 index be50e3c1bc50ca7b366a2df229a63f744a557739..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130385 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 MJuq2r-?Pd80}_$>u>b%7 -- 2.27.0 From 56b8ee6117eb236f02f3169904638f45f75a9056 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 20 Feb 2022 01:32:51 -0600 Subject: [PATCH 16/41] Import refactor, deb build update, docstring additions --- bin/solarfm-0-0-1-x64.deb | Bin 130472 -> 132972 bytes src/debs/clear_pycache_dirs.sh | 12 - .../opt/SolarFM/__builtins__.py | 35 +-- .../solarfm-0-0-1-x64/opt/SolarFM/__init__.py | 8 +- .../solarfm-0-0-1-x64/opt/SolarFM/__main__.py | 2 + .../opt/SolarFM/context/__init__.py | 3 + .../opt/SolarFM/context/controller.py} | 10 +- .../opt/SolarFM/context/controller_data.py} | 4 +- .../opt/SolarFM/context/ipc_server_mixin.py} | 2 +- .../opt/SolarFM/context/mixins/__init__.py | 0 .../context/mixins/exception_hook_mixin.py} | 2 +- .../context/mixins/show_hide_mixin.py} | 0 .../opt/SolarFM/context/mixins/ui/__init__.py | 0 .../SolarFM/context/mixins/ui/pane_mixin.py} | 0 .../SolarFM/context/mixins/ui/tab_mixin.py} | 2 +- .../mixins/ui/widget_file_action_mixin.py} | 2 + .../context/mixins/ui/widget_mixin.py} | 2 + .../context/mixins/ui/window_mixin.py} | 4 +- .../opt/SolarFM/context/mixins/ui_mixin.py | 14 ++ .../opt/SolarFM/context/signals/__init__.py | 0 .../context/signals/ipc_signals_mixin.py} | 2 + .../signals/keyboard_signals_mixin.py} | 2 + .../opt/SolarFM/plugins/__init__.py | 3 + .../opt/SolarFM/plugins/plugins.py} | 3 +- .../opt/SolarFM/shellfm/__init__.py | 1 - .../opt/SolarFM/shellfm/windows/Window.py | 66 ------ .../shellfm/windows/WindowController.py | 181 --------------- .../opt/SolarFM/shellfm/windows/__init__.py | 2 - .../opt/SolarFM/shellfm/windows/controller.py | 185 ++++++++++++++++ .../SolarFM/shellfm/windows/view/__init__.py | 5 - .../shellfm/windows/view/icons/__init__.py | 4 - .../windows/view/icons/mixins/__init__.py | 4 - .../shellfm/windows/view/utils/__init__.py | 3 - .../SolarFM/shellfm/windows/views/__init__.py | 0 .../shellfm/windows/views/icons/__init__.py | 0 .../icons/Icon.py => views/icons/icon.py} | 5 +- .../windows/views/icons/mixins/__init__.py | 0 .../icons/mixins/desktopiconmixin.py} | 3 - .../icons/mixins/videoiconmixin.py} | 0 .../icons/mixins/xdg/BaseDirectory.py | 0 .../icons/mixins/xdg/Config.py | 0 .../icons/mixins/xdg/DesktopEntry.py | 0 .../icons/mixins/xdg/Exceptions.py | 0 .../icons/mixins/xdg/IconTheme.py | 0 .../icons/mixins/xdg/IniFile.py | 0 .../icons/mixins/xdg/Locale.py | 0 .../{view => views}/icons/mixins/xdg/Menu.py | 0 .../icons/mixins/xdg/MenuEditor.py | 0 .../{view => views}/icons/mixins/xdg/Mime.py | 0 .../icons/mixins/xdg/RecentFiles.py | 0 .../icons/mixins/xdg/__init__.py | 0 .../{view => views}/icons/mixins/xdg/util.py | 0 .../windows/{view/Path.py => views/path.py} | 0 .../shellfm/windows/views/utils/__init__.py | 0 .../utils/filehandler.py} | 2 +- .../Launcher.py => views/utils/launcher.py} | 0 .../Settings.py => views/utils/settings.py} | 0 .../windows/{view/View.py => views/view.py} | 209 ++++++++++-------- .../opt/SolarFM/shellfm/windows/window.py | 89 ++++++++ .../opt/SolarFM/signal_classes/Controller.py | 165 -------------- .../opt/SolarFM/signal_classes/Plugins.py | 41 ---- .../opt/SolarFM/signal_classes/__init__.py | 8 - .../SolarFM/signal_classes/mixins/__init__.py | 2 - .../signal_classes/mixins/ui/__init__.py | 5 - .../opt/SolarFM/trasher/trash.py | 2 +- .../opt/SolarFM/utils/__init__.py | 6 - .../SolarFM/utils/{Logger.py => logger.py} | 0 .../opt/SolarFM/utils/settings.py} | 2 +- .../SolarFM/solarfm/__builtins__.py | 2 +- .../solarfm-0.0.1/SolarFM/solarfm/__init__.py | 6 +- .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 2 +- .../SolarFM/solarfm/context/__init__.py | 3 + .../SolarFM/solarfm/context/controller.py | 172 ++++++++++++++ .../solarfm/context/controller_data.py} | 62 +++++- .../solarfm/context/ipc_server_mixin.py} | 7 +- .../solarfm/context/mixins/__init__.py | 0 .../context/mixins/exception_hook_mixin.py | 62 ++++++ .../context/mixins/show_hide_mixin.py} | 27 ++- .../solarfm/context/mixins/ui/__init__.py | 0 .../solarfm/context/mixins/ui/pane_mixin.py} | 2 +- .../solarfm/context/mixins/ui/tab_mixin.py} | 49 ++-- .../mixins/ui/widget_file_action_mixin.py} | 123 +++++++++-- .../context/mixins/ui/widget_mixin.py} | 20 +- .../context/mixins/ui/window_mixin.py} | 32 +-- .../solarfm/context/mixins/ui_mixin.py | 14 ++ .../solarfm/context/signals/__init__.py | 0 .../context/signals/ipc_signals_mixin.py | 29 +++ .../signals/keyboard_signals_mixin.py} | 20 +- .../SolarFM/solarfm/controller/__init__.py | 7 - .../solarfm/controller/mixins/UIMixin.py | 11 - .../solarfm/controller/mixins/__init__.py | 3 - .../solarfm/controller/mixins/ui/__init__.py | 5 - .../solarfm/controller/signals/__init__.py | 2 - .../SolarFM/solarfm/plugins/__init__.py | 1 - .../SolarFM/solarfm/plugins/plugins.py | 99 +++++++++ .../SolarFM/solarfm/utils/__init__.py | 6 - .../solarfm/utils/{Logger.py => logger.py} | 0 .../SolarFM/solarfm/utils/settings.py} | 26 +-- .../usr/share/solarfm/Main_Window.glade | 40 ++-- .../usr/share/solarfm/solarfm-64x64.png | Bin 0 -> 16172 bytes user_config/usr/share/solarfm/solarfm.png | Bin 0 -> 16172 bytes 101 files changed, 1117 insertions(+), 817 deletions(-) delete mode 100644 src/debs/clear_pycache_dirs.sh create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/__init__.py rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py} (94%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller_data.py} (97%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/ipc_server_mixin.py} (94%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/__init__.py rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/exception_hook_mixin.py} (96%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/show_hide_mixin.py} (100%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/__init__.py rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py} (100%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py} (99%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_file_action_mixin.py} (99%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py} (99%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py} (99%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui_mixin.py create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/__init__.py rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/IPCSignalsMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/ipc_signals_mixin.py} (89%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py => debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py} (98%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/__init__.py rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py => debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py} (99%) delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/Window.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/WindowController.py create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/controller.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/__init__.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/__init__.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/__init__.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/__init__.py create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/__init__.py create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/__init__.py rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view/icons/Icon.py => views/icons/icon.py} (94%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/__init__.py rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view/icons/mixins/DesktopIconMixin.py => views/icons/mixins/desktopiconmixin.py} (96%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view/icons/mixins/VideoIconMixin.py => views/icons/mixins/videoiconmixin.py} (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/BaseDirectory.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/Config.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/DesktopEntry.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/Exceptions.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/IconTheme.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/IniFile.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/Locale.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/Menu.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/MenuEditor.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/Mime.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/RecentFiles.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/__init__.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view => views}/icons/mixins/xdg/util.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view/Path.py => views/path.py} (100%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/__init__.py rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view/utils/FileHandler.py => views/utils/filehandler.py} (98%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view/utils/Launcher.py => views/utils/launcher.py} (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view/utils/Settings.py => views/utils/settings.py} (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{view/View.py => views/view.py} (51%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Plugins.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/__init__.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/__init__.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/__init__.py rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/{Logger.py => logger.py} (100%) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/utils/Settings.py => debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py} (99%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller.py rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller_Data.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py} (68%) rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/IPCServerMixin.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/ipc_server_mixin.py} (83%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/exception_hook_mixin.py rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ShowHideMixin.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py} (87%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/PaneMixin.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py} (98%) rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/TabMixin.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py} (82%) rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetFileActionMixin.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py} (74%) rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetMixin.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py} (93%) rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WindowMixin.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py} (91%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui_mixin.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/KeyboardSignalsMixin.py => versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py} (90%) delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/__init__.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/UIMixin.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/__init__.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/{Logger.py => logger.py} (100%) rename src/{debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Settings.py => versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py} (78%) create mode 100644 user_config/usr/share/solarfm/solarfm-64x64.png create mode 100644 user_config/usr/share/solarfm/solarfm.png diff --git a/bin/solarfm-0-0-1-x64.deb b/bin/solarfm-0-0-1-x64.deb index 744a9c4e99507452717284b862c0db7053bd5651..f09cae707b9219bcd13137661349d7f3bc4834b5 100644 GIT binary patch literal 132972 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

qFEiKeBN=9-czjPK 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

6&UbXW7K|pj2RhWjq!u0_pS^z4ed6P*3xxD_ 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

Q@}XeG*FY9Ma4r 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 n-dGI)A6`5Zk{IV=00E@+2Y`?vb>YT 0: - return self._fm_events.pop(0) + def _pop_module_event(self): + if len(self._module_events) > 0: + return self._module_events.pop(0) return None @@ -41,32 +44,32 @@ class Builtins(IPCServerMixin): self._gui_events.append(event) return None - raise Exception("Invald event format! Please do: [type, target, data]") + raise Exception("Invald event format! Please do: [type, target, (data,)]") - def push_fm_event(self, event): + def push_module_event(self, event): if len(event) == 3: - self._fm_events.append(event) + self._module_events.append(event) return None - raise Exception("Invald event format! Please do: [type, target, data]") + raise Exception("Invald event format! Please do: [type, target, (data,)]") def read_gui_event(self): return self._gui_events[0] - def read_fm_event(self): - return self._fm_events[0] + def read_module_event(self): + return self._module_events[0] def consume_gui_event(self): return self._pop_gui_event() - def consume_fm_event(self): - return self._pop_fm_event() + def consume_module_event(self): + return self._pop_module_event() # NOTE: Just reminding myself we can add to builtins two different ways... # __builtins__.update({"event_system": Builtins()}) -builtins.app_name = "SolarFM" +builtins.app_name = "SolarFM" builtins.event_system = Builtins() builtins.event_sleep_time = 0.2 builtins.debug = False diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py index a3de649..40d42d5 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py @@ -4,20 +4,22 @@ import os, inspect, time # Lib imports # Application imports -from utils import Settings -from signal_classes import Controller +from utils.settings import Settings +from context.controller import Controller from __builtins__ import Builtins class Main(Builtins): + """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ + def __init__(self, args, unknownargs): if not debug: event_system.create_ipc_server() time.sleep(0.2) - if not trace_debug: + if not trace_debug and not debug: if not event_system.is_ipc_alive: if unknownargs: for arg in unknownargs: diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py index 66870d2..ba575d0 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py @@ -19,6 +19,8 @@ from __init__ import Main if __name__ == "__main__": + """ Set process title, get arguments, and create GTK main thread. """ + try: # import web_pdb # web_pdb.set_trace() diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/__init__.py new file mode 100644 index 0000000..90cfadc --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/__init__.py @@ -0,0 +1,3 @@ +""" + Gtk Bound Signal Module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py similarity index 94% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py index 3f8cf71..3c620f4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py @@ -7,9 +7,11 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib # Application imports -from .mixins import ExceptionHookMixin, UIMixin -from .signals import IPCSignalsMixin, KeyboardSignalsMixin -from . import Controller_Data +from .mixins.exception_hook_mixin import ExceptionHookMixin +from .mixins.ui_mixin import UIMixin +from .signals.ipc_signals_mixin import IPCSignalsMixin +from .signals.keyboard_signals_mixin import KeyboardSignalsMixin +from .controller_data import Controller_Data def threaded(fn): @@ -21,7 +23,7 @@ def threaded(fn): class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): - ''' Controller coordinates the mixins and is somewhat the root hub of it all. ''' + """ Controller coordinates the mixins and is somewhat the root hub of it all. """ def __init__(self, args, unknownargs, _settings): self.setup_controller_data(_settings) self.window.show() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller_data.py similarity index 97% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller_data.py index c737fb3..6d0a849 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller_Data.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller_data.py @@ -7,13 +7,13 @@ from gi.repository import GLib # Application imports from trasher.xdgtrash import XDGTrash from shellfm.windows.controller import WindowController -from plugins import Plugins +from plugins.plugins import Plugins 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): self.trashman = XDGTrash() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/ipc_server_mixin.py similarity index 94% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/ipc_server_mixin.py index 575f62c..ff12d4a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/ipc_server_mixin.py @@ -16,7 +16,7 @@ def threaded(fn): class IPCServerMixin: - ''' Create a listener so that other SolarFM instances send requests back to existing instance. ''' + """ Create a listener so that other SolarFM instances send requests back to existing instance. """ @threaded def create_ipc_server(self): diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/exception_hook_mixin.py similarity index 96% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/exception_hook_mixin.py index 0efee11..dbaa065 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ExceptionHookMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/exception_hook_mixin.py @@ -16,7 +16,7 @@ def threaded(fn): class ExceptionHookMixin: - ''' ExceptionHookMixin custom exception hook to reroute to a Gtk text area. ''' + """ ExceptionHookMixin custom exception hook to reroute to a Gtk text area. """ def custom_except_hook(self, exec_type, value, _traceback): trace = ''.join(traceback.format_tb(_traceback)) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/show_hide_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ShowHideMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/show_hide_mixin.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py similarity index 99% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py index 41cc2c8..bfa78cc 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py @@ -7,7 +7,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from . import WidgetMixin +from .widget_mixin import WidgetMixin diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_file_action_mixin.py similarity index 99% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_file_action_mixin.py index dd8e274..eda4c7f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetFileActionMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_file_action_mixin.py @@ -16,6 +16,8 @@ def threaded(fn): class WidgetFileActionMixin: + """docstring for WidgetFileActionMixin""" + def sizeof_fmt(self, num, suffix="B"): for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: if abs(num) < 1024.0: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py similarity index 99% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py index 17c40cb..84ecac0 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WidgetMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py @@ -20,6 +20,8 @@ def threaded(fn): class WidgetMixin: + """docstring for WidgetMixin""" + def load_store(self, view, store, save_state=False): store.clear() dir = view.get_current_directory() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py similarity index 99% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py index 89167cb..ee1ad8b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/WindowMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py @@ -9,13 +9,15 @@ gi.require_version('Gdk', '3.0') from gi.repository import Gdk, Gio # Application imports -from . import TabMixin, WidgetMixin +from .tab_mixin import TabMixin +from .widget_mixin import WidgetMixin class WindowMixin(TabMixin): """docstring for WindowMixin""" + def generate_windows(self, session_json = None): if session_json: for j, value in enumerate(session_json): diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui_mixin.py new file mode 100644 index 0000000..d127b28 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui_mixin.py @@ -0,0 +1,14 @@ +# Python imports + +# Gtk imports + +# Application imports +from .show_hide_mixin import ShowHideMixin +from .ui.widget_file_action_mixin import WidgetFileActionMixin +from .ui.pane_mixin import PaneMixin +from .ui.window_mixin import WindowMixin +from .show_hide_mixin import ShowHideMixin + + +class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin): + pass diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/IPCSignalsMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/ipc_signals_mixin.py similarity index 89% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/IPCSignalsMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/ipc_signals_mixin.py index 8a2d4d3..3f46614 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/IPCSignalsMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/ipc_signals_mixin.py @@ -6,6 +6,8 @@ class IPCSignalsMixin: + """ IPCSignalsMixin handle messages from another starting solarfm process. """ + def print_to_console(self, message=None): print(self) print(message) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py similarity index 98% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py index ee27648..a801365 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py @@ -14,6 +14,8 @@ valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") class KeyboardSignalsMixin: + """ KeyboardSignalsMixin keyboard hooks controller. """ + def unset_keys_and_data(self, widget=None, eve=None): self.ctrlDown = False self.shiftDown = False diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/__init__.py new file mode 100644 index 0000000..5624b32 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/__init__.py @@ -0,0 +1,3 @@ +""" + Gtk Bound Plugins Module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py similarity index 99% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py index 7bb9403..dcfa61b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py @@ -19,7 +19,8 @@ class Plugin: class Plugins: - """docstring for Plugins""" + """Plugins controller""" + def __init__(self, settings): self._settings = settings self._plugin_list_widget = self._settings.get_builder().get_object("plugin_list") diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py index 0c8b591..e69de29 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py @@ -1 +0,0 @@ -from .windows import WindowController diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/Window.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/Window.py deleted file mode 100644 index 78c5241..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/Window.py +++ /dev/null @@ -1,66 +0,0 @@ -# Python imports -from random import randint - - -# Lib imports - - -# Application imports -from .view import View - - -class Window: - def __init__(self): - self.id_length = 10 - self.id = "" - self.name = "" - self.nickname = "" - self.isHidden = False - self.views = [] - - self.generate_id() - - - def random_with_N_digits(self, n): - range_start = 10**(n-1) - range_end = (10**n)-1 - return randint(range_start, range_end) - - def generate_id(self): - self.id = str(self.random_with_N_digits(self.id_length)) - - def get_window_id(self): - return self.id - - def create_view(self): - view = View() - self.views.append(view) - return view - - def pop_view(self): - self.views.pop() - - def delete_view_by_id(self, vid): - for view in self.views: - if view.id == vid: - self.views.remove(view) - break - - - def get_view_by_id(self, vid): - for view in self.views: - if view.id == vid: - return view - - def get_view_by_index(self, index): - return self.views[index] - - def get_views_count(self): - return len(self.views) - - def get_all_views(self): - return self.views - - def list_files_from_views(self): - for view in self.views: - print(view.files) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/WindowController.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/WindowController.py deleted file mode 100644 index 7f068e7..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/WindowController.py +++ /dev/null @@ -1,181 +0,0 @@ -# Python imports -import threading, subprocess, time, json -from os import path - -# Lib imports - -# Application imports -from . import Window - - -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - -class WindowController: - def __init__(self): - USER_HOME = path.expanduser('~') - CONFIG_PATH = USER_HOME + "/.config/solarfm" - self.session_file = CONFIG_PATH + "/session.json" - - self._event_sleep_time = 1 - self.active_window_id = "" - self.active_tab_id = "" - self.windows = [] - - if not trace_debug: - self.fm_event_observer() - - @threaded - def fm_event_observer(self): - while True: - time.sleep(event_sleep_time) - event = event_system.consume_fm_event() - if event: - print(event) - - def set_active_data(self, wid, tid): - self.active_window_id = str(wid) - self.active_tab_id = str(tid) - - def get_active_data(self): - return self.active_window_id, self.active_tab_id - - def create_window(self): - window = Window() - window.name = "window_" + window.id - window.nickname = "window_" + str(len(self.windows) + 1) - - self.windows.append(window) - return window - - - def add_view_for_window(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.create_view() - - def add_view_for_window_by_name(self, name): - for window in self.windows: - if window.name == name: - return window.create_view() - - def add_view_for_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - return window.create_view() - - def pop_window(self): - self.windows.pop() - - def delete_window_by_id(self, win_id): - for window in self.windows: - if window.id == win_id: - self.windows.remove(window) - break - - def delete_window_by_name(self, name): - for window in self.windows: - if window.name == name: - self.windows.remove(window) - break - - def delete_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - self.windows.remove(window) - break - - def get_window_by_id(self, win_id): - for window in self.windows: - if window.id == win_id: - return window - - raise(f"No Window by ID {win_id} found!") - - def get_window_by_name(self, name): - for window in self.windows: - if window.name == name: - return window - - raise(f"No Window by Name {name} found!") - - def get_window_by_nickname(self, nickname): - for window in self.windows: - if window.nickname == nickname: - return window - - raise(f"No Window by Nickname {nickname} found!") - - def get_window_by_index(self, index): - return self.windows[index] - - def get_all_windows(self): - return self.windows - - def set_window_nickname(self, win_id = None, nickname = ""): - for window in self.windows: - if window.id == win_id: - window.nickname = nickname - - def list_windows(self): - print("\n[ ---- Windows ---- ]\n") - for window in self.windows: - print(f"\nID: {window.id}") - print(f"Name: {window.name}") - print(f"Nickname: {window.nickname}") - print(f"Is Hidden: {window.isHidden}") - print(f"View Count: {window.get_views_count()}") - print("\n-------------------------\n") - - - - def list_files_from_views_of_window(self, win_id): - for window in self.windows: - if window.id == win_id: - window.list_files_from_views() - break - - def get_views_count(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.get_views_count() - - def get_views_from_window(self, win_id): - for window in self.windows: - if window.id == win_id: - return window.get_all_views() - - - - - def save_state(self): - windows = [] - for window in self.windows: - views = [] - for view in window.views: - views.append(view.get_current_directory()) - - windows.append( - [ - { - 'window':{ - "ID": window.id, - "Name": window.name, - "Nickname": window.nickname, - "isHidden": f"{window.isHidden}", - 'views': views - } - } - ] - ) - - with open(self.session_file, 'w') as outfile: - json.dump(windows, outfile, separators=(',', ':'), indent=4) - - def load_state(self): - if path.isfile(self.session_file): - with open(self.session_file) as infile: - return json.load(infile) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py index cd9f6ce..e69de29 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py @@ -1,2 +0,0 @@ -from .Window import Window -from .WindowController import WindowController diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/controller.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/controller.py new file mode 100644 index 0000000..67457b1 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/controller.py @@ -0,0 +1,185 @@ +# Python imports +import threading, json +from os import path + +# Lib imports + +# Application imports +from .window import Window + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class WindowController: + def __init__(self): + USER_HOME = path.expanduser('~') + CONFIG_PATH = USER_HOME + "/.config/solarfm" + self._session_file = CONFIG_PATH + "/session.json" + + self._event_sleep_time = 1 + self._active_window_id = "" + self._active_tab_id = "" + self._windows = [] + + + def set__wid_and_tid(self, wid, tid): + self._active_window_id = str(wid) + self._active_tab_id = str(tid) + + def get_active_wid_and_tid(self): + return self._active_window_id, self._active_tab_id + + def create_window(self): + window = Window() + window.set_nickname(f"window_{str(len(self._windows) + 1)}") + self._windows.append(window) + return window + + + def add_view_for_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.create_view() + + def add_view_for_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + return window.create_view() + + def add_view_for_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + return window.create_view() + + def pop_window(self): + self._windows.pop() + + def delete_window_by_id(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + self._windows.remove(window) + break + + def delete_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + self._windows.remove(window) + break + + def delete_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + self._windows.remove(window) + break + + def get_window_by_id(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window + + raise(f"No Window by ID {win_id} found!") + + def get_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + return window + + raise(f"No Window by Name {name} found!") + + def get_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + return window + + raise(f"No Window by Nickname {nickname} found!") + + def get_window_by_index(self, index): + return self._windows[index] + + def get_all_windows(self): + return self._windows + + + def set_window_nickname(self, win_id = None, nickname = ""): + for window in self._windows: + if window.get_id() == win_id: + window.set_nickname(nickname) + + def list_windows(self): + print("\n[ ---- Windows ---- ]\n") + for window in self._windows: + print(f"\nID: {window.get_id()}") + print(f"Name: {window.get_name()}") + print(f"Nickname: {window.get_nickname()}") + print(f"Is Hidden: {window.is_hidden()}") + print(f"View Count: {window.get_views_count()}") + print("\n-------------------------\n") + + + + def list_files_from_views_of_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + window.list_files_from_views() + break + + def get_views_count(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_views_count() + + def get_views_from_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_all_views() + + + + + def unload_views_and_windows(self): + for window in self._windows: + window.get_all_views().clear() + + self._windows.clear() + + def save_state(self, session_file = None): + if not session_file: + session_file = self._session_file + + if len(self._windows) > 0: + windows = [] + for window in self._windows: + views = [] + for view in window.get_all_views(): + views.append(view.get_current_directory()) + + windows.append( + [ + { + 'window':{ + "ID": window.get_id(), + "Name": window.get_name(), + "Nickname": window.get_nickname(), + "isHidden": f"{window.is_hidden()}", + 'views': views + } + } + ] + ) + + with open(session_file, 'w') as outfile: + json.dump(windows, outfile, separators=(',', ':'), indent=4) + else: + raise Exception("Window data corrupted! Can not save session!") + + def load_state(self, session_file = None): + if not session_file: + session_file = self._session_file + + if path.isfile(session_file): + with open(session_file) as infile: + return json.load(infile) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/__init__.py deleted file mode 100644 index 07d9ad7..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .utils import * -from .icons import * - -from .Path import Path -from .View import View diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/__init__.py deleted file mode 100644 index b946d44..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .mixins import DesktopIconMixin -from .mixins import VideoIconMixin - -from .Icon import Icon diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/__init__.py deleted file mode 100644 index 54bfe39..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import xdg - -from .VideoIconMixin import VideoIconMixin -from .DesktopIconMixin import DesktopIconMixin diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/__init__.py deleted file mode 100644 index 3efd664..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .Settings import Settings -from .Launcher import Launcher -from .FileHandler import FileHandler diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/Icon.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/icon.py similarity index 94% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/Icon.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/icon.py index 3c14d2f..6afa0e5 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/Icon.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/icon.py @@ -3,10 +3,13 @@ import os, subprocess, threading, hashlib from os.path import isfile # Gtk imports +import gi +gi.require_version('GdkPixbuf', '2.0') from gi.repository import GdkPixbuf # Application imports -from .mixins import * +from .mixins.desktopiconmixin import DesktopIconMixin +from .mixins.videoiconmixin import VideoIconMixin def threaded(fn): diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/DesktopIconMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/desktopiconmixin.py similarity index 96% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/DesktopIconMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/desktopiconmixin.py index 2d3c30b..9f5ed2e 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/DesktopIconMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/desktopiconmixin.py @@ -3,9 +3,6 @@ import os, subprocess, hashlib from os.path import isfile # Gtk imports -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk # Application imports from .xdg.DesktopEntry import DesktopEntry diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/VideoIconMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/videoiconmixin.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/VideoIconMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/videoiconmixin.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/BaseDirectory.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Config.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Config.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Config.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Config.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/DesktopEntry.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Exceptions.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Exceptions.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Exceptions.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Exceptions.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/IconTheme.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/IconTheme.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/IconTheme.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/IconTheme.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/IniFile.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/IniFile.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/IniFile.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/IniFile.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Locale.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Locale.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Locale.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Locale.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Menu.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Menu.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Menu.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Menu.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/MenuEditor.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Mime.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Mime.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/Mime.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Mime.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/RecentFiles.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/__init__.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/__init__.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/__init__.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/util.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/util.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/mixins/xdg/util.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/util.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/Path.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/path.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/Path.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/path.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/FileHandler.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/filehandler.py similarity index 98% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/FileHandler.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/filehandler.py index d0f7396..105782a 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/FileHandler.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/filehandler.py @@ -1,5 +1,5 @@ # Python imports -import os, shutil, subprocess, threading +import os, shutil # Lib imports diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Launcher.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/launcher.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Launcher.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/launcher.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Settings.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/settings.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/utils/Settings.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/settings.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/View.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/view.py similarity index 51% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/View.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/view.py index 594dee1..5676659 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/View.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/view.py @@ -1,6 +1,5 @@ # Python imports -import hashlib -import os +import hashlib, re from os import listdir from os.path import isdir, isfile, join @@ -11,64 +10,43 @@ from random import randint # Application imports -from .utils import Settings, Launcher, FileHandler -from .icons import Icon -from . import Path +from .utils.settings import Settings +from .utils.launcher import Launcher +from .utils.filehandler import FileHandler + +from .icons.icon import Icon +from .path import Path class View(Settings, FileHandler, Launcher, Icon, Path): def __init__(self): - self. logger = None - self.id_length = 10 + self.logger = None + self._id_length = 10 - self.id = "" - self.wid = None - self.dir_watcher = None - self.hide_hidden = self.HIDE_HIDDEN_FILES - self.files = [] - self.dirs = [] - self.vids = [] - self.images = [] - self.desktop = [] - self.ungrouped = [] - self.hidden = [] + self._id = "" + self._wid = None + self._dir_watcher = None + self._hide_hidden = self.HIDE_HIDDEN_FILES + self._files = [] + self._dirs = [] + self._vids = [] + self._images = [] + self._desktop = [] + self._ungrouped = [] + self._hidden = [] - self.generate_id() + self._generate_id() self.set_to_home() - - def random_with_N_digits(self, n): - range_start = 10**(n-1) - range_end = (10**n)-1 - return randint(range_start, range_end) - - def generate_id(self): - self.id = str(self.random_with_N_digits(self.id_length)) - - def get_tab_id(self): - return self.id - - def set_wid(self, _wid): - self.wid = _wid - - def get_wid(self): - return self.wid - - def set_dir_watcher(self, watcher): - self.dir_watcher = watcher - - def get_dir_watcher(self): - return self.dir_watcher - def load_directory(self): - path = self.get_path() - self.dirs = [] - self.vids = [] - self.images = [] - self.desktop = [] - self.ungrouped = [] - self.hidden = [] - self.files = [] + path = self.get_path() + self._dirs = [] + self._vids = [] + self._images = [] + self._desktop = [] + self._ungrouped = [] + self._hidden = [] + self._files = [] if not isdir(path): self.set_to_home() @@ -76,40 +54,31 @@ class View(Settings, FileHandler, Launcher, Icon, Path): for f in listdir(path): file = join(path, f) - if self.hide_hidden: + if self._hide_hidden: if f.startswith('.'): - self.hidden.append(f) + self._hidden.append(f) continue if isfile(file): lowerName = file.lower() if lowerName.endswith(self.fvideos): - self.vids.append(f) + self._vids.append(f) elif lowerName.endswith(self.fimages): - self.images.append(f) + self._images.append(f) elif lowerName.endswith((".desktop",)): - self.desktop.append(f) + self._desktop.append(f) else: - self.ungrouped.append(f) + self._ungrouped.append(f) else: - self.dirs.append(f) + self._dirs.append(f) - self.dirs.sort() - self.vids.sort() - self.images.sort() - self.desktop.sort() - self.ungrouped.sort() + self._dirs.sort(key=self._natural_keys) + self._vids.sort(key=self._natural_keys) + self._images.sort(key=self._natural_keys) + self._desktop.sort(key=self._natural_keys) + self._ungrouped.sort(key=self._natural_keys) - self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped - - def hash_text(self, text): - return hashlib.sha256(str.encode(text)).hexdigest()[:18] - - def hash_set(self, arry): - data = [] - for arr in arry: - data.append([arr, self.hash_text(arr)]) - return data + self._files = self._dirs + self._vids + self._images + self._desktop + self._ungrouped def is_folder_locked(self, hash): if self.lock_folder: @@ -129,18 +98,18 @@ class View(Settings, FileHandler, Launcher, Icon, Path): def get_not_hidden_count(self): - return len(self.files) + \ - len(self.dirs) + \ - len(self.vids) + \ - len(self.images) + \ - len(self.desktop) + \ - len(self.ungrouped) + return len(self._files) + \ + len(self._dirs) + \ + len(self._vids) + \ + len(self._images) + \ + len(self._desktop) + \ + len(self._ungrouped) def get_hidden_count(self): - return len(self.hidden) + return len(self._hidden) def get_files_count(self): - return len(self.files) + return len(self._files) def get_path_part_from_hash(self, hash): files = self.get_files() @@ -154,13 +123,13 @@ class View(Settings, FileHandler, Launcher, Icon, Path): return file def get_files_formatted(self): - files = self.hash_set(self.files), - dirs = self.hash_set(self.dirs), + files = self._hash_set(self._files), + dirs = self._hash_set(self._dirs), videos = self.get_videos(), - images = self.hash_set(self.images), - desktops = self.hash_set(self.desktop), - ungrouped = self.hash_set(self.ungrouped) - hidden = self.hash_set(self.hidden) + images = self._hash_set(self._images), + desktops = self._hash_set(self._desktop), + ungrouped = self._hash_set(self._ungrouped) + hidden = self._hash_set(self._hidden) return { 'path_head': self.get_path(), @@ -178,7 +147,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path): def get_pixbuf_icon_str_combo(self): data = [] dir = self.get_current_directory() - for file in self.files: + for file in self._files: icon = self.create_icon(dir, file).get_pixbuf() data.append([icon, file]) @@ -188,7 +157,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path): def get_gtk_icon_str_combo(self): data = [] dir = self.get_current_directory() - for file in self.files: + for file in self._files: icon = self.create_icon(dir, file) data.append([icon, file[0]]) @@ -207,23 +176,71 @@ class View(Settings, FileHandler, Launcher, Icon, Path): size = len(parts) return parts[size - 1] + + def set_hiding_hidden(self, state): + self._hide_hidden = state + + def is_hiding_hidden(self): + return self._hide_hidden + def get_dot_dots(self): - return self.hash_set(['.', '..']) + return self._hash_set(['.', '..']) def get_files(self): - return self.hash_set(self.files) + return self._hash_set(self._files) def get_dirs(self): - return self.hash_set(self.dirs) + return self._hash_set(self._dirs) def get_videos(self): - return self.hash_set(self.vids) + return self._hash_set(self._vids) def get_images(self): - return self.hash_set(self.images) + return self._hash_set(self._images) def get_desktops(self): - return self.hash_set(self.desktop) + return self._hash_set(self._desktop) def get_ungrouped(self): - return self.hash_set(self.ungrouped) + return self._hash_set(self._ungrouped) + + def get_hidden(self): + return self._hash_set(self._hidden) + + def get_id(self): + return self._id + + def set_wid(self, _wid): + self._wid = _wid + + def get_wid(self): + return self._wid + + def set_dir_watcher(self, watcher): + self._dir_watcher = watcher + + def get_dir_watcher(self): + return self._dir_watcher + + def _atoi(self, text): + return int(text) if text.isdigit() else text + + def _natural_keys(self, text): + return [ self._atoi(c) for c in re.split('(\d+)',text) ] + + def _hash_text(self, text): + return hashlib.sha256(str.encode(text)).hexdigest()[:18] + + def _hash_set(self, arry): + data = [] + for arr in arry: + data.append([arr, self._hash_text(arr)]) + return data + + def _random_with_N_digits(self, n): + range_start = 10**(n-1) + range_end = (10**n)-1 + return randint(range_start, range_end) + + def _generate_id(self): + self._id = str(self._random_with_N_digits(self._id_length)) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py new file mode 100644 index 0000000..58d8414 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py @@ -0,0 +1,89 @@ +# Python imports +from random import randint + + +# Lib imports + + +# Application imports +from .views.view import View + + +class Window: + def __init__(self): + self._id_length = 10 + self._id = "" + self._name = "" + self._nickname = "" + self._isHidden = False + self._views = [] + + self._generate_id() + self._set_name() + + + def create_view(self): + view = View() + self._views.append(view) + return view + + def pop_view(self): + self._views.pop() + + def delete_view_by_id(self, vid): + for view in self._views: + if view.get_id() == vid: + self._views.remove(view) + break + + + def get_view_by_id(self, vid): + for view in self._views: + if view.get_id() == vid: + return view + + def get_view_by_index(self, index): + return self._views[index] + + def get_views_count(self): + return len(self._views) + + def get_all_views(self): + return self._views + + def list_files_from_views(self): + for view in self._views: + print(view.get_files()) + + + def get_id(self): + return self._id + + def get_name(self): + return self._name + + def get_nickname(self): + return self._nickname + + def is_hidden(self): + return self._isHidden + + + + + def set_nickname(self, nickname): + self._nickname = f"{nickname}" + + def set_is_hidden(self, state): + self._isHidden = f"{state}" + + def _set_name(self): + self._name = "window_" + self.get_id() + + def _random_with_N_digits(self, n): + range_start = 10**(n-1) + range_end = (10**n)-1 + return randint(range_start, range_end) + + def _generate_id(self): + self._id = str(self._random_with_N_digits(self._id_length)) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller.py deleted file mode 100644 index 5673203..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller.py +++ /dev/null @@ -1,165 +0,0 @@ -# Python imports -import sys, traceback, threading, inspect, os, time - -# Lib imports -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib - -# Application imports -from .mixins.ui import * -from .mixins import ShowHideMixin, KeyboardSignalsMixin -from . import Controller_Data - - -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - - - -class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \ - KeyboardSignalsMixin, Controller_Data): - def __init__(self, args, unknownargs, _settings): - # sys.excepthook = self.custom_except_hook - self.setup_controller_data(_settings) - self.window.show() - self.generate_windows(self.state) - self.plugins.launch_plugins() - - if not trace_debug: - self.gui_event_observer() - - if unknownargs: - for arg in unknownargs: - if os.path.isdir(arg): - message = f"FILE|{arg}" - event_system.send_ipc_message(message) - - if args.new_tab and os.path.isdir(args.new_tab): - message = f"FILE|{args.new_tab}" - event_system.send_ipc_message(message) - - - def tear_down(self, widget=None, eve=None): - event_system.send_ipc_message("close server") - self.window_controller.save_state() - time.sleep(event_sleep_time) - Gtk.main_quit() - - - @threaded - def gui_event_observer(self): - while True: - time.sleep(event_sleep_time) - event = event_system.consume_gui_event() - if event: - try: - type, target, data = event - method = getattr(self.__class__, type) - GLib.idle_add(method, (self, data,)) - except Exception as e: - print(repr(e)) - - - def custom_except_hook(self, exctype, value, _traceback): - trace = ''.join(traceback.format_tb(_traceback)) - data = f"Exectype: {exctype} <--> 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) - - def display_message(self, type, text, seconds=None): - self.message_buffer.insert_at_cursor(text) - self.message_widget.popup() - if seconds: - self.hide_message_timeout(seconds) - - @threaded - def hide_message_timeout(self, seconds=3): - time.sleep(seconds) - GLib.idle_add(self.message_widget.popdown) - - def save_debug_alerts(self, widget=None, eve=None): - start_itr, end_itr = self.message_buffer.get_bounds() - save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \ - action = Gtk.FileChooserAction.SAVE, \ - buttons = (Gtk.STOCK_CANCEL, \ - Gtk.ResponseType.CANCEL, \ - Gtk.STOCK_SAVE, \ - Gtk.ResponseType.OK)) - - text = self.message_buffer.get_text(start_itr, end_itr, False) - resp = save_location_prompt.run() - if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT): - pass - elif resp == Gtk.ResponseType.OK: - target = save_location_prompt.get_filename(); - with open(target, "w") as f: - f.write(text) - - save_location_prompt.destroy() - - - def set_arc_buffer_text(self, widget=None, eve=None): - id = widget.get_active_id() - self.arc_command_buffer.set_text(self.arc_commands[int(id)]) - - - def clear_children(self, widget): - for child in widget.get_children(): - widget.remove(child) - - def get_current_state(self): - wid, tid = self.window_controller.get_active_data() - view = self.get_fm_window(wid).get_view_by_id(tid) - iconview = self.builder.get_object(f"{wid}|{tid}|iconview") - store = iconview.get_model() - return wid, tid, view, iconview, store - - def do_action_from_menu_controls(self, widget, eventbutton): - action = widget.get_name() - self.ctrlDown = True - self.hide_context_menu() - self.hide_new_file_menu() - self.hide_edit_file_menu() - - if action == "open": - self.open_files() - if action == "open_with": - self.show_appchooser_menu() - if action == "execute": - self.execute_files() - if action == "execute_in_terminal": - self.execute_files(in_terminal=True) - if action == "rename": - self.rename_files() - if action == "cut": - self.to_copy_files.clear() - self.cut_files() - if action == "copy": - self.to_cut_files.clear() - self.copy_files() - if action == "paste": - self.paste_files() - if action == "archive": - self.show_archiver_dialogue() - if action == "delete": - self.delete_files() - if action == "trash": - self.trash_files() - if action == "go_to_trash": - self.builder.get_object("path_entry").set_text(self.trash_files_path) - if action == "restore_from_trash": - self.restore_trash_files() - if action == "empty_trash": - self.empty_trash() - - - if action == "create": - self.create_files() - self.hide_new_file_menu() - - self.ctrlDown = False diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Plugins.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Plugins.py deleted file mode 100644 index 8715aac..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Plugins.py +++ /dev/null @@ -1,41 +0,0 @@ -# Python imports -import importlib - -# Lib imports -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gio - -# Application imports - - - - -class Plugins: - """docstring for Plugins""" - def __init__(self, settings): - self._settings = settings - self._plugins_path = self._settings.get_plugins_path() - self._plugins_dir_watcher = None - self._socket = Gtk.Socket().new() - - def launch_plugins(self): - self._set_plugins_watcher() - self.load_plugins() - - def _set_plugins_watcher(self): - self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \ - .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) - self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ()) - - def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None): - if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, - Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, - Gio.FileMonitorEvent.MOVED_OUT]: - self.load_plugins(file) - - def load_plugins(self, file=None): - print(f"(Re)loading plugins...") - print(locals()) - - # importlib.reload(stl_utils) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/__init__.py deleted file mode 100644 index 314f976..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" - Gtk Bound Signal Module -""" -from .mixins import * -from .IPCServerMixin import IPCServerMixin -from .Plugins import Plugins -from .Controller_Data import Controller_Data -from .Controller import Controller diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/__init__.py deleted file mode 100644 index e457cb5..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .KeyboardSignalsMixin import KeyboardSignalsMixin -from .ShowHideMixin import ShowHideMixin diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/__init__.py deleted file mode 100644 index cd23f8d..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .PaneMixin import PaneMixin -from .WidgetMixin import WidgetMixin -from .TabMixin import TabMixin -from .WindowMixin import WindowMixin -from .WidgetFileActionMixin import WidgetFileActionMixin diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py index be29701..4210f9c 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py @@ -21,7 +21,7 @@ class Trash(object): if os.path.isfile(item): size = size + os.path.getsize(item) elif os.path.isdir(item): - size = size + size_dir(item) + size = size + self.size_dir(item) return size diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py index 415301e..e69de29 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py @@ -1,6 +0,0 @@ -""" - Utils module -""" - -from .Logger import Logger -from .Settings import Settings diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Logger.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/logger.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Logger.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/logger.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/Settings.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py similarity index 99% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/Settings.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py index d6a42ee..a02f0ce 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/Settings.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py @@ -12,7 +12,7 @@ from gi.repository import Gdk as gdk # Application imports -from . import Logger +from .logger import Logger class Settings: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 3dcbd14..137213e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -4,7 +4,7 @@ import builtins # Lib imports # Application imports -from controller import IPCServerMixin +from context.ipc_server_mixin import IPCServerMixin diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py index de673d5..40d42d5 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py @@ -4,15 +4,15 @@ import os, inspect, time # Lib imports # Application imports -from utils import Settings -from controller import Controller +from utils.settings import Settings +from context.controller import Controller from __builtins__ import Builtins class Main(Builtins): - ''' Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes.''' + """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ def __init__(self, args, unknownargs): if not debug: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index 727a85f..ba575d0 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -19,7 +19,7 @@ from __init__ import Main if __name__ == "__main__": - ''' Set process title, get arguments, and create GTK main thread. ''' + """ Set process title, get arguments, and create GTK main thread. """ try: # import web_pdb diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/__init__.py new file mode 100644 index 0000000..90cfadc --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/__init__.py @@ -0,0 +1,3 @@ +""" + Gtk Bound Signal Module +""" 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 new file mode 100644 index 0000000..3c620f4 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller.py @@ -0,0 +1,172 @@ +# Python imports +import os, gc, threading, time + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib + +# Application imports +from .mixins.exception_hook_mixin import ExceptionHookMixin +from .mixins.ui_mixin import UIMixin +from .signals.ipc_signals_mixin import IPCSignalsMixin +from .signals.keyboard_signals_mixin import KeyboardSignalsMixin +from .controller_data import Controller_Data + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): + """ Controller coordinates the mixins and is somewhat the root hub of it all. """ + def __init__(self, args, unknownargs, _settings): + self.setup_controller_data(_settings) + self.window.show() + + self.generate_windows(self.state) + self.plugins.launch_plugins() + + if debug: + self.window.set_interactive_debugging(True) + + if not trace_debug: + self.gui_event_observer() + + if unknownargs: + for arg in unknownargs: + if os.path.isdir(arg): + message = f"FILE|{arg}" + event_system.send_ipc_message(message) + + if args.new_tab and os.path.isdir(args.new_tab): + message = f"FILE|{args.new_tab}" + event_system.send_ipc_message(message) + + + def tear_down(self, widget=None, eve=None): + event_system.send_ipc_message("close server") + self.window_controller.save_state() + time.sleep(event_sleep_time) + Gtk.main_quit() + + + @threaded + def gui_event_observer(self): + while True: + time.sleep(event_sleep_time) + event = event_system.consume_gui_event() + if event: + try: + type, target, data = event + if type: + method = getattr(self.__class__, "handle_gui_event_and_set_message") + GLib.idle_add(method, *(self, type, target, data)) + else: + method = getattr(self.__class__, target) + GLib.idle_add(method, *(self, *data,)) + except Exception as e: + print(repr(e)) + + def handle_gui_event_and_set_message(self, type, target, parameters): + method = getattr(self.__class__, f"{target}") + data = method(*(self, *parameters)) + self.plugins.set_message_on_plugin(type, data) + + def open_terminal(self, widget=None, eve=None): + wid, tid = self.window_controller.get_active_wid_and_tid() + view = self.get_fm_window(wid).get_view_by_id(tid) + dir = view.get_current_directory() + view.execute(f"{view.terminal_app}", dir) + + def save_load_session(self, action="save_session"): + wid, tid = self.window_controller.get_active_wid_and_tid() + view = self.get_fm_window(wid).get_view_by_id(tid) + save_load_dialog = self.builder.get_object("save_load_dialog") + + if action == "save_session": + self.window_controller.save_state() + return + elif action == "save_session_as": + save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) + elif action == "load_session": + save_load_dialog.set_action(Gtk.FileChooserAction.OPEN) + else: + raise Exception(f"Unknown action given: {action}") + + save_load_dialog.set_current_folder(view.get_current_directory()) + save_load_dialog.set_current_name("session.json") + response = save_load_dialog.run() + if response == Gtk.ResponseType.OK: + if action == "save_session": + path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" + self.window_controller.save_state(path) + elif action == "load_session": + path = f"{save_load_dialog.get_file().get_path()}" + session_json = self.window_controller.load_state(path) + self.load_session(session_json) + if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): + pass + + save_load_dialog.hide() + + def load_session(self, session_json): + if debug: + print(f"Session Data: {session_json}") + + self.ctrlDown = False + self.shiftDown = False + self.altDown = False + for notebook in self.notebooks: + self.clear_children(notebook) + + self.window_controller.unload_views_and_windows() + self.generate_windows(session_json) + gc.collect() + + + def do_action_from_menu_controls(self, widget, event_button): + action = widget.get_name() + self.hide_context_menu() + self.hide_new_file_menu() + self.hide_edit_file_menu() + + if action == "open": + self.open_files() + if action == "open_with": + self.show_appchooser_menu() + if action == "execute": + self.execute_files() + if action == "execute_in_terminal": + self.execute_files(in_terminal=True) + if action == "rename": + self.rename_files() + if action == "cut": + self.to_copy_files.clear() + self.cut_files() + if action == "copy": + self.to_cut_files.clear() + self.copy_files() + if action == "paste": + self.paste_files() + if action == "archive": + self.show_archiver_dialogue() + if action == "delete": + self.delete_files() + if action == "trash": + self.trash_files() + if action == "go_to_trash": + self.path_entry.set_text(self.trash_files_path) + if action == "restore_from_trash": + self.restore_trash_files() + if action == "empty_trash": + self.empty_trash() + + if action == "create": + self.show_new_file_menu() + if action in ["save_session", "save_session_as", "load_session"]: + self.save_load_session(action) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller_Data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py similarity index 68% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller_Data.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py index 95d8008..6d0a849 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller_Data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py @@ -1,20 +1,19 @@ # Python imports -import signal +import sys, os, signal # Lib imports from gi.repository import GLib # Application imports -from shellfm import WindowController from trasher.xdgtrash import XDGTrash -from . import Plugins +from shellfm.windows.controller import WindowController +from plugins.plugins import Plugins class Controller_Data: - def has_method(self, o, name): - return callable(getattr(o, name, None)) + """ 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.trashman = XDGTrash() @@ -37,13 +36,14 @@ class Controller_Data: self.message_buffer = self.builder.get_object("message_buffer") self.arc_command_buffer = self.builder.get_object("arc_command_buffer") + self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn") self.warning_alert = self.builder.get_object("warning_alert") self.edit_file_menu = self.builder.get_object("edit_file_menu") self.file_exists_dialog = self.builder.get_object("file_exists_dialog") self.exists_file_label = self.builder.get_object("exists_file_label") self.exists_file_field = self.builder.get_object("exists_file_field") self.path_menu = self.builder.get_object("path_menu") - self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn") + self.path_entry = self.builder.get_object("path_entry") self.bottom_size_label = self.builder.get_object("bottom_size_label") self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label") @@ -82,6 +82,7 @@ class Controller_Data: self.selected_files = [] self.to_copy_files = [] self.to_cut_files = [] + self.soft_update_lock = {} self.single_click_open = False self.is_pane1_hidden = False @@ -104,6 +105,53 @@ class Controller_Data: self.warning = "#ffa800" self.error = "#ff0000" - + sys.excepthook = self.custom_except_hook 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. + + Parameters: + a (obj): self + + Returns: + wid, tid, view, iconview, store + ''' + wid, tid = self.window_controller.get_active_wid_and_tid() + view = self.get_fm_window(wid).get_view_by_id(tid) + iconview = self.builder.get_object(f"{wid}|{tid}|iconview") + store = iconview.get_model() + return wid, tid, view, iconview, store + + + def clear_console(self): + ''' Clears the terminal screen. ''' + os.system('cls' if os.name == 'nt' else 'clear') + + def call_method(self, _method_name, data = None): + ''' + Calls a method from scope of class. + + Parameters: + a (obj): self + b (str): method name to be called + c (*): Data (if any) to be passed to the method. + Note: It must be structured according to the given methods requirements. + + Returns: + Return data is that which the calling method gives. + ''' + method_name = str(_method_name) + method = getattr(self, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}") + return method(data) if data else method() + + def has_method(self, obj, name): + ''' Checks if a given method exists. ''' + return callable(getattr(obj, name, None)) + + def clear_children(self, widget): + ''' Clear children of a gtk widget. ''' + for child in widget.get_children(): + widget.remove(child) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/IPCServerMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/ipc_server_mixin.py similarity index 83% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/IPCServerMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/ipc_server_mixin.py index be92ace..ff12d4a 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/IPCServerMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/ipc_server_mixin.py @@ -1,5 +1,5 @@ # Python imports -import threading, socket, time +import threading, time from multiprocessing.connection import Listener, Client # Lib imports @@ -16,6 +16,7 @@ def threaded(fn): class IPCServerMixin: + """ Create a listener so that other SolarFM instances send requests back to existing instance. """ @threaded def create_ipc_server(self): @@ -34,7 +35,7 @@ class IPCServerMixin: if "FILE|" in msg: file = msg.split("FILE|")[1].strip() if file: - event_system.push_gui_event(["create_tab_from_ipc", None, file]) + event_system.push_gui_event([None, "handle_file_from_ipc", (file,)]) conn.close() break @@ -47,7 +48,7 @@ class IPCServerMixin: conn.close() break - # NOTE: Not perfect but insures we don't lockup the connection for too long. + # NOTE: Not perfect but insures we don't lock up the connection for too long. end_time = time.time() if (end - start) > self.ipc_timeout: conn.close() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py new file mode 100644 index 0000000..e69de29 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 new file mode 100644 index 0000000..dbaa065 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/exception_hook_mixin.py @@ -0,0 +1,62 @@ +# Python imports +import traceback, threading, time + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib + +# Application imports + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class ExceptionHookMixin: + """ ExceptionHookMixin custom exception hook to reroute to a Gtk text area. """ + + def custom_except_hook(self, exec_type, value, _traceback): + trace = ''.join(traceback.format_tb(_traceback)) + 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) + + def display_message(self, type, text, seconds=None): + self.message_buffer.insert_at_cursor(text) + self.message_widget.popup() + if seconds: + self.hide_message_timeout(seconds) + + @threaded + def hide_message_timeout(self, seconds=3): + time.sleep(seconds) + GLib.idle_add(self.message_widget.popdown) + + def save_debug_alerts(self, widget=None, eve=None): + start_itr, end_itr = self.message_buffer.get_bounds() + save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \ + action = Gtk.FileChooserAction.SAVE, \ + buttons = (Gtk.STOCK_CANCEL, \ + Gtk.ResponseType.CANCEL, \ + Gtk.STOCK_SAVE, \ + Gtk.ResponseType.OK)) + + text = self.message_buffer.get_text(start_itr, end_itr, False) + resp = save_location_prompt.run() + if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT): + pass + elif resp == Gtk.ResponseType.OK: + target = save_location_prompt.get_filename(); + with open(target, "w") as f: + f.write(text) + + save_location_prompt.destroy() + + + def set_arc_buffer_text(self, widget=None, eve=None): + sid = widget.get_active_id() + self.arc_command_buffer.set_text(self.arc_commands[int(sid)]) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ShowHideMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py similarity index 87% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ShowHideMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py index 49b29d8..d73d098 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ShowHideMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py @@ -4,7 +4,7 @@ import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk, Gio +from gi.repository import Gtk, Gdk # Application imports @@ -16,7 +16,6 @@ class ShowHideMixin: def stop_file_searching(self, widget=None, eve=None): self.is_searching = False - def show_exists_page(self, widget=None, eve=None): response = self.file_exists_dialog.run() self.file_exists_dialog.hide() @@ -57,7 +56,7 @@ class ShowHideMixin: def show_archiver_dialogue(self, widget=None, eve=None): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) archiver_dialogue = self.builder.get_object("archiver_dialogue") archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) @@ -81,12 +80,14 @@ class ShowHideMixin: appchooser_widget = self.builder.get_object("appchooser_widget") response = appchooser_menu.run() - if response == Gtk.ResponseType.CANCEL: - self.hide_appchooser_menu() if response == Gtk.ResponseType.OK: self.open_with_files(appchooser_widget) self.hide_appchooser_menu() + if response == Gtk.ResponseType.CANCEL: + self.hide_appchooser_menu() + + def hide_appchooser_menu(self, widget=None, eve=None): self.builder.get_object("appchooser_menu").hide() @@ -95,6 +96,12 @@ class ShowHideMixin: dialog.response(Gtk.ResponseType.OK) + def show_plugins_popup(self, widget=None, eve=None): + self.builder.get_object("plugin_list").popup() + + def hide_plugins_popup(self, widget=None, eve=None): + self.builder.get_object("plugin_list").hide() + def show_context_menu(self, widget=None, eve=None): self.builder.get_object("context_menu").run() @@ -103,12 +110,18 @@ class ShowHideMixin: def show_new_file_menu(self, widget=None, eve=None): - self.builder.get_object("new_file_menu").run() + self.builder.get_object("context_menu_fname").set_text("") + + new_file_menu = self.builder.get_object("new_file_menu") + response = new_file_menu.run() + if response == Gtk.ResponseType.APPLY: + self.create_files() + if response == Gtk.ResponseType.CANCEL: + self.hide_new_file_menu() def hide_new_file_menu(self, widget=None, eve=None): self.builder.get_object("new_file_menu").hide() - def show_edit_file_menu(self, widget=None, eve=None): if widget: widget.grab_focus() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/PaneMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py similarity index 98% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/PaneMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py index 235736d..accd2c4 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/PaneMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py @@ -61,5 +61,5 @@ class PaneMixin: def _save_state(self, state, pane_index): window = self.window_controller.get_window_by_index(pane_index - 1) - window.isHidden = state + window.set_is_hidden(state) self.window_controller.save_state() diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/TabMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py similarity index 82% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/TabMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py index 51960dd..bfa78cc 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/TabMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py @@ -4,11 +4,10 @@ import os # Lib imports import gi gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk +from gi.repository import Gtk # Application imports -from . import WidgetMixin +from .widget_mixin import WidgetMixin @@ -16,24 +15,6 @@ from . import WidgetMixin class TabMixin(WidgetMixin): """docstring for TabMixin""" - def create_tab_from_ipc(data): - self, path = data - wid, tid = self.window_controller.get_active_data() - notebook = self.builder.get_object(f"window_{wid}") - if notebook.is_visible(): - self.create_tab(wid, path) - return - - if not self.is_pane4_hidden: - self.create_tab(4, path) - elif not self.is_pane3_hidden: - self.create_tab(3, path) - elif not self.is_pane2_hidden: - self.create_tab(2, path) - elif not self.is_pane1_hidden: - self.create_tab(1, path) - - def create_tab(self, wid, path=None): notebook = self.builder.get_object(f"window_{wid}") path_entry = self.builder.get_object(f"path_entry") @@ -48,7 +29,7 @@ class TabMixin(WidgetMixin): # scroll, store = self.create_grid_treeview_widget(view, wid) index = notebook.append_page(scroll, tab) - self.window_controller.set_active_data(wid, view.get_tab_id()) + self.window_controller.set__wid_and_tid(wid, view.get_id()) path_entry.set_text(view.get_current_directory()) notebook.show_all() notebook.set_current_page(index) @@ -65,7 +46,7 @@ class TabMixin(WidgetMixin): def close_tab(self, button, eve=None): notebook = button.get_parent().get_parent() - tid = self.get_tab_id_from_tab_box(button.get_parent()) + tid = self.get_id_from_tab_box(button.get_parent()) wid = int(notebook.get_name()[-1]) scroll = self.builder.get_object(f"{wid}|{tid}") page = notebook.page_num(scroll) @@ -83,12 +64,12 @@ class TabMixin(WidgetMixin): window = self.get_fm_window(wid) view = None - for i, view in enumerate(window.views): - if view.id == tid: + for i, view in enumerate(window.get_all_views()): + if view.get_id() == tid: _view = window.get_view_by_id(tid) watcher = _view.get_dir_watcher() watcher.cancel() - window.views.insert(new_index, window.views.pop(i)) + window.get_all_views().insert(new_index, window.get_all_views().pop(i)) view = window.get_view_by_id(tid) self.set_file_watcher(view) @@ -97,11 +78,11 @@ class TabMixin(WidgetMixin): def on_tab_switch_update(self, notebook, content=None, index=None): self.selected_files.clear() wid, tid = content.get_children()[0].get_name().split("|") - self.window_controller.set_active_data(wid, tid) + self.window_controller.set__wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() - def get_tab_id_from_tab_box(self, tab_box): + def get_id_from_tab_box(self, tab_box): tid = tab_box.get_children()[2] return tid.get_text() @@ -132,7 +113,7 @@ class TabMixin(WidgetMixin): def do_action_from_bar_controls(self, widget, eve=None): action = widget.get_name() - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") view = self.get_fm_window(wid).get_view_by_id(tid) @@ -156,11 +137,11 @@ class TabMixin(WidgetMixin): if isinstance(focused_obj, Gtk.Entry): button_box = self.path_menu.get_children()[0].get_children()[0].get_children()[0] query = widget.get_text().replace(dir, "") - files = view.files + view.hidden + files = view.get_files() + view.get_hidden() self.clear_children(button_box) show_path_menu = False - for file in files: + for file, hash in files: if os.path.isdir(f"{dir}{file}"): if query.lower() in file.lower(): button = Gtk.Button(label=file) @@ -201,7 +182,7 @@ class TabMixin(WidgetMixin): self.path_menu.popdown() def keyboard_close_tab(self): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_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) @@ -216,8 +197,8 @@ class TabMixin(WidgetMixin): # File control events def show_hide_hidden_files(self): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) - view.hide_hidden = not view.hide_hidden + view.set_hiding_hidden(not view.is_hiding_hidden()) view.load_directory() self.builder.get_object("refresh_view").released() diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetFileActionMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py similarity index 74% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetFileActionMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py index 06ba422..eda4c7f 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetFileActionMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py @@ -1,17 +1,23 @@ # Python imports -import os +import os, time, threading # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GObject, Gio +from gi.repository import Gtk, GObject, GLib, Gio # Application imports +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper class WidgetFileActionMixin: + """docstring for WidgetFileActionMixin""" + def sizeof_fmt(self, num, suffix="B"): for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: if abs(num) < 1024.0: @@ -52,29 +58,60 @@ class WidgetFileActionMixin: .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) wid = view.get_wid() - tid = view.get_tab_id() + tid = view.get_id() dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",)) view.set_dir_watcher(dir_watcher) + # NOTE: Too lazy to impliment a proper update handler and so just regen store and update view. + # Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None): if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, - Gio.FileMonitorEvent.MOVED_OUT]: + Gio.FileMonitorEvent.MOVED_OUT]: if debug: print(eve_type) - wid, tid = data[0].split("|") - notebook = self.builder.get_object(f"window_{wid}") - view = self.get_fm_window(wid).get_view_by_id(tid) - iconview = self.builder.get_object(f"{wid}|{tid}|iconview") - store = iconview.get_model() - _store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") + if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: + self.update_on_end_soft_lock(data[0]) + elif data[0] in self.soft_update_lock.keys(): + self.soft_update_lock[data[0]]["last_update_time"] = time.time() + else: + self.soft_lock_countdown(data[0]) - view.load_directory() - self.load_store(view, store) - tab_label.set_label(view.get_end_of_path()) - self.set_bottom_labels(view) + @threaded + def soft_lock_countdown(self, tab): + self.soft_update_lock[tab] = { "last_update_time": time.time()} + lock = True + while lock: + time.sleep(0.6) + last_update_time = self.soft_update_lock[tab]["last_update_time"] + current_time = time.time() + if (current_time - last_update_time) > 0.6: + lock = False + + + self.soft_update_lock.pop(tab, None) + GLib.idle_add(self.update_on_end_soft_lock, *(tab,)) + + + def update_on_end_soft_lock(self, tab): + wid, tid = tab.split("|") + notebook = self.builder.get_object(f"window_{wid}") + view = self.get_fm_window(wid).get_view_by_id(tid) + iconview = self.builder.get_object(f"{wid}|{tid}|iconview") + store = iconview.get_model() + _store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") + + view.load_directory() + self.load_store(view, store) + + tab_label.set_label(view.get_end_of_path()) + + _wid, _tid, _view, _iconview, _store = self.get_current_state() + + if [wid, tid] in [_wid, _tid]: + self.set_bottom_labels(view) def popup_search_files(self, wid, keyname): @@ -87,8 +124,8 @@ class WidgetFileActionMixin: def do_file_search(self, widget, eve=None): query = widget.get_text() self.search_iconview.unselect_all() - for i, file in enumerate(self.search_view.files): - if query and query in file.lower(): + for i, file in enumerate(self.search_view.get_files()): + if query and query in file[0].lower(): path = Gtk.TreePath().new_from_indices([i]) self.search_iconview.select_path(path) @@ -174,7 +211,7 @@ class WidgetFileActionMixin: self.to_copy_files = uris def paste_files(self): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) target = f"{view.get_current_directory()}" @@ -227,7 +264,7 @@ class WidgetFileActionMixin: file_name = fname_field.get_text().strip() type = self.builder.get_object("context_menu_type_toggle").get_state() - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) target = f"{view.get_current_directory()}" @@ -239,12 +276,12 @@ class WidgetFileActionMixin: else: # Create Folder self.handle_files([path], "create_dir") - fname_field.set_text("") + self.hide_new_file_menu() def move_files(self, files, target): self.handle_files(files, "move", target) - # NOTE: Gtk recommends using fail flow than pre check existence which is more + # NOTE: Gtk recommends using fail flow than pre check which is more # race condition proof. They're right; but, they can't even delete # directories properly. So... f**k them. I'll do it my way. def handle_files(self, paths, action, _target_path=None): @@ -273,8 +310,7 @@ class WidgetFileActionMixin: if _file.query_exists(): if not overwrite_all and not rename_auto_all: - self.exists_file_label.set_label(_file.get_basename()) - self.exists_file_field.set_text(_file.get_basename()) + self.setup_exists_data(file, _file) response = self.show_exists_page() if response == "overwrite_all": @@ -295,7 +331,7 @@ class WidgetFileActionMixin: type = _file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) view.delete_file( _file.get_path() ) else: @@ -322,7 +358,7 @@ class WidgetFileActionMixin: type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() view = self.get_fm_window(wid).get_view_by_id(tid) fPath = file.get_path() tPath = target.get_path() @@ -344,6 +380,45 @@ class WidgetFileActionMixin: self.exists_file_rename_bttn.set_sensitive(False) + def setup_exists_data(self, from_file, to_file): + from_info = from_file.query_info("standard::*,time::modified", 0, cancellable=None) + to_info = to_file.query_info("standard::*,time::modified", 0, cancellable=None) + exists_file_diff_from = self.builder.get_object("exists_file_diff_from") + exists_file_diff_to = self.builder.get_object("exists_file_diff_to") + exists_file_from = self.builder.get_object("exists_file_from") + exists_file_to = self.builder.get_object("exists_file_to") + from_date = from_info.get_modification_date_time() + to_date = to_info.get_modification_date_time() + from_size = from_info.get_size() + to_size = to_info.get_size() + + exists_file_from.set_label(from_file.get_parent().get_path()) + exists_file_to.set_label(to_file.get_parent().get_path()) + self.exists_file_label.set_label(to_file.get_basename()) + self.exists_file_field.set_text(to_file.get_basename()) + + # Returns: -1, 0 or 1 if dt1 is less than, equal to or greater than dt2. + age = GLib.DateTime.compare(from_date, to_date) + age_text = "( same time )" + if age == -1: + age_text = "older" + if age == 1: + age_text = "newer" + + size_text = "( same size )" + if from_size < to_size: + size_text = "smaller" + if from_size > to_size: + size_text = "larger" + + from_label_text = f"{age_text} & {size_text}" + if age_text != "( same time )" or size_text != "( same size )": + from_label_text = f"{from_date.format('%F %R')} {self.sizeof_fmt(from_size)} ( {from_size} bytes ) ( {age_text} & {size_text} )" + to_label_text = f"{to_date.format('%F %R')} {self.sizeof_fmt(to_size)} ( {to_size} bytes )" + + exists_file_diff_from.set_text(from_label_text) + exists_file_diff_to.set_text(to_label_text) + def rename_proc(self, gio_file): full_path = gio_file.get_path() diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py similarity index 93% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py index 349cba7..84ecac0 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py @@ -20,6 +20,8 @@ def threaded(fn): class WidgetMixin: + """docstring for WidgetMixin""" + def load_store(self, view, store, save_state=False): store.clear() dir = view.get_current_directory() @@ -100,7 +102,7 @@ class WidgetMixin: label.set_label(f"{view.get_end_of_path()}") label.set_width_chars(len(view.get_end_of_path())) label.set_xalign(0.0) - tid.set_label(f"{view.id}") + tid.set_label(f"{view.get_id()}") close.add(icon) tab.add(label) @@ -133,8 +135,6 @@ class WidgetMixin: grid.connect("button_release_event", self.grid_icon_single_click) grid.connect("item-activated", self.grid_icon_double_click) - # grid.connect("toggle-cursor-item", self.grid_cursor_toggled) - # grid.connect("notify", self.grid_cursor_toggled) grid.connect("selection-changed", self.grid_set_selected_items) grid.connect("drag-data-get", self.grid_on_drag_set) grid.connect("drag-data-received", self.grid_on_drag_data_received) @@ -150,10 +150,10 @@ class WidgetMixin: grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{view.id}") - scroll.set_name(f"{wid}|{view.id}") - self.builder.expose_object(f"{wid}|{view.id}|iconview", grid) - self.builder.expose_object(f"{wid}|{view.id}", scroll) + grid.set_name(f"{wid}|{view.get_id()}") + scroll.set_name(f"{wid}|{view.get_id()}") + self.builder.expose_object(f"{wid}|{view.get_id()}|iconview", grid) + self.builder.expose_object(f"{wid}|{view.get_id()}", scroll) return scroll, store def create_grid_treeview_widget(self, view, wid): @@ -199,10 +199,10 @@ class WidgetMixin: grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{view.id}") - scroll.set_name(f"{wid}|{view.id}") + grid.set_name(f"{wid}|{view.get_id()}") + scroll.set_name(f"{wid}|{view.get_id()}") grid.columns_autosize() - self.builder.expose_object(f"{wid}|{view.id}", scroll) + self.builder.expose_object(f"{wid}|{view.get_id()}", scroll) return scroll, store diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WindowMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py similarity index 91% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WindowMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py index 17e4be3..ee1ad8b 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WindowMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py @@ -9,16 +9,18 @@ gi.require_version('Gdk', '3.0') from gi.repository import Gdk, Gio # Application imports -from . import TabMixin, WidgetMixin +from .tab_mixin import TabMixin +from .widget_mixin import WidgetMixin class WindowMixin(TabMixin): """docstring for WindowMixin""" - def generate_windows(self, data = None): - if data: - for j, value in enumerate(data): + + def generate_windows(self, session_json = None): + if session_json: + for j, value in enumerate(session_json): i = j + 1 isHidden = True if value[0]["window"]["isHidden"] == "True" else False object = self.builder.get_object(f"tggl_notebook_{i}") @@ -79,8 +81,8 @@ class WindowMixin(TabMixin): def set_bottom_labels(self, view): - _wid, _tid, _view, iconview, store = self.get_current_state() - selected_files = iconview.get_selected_items() + _wid, _tid, _view, icon_view, store = self.get_current_state() + selected_files = icon_view.get_selected_items() current_directory = view.get_current_directory() path_file = Gio.File.new_for_path(current_directory) mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None) @@ -113,7 +115,7 @@ class WindowMixin(TabMixin): formatted_size = self.sizeof_fmt(combined_size) - if view.hide_hidden: + if view.get_hidden(): self.bottom_path_label.set_label(f" {len(uris)} / {view.get_files_count()} ({formatted_size})") else: self.bottom_path_label.set_label(f" {len(uris)} / {view.get_not_hidden_count()} ({formatted_size})") @@ -121,7 +123,7 @@ class WindowMixin(TabMixin): return # If nothing selected - if view.hide_hidden: + if view.get_hidden(): if view.get_hidden_count() > 0: self.bottom_file_count_label.set_label(f"{view.get_not_hidden_count()} visible ({view.get_hidden_count()} hidden)") else: @@ -132,7 +134,7 @@ class WindowMixin(TabMixin): def set_window_title(self): - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") view = self.get_fm_window(wid).get_view_by_id(tid) dir = view.get_current_directory() @@ -146,7 +148,7 @@ class WindowMixin(TabMixin): ctx.remove_class("notebook-unselected-focus") ctx.add_class("notebook-selected-focus") - self.window.set_title("SolarFM ~ " + dir) + self.window.set_title(f"SolarFM ~ {dir}") self.set_bottom_labels(view) def set_path_text(self, wid, tid): @@ -164,7 +166,7 @@ class WindowMixin(TabMixin): try: self.path_menu.popdown() wid, tid = iconview.get_name().split("|") - self.window_controller.set_active_data(wid, tid) + self.window_controller.set__wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() @@ -182,9 +184,11 @@ class WindowMixin(TabMixin): def grid_icon_double_click(self, iconview, item, data=None): try: if self.ctrlDown and self.shiftDown: + self.unset_keys_and_data() self.execute_files(in_terminal=True) return elif self.ctrlDown: + self.unset_keys_and_data() self.execute_files() return @@ -221,7 +225,7 @@ class WindowMixin(TabMixin): data.set_text(uris_text, -1) def grid_on_drag_motion(self, iconview, drag_context, x, y, data): - current = '|'.join(self.window_controller.get_active_data()) + current = '|'.join(self.window_controller.get_active_wid_and_tid()) target = iconview.get_name() wid, tid = target.split("|") store = iconview.get_model() @@ -232,12 +236,12 @@ class WindowMixin(TabMixin): self.override_drop_dest = uri if isdir(uri) else None if target not in current: - self.window_controller.set_active_data(wid, tid) + self.window_controller.set__wid_and_tid(wid, tid) def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): if info == 80: - wid, tid = self.window_controller.get_active_data() + wid, tid = self.window_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") view = self.get_fm_window(wid).get_view_by_id(tid) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui_mixin.py new file mode 100644 index 0000000..d127b28 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui_mixin.py @@ -0,0 +1,14 @@ +# Python imports + +# Gtk imports + +# Application imports +from .show_hide_mixin import ShowHideMixin +from .ui.widget_file_action_mixin import WidgetFileActionMixin +from .ui.pane_mixin import PaneMixin +from .ui.window_mixin import WindowMixin +from .show_hide_mixin import ShowHideMixin + + +class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin): + pass diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py new file mode 100644 index 0000000..3f46614 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py @@ -0,0 +1,29 @@ +# Python imports + +# Lib imports + +# Application imports + + +class IPCSignalsMixin: + """ IPCSignalsMixin handle messages from another starting solarfm process. """ + + def print_to_console(self, message=None): + print(self) + print(message) + + def handle_file_from_ipc(self, path): + wid, tid = self.window_controller.get_active_wid_and_tid() + notebook = self.builder.get_object(f"window_{wid}") + if notebook.is_visible(): + self.create_tab(wid, path) + return + + if not self.is_pane4_hidden: + self.create_tab(4, path) + elif not self.is_pane3_hidden: + self.create_tab(3, path) + elif not self.is_pane2_hidden: + self.create_tab(2, path) + elif not self.is_pane1_hidden: + self.create_tab(1, path) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/KeyboardSignalsMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py similarity index 90% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/KeyboardSignalsMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py index b397027..a801365 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/KeyboardSignalsMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py @@ -14,6 +14,8 @@ valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") class KeyboardSignalsMixin: + """ KeyboardSignalsMixin keyboard hooks controller. """ + def unset_keys_and_data(self, widget=None, eve=None): self.ctrlDown = False self.shiftDown = False @@ -47,6 +49,7 @@ class KeyboardSignalsMixin: if self.ctrlDown and self.shiftDown and keyname == "t": + self.unset_keys_and_data() self.trash_files() @@ -57,6 +60,7 @@ class KeyboardSignalsMixin: if isinstance(focused_obj, Gtk.IconView): self.is_searching = True wid, tid, self.search_view, self.search_iconview, store = self.get_current_state() + self.unset_keys_and_data() self.popup_search_files(wid, keyname) return @@ -79,26 +83,30 @@ class KeyboardSignalsMixin: if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"): self.builder.get_object("go_up").released() if self.ctrlDown and keyname == "l": + self.unset_keys_and_data() self.builder.get_object("path_entry").grab_focus() if self.ctrlDown and keyname == "t": self.builder.get_object("create_tab").released() if self.ctrlDown and keyname == "o": + self.unset_keys_and_data() self.open_files() if self.ctrlDown and keyname == "w": self.keyboard_close_tab() if self.ctrlDown and keyname == "h": self.show_hide_hidden_files() if (self.ctrlDown and keyname == "e"): - self.edit_files() + self.unset_keys_and_data() + self.rename_files() if self.ctrlDown and keyname == "c": - self.to_cut_files.clear() self.copy_files() + self.to_cut_files.clear() if self.ctrlDown and keyname == "x": self.to_copy_files.clear() self.cut_files() if self.ctrlDown and keyname == "v": self.paste_files() if self.ctrlDown and keyname == "n": + self.unset_keys_and_data() self.show_new_file_menu() @@ -110,11 +118,11 @@ class KeyboardSignalsMixin: else: top_main_menubar.show() 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": - wid, tid = self.window_controller.get_active_data() - view = self.get_fm_window(wid).get_view_by_id(tid) - dir = view.get_current_directory() - view.execute(f"{view.terminal_app}", dir) + self.unset_keys_and_data() + self.open_terminal() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/__init__.py deleted file mode 100644 index 52c4098..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" - Gtk Bound Signal Module -""" -from .mixins import * -from .IPCServerMixin import IPCServerMixin -from .Controller_Data import Controller_Data -from .Controller import Controller diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/UIMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/UIMixin.py deleted file mode 100644 index f6e265f..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/UIMixin.py +++ /dev/null @@ -1,11 +0,0 @@ -# Python imports - -# Gtk imports - -# Application imports -from . import ShowHideMixin -from .ui import * - - -class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin): - pass diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py deleted file mode 100644 index ec27013..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .ShowHideMixin import ShowHideMixin -from .ExceptionHookMixin import ExceptionHookMixin -from .UIMixin import UIMixin diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/__init__.py deleted file mode 100644 index cd23f8d..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .PaneMixin import PaneMixin -from .WidgetMixin import WidgetMixin -from .TabMixin import TabMixin -from .WindowMixin import WindowMixin -from .WidgetFileActionMixin import WidgetFileActionMixin diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/__init__.py deleted file mode 100644 index f7ae310..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .KeyboardSignalsMixin import KeyboardSignalsMixin -from .IPCSignalsMixin import IPCSignalsMixin diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/__init__.py index b6a753c..5624b32 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/__init__.py @@ -1,4 +1,3 @@ """ Gtk Bound Plugins Module """ -from .Plugins import Plugins diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py new file mode 100644 index 0000000..dcfa61b --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -0,0 +1,99 @@ +# Python imports +import os, sys, importlib, traceback +from os.path import join, isdir + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gio + +# Application imports + + +class Plugin: + name = None + module = None + gtk_socket_id = None + gtk_socket = None + reference = None + + +class Plugins: + """Plugins controller""" + + def __init__(self, settings): + self._settings = settings + self._plugin_list_widget = self._settings.get_builder().get_object("plugin_list") + self._plugin_list_socket = self._settings.get_builder().get_object("plugin_socket") + self._plugins_path = self._settings.get_plugins_path() + self._plugins_dir_watcher = None + self._plugin_collection = [] + + + def launch_plugins(self): + self._set_plugins_watcher() + self.load_plugins() + + def _set_plugins_watcher(self): + self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \ + .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) + self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ()) + + def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None): + if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, + Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, + Gio.FileMonitorEvent.MOVED_OUT]: + self.reload_plugins(file) + + # @threaded + def load_plugins(self, file=None): + print(f"Loading plugins...") + parent_path = os.getcwd() + + for file in os.listdir(self._plugins_path): + try: + path = join(self._plugins_path, file) + if isdir(path): + os.chdir(path) + + gtk_socket = Gtk.Socket().new() + self._plugin_list_socket.add(gtk_socket) + # NOTE: Must get ID after adding socket to window. Else issues.... + gtk_socket_id = gtk_socket.get_id() + + sys.path.insert(0, path) + spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + ref = module.Main(gtk_socket_id, event_system) + plugin = Plugin() + plugin.name = ref.get_plugin_name() + plugin.module = path + plugin.gtk_socket_id = gtk_socket_id + plugin.gtk_socket = gtk_socket + plugin.reference = ref + + self._plugin_collection.append(plugin) + gtk_socket.show_all() + except Exception as e: + print("Malformed plugin! Not loading!") + traceback.print_exc() + + os.chdir(parent_path) + + + def reload_plugins(self, file=None): + print(f"Reloading plugins...") + # if self._plugin_collection: + # to_unload = [] + # for dir in self._plugin_collection: + # if not os.path.isdir(os.path.join(self._plugins_path, dir)): + # to_unload.append(dir) + + def set_message_on_plugin(self, type, data): + print("Trying to send message to plugin...") + for plugin in self._plugin_collection: + if type in plugin.name: + print('Found plugin; posting message...') + plugin.reference.set_message(data) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/__init__.py index 415301e..e69de29 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/__init__.py @@ -1,6 +0,0 @@ -""" - Utils module -""" - -from .Logger import Logger -from .Settings import Settings diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/Logger.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/Logger.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py similarity index 78% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Settings.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py index 380210a..a02f0ce 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Settings.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py @@ -12,7 +12,7 @@ from gi.repository import Gdk as gdk # Application imports -from . import Logger +from .logger import Logger class Settings: @@ -25,10 +25,10 @@ class Settings: self.PLUGINS_PATH = f"{self.CONFIG_PATH}/plugins" self.USR_SOLARFM = f"/usr/share/{app_name.lower()}" - self.cssFile = f"{self.CONFIG_PATH}/stylesheet.css" - self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade" + self.CSS_FILE = f"{self.CONFIG_PATH}/stylesheet.css" + self.WINDOWS_GLADE = f"{self.CONFIG_PATH}/Main_Window.glade" self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons" - self.window_icon = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png" + self.WINDOW_ICON = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png" self.main_window = None if not os.path.exists(self.CONFIG_PATH): @@ -36,17 +36,17 @@ class Settings: if not os.path.exists(self.PLUGINS_PATH): os.mkdir(self.PLUGINS_PATH) - if not os.path.exists(self.windows_glade): - self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade" - if not os.path.exists(self.cssFile): - self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css" - if not os.path.exists(self.window_icon): - self.window_icon = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png" + if not os.path.exists(self.WINDOWS_GLADE): + self.WINDOWS_GLADE = f"{self.USR_SOLARFM}/Main_Window.glade" + if not os.path.exists(self.CSS_FILE): + self.CSS_FILE = f"{self.USR_SOLARFM}/stylesheet.css" + if not os.path.exists(self.WINDOW_ICON): + self.WINDOW_ICON = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png" if not os.path.exists(self.DEFAULT_ICONS): self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons" self.logger = Logger(self.CONFIG_PATH).get_logger() - self.builder.add_from_file(self.windows_glade) + self.builder.add_from_file(self.WINDOWS_GLADE) @@ -56,7 +56,7 @@ class Settings: self._set_window_data() def _set_window_data(self): - self.main_window.set_icon_from_file(self.window_icon) + self.main_window.set_icon_from_file(self.WINDOW_ICON) screen = self.main_window.get_screen() visual = screen.get_rgba_visual() @@ -67,7 +67,7 @@ class Settings: # bind css file cssProvider = gtk.CssProvider() - cssProvider.load_from_path(self.cssFile) + cssProvider.load_from_path(self.CSS_FILE) screen = gdk.Screen.get_default() styleContext = gtk.StyleContext() styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER) diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 07da0ac..5842ded 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1784,6 +1784,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 0 + + + path_entry + True + True + True + Path... + + + + True + True + 1 + + gtk-refresh @@ -1798,7 +1813,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 1 + 2 @@ -1815,21 +1830,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 2 - - - - - path_entry - True - True - True - Path... - - - - True - True 3 @@ -1907,7 +1907,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - True + False True @@ -1945,7 +1945,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe

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

- True + False True
@@ -1997,7 +1997,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
- True + False True
@@ -2034,7 +2034,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
- True + False True
diff --git a/user_config/usr/share/solarfm/solarfm-64x64.png b/user_config/usr/share/solarfm/solarfm-64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..1a403ae3ca0be18c3661898bdcd9bfb5dfd2b80a GIT binary patch literal 16172 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/solarfm.png b/user_config/usr/share/solarfm/solarfm.png new file mode 100644 index 0000000000000000000000000000000000000000..1a403ae3ca0be18c3661898bdcd9bfb5dfd2b80a GIT binary patch literal 16172 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 literal 0 HcmV?d00001 -- 2.27.0 From bddcc8e3e6f71b379652bd1309a79dff338d8b6e Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 25 Feb 2022 01:29:37 -0600 Subject: [PATCH 17/41] Plugin sys changes, glade changes --- plugins/template/__main__.py | 15 ++++--- plugins/youtube_download/__main__.py | 21 +++++---- plugins/youtube_download/download.sh | 1 + .../SolarFM/solarfm/plugins/plugins.py | 23 +++------- .../usr/share/solarfm/Main_Window.glade | 44 +++++++++---------- 5 files changed, 47 insertions(+), 57 deletions(-) diff --git a/plugins/template/__main__.py b/plugins/template/__main__.py index 3ae0454..ac0f74f 100644 --- a/plugins/template/__main__.py +++ b/plugins/template/__main__.py @@ -15,19 +15,20 @@ def threaded(fn): return wrapper -class Main: - def __init__(self, socket_id, event_system): +class Plugin: + def __init__(self, builder, event_system): self._plugin_name = "Example Plugin" + self._builder = builder self._event_system = event_system - self._socket_id = socket_id - self._gtk_plug = Gtk.Plug.new(self._socket_id) - button = Gtk.Button(label=self._plugin_name) self._message = None self._time_out = 5 + button = Gtk.Button(label=self._plugin_name) button.connect("button-release-event", self._do_action) - self._gtk_plug.add(button) - self._gtk_plug.show_all() + + plugin_list = self._builder.get_object("plugin_socket") + plugin_list.add(button) + plugin_list.show_all() @threaded diff --git a/plugins/youtube_download/__main__.py b/plugins/youtube_download/__main__.py index c43a38d..5482b09 100644 --- a/plugins/youtube_download/__main__.py +++ b/plugins/youtube_download/__main__.py @@ -15,20 +15,22 @@ def threaded(fn): return wrapper -class Main: - def __init__(self, socket_id, event_system): - self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) +class Plugin: + def __init__(self, builder, event_system): self._plugin_name = "Youtube Download" + self._builder = builder self._event_system = event_system - self._socket_id = socket_id - self._gtk_plug = Gtk.Plug.new(self._socket_id) - button = Gtk.Button(label=self._plugin_name) self._message = None self._time_out = 5 + self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + + button = Gtk.Button(label=self._plugin_name) button.connect("button-release-event", self._do_download) - self._gtk_plug.add(button) - self._gtk_plug.show_all() + + plugin_list = self._builder.get_object("plugin_socket") + plugin_list.add(button) + plugin_list.show_all() @threaded @@ -48,9 +50,6 @@ class Main: def get_plugin_name(self): return self._plugin_name - def get_socket_id(self): - return self._socket_id - def _run_timeout(self): timeout = 0 while not self._message and timeout < self._time_out: diff --git a/plugins/youtube_download/download.sh b/plugins/youtube_download/download.sh index 073b354..dc6df21 100755 --- a/plugins/youtube_download/download.sh +++ b/plugins/youtube_download/download.sh @@ -10,6 +10,7 @@ function main() { cd "$(dirname "")" echo "Working Dir: " $(pwd) + source "/home/abaddon/Portable_Apps/py-venvs/yt-dlp-venv/venv/bin/activate" LINK=`xclip -selection clipboard -o` yt-dlp --write-sub --embed-sub --sub-langs en -o "${1}/%(title)s.%(ext)s" "${LINK}" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index dcfa61b..b3a568c 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -13,8 +13,6 @@ from gi.repository import Gtk, Gio class Plugin: name = None module = None - gtk_socket_id = None - gtk_socket = None reference = None @@ -23,8 +21,7 @@ class Plugins: def __init__(self, settings): self._settings = settings - self._plugin_list_widget = self._settings.get_builder().get_object("plugin_list") - self._plugin_list_socket = self._settings.get_builder().get_object("plugin_socket") + self._builder = self._settings.get_builder() self._plugins_path = self._settings.get_plugins_path() self._plugins_dir_watcher = None self._plugin_collection = [] @@ -56,26 +53,18 @@ class Plugins: if isdir(path): os.chdir(path) - gtk_socket = Gtk.Socket().new() - self._plugin_list_socket.add(gtk_socket) - # NOTE: Must get ID after adding socket to window. Else issues.... - gtk_socket_id = gtk_socket.get_id() - sys.path.insert(0, path) spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) + app = importlib.util.module_from_spec(spec) + spec.loader.exec_module(app) - ref = module.Main(gtk_socket_id, event_system) + plugin_reference = app.Plugin(self._builder, event_system) plugin = Plugin() - plugin.name = ref.get_plugin_name() + plugin.name = plugin_reference.get_plugin_name() plugin.module = path - plugin.gtk_socket_id = gtk_socket_id - plugin.gtk_socket = gtk_socket - plugin.reference = ref + plugin.reference = plugin_reference self._plugin_collection.append(plugin) - gtk_socket.show_all() except Exception as e: print("Malformed plugin! Not loading!") traceback.print_exc() diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 5842ded..506921e 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1785,24 +1785,9 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - - path_entry - True - True - True - Path... - - - - True - True - 1 - - - - - gtk-refresh - refresh_view + + gtk-add + create_tab True True True @@ -1813,7 +1798,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 2 + 1 @@ -1830,13 +1815,28 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True + 2 + + + + + path_entry + True + True + True + Path... + + + + True + True 3 - - gtk-add - create_tab + + gtk-refresh + refresh_view True True True -- 2.27.0 From 674dac591848d5e5997f1d01fab9cb106a205f05 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 25 Feb 2022 17:58:11 -0600 Subject: [PATCH 18/41] Nameing update, start structure changed, refactoring --- .../SolarFM/solarfm/__builtins__.py | 14 +- .../solarfm-0.0.1/SolarFM/solarfm/__init__.py | 56 ------- .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 2 +- .../SolarFM/solarfm/context/controller.py | 31 ++-- .../solarfm/context/controller_data.py | 60 +++---- .../context/mixins/exception_hook_mixin.py | 4 +- .../solarfm/context/mixins/show_hide_mixin.py | 12 +- .../solarfm/context/mixins/ui/pane_mixin.py | 12 +- .../solarfm/context/mixins/ui/tab_mixin.py | 148 +++++++++--------- .../mixins/ui/widget_file_action_mixin.py | 129 ++++++++------- .../solarfm/context/mixins/ui/widget_mixin.py | 91 ++++++----- .../solarfm/context/mixins/ui/window_mixin.py | 146 +++++++++-------- .../context/signals/ipc_signals_mixin.py | 2 +- .../context/signals/keyboard_signals_mixin.py | 121 +++++++------- .../ipc_server_mixin.py => ipc_server.py} | 9 +- .../solarfm-0.0.1/SolarFM/solarfm/main.py | 55 +++++++ .../solarfm/shellfm/windows/controller.py | 38 ++--- .../windows/{views => tabs}/__init__.py | 0 .../windows/{views => tabs}/icons/__init__.py | 0 .../windows/{views => tabs}/icons/icon.py | 0 .../{views => tabs}/icons/mixins/__init__.py | 0 .../icons/mixins/desktopiconmixin.py | 0 .../icons/mixins/videoiconmixin.py | 0 .../icons/mixins/xdg/BaseDirectory.py | 0 .../icons/mixins/xdg/Config.py | 0 .../icons/mixins/xdg/DesktopEntry.py | 0 .../icons/mixins/xdg/Exceptions.py | 0 .../icons/mixins/xdg/IconTheme.py | 0 .../icons/mixins/xdg/IniFile.py | 0 .../icons/mixins/xdg/Locale.py | 0 .../{views => tabs}/icons/mixins/xdg/Menu.py | 0 .../icons/mixins/xdg/MenuEditor.py | 0 .../{views => tabs}/icons/mixins/xdg/Mime.py | 0 .../icons/mixins/xdg/RecentFiles.py | 0 .../icons/mixins/xdg/__init__.py | 0 .../{views => tabs}/icons/mixins/xdg/util.py | 0 .../shellfm/windows/{views => tabs}/path.py | 0 .../windows/{views/view.py => tabs/tab.py} | 2 +- .../windows/{views => tabs}/utils/__init__.py | 0 .../{views => tabs}/utils/filehandler.py | 0 .../windows/{views => tabs}/utils/launcher.py | 0 .../windows/{views => tabs}/utils/settings.py | 0 .../SolarFM/solarfm/shellfm/windows/window.py | 54 +++---- .../SolarFM/solarfm/utils/__init__.py | 3 + .../SolarFM/solarfm/utils/settings.py | 75 +++++---- .../usr/share/solarfm/Main_Window.glade | 20 +-- 46 files changed, 530 insertions(+), 554 deletions(-) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context/ipc_server_mixin.py => ipc_server.py} (88%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/icon.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/desktopiconmixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/videoiconmixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/BaseDirectory.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/Config.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/DesktopEntry.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/Exceptions.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/IconTheme.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/IniFile.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/Locale.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/Menu.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/MenuEditor.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/Mime.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/RecentFiles.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/icons/mixins/xdg/util.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/path.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views/view.py => tabs/tab.py} (99%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/utils/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/utils/filehandler.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/utils/launcher.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/{views => tabs}/utils/settings.py (100%) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 137213e..02c8d57 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -4,15 +4,17 @@ import builtins # Lib imports # Application imports -from context.ipc_server_mixin import IPCServerMixin +from ipc_server import IPCServer -class Builtins(IPCServerMixin): +class EventSystem(IPCServer): """ Inheret IPCServerMixin. Create an pub/sub systems. """ def __init__(self): + super(EventSystem, self).__init__() + # NOTE: The format used is list of [type, target, (data,)] Where: # type is useful context for control flow, # target is the method to call, @@ -20,11 +22,7 @@ class Builtins(IPCServerMixin): # Where data may be any kind of data self._gui_events = [] self._module_events = [] - self.is_ipc_alive = False - self.ipc_authkey = b'solarfm-ipc' - self.ipc_address = '127.0.0.1' - self.ipc_port = 4848 - self.ipc_timeout = 15.0 + # Makeshift fake "events" type system FIFO @@ -70,7 +68,7 @@ class Builtins(IPCServerMixin): # NOTE: Just reminding myself we can add to builtins two different ways... # __builtins__.update({"event_system": Builtins()}) builtins.app_name = "SolarFM" -builtins.event_system = Builtins() +builtins.event_system = EventSystem() builtins.event_sleep_time = 0.2 builtins.debug = False builtins.trace_debug = False diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py index 40d42d5..e69de29 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py @@ -1,56 +0,0 @@ -# Python imports -import os, inspect, time - -# Lib imports - -# Application imports -from utils.settings import Settings -from context.controller import Controller -from __builtins__ import Builtins - - - - -class Main(Builtins): - """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ - - def __init__(self, args, unknownargs): - if not debug: - event_system.create_ipc_server() - - time.sleep(0.2) - if not trace_debug and not debug: - if not event_system.is_ipc_alive: - if unknownargs: - for arg in unknownargs: - if os.path.isdir(arg): - message = f"FILE|{arg}" - event_system.send_ipc_message(message) - - if args.new_tab and os.path.isdir(args.new_tab): - message = f"FILE|{args.new_tab}" - event_system.send_ipc_message(message) - - raise Exception("IPC Server Exists: Will send path(s) to it and close...") - - - settings = Settings() - settings.create_window() - - controller = Controller(args, unknownargs, settings) - if not controller: - raise Exception("Controller exited and doesn't exist...") - - # Gets the methods from the classes and sets to handler. - # Then, builder connects to any signals it needs. - classes = [controller] - handlers = {} - for c in classes: - methods = None - try: - methods = inspect.getmembers(c, predicate=inspect.ismethod) - handlers.update(methods) - except Exception as e: - print(repr(e)) - - settings.builder.connect_signals(handlers) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index ba575d0..0941597 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -15,7 +15,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from __init__ import Main +from main import Main if __name__ == "__main__": 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 3c620f4..b801901 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 @@ -50,7 +50,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def tear_down(self, widget=None, eve=None): event_system.send_ipc_message("close server") - self.window_controller.save_state() + self.fm_controller.save_state() time.sleep(event_sleep_time) Gtk.main_quit() @@ -78,18 +78,18 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.plugins.set_message_on_plugin(type, data) def open_terminal(self, widget=None, eve=None): - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - dir = view.get_current_directory() - view.execute(f"{view.terminal_app}", dir) + 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.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) save_load_dialog = self.builder.get_object("save_load_dialog") if action == "save_session": - self.window_controller.save_state() + self.fm_controller.save_state() return elif action == "save_session_as": save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) @@ -98,16 +98,16 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi else: raise Exception(f"Unknown action given: {action}") - save_load_dialog.set_current_folder(view.get_current_directory()) + save_load_dialog.set_current_folder(tab.get_current_directory()) save_load_dialog.set_current_name("session.json") response = save_load_dialog.run() if response == Gtk.ResponseType.OK: if action == "save_session": path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" - self.window_controller.save_state(path) + self.fm_controller.save_state(path) elif action == "load_session": path = f"{save_load_dialog.get_file().get_path()}" - session_json = self.window_controller.load_state(path) + session_json = self.fm_controller.load_state(path) self.load_session(session_json) if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): pass @@ -118,13 +118,13 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi if debug: print(f"Session Data: {session_json}") - self.ctrlDown = False - self.shiftDown = False - self.altDown = False + self.ctrl_down = False + self.shift_down = False + self.alt_down = False for notebook in self.notebooks: self.clear_children(notebook) - self.window_controller.unload_views_and_windows() + self.fm_controller.unload_tabs_and_windows() self.generate_windows(session_json) gc.collect() @@ -165,7 +165,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.restore_trash_files() if action == "empty_trash": self.empty_trash() - if action == "create": self.show_new_file_menu() if action in ["save_session", "save_session_as", "load_session"]: 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 6d0a849..28a09e8 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 @@ -17,9 +17,9 @@ class Controller_Data: def setup_controller_data(self, _settings): self.trashman = XDGTrash() - self.window_controller = WindowController() + self.fm_controller = WindowController() self.plugins = Plugins(_settings) - self.state = self.window_controller.load_state() + self.state = self.fm_controller.load_state() self.trashman.regenerate() self.settings = _settings @@ -31,8 +31,8 @@ class Controller_Data: self.window2 = self.builder.get_object("window_2") self.window3 = self.builder.get_object("window_3") self.window4 = self.builder.get_object("window_4") - self.message_widget = self.builder.get_object("message_widget") - self.message_view = self.builder.get_object("message_view") + self.message_popup_widget = self.builder.get_object("message_popup_widget") + self.message_text_view = self.builder.get_object("message_text_view") self.message_buffer = self.builder.get_object("message_buffer") self.arc_command_buffer = self.builder.get_object("arc_command_buffer") @@ -78,32 +78,32 @@ class Controller_Data: 'xz -cz %N > %O' ] - self.notebooks = [self.window1, self.window2, self.window3, self.window4] - self.selected_files = [] - self.to_copy_files = [] - self.to_cut_files = [] - self.soft_update_lock = {} + self.notebooks = [self.window1, self.window2, self.window3, self.window4] + self.selected_files = [] + self.to_copy_files = [] + self.to_cut_files = [] + self.soft_update_lock = {} - self.single_click_open = False - self.is_pane1_hidden = False - self.is_pane2_hidden = False - self.is_pane3_hidden = False - self.is_pane4_hidden = False + self.single_click_open = False + self.is_pane1_hidden = False + self.is_pane2_hidden = False + self.is_pane3_hidden = False + self.is_pane4_hidden = False self.override_drop_dest = None self.is_searching = False - self.search_iconview = None - self.search_view = None + self.search_icon_grid = None + self.search_tab = None - self.skip_edit = False - self.cancel_edit = False - self.ctrlDown = False - self.shiftDown = False - self.altDown = False + self.skip_edit = False + self.cancel_edit = False + self.ctrl_down = False + self.shift_down = False + self.alt_down = False - self.success = "#88cc27" - self.warning = "#ffa800" - self.error = "#ff0000" + self.success_color = self.settings.get_success_color() + self.warning_color = self.settings.get_warning_color() + self.error_color = self.settings.get_error_color() sys.excepthook = self.custom_except_hook self.window.connect("delete-event", self.tear_down) @@ -117,13 +117,13 @@ class Controller_Data: a (obj): self Returns: - wid, tid, view, iconview, store + wid, tid, tab, icon_grid, store ''' - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - iconview = self.builder.get_object(f"{wid}|{tid}|iconview") - store = iconview.get_model() - return wid, tid, view, iconview, store + 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 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 dbaa065..08d29cb 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 @@ -27,14 +27,14 @@ class ExceptionHookMixin: def display_message(self, type, text, seconds=None): self.message_buffer.insert_at_cursor(text) - self.message_widget.popup() + self.message_popup_widget.popup() if seconds: self.hide_message_timeout(seconds) @threaded def hide_message_timeout(self, seconds=3): time.sleep(seconds) - GLib.idle_add(self.message_widget.popdown) + GLib.idle_add(self.message_popup_widget.popdown) def save_debug_alerts(self, widget=None, eve=None): start_itr, end_itr = self.message_buffer.get_bounds() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py index d73d098..2e7a4fd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py @@ -11,7 +11,7 @@ from gi.repository import Gtk, Gdk class ShowHideMixin: def show_messages_popup(self, type, text, seconds=None): - self.message_widget.popup() + self.message_popup_widget.popup() def stop_file_searching(self, widget=None, eve=None): self.is_searching = False @@ -48,7 +48,7 @@ class ShowHideMixin: def show_about_page(self, widget=None, eve=None): about_page = self.builder.get_object("about_page") response = about_page.run() - if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): + if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: self.hide_about_page() def hide_about_page(self, widget=None, eve=None): @@ -56,11 +56,11 @@ class ShowHideMixin: def show_archiver_dialogue(self, widget=None, eve=None): - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) archiver_dialogue = self.builder.get_object("archiver_dialogue") archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) - archiver_dialogue.set_current_folder(view.get_current_directory()) + archiver_dialogue.set_current_folder(tab.get_current_directory()) archiver_dialogue.set_current_name("arc.7z") response = archiver_dialogue.run() @@ -137,7 +137,7 @@ class ShowHideMixin: def hide_edit_file_menu_enter_key(self, widget=None, eve=None): keyname = Gdk.keyval_name(eve.keyval).lower() - if "return" in keyname or "enter" in keyname: + if keyname in ["return", "enter"]: self.builder.get_object("edit_file_menu").hide() def hide_edit_file_menu_skip(self, widget=None, eve=None): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py index accd2c4..3d2c888 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py @@ -39,8 +39,6 @@ class PaneMixin: def toggle_notebook_pane(self, widget, eve=None): name = widget.get_name() pane_index = int(name[-1]) - pane = None - master_pane = self.builder.get_object("pane_master") pane = self.builder.get_object("pane_top") if pane_index in [1, 2] else self.builder.get_object("pane_bottom") @@ -50,16 +48,12 @@ class PaneMixin: self._save_state(state, pane_index) return - child = None - if pane_index in [1, 3]: - child = pane.get_child1() - elif pane_index in [2, 4]: - child = pane.get_child2() + child = pane.get_child1() if pane_index in [1, 3] else pane.get_child2() self.toggle_pane(child) self._save_state(state, pane_index) def _save_state(self, state, pane_index): - window = self.window_controller.get_window_by_index(pane_index - 1) + window = self.fm_controller.get_window_by_index(pane_index - 1) window.set_is_hidden(state) - self.window_controller.save_state() + self.fm_controller.save_state() 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 bfa78cc..9ce3953 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 @@ -18,128 +18,128 @@ class TabMixin(WidgetMixin): def create_tab(self, wid, path=None): notebook = self.builder.get_object(f"window_{wid}") path_entry = self.builder.get_object(f"path_entry") - view = self.window_controller.add_view_for_window_by_nickname(f"window_{wid}") - view.logger = self.logger + tab = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}") + tab.logger = self.logger - view.set_wid(wid) - if path: view.set_path(path) + tab.set_wid(wid) + if path: tab.set_path(path) - tab = self.create_tab_widget(view) - scroll, store = self.create_grid_iconview_widget(view, wid) - # scroll, store = self.create_grid_treeview_widget(view, wid) - index = notebook.append_page(scroll, tab) + tab_widget = self.create_tab_widget(tab) + scroll, store = self.create_icon_grid_widget(tab, wid) + # TODO: Fix global logic to make the below work too + # scroll, store = self.create_icon_tree_widget(tab, wid) + index = notebook.append_page(scroll, tab_widget) - self.window_controller.set__wid_and_tid(wid, view.get_id()) - path_entry.set_text(view.get_current_directory()) + self.fm_controller.set__wid_and_tid(wid, tab.get_id()) + path_entry.set_text(tab.get_current_directory()) notebook.show_all() notebook.set_current_page(index) ctx = notebook.get_style_context() ctx.add_class("notebook-unselected-focus") notebook.set_tab_reorderable(scroll, True) - self.load_store(view, store) + self.load_store(tab, store) self.set_window_title() - self.set_file_watcher(view) + self.set_file_watcher(tab) def close_tab(self, button, eve=None): notebook = button.get_parent().get_parent() - tid = self.get_id_from_tab_box(button.get_parent()) wid = int(notebook.get_name()[-1]) + tid = self.get_id_from_tab_box(button.get_parent()) scroll = self.builder.get_object(f"{wid}|{tid}") page = notebook.page_num(scroll) - view = self.get_fm_window(wid).get_view_by_id(tid) - watcher = view.get_dir_watcher() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + watcher = tab.get_dir_watcher() watcher.cancel() - self.get_fm_window(wid).delete_view_by_id(tid) + self.get_fm_window(wid).delete_tab_by_id(tid) notebook.remove_page(page) - self.window_controller.save_state() + self.fm_controller.save_state() self.set_window_title() def on_tab_reorder(self, child, page_num, new_index): wid, tid = page_num.get_name().split("|") window = self.get_fm_window(wid) - view = None + tab = None - for i, view in enumerate(window.get_all_views()): - if view.get_id() == tid: - _view = window.get_view_by_id(tid) - watcher = _view.get_dir_watcher() + for i, tab in enumerate(window.get_all_tabs()): + if tab.get_id() == tid: + _tab = window.get_tab_by_id(tid) + watcher = _tab.get_dir_watcher() watcher.cancel() - window.get_all_views().insert(new_index, window.get_all_views().pop(i)) + window.get_all_tabs().insert(new_index, window.get_all_tabs().pop(i)) - view = window.get_view_by_id(tid) - self.set_file_watcher(view) - self.window_controller.save_state() + tab = window.get_tab_by_id(tid) + self.set_file_watcher(tab) + self.fm_controller.save_state() def on_tab_switch_update(self, notebook, content=None, index=None): self.selected_files.clear() wid, tid = content.get_children()[0].get_name().split("|") - self.window_controller.set__wid_and_tid(wid, tid) + self.fm_controller.set__wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() def get_id_from_tab_box(self, tab_box): - tid = tab_box.get_children()[2] - return tid.get_text() + return tab_box.get_children()[2].get_text() - def get_tab_label(self, notebook, iconview): - return notebook.get_tab_label(iconview.get_parent()).get_children()[0] + def get_tab_label(self, notebook, icon_grid): + return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0] - def get_tab_close(self, notebook, iconview): - return notebook.get_tab_label(iconview.get_parent()).get_children()[1] + def get_tab_close(self, notebook, icon_grid): + return notebook.get_tab_label(icon_grid.get_parent()).get_children()[1] - def get_tab_iconview_from_notebook(self, notebook): + def get_tab_icon_grid_from_notebook(self, notebook): return notebook.get_children()[1].get_children()[0] def refresh_tab(data=None): - wid, tid, view, iconview, store = self.get_current_state() - view.load_directory() - self.load_store(view, store) + wid, tid, tab, icon_grid, store = self.get_current_state() + tab.load_directory() + self.load_store(tab, store) - def update_view(self, tab_label, view, store, wid, tid): - self.load_store(view, store) + def update_tab(self, tab_label, tab, store, wid, tid): + self.load_store(tab, store) self.set_path_text(wid, tid) - char_width = len(view.get_end_of_path()) + char_width = len(tab.get_end_of_path()) tab_label.set_width_chars(char_width) - tab_label.set_label(view.get_end_of_path()) + tab_label.set_label(tab.get_end_of_path()) self.set_window_title() - self.set_file_watcher(view) - self.window_controller.save_state() + self.set_file_watcher(tab) + self.fm_controller.save_state() def do_action_from_bar_controls(self, widget, eve=None): action = widget.get_name() - wid, tid = self.window_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") - view = self.get_fm_window(wid).get_view_by_id(tid) + tab = self.get_fm_window(wid).get_tab_by_id(tid) - if action == "go_up": - view.pop_from_path() - if action == "go_home": - view.set_to_home() - if action == "refresh_view": - view.load_directory() if action == "create_tab": - dir = view.get_current_directory() + dir = tab.get_current_directory() self.create_tab(wid, dir) - self.window_controller.save_state() + self.fm_controller.save_state() return + if action == "go_up": + tab.pop_from_path() + if action == "go_home": + tab.set_to_home() + if action == "refresh_tab": + tab.load_directory() if action == "path_entry": focused_obj = self.window.get_focus() - dir = f"{view.get_current_directory()}/" + dir = f"{tab.get_current_directory()}/" path = widget.get_text() if isinstance(focused_obj, Gtk.Entry): - button_box = self.path_menu.get_children()[0].get_children()[0].get_children()[0] - query = widget.get_text().replace(dir, "") - files = view.get_files() + view.get_hidden() + path_menu_buttons = self.builder.get_object("path_menu_buttons") + query = widget.get_text().replace(dir, "") + files = tab.get_files() + tab.get_hidden() - self.clear_children(button_box) + self.clear_children(path_menu_buttons) show_path_menu = False for file, hash in files: if os.path.isdir(f"{dir}{file}"): @@ -147,7 +147,7 @@ class TabMixin(WidgetMixin): button = Gtk.Button(label=file) button.show() button.connect("clicked", self.set_path_entry) - button_box.add(button) + path_menu_buttons.add(button) show_path_menu = True if not show_path_menu: @@ -160,11 +160,10 @@ class TabMixin(WidgetMixin): if path.endswith(".") or path == dir: return - traversed = view.set_path(path) - if not traversed: + if not tab.set_path(path): return - self.update_view(tab_label, view, store, wid, tid) + self.update_tab(tab_label, tab, store, wid, tid) try: widget.grab_focus_without_selecting() @@ -173,8 +172,8 @@ class TabMixin(WidgetMixin): pass def set_path_entry(self, button=None, eve=None): - wid, tid, view, iconview, store = self.get_current_state() - path = f"{view.get_current_directory()}/{button.get_label()}" + wid, tid, tab, icon_grid, store = self.get_current_state() + path = f"{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() @@ -182,23 +181,22 @@ class TabMixin(WidgetMixin): self.path_menu.popdown() def keyboard_close_tab(self): - wid, tid = self.window_controller.get_active_wid_and_tid() + 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) - view = self.get_fm_window(wid).get_view_by_id(tid) - watcher = view.get_dir_watcher() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + watcher = tab.get_dir_watcher() watcher.cancel() - self.get_fm_window(wid).delete_view_by_id(tid) + self.get_fm_window(wid).delete_tab_by_id(tid) notebook.remove_page(page) - self.window_controller.save_state() + self.fm_controller.save_state() self.set_window_title() - # File control events def show_hide_hidden_files(self): - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - view.set_hiding_hidden(not view.is_hiding_hidden()) - view.load_directory() - self.builder.get_object("refresh_view").released() + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + tab.set_hiding_hidden(not tab.is_hiding_hidden()) + tab.load_directory() + self.builder.get_object("refresh_tab").released() 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 eda4c7f..04f7862 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 @@ -40,29 +40,24 @@ class WidgetFileActionMixin: return size - def set_file_watcher(self, view): - if view.get_dir_watcher(): - watcher = view.get_dir_watcher() + def set_file_watcher(self, tab): + if tab.get_dir_watcher(): + watcher = tab.get_dir_watcher() watcher.cancel() if debug: print(f"Watcher Is Cancelled: {watcher.is_cancelled()}") - cur_dir = view.get_current_directory() - # Temp updating too much with current events we are checking for. - # Seems to cause invalid iter errors in WidbetMixin > update_store - if cur_dir == "/tmp": - watcher = None - return + cur_dir = tab.get_current_directory() dir_watcher = Gio.File.new_for_path(cur_dir) \ .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) - wid = view.get_wid() - tid = view.get_id() + wid = tab.get_wid() + tid = tab.get_id() dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",)) - view.set_dir_watcher(dir_watcher) + tab.set_dir_watcher(dir_watcher) - # NOTE: Too lazy to impliment a proper update handler and so just regen store and update view. + # NOTE: Too lazy to impliment a proper update handler and so just regen store and update tab. # Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None): if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, @@ -72,46 +67,46 @@ class WidgetFileActionMixin: print(eve_type) if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: - self.update_on_end_soft_lock(data[0]) + self.update_on_soft_lock_end(data[0]) elif data[0] in self.soft_update_lock.keys(): self.soft_update_lock[data[0]]["last_update_time"] = time.time() else: self.soft_lock_countdown(data[0]) @threaded - def soft_lock_countdown(self, tab): - self.soft_update_lock[tab] = { "last_update_time": time.time()} + def soft_lock_countdown(self, tab_widget): + self.soft_update_lock[tab_widget] = { "last_update_time": time.time()} lock = True while lock: time.sleep(0.6) - last_update_time = self.soft_update_lock[tab]["last_update_time"] + last_update_time = self.soft_update_lock[tab_widget]["last_update_time"] current_time = time.time() if (current_time - last_update_time) > 0.6: lock = False - self.soft_update_lock.pop(tab, None) - GLib.idle_add(self.update_on_end_soft_lock, *(tab,)) + self.soft_update_lock.pop(tab_widget, None) + GLib.idle_add(self.update_on_soft_lock_end, *(tab_widget,)) - def update_on_end_soft_lock(self, tab): - wid, tid = tab.split("|") + def update_on_soft_lock_end(self, tab_widget): + wid, tid = tab_widget.split("|") notebook = self.builder.get_object(f"window_{wid}") - view = self.get_fm_window(wid).get_view_by_id(tid) - iconview = self.builder.get_object(f"{wid}|{tid}|iconview") - store = iconview.get_model() - _store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{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() + _store, tab_widget_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") - view.load_directory() - self.load_store(view, store) + tab.load_directory() + self.load_store(tab, store) - tab_label.set_label(view.get_end_of_path()) + tab_widget_label.set_label(tab.get_end_of_path()) - _wid, _tid, _view, _iconview, _store = self.get_current_state() + _wid, _tid, _tab, _icon_grid, _store = self.get_current_state() if [wid, tid] in [_wid, _tid]: - self.set_bottom_labels(view) + self.set_bottom_labels(tab) def popup_search_files(self, wid, keyname): @@ -123,43 +118,43 @@ class WidgetFileActionMixin: def do_file_search(self, widget, eve=None): query = widget.get_text() - self.search_iconview.unselect_all() - for i, file in enumerate(self.search_view.get_files()): + self.search_icon_grid.unselect_all() + for i, file in enumerate(self.search_tab.get_files()): if query and query in file[0].lower(): path = Gtk.TreePath().new_from_indices([i]) - self.search_iconview.select_path(path) + self.search_icon_grid.select_path(path) - items = self.search_iconview.get_selected_items() + items = self.search_icon_grid.get_selected_items() if len(items) == 1: - self.search_iconview.scroll_to_path(items[0], True, 0.5, 0.5) + self.search_icon_grid.scroll_to_path(items[0], True, 0.5, 0.5) def open_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) for file in uris: - view.open_file_locally(file) + tab.open_file_locally(file) def open_with_files(self, appchooser_widget): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() app_info = appchooser_widget.get_app_info() uris = self.format_to_uris(store, wid, tid, self.selected_files) - view.app_chooser_exec(app_info, uris) + tab.app_chooser_exec(app_info, uris) def execute_files(self, in_terminal=False): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() paths = self.format_to_uris(store, wid, tid, self.selected_files, True) - current_dir = view.get_current_directory() + current_dir = tab.get_current_directory() command = None for path in paths: - command = f"exec '{path}'" if not in_terminal else f"{view.terminal_app} -e '{path}'" - view.execute(command, start_dir=view.get_current_directory(), use_os_system=False) + 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) def archive_files(self, archiver_dialogue): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() paths = self.format_to_uris(store, wid, tid, self.selected_files, True) save_target = archiver_dialogue.get_filename(); @@ -167,14 +162,14 @@ class WidgetFileActionMixin: 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"{view.terminal_app} -e '{pre_command}'" + command = f"{tab.terminal_app} -e '{pre_command}'" - view.execute(command, start_dir=None, use_os_system=True) + 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, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) for uri in uris: @@ -191,7 +186,7 @@ class WidgetFileActionMixin: break rname_to = rename_input.get_text().strip() - target = f"{view.get_current_directory()}/{rname_to}" + target = f"{tab.get_current_directory()}/{rname_to}" self.handle_files([uri], "rename", target) @@ -201,19 +196,19 @@ class WidgetFileActionMixin: self.selected_files.clear() def cut_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) self.to_cut_files = uris def copy_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) self.to_copy_files = uris def paste_files(self): - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - target = f"{view.get_current_directory()}" + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + target = f"{tab.get_current_directory()}" if len(self.to_copy_files) > 0: self.handle_files(self.to_copy_files, "copy", target) @@ -221,7 +216,7 @@ class WidgetFileActionMixin: self.handle_files(self.to_cut_files, "move", target) def delete_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) response = None @@ -236,7 +231,7 @@ class WidgetFileActionMixin: type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - view.delete_file( file.get_path() ) + tab.delete_file( file.get_path() ) else: file.delete(cancellable=None) else: @@ -244,13 +239,13 @@ class WidgetFileActionMixin: def trash_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) for uri in uris: self.trashman.trash(uri, False) def restore_trash_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) for uri in uris: self.trashman.restore(filename=uri.split("/")[-1], verbose=False) @@ -264,9 +259,9 @@ class WidgetFileActionMixin: file_name = fname_field.get_text().strip() type = self.builder.get_object("context_menu_type_toggle").get_state() - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - target = f"{view.get_current_directory()}" + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + target = f"{tab.get_current_directory()}" if file_name: path = f"{target}/{file_name}" @@ -331,9 +326,9 @@ class WidgetFileActionMixin: type = _file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - view.delete_file( _file.get_path() ) + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + tab.delete_file( _file.get_path() ) else: _file.delete(cancellable=None) @@ -358,16 +353,16 @@ class WidgetFileActionMixin: type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) fPath = file.get_path() tPath = target.get_path() state = True if action == "copy": - view.copy_file(fPath, tPath) + tab.copy_file(fPath, tPath) if action == "move" or action == "rename": - view.move_file(fPath, tPath) + tab.move_file(fPath, tPath) else: if action == "copy": file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py index 84ecac0..6f496f4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py @@ -22,29 +22,29 @@ def threaded(fn): class WidgetMixin: """docstring for WidgetMixin""" - def load_store(self, view, store, save_state=False): + def load_store(self, tab, store, save_state=False): store.clear() - dir = view.get_current_directory() - files = view.get_files() + dir = tab.get_current_directory() + files = tab.get_files() for i, file in enumerate(files): store.append([None, file[0]]) - self.create_icon(i, view, store, dir, file[0]) + self.create_icon(i, tab, store, dir, file[0]) # NOTE: Not likely called often from here but it could be useful if save_state: - self.window_controller.save_state() + self.fm_controller.save_state() @threaded - def create_icon(self, i, view, store, dir, file): - icon = view.create_icon(dir, file) + def create_icon(self, i, tab, store, dir, file): + icon = tab.create_icon(dir, file) fpath = f"{dir}/{file}" - GLib.idle_add(self.update_store, (i, store, icon, view, fpath,)) + GLib.idle_add(self.update_store, (i, store, icon, tab, fpath,)) # NOTE: Might need to keep an eye on this throwing invalid iters when too # many updates are happening to a folder. Example: /tmp def update_store(self, item): - i, store, icon, view, fpath = item + i, store, icon, tab, fpath = item itr = None try: @@ -60,12 +60,12 @@ class WidgetMixin: return if not icon: - icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0]) + icon = self.get_system_thumbnail(fpath, tab.SYS_ICON_WH[0]) if not icon: if fpath.endswith(".gif"): icon = GdkPixbuf.PixbufAnimation.get_static_image(fpath) else: - icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON) + icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) store.set_value(itr, 0, icon) @@ -90,31 +90,29 @@ class WidgetMixin: return None - - - def create_tab_widget(self, view): - tab = Gtk.ButtonBox() + def create_tab_widget(self, tab): + tab_widget = Gtk.ButtonBox() label = Gtk.Label() tid = Gtk.Label() close = Gtk.Button() icon = Gtk.Image(stock=Gtk.STOCK_CLOSE) - label.set_label(f"{view.get_end_of_path()}") - label.set_width_chars(len(view.get_end_of_path())) + label.set_label(f"{tab.get_end_of_path()}") + label.set_width_chars(len(tab.get_end_of_path())) label.set_xalign(0.0) - tid.set_label(f"{view.get_id()}") + tid.set_label(f"{tab.get_id()}") close.add(icon) - tab.add(label) - tab.add(close) - tab.add(tid) + tab_widget.add(label) + tab_widget.add(close) + tab_widget.add(tid) close.connect("released", self.close_tab) - tab.show_all() + tab_widget.show_all() tid.hide() - return tab + return tab_widget - def create_grid_iconview_widget(self, view, wid): + def create_icon_grid_widget(self, tab, wid): scroll = Gtk.ScrolledWindow() grid = Gtk.IconView() store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) @@ -134,11 +132,11 @@ class WidgetMixin: grid.set_column_spacing(18) grid.connect("button_release_event", self.grid_icon_single_click) - grid.connect("item-activated", self.grid_icon_double_click) - grid.connect("selection-changed", self.grid_set_selected_items) - grid.connect("drag-data-get", self.grid_on_drag_set) - grid.connect("drag-data-received", self.grid_on_drag_data_received) - grid.connect("drag-motion", self.grid_on_drag_motion) + grid.connect("item-activated", self.grid_icon_double_click) + grid.connect("selection-changed", self.grid_set_selected_items) + grid.connect("drag-data-get", self.grid_on_drag_set) + grid.connect("drag-data-received", self.grid_on_drag_data_received) + grid.connect("drag-motion", self.grid_on_drag_motion) URI_TARGET_TYPE = 80 @@ -150,17 +148,16 @@ class WidgetMixin: grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{view.get_id()}") - scroll.set_name(f"{wid}|{view.get_id()}") - self.builder.expose_object(f"{wid}|{view.get_id()}|iconview", grid) - self.builder.expose_object(f"{wid}|{view.get_id()}", scroll) + grid.set_name(f"{wid}|{tab.get_id()}") + scroll.set_name(f"{wid}|{tab.get_id()}") + self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) + self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) return scroll, store - def create_grid_treeview_widget(self, view, wid): + def create_icon_tree_widget(self, tab, wid): scroll = Gtk.ScrolledWindow() grid = Gtk.TreeView() - store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) - # store = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str) + store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) column = Gtk.TreeViewColumn("Icons") icon = Gtk.CellRendererPixbuf() name = Gtk.CellRendererText() @@ -184,10 +181,10 @@ class WidgetMixin: grid.set_enable_tree_lines(False) grid.connect("button_release_event", self.grid_icon_single_click) - grid.connect("row-activated", self.grid_icon_double_click) - grid.connect("drag-data-get", self.grid_on_drag_set) - grid.connect("drag-data-received", self.grid_on_drag_data_received) - grid.connect("drag-motion", self.grid_on_drag_motion) + grid.connect("row-activated", self.grid_icon_double_click) + grid.connect("drag-data-get", self.grid_on_drag_set) + grid.connect("drag-data-received", self.grid_on_drag_data_received) + grid.connect("drag-motion", self.grid_on_drag_motion) URI_TARGET_TYPE = 80 uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) @@ -199,23 +196,23 @@ class WidgetMixin: grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{view.get_id()}") - scroll.set_name(f"{wid}|{view.get_id()}") + grid.set_name(f"{wid}|{tab.get_id()}") + scroll.set_name(f"{wid}|{tab.get_id()}") grid.columns_autosize() - self.builder.expose_object(f"{wid}|{view.get_id()}", scroll) + self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) return scroll, store def get_store_and_label_from_notebook(self, notebook, _name): - icon_view = None + icon_grid = None tab_label = None store = None for obj in notebook.get_children(): - icon_view = obj.get_children()[0] - name = icon_view.get_name() + icon_grid = obj.get_children()[0] + name = icon_grid.get_name() if name == _name: - store = icon_view.get_model() + store = icon_grid.get_model() tab_label = notebook.get_tab_label(obj).get_children()[0] return store, tab_label 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 ee1ad8b..9bf1ea4 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 @@ -10,9 +10,6 @@ from gi.repository import Gdk, Gio # Application imports from .tab_mixin import TabMixin -from .widget_mixin import WidgetMixin - - class WindowMixin(TabMixin): @@ -22,47 +19,46 @@ class WindowMixin(TabMixin): if session_json: for j, value in enumerate(session_json): i = j + 1 - isHidden = True if value[0]["window"]["isHidden"] == "True" else False - object = self.builder.get_object(f"tggl_notebook_{i}") - views = value[0]["window"]["views"] - self.window_controller.create_window() - object.set_active(True) + notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}") + is_hidden = True if value[0]["window"]["isHidden"] == "True" else False + tabs = value[0]["window"]["tabs"] + self.fm_controller.create_window() + notebook_tggl_button.set_active(True) - for view in views: - self.create_new_view_notebook(None, i, view) + for tab in tabs: + self.create_new_tab_notebook(None, i, tab) - if isHidden: - self.toggle_notebook_pane(object) + if is_hidden: + self.toggle_notebook_pane(notebook_tggl_button) try: if not self.is_pane4_hidden: - icon_view = self.window4.get_children()[1].get_children()[0] - icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid = self.window4.get_children()[1].get_children()[0] elif not self.is_pane3_hidden: - icon_view = self.window3.get_children()[1].get_children()[0] - icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid = self.window3.get_children()[1].get_children()[0] elif not self.is_pane2_hidden: - icon_view = self.window2.get_children()[1].get_children()[0] - icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid = self.window2.get_children()[1].get_children()[0] elif not self.is_pane1_hidden: - icon_view = self.window1.get_children()[1].get_children()[0] - icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid = self.window1.get_children()[1].get_children()[0] + + icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) except Exception as e: print("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n") print(repr(e)) else: for j in range(0, 4): i = j + 1 - self.window_controller.create_window() - self.create_new_view_notebook(None, i, None) + self.fm_controller.create_window() + self.create_new_tab_notebook(None, i, None) def get_fm_window(self, wid): - return self.window_controller.get_window_by_nickname(f"window_{wid}") + return self.fm_controller.get_window_by_nickname(f"window_{wid}") def format_to_uris(self, store, wid, tid, treePaths, use_just_path=False): - view = self.get_fm_window(wid).get_view_by_id(tid) - dir = view.get_current_directory() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + dir = tab.get_current_directory() uris = [] for path in treePaths: @@ -80,10 +76,10 @@ class WindowMixin(TabMixin): return uris - def set_bottom_labels(self, view): - _wid, _tid, _view, icon_view, store = self.get_current_state() - selected_files = icon_view.get_selected_items() - current_directory = view.get_current_directory() + def set_bottom_labels(self, tab): + _wid, _tid, _tab, icon_grid, store = self.get_current_state() + selected_files = 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) formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) ) @@ -98,7 +94,7 @@ class WindowMixin(TabMixin): # If something selected self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}") - self.bottom_path_label.set_label(view.get_current_directory()) + self.bottom_path_label.set_label(tab.get_current_directory()) if len(selected_files) > 0: uris = self.format_to_uris(store, _wid, _tid, selected_files, True) combined_size = 0 @@ -115,29 +111,29 @@ class WindowMixin(TabMixin): formatted_size = self.sizeof_fmt(combined_size) - if view.get_hidden(): - self.bottom_path_label.set_label(f" {len(uris)} / {view.get_files_count()} ({formatted_size})") + if tab.is_hiding_hidden(): + self.bottom_path_label.set_label(f" {len(uris)} / {tab.get_files_count()} ({formatted_size})") else: - self.bottom_path_label.set_label(f" {len(uris)} / {view.get_not_hidden_count()} ({formatted_size})") + self.bottom_path_label.set_label(f" {len(uris)} / {tab.get_not_hidden_count()} ({formatted_size})") return # If nothing selected - if view.get_hidden(): - if view.get_hidden_count() > 0: - self.bottom_file_count_label.set_label(f"{view.get_not_hidden_count()} visible ({view.get_hidden_count()} hidden)") + if tab.get_hidden(): + if tab.get_hidden_count() > 0: + self.bottom_file_count_label.set_label(f"{tab.get_not_hidden_count()} visible ({tab.get_hidden_count()} hidden)") else: - self.bottom_file_count_label.set_label(f"{view.get_files_count()} items") + self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items") else: - self.bottom_file_count_label.set_label(f"{view.get_files_count()} items") + self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items") def set_window_title(self): - wid, tid = self.window_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") - view = self.get_fm_window(wid).get_view_by_id(tid) - dir = view.get_current_directory() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + dir = tab.get_current_directory() for _notebook in self.notebooks: ctx = _notebook.get_style_context() @@ -149,73 +145,73 @@ class WindowMixin(TabMixin): ctx.add_class("notebook-selected-focus") self.window.set_title(f"SolarFM ~ {dir}") - self.set_bottom_labels(view) + self.set_bottom_labels(tab) def set_path_text(self, wid, tid): path_entry = self.builder.get_object("path_entry") - view = self.get_fm_window(wid).get_view_by_id(tid) - path_entry.set_text(view.get_current_directory()) + tab = self.get_fm_window(wid).get_tab_by_id(tid) + path_entry.set_text(tab.get_current_directory()) - def grid_set_selected_items(self, iconview): - self.selected_files = iconview.get_selected_items() + def grid_set_selected_items(self, icons_grid): + self.selected_files = icons_grid.get_selected_items() - def grid_cursor_toggled(self, iconview): + def grid_cursor_toggled(self, icons_grid): print("wat...") - def grid_icon_single_click(self, iconview, eve): + def grid_icon_single_click(self, icons_grid, eve): try: self.path_menu.popdown() - wid, tid = iconview.get_name().split("|") - self.window_controller.set__wid_and_tid(wid, tid) + wid, tid = icons_grid.get_name().split("|") + self.fm_controller.set__wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1: # l-click if self.single_click_open: # FIXME: need to find a way to pass the model index - self.grid_icon_double_click(iconview) + self.grid_icon_double_click(icons_grid) elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click self.show_context_menu() except Exception as e: print(repr(e)) - self.display_message(self.error, f"{repr(e)}") + self.display_message(self.error_color, f"{repr(e)}") - def grid_icon_double_click(self, iconview, item, data=None): + def grid_icon_double_click(self, icons_grid, item, data=None): try: - if self.ctrlDown and self.shiftDown: + if self.ctrl_down and self.shift_down: self.unset_keys_and_data() self.execute_files(in_terminal=True) return - elif self.ctrlDown: + elif self.ctrl_down: self.unset_keys_and_data() self.execute_files() return - wid, tid, view, _iconview, store = self.get_current_state() + wid, tid, tab, _icons_grid, store = self.get_current_state() notebook = self.builder.get_object(f"window_{wid}") - tab_label = self.get_tab_label(notebook, iconview) + tab_label = self.get_tab_label(notebook, icons_grid) fileName = store[item][1] - dir = view.get_current_directory() + dir = tab.get_current_directory() file = f"{dir}/{fileName}" if isdir(file): - view.set_path(file) - self.update_view(tab_label, view, store, wid, tid) + tab.set_path(file) + self.update_tab(tab_label, tab, store, wid, tid) else: self.open_files() except Exception as e: - self.display_message(self.error, f"{repr(e)}") + self.display_message(self.error_color, f"{repr(e)}") - def grid_on_drag_set(self, iconview, drag_context, data, info, time): - action = iconview.get_name() + def grid_on_drag_set(self, icons_grid, drag_context, data, info, time): + action = icons_grid.get_name() wid, tid = action.split("|") - store = iconview.get_model() - treePaths = iconview.get_selected_items() + store = icons_grid.get_model() + treePaths = icons_grid.get_selected_items() # NOTE: Need URIs as URI format for DnD to work. Will strip 'file://' # further down call chain when doing internal fm stuff. uris = self.format_to_uris(store, wid, tid, treePaths) @@ -224,30 +220,30 @@ class WindowMixin(TabMixin): data.set_uris(uris) data.set_text(uris_text, -1) - def grid_on_drag_motion(self, iconview, drag_context, x, y, data): - current = '|'.join(self.window_controller.get_active_wid_and_tid()) - target = iconview.get_name() + def grid_on_drag_motion(self, icons_grid, drag_context, x, y, data): + current = '|'.join(self.fm_controller.get_active_wid_and_tid()) + target = icons_grid.get_name() wid, tid = target.split("|") - store = iconview.get_model() - treePath = iconview.get_drag_dest_item().path + store = icons_grid.get_model() + treePath = icons_grid.get_drag_dest_item().path if treePath: uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "") self.override_drop_dest = uri if isdir(uri) else None if target not in current: - self.window_controller.set__wid_and_tid(wid, tid) + self.fm_controller.set__wid_and_tid(wid, tid) def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): if info == 80: - wid, tid = self.window_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") - view = self.get_fm_window(wid).get_view_by_id(tid) + tab = self.get_fm_window(wid).get_tab_by_id(tid) uris = data.get_uris() - dest = f"{view.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest + dest = f"{tab.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest if len(uris) == 0: uris = data.get_text().split("\n") @@ -256,5 +252,5 @@ class WindowMixin(TabMixin): self.move_files(uris, dest) - def create_new_view_notebook(self, widget=None, wid=None, path=None): + def create_new_tab_notebook(self, widget=None, wid=None, path=None): self.create_tab(wid, path) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py index 3f46614..64b86dc 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py @@ -13,7 +13,7 @@ class IPCSignalsMixin: print(message) def handle_file_from_ipc(self, path): - wid, tid = self.window_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") if notebook.is_visible(): self.create_tab(wid, path) 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 a801365..8d30d77 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 @@ -17,20 +17,20 @@ class KeyboardSignalsMixin: """ KeyboardSignalsMixin keyboard hooks controller. """ def unset_keys_and_data(self, widget=None, eve=None): - self.ctrlDown = False - self.shiftDown = False - self.altDown = False + 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): keyname = Gdk.keyval_name(user_data.keyval).lower() - if "control" in keyname or "alt" in keyname or "shift" in keyname: + if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: if "control" in keyname: - self.ctrlDown = True + self.ctrl_down = True if "shift" in keyname: - self.shiftDown = True + self.shift_down = True if "alt" in keyname: - self.altDown = True + self.alt_down = True # NOTE: Yes, this should actually be mapped to some key controller setting # file or something. Sue me. @@ -39,84 +39,69 @@ class KeyboardSignalsMixin: if debug: print(f"global_key_release_controller > key > {keyname}") - if "control" in keyname or "alt" in keyname or "shift" in keyname: + if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: if "control" in keyname: - self.ctrlDown = False + self.ctrl_down = False if "shift" in keyname: - self.shiftDown = False + self.shift_down = False if "alt" in keyname: - self.altDown = False + self.alt_down = False - - if self.ctrlDown and self.shiftDown and keyname == "t": + if self.ctrl_down and self.shift_down and keyname == "t": self.unset_keys_and_data() self.trash_files() - if re.fullmatch(valid_keyvalue_pat, keyname): - if not self.is_searching and not self.ctrlDown \ - and not self.shiftDown and not self.altDown: + 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_view, self.search_iconview, store = self.get_current_state() + 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 (self.ctrlDown and keyname in ["1", "kp_1"]): - self.builder.get_object("tggl_notebook_1").released() - if (self.ctrlDown and keyname in ["2", "kp_2"]): - self.builder.get_object("tggl_notebook_2").released() - if (self.ctrlDown and keyname in ["3", "kp_3"]): - self.builder.get_object("tggl_notebook_3").released() - if (self.ctrlDown and keyname in ["4", "kp_4"]): - self.builder.get_object("tggl_notebook_4").released() - - if self.ctrlDown and keyname == "q": - self.tear_down() - if (self.ctrlDown and keyname == "slash") or keyname == "home": - self.builder.get_object("go_home").released() - if (self.ctrlDown and keyname == "r") or keyname == "f5": - self.builder.get_object("refresh_view").released() - if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"): - self.builder.get_object("go_up").released() - if self.ctrlDown and keyname == "l": - self.unset_keys_and_data() - self.builder.get_object("path_entry").grab_focus() - if self.ctrlDown and keyname == "t": - self.builder.get_object("create_tab").released() - if self.ctrlDown and keyname == "o": - self.unset_keys_and_data() - self.open_files() - if self.ctrlDown and keyname == "w": - self.keyboard_close_tab() - if self.ctrlDown and keyname == "h": - self.show_hide_hidden_files() - if (self.ctrlDown and keyname == "e"): - self.unset_keys_and_data() - self.rename_files() - if self.ctrlDown and keyname == "c": - self.copy_files() - self.to_cut_files.clear() - if self.ctrlDown and keyname == "x": - self.to_copy_files.clear() - self.cut_files() - if self.ctrlDown and keyname == "v": - self.paste_files() - if self.ctrlDown and keyname == "n": - self.unset_keys_and_data() - self.show_new_file_menu() - - - + if (self.ctrl_down and 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 in ["alt_l", "alt_r"]: top_main_menubar = self.builder.get_object("top_main_menubar") - if top_main_menubar.is_visible(): - top_main_menubar.hide() - else: - top_main_menubar.show() + top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show() + + if self.ctrl_down and keyname == "q": + self.tear_down() + if (self.ctrl_down and keyname == "slash") or keyname == "home": + self.builder.get_object("go_home").released() + if (self.ctrl_down and keyname == "r") or keyname == "f5": + self.builder.get_object("refresh_tab").released() + if (self.ctrl_down and keyname == "up") or (self.ctrl_down and keyname == "u"): + self.builder.get_object("go_up").released() + if self.ctrl_down and keyname == "l": + self.unset_keys_and_data() + self.builder.get_object("path_entry").grab_focus() + if self.ctrl_down and keyname == "t": + self.builder.get_object("create_tab").released() + if self.ctrl_down and keyname == "o": + self.unset_keys_and_data() + self.open_files() + if self.ctrl_down and keyname == "w": + self.keyboard_close_tab() + if self.ctrl_down and keyname == "h": + self.show_hide_hidden_files() + if (self.ctrl_down and keyname == "e"): + self.unset_keys_and_data() + self.rename_files() + if self.ctrl_down and keyname == "c": + self.copy_files() + self.to_cut_files.clear() + if self.ctrl_down and keyname == "x": + self.to_copy_files.clear() + self.cut_files() + if self.ctrl_down and keyname == "v": + self.paste_files() + if self.ctrl_down and keyname == "n": + self.unset_keys_and_data() + self.show_new_file_menu() if keyname == "delete": self.unset_keys_and_data() self.delete_files() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/ipc_server_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py similarity index 88% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/ipc_server_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py index ff12d4a..9223edd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/ipc_server_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py @@ -15,8 +15,15 @@ def threaded(fn): -class IPCServerMixin: +class IPCServer: """ Create a listener so that other SolarFM instances send requests back to existing instance. """ + def __init__(self): + self.is_ipc_alive = False + self.ipc_authkey = b'solarfm-ipc' + self.ipc_address = '127.0.0.1' + self.ipc_port = 4848 + self.ipc_timeout = 15.0 + @threaded def create_ipc_server(self): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py new file mode 100644 index 0000000..4a26b4d --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py @@ -0,0 +1,55 @@ +# Python imports +import os, inspect, time + +# Lib imports + +# Application imports +from utils.settings import Settings +from context.controller import Controller +from __builtins__ import EventSystem + + + + +class Main(EventSystem): + """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ + + def __init__(self, args, unknownargs): + if not trace_debug: + event_system.create_ipc_server() + time.sleep(0.2) # Make sure everything's up before proceeding. + + if not event_system.is_ipc_alive: + if unknownargs: + for arg in unknownargs: + if os.path.isdir(arg): + message = f"FILE|{arg}" + event_system.send_ipc_message(message) + + if args.new_tab and os.path.isdir(args.new_tab): + message = f"FILE|{args.new_tab}" + event_system.send_ipc_message(message) + + raise Exception("IPC Server Exists: Will send path(s) to it and close...") + + + settings = Settings() + settings.create_window() + + controller = Controller(args, unknownargs, settings) + if not controller: + raise Exception("Controller exited and doesn't exist...") + + # Gets the methods from the classes and sets to handler. + # Then, builder connects to any signals it needs. + classes = [controller] + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + print(repr(e)) + + settings.builder.connect_signals(handlers) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py index 67457b1..60c3379 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py @@ -40,20 +40,20 @@ class WindowController: return window - def add_view_for_window(self, win_id): + def add_tab_for_window(self, win_id): for window in self._windows: if window.get_id() == win_id: - return window.create_view() + return window.create_tab() - def add_view_for_window_by_name(self, name): + def add_tab_for_window_by_name(self, name): for window in self._windows: if window.get_name() == name: - return window.create_view() + return window.create_tab() - def add_view_for_window_by_nickname(self, nickname): + def add_tab_for_window_by_nickname(self, nickname): for window in self._windows: if window.get_nickname() == nickname: - return window.create_view() + return window.create_tab() def pop_window(self): self._windows.pop() @@ -116,33 +116,33 @@ class WindowController: print(f"Name: {window.get_name()}") print(f"Nickname: {window.get_nickname()}") print(f"Is Hidden: {window.is_hidden()}") - print(f"View Count: {window.get_views_count()}") + print(f"Tab Count: {window.get_tabs_count()}") print("\n-------------------------\n") - def list_files_from_views_of_window(self, win_id): + def list_files_from_tabs_of_window(self, win_id): for window in self._windows: if window.get_id() == win_id: - window.list_files_from_views() + window.list_files_from_tabs() break - def get_views_count(self, win_id): + def get_tabs_count(self, win_id): for window in self._windows: if window.get_id() == win_id: - return window.get_views_count() + return window.get_tabs_count() - def get_views_from_window(self, win_id): + def get_tabs_from_window(self, win_id): for window in self._windows: if window.get_id() == win_id: - return window.get_all_views() + return window.get_all_tabs() - def unload_views_and_windows(self): + def unload_tabs_and_windows(self): for window in self._windows: - window.get_all_views().clear() + window.get_all_tabs().clear() self._windows.clear() @@ -153,9 +153,9 @@ class WindowController: if len(self._windows) > 0: windows = [] for window in self._windows: - views = [] - for view in window.get_all_views(): - views.append(view.get_current_directory()) + tabs = [] + for tab in window.get_all_tabs(): + tabs.append(tab.get_current_directory()) windows.append( [ @@ -165,7 +165,7 @@ class WindowController: "Name": window.get_name(), "Nickname": window.get_nickname(), "isHidden": f"{window.is_hidden()}", - 'views': views + 'tabs': tabs } } ] diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/icon.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/icon.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/desktopiconmixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/desktopiconmixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/videoiconmixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/videoiconmixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/videoiconmixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/videoiconmixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/BaseDirectory.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/BaseDirectory.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Config.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Config.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Config.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Config.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/DesktopEntry.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/DesktopEntry.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Exceptions.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Exceptions.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Exceptions.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Exceptions.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/IconTheme.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/IconTheme.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/IconTheme.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/IconTheme.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/IniFile.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/IniFile.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/IniFile.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/IniFile.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Locale.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Locale.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Locale.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Locale.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Menu.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Menu.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Menu.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Menu.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/MenuEditor.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/MenuEditor.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Mime.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Mime.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/Mime.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/Mime.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/RecentFiles.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/RecentFiles.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/util.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/util.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/icons/mixins/xdg/util.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/util.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/path.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/path.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/path.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/path.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py similarity index 99% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py index 5676659..70acaed 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/view.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py @@ -18,7 +18,7 @@ from .icons.icon import Icon from .path import Path -class View(Settings, FileHandler, Launcher, Icon, Path): +class Tab(Settings, FileHandler, Launcher, Icon, Path): def __init__(self): self.logger = None self._id_length = 10 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/filehandler.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/filehandler.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/filehandler.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/filehandler.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/launcher.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/launcher.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/views/utils/settings.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py index 58d8414..9a09233 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py @@ -6,7 +6,7 @@ from random import randint # Application imports -from .views.view import View +from .tabs.tab import Tab class Window: @@ -16,45 +16,40 @@ class Window: self._name = "" self._nickname = "" self._isHidden = False - self._views = [] + self._tabs = [] self._generate_id() self._set_name() - def create_view(self): - view = View() - self._views.append(view) - return view + def create_tab(self): + tab = Tab() + self._tabs.append(tab) + return tab - def pop_view(self): - self._views.pop() + def pop_tab(self): + self._tabs.pop() - def delete_view_by_id(self, vid): - for view in self._views: - if view.get_id() == vid: - self._views.remove(view) + def delete_tab_by_id(self, vid): + for tab in self._tabs: + if tab.get_id() == vid: + self._tabs.remove(tab) break - def get_view_by_id(self, vid): - for view in self._views: - if view.get_id() == vid: - return view + def get_tab_by_id(self, vid): + for tab in self._tabs: + if tab.get_id() == vid: + return tab - def get_view_by_index(self, index): - return self._views[index] + def get_tab_by_index(self, index): + return self._tabs[index] - def get_views_count(self): - return len(self._views) - - def get_all_views(self): - return self._views - - def list_files_from_views(self): - for view in self._views: - print(view.get_files()) + def get_tabs_count(self): + return len(self._tabs) + def get_all_tabs(self): + return self._tabs def get_id(self): return self._id @@ -68,7 +63,9 @@ class Window: def is_hidden(self): return self._isHidden - + def list_files_from_tabs(self): + for tab in self._tabs: + print(tab.get_files()) def set_nickname(self, nickname): @@ -80,6 +77,7 @@ class Window: def _set_name(self): self._name = "window_" + self.get_id() + def _random_with_N_digits(self, n): range_start = 10**(n-1) range_end = (10**n)-1 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/__init__.py index e69de29..a8e5edd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/__init__.py @@ -0,0 +1,3 @@ +""" + Utils module +""" 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 a02f0ce..df454c7 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 @@ -7,8 +7,8 @@ import gi, cairo gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') -from gi.repository import Gtk as gtk -from gi.repository import Gdk as gdk +from gi.repository import Gtk +from gi.repository import Gdk # Application imports @@ -17,36 +17,39 @@ from .logger import Logger class Settings: def __init__(self): - self.builder = gtk.Builder() + self._SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + self._USER_HOME = path.expanduser('~') + self._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}" + self._PLUGINS_PATH = f"{self._CONFIG_PATH}/plugins" + self._USR_SOLARFM = f"/usr/share/{app_name.lower()}" - self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) - self.USER_HOME = path.expanduser('~') - self.CONFIG_PATH = f"{self.USER_HOME}/.config/{app_name.lower()}" - self.PLUGINS_PATH = f"{self.CONFIG_PATH}/plugins" - self.USR_SOLARFM = f"/usr/share/{app_name.lower()}" + self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css" + self._WINDOWS_GLADE = f"{self._CONFIG_PATH}/Main_Window.glade" + self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" + self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" - self.CSS_FILE = f"{self.CONFIG_PATH}/stylesheet.css" - self.WINDOWS_GLADE = f"{self.CONFIG_PATH}/Main_Window.glade" - self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons" - self.WINDOW_ICON = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png" - self.main_window = None + if not os.path.exists(self._CONFIG_PATH): + os.mkdir(self._CONFIG_PATH) + if not os.path.exists(self._PLUGINS_PATH): + os.mkdir(self._PLUGINS_PATH) - if not os.path.exists(self.CONFIG_PATH): - os.mkdir(self.CONFIG_PATH) - if not os.path.exists(self.PLUGINS_PATH): - os.mkdir(self.PLUGINS_PATH) + if not os.path.exists(self._WINDOWS_GLADE): + self._WINDOWS_GLADE = f"{self._USR_SOLARFM}/Main_Window.glade" + if not os.path.exists(self._CSS_FILE): + self._CSS_FILE = f"{self._USR_SOLARFM}/stylesheet.css" + if not os.path.exists(self._WINDOW_ICON): + self._WINDOW_ICON = f"{self._USR_SOLARFM}/icons/{app_name.lower()}.png" + if not os.path.exists(self._DEFAULT_ICONS): + self._DEFAULT_ICONS = f"{self._USR_SOLARFM}/icons" - if not os.path.exists(self.WINDOWS_GLADE): - self.WINDOWS_GLADE = f"{self.USR_SOLARFM}/Main_Window.glade" - if not os.path.exists(self.CSS_FILE): - self.CSS_FILE = f"{self.USR_SOLARFM}/stylesheet.css" - if not os.path.exists(self.WINDOW_ICON): - self.WINDOW_ICON = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png" - if not os.path.exists(self.DEFAULT_ICONS): - self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons" + self._success_color = "#88cc27" + self._warning_color = "#ffa800" + self._error_color = "#ff0000" - self.logger = Logger(self.CONFIG_PATH).get_logger() - self.builder.add_from_file(self.WINDOWS_GLADE) + self.main_window = None + self.logger = Logger(self._CONFIG_PATH).get_logger() + self.builder = Gtk.Builder() + self.builder.add_from_file(self._WINDOWS_GLADE) @@ -56,7 +59,7 @@ class Settings: self._set_window_data() def _set_window_data(self): - self.main_window.set_icon_from_file(self.WINDOW_ICON) + self.main_window.set_icon_from_file(self._WINDOW_ICON) screen = self.main_window.get_screen() visual = screen.get_rgba_visual() @@ -66,11 +69,11 @@ class Settings: self.main_window.connect("draw", self._area_draw) # bind css file - cssProvider = gtk.CssProvider() - cssProvider.load_from_path(self.CSS_FILE) - screen = gdk.Screen.get_default() - styleContext = gtk.StyleContext() - styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER) + cssProvider = Gtk.CssProvider() + cssProvider.load_from_path(self._CSS_FILE) + screen = Gdk.Screen.get_default() + styleContext = Gtk.StyleContext() + styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) def _area_draw(self, widget, cr): cr.set_source_rgba(0, 0, 0, 0.54) @@ -92,4 +95,8 @@ class Settings: def get_builder(self): return self.builder def get_logger(self): return self.logger 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 + + def get_success_color(self): return self._success_color + def get_warning_color(self): return self._warning_color + def get_error_color(self): return self._error_color diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 506921e..c0e535c 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -616,7 +616,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-save-as - + True False gtk-new @@ -666,7 +666,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True Create File/Folder... - createImage + create_img True @@ -1417,11 +1417,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe vertical top - + True False - + True False @@ -1834,7 +1834,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - + gtk-refresh refresh_view True @@ -2052,7 +2052,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - + True False 10 @@ -2106,11 +2106,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - + 320 False True - top_main_menubar + app_menu_bar bottom @@ -2141,7 +2141,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe in False - + message_view True True @@ -2179,7 +2179,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True False - + True False vertical -- 2.27.0 From 7e5d603eb9aac5b3be80a6e5e530ef8f5acaf0c5 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 26 Feb 2022 02:30:14 -0600 Subject: [PATCH 19/41] Slightly better key mapping --- .../context/signals/keyboard_signals_mixin.py | 101 +++++++++--------- 1 file changed, 51 insertions(+), 50 deletions(-) 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 8d30d77..16ed902 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 @@ -51,6 +51,57 @@ class KeyboardSignalsMixin: 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() + + 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 \ and not self.shift_down and not self.alt_down: @@ -61,53 +112,3 @@ class KeyboardSignalsMixin: self.unset_keys_and_data() self.popup_search_files(wid, keyname) return - - if (self.ctrl_down and 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 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 and keyname == "q": - self.tear_down() - if (self.ctrl_down and keyname == "slash") or keyname == "home": - self.builder.get_object("go_home").released() - if (self.ctrl_down and keyname == "r") or keyname == "f5": - self.builder.get_object("refresh_tab").released() - if (self.ctrl_down and keyname == "up") or (self.ctrl_down and keyname == "u"): - self.builder.get_object("go_up").released() - if self.ctrl_down and keyname == "l": - self.unset_keys_and_data() - self.builder.get_object("path_entry").grab_focus() - if self.ctrl_down and keyname == "t": - self.builder.get_object("create_tab").released() - if self.ctrl_down and keyname == "o": - self.unset_keys_and_data() - self.open_files() - if self.ctrl_down and keyname == "w": - self.keyboard_close_tab() - if self.ctrl_down and keyname == "h": - self.show_hide_hidden_files() - if (self.ctrl_down and keyname == "e"): - self.unset_keys_and_data() - self.rename_files() - if self.ctrl_down and keyname == "c": - self.copy_files() - self.to_cut_files.clear() - if self.ctrl_down and keyname == "x": - self.to_copy_files.clear() - self.cut_files() - if self.ctrl_down and keyname == "v": - self.paste_files() - if self.ctrl_down and 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() -- 2.27.0 From 8f1c1848fde77b727f37291d2f304a564a293712 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 26 Feb 2022 02:49:08 -0600 Subject: [PATCH 20/41] PEP8 small cleanup --- .../SolarFM/solarfm/context/controller.py | 2 +- .../mixins/ui/widget_file_action_mixin.py | 4 ++-- .../solarfm/context/mixins/ui/window_mixin.py | 2 +- .../SolarFM/solarfm/plugins/plugins.py | 23 ++++++++----------- 4 files changed, 13 insertions(+), 18 deletions(-) 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 b801901..2dbf22d 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 @@ -75,7 +75,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def handle_gui_event_and_set_message(self, type, target, parameters): method = getattr(self.__class__, f"{target}") data = method(*(self, *parameters)) - self.plugins.set_message_on_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() 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 04f7862..1a84305 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 @@ -210,9 +210,9 @@ class WidgetFileActionMixin: tab = self.get_fm_window(wid).get_tab_by_id(tid) target = f"{tab.get_current_directory()}" - if len(self.to_copy_files) > 0: + if self.to_copy_files: self.handle_files(self.to_copy_files, "copy", target) - elif len(self.to_cut_files) > 0: + elif self.to_cut_files: self.handle_files(self.to_cut_files, "move", target) def delete_files(self): 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 9bf1ea4..b1e3878 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 @@ -95,7 +95,7 @@ class WindowMixin(TabMixin): # If something selected 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 len(selected_files) > 0: + if selected_files: uris = self.format_to_uris(store, _wid, _tid, selected_files, True) combined_size = 0 for uri in uris: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index b3a568c..49230f5 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -54,15 +54,15 @@ class Plugins: os.chdir(path) sys.path.insert(0, path) - spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) - app = importlib.util.module_from_spec(spec) + spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) + app = importlib.util.module_from_spec(spec) spec.loader.exec_module(app) - plugin_reference = app.Plugin(self._builder, event_system) - plugin = Plugin() - plugin.name = plugin_reference.get_plugin_name() - plugin.module = path - plugin.reference = plugin_reference + plugin_reference = app.Plugin(self._builder, event_system) + plugin = Plugin() + plugin.name = plugin_reference.get_plugin_name() + plugin.module = path + plugin.reference = plugin_reference self._plugin_collection.append(plugin) except Exception as e: @@ -73,14 +73,9 @@ class Plugins: def reload_plugins(self, file=None): - print(f"Reloading plugins...") - # if self._plugin_collection: - # to_unload = [] - # for dir in self._plugin_collection: - # if not os.path.isdir(os.path.join(self._plugins_path, dir)): - # to_unload.append(dir) + print(f"Reloading plugins... stub.") - def set_message_on_plugin(self, type, data): + def send_message_to_plugin(self, type, data): print("Trying to send message to plugin...") for plugin in self._plugin_collection: if type in plugin.name: -- 2.27.0 From 32f061ff761dd263ce4a8eb8c5e809a40e8bdd75 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 2 Mar 2022 19:30:35 -0600 Subject: [PATCH 21/41] Updated readme, fixed save session as --- README.md | 3 +++ .../solarfm-0.0.1/SolarFM/solarfm/context/controller.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3553c5a..c61ddf6 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,11 @@ sudo apt-get install python3.8 wget python3-setproctitle python3-gi ffmpegthumbn # TODO
  • Add simpleish plugin system to run bash/python scripts.
  • +
  • Add simpleish preview plugin for various file types.
  • +
  • Add simpleish file chmod, chown, stats, etc plugin for file management.
  • Add simpleish search plugin to do recursive search and show.
  • Add simpleish bulk-renamer.
  • +
  • Add a basic favorites manager plugin.
# Images 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 2dbf22d..fc7fa7c 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 @@ -102,7 +102,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi save_load_dialog.set_current_name("session.json") response = save_load_dialog.run() if response == Gtk.ResponseType.OK: - if action == "save_session": + if action == "save_session_as": path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" self.fm_controller.save_state(path) elif action == "load_session": -- 2.27.0 From 8e242f54755afbca602d29fa8d1f14bf06082664 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 3 Mar 2022 01:24:59 -0600 Subject: [PATCH 22/41] Converted to use unix socket --- .../SolarFM/solarfm/ipc_server.py | 30 +++++++++++++++---- .../solarfm-0.0.1/SolarFM/solarfm/main.py | 2 +- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py index 9223edd..0972d86 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py @@ -1,5 +1,5 @@ # Python imports -import threading, time +import os, threading, time from multiprocessing.connection import Listener, Client # Lib imports @@ -17,17 +17,30 @@ def threaded(fn): class IPCServer: """ Create a listener so that other SolarFM instances send requests back to existing instance. """ - def __init__(self): + def __init__(self, conn_type="socket"): self.is_ipc_alive = False + self._conn_type = conn_type self.ipc_authkey = b'solarfm-ipc' - self.ipc_address = '127.0.0.1' - self.ipc_port = 4848 self.ipc_timeout = 15.0 + if conn_type == "socket": + self.ipc_address = '/tmp/solarfm-ipc.sock' + else: + self.ipc_address = '127.0.0.1' + self.ipc_port = 4848 + @threaded def create_ipc_server(self): - listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + if self._conn_type == "socket": + if os.path.exists(self.ipc_address): + return + + listener = Listener(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) + else: + listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + + self.is_ipc_alive = True while True: conn = listener.accept() @@ -65,7 +78,12 @@ class IPCServer: def send_ipc_message(self, message="Empty Data..."): try: - conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + if self._conn_type == "socket": + conn = Client(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) + else: + conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + + conn.send(message) conn.send('close connection') except Exception as e: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py index 4a26b4d..ecd2d03 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py @@ -17,7 +17,7 @@ class Main(EventSystem): def __init__(self, args, unknownargs): if not trace_debug: event_system.create_ipc_server() - time.sleep(0.2) # Make sure everything's up before proceeding. + time.sleep(0.1) if not event_system.is_ipc_alive: if unknownargs: -- 2.27.0 From 8eccdfce7c2246985c1da199fbf9b5939fd3d9f3 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 3 Mar 2022 16:45:37 -0600 Subject: [PATCH 23/41] Fixing missing empty init files --- .../opt/SolarFM/__builtins__.py | 14 +- .../solarfm-0-0-1-x64/opt/SolarFM/__init__.py | 59 +------ .../solarfm-0-0-1-x64/opt/SolarFM/__main__.py | 4 +- .../solarfm-0-0-1-x64/opt/SolarFM/app.py} | 2 +- .../opt/SolarFM/context/controller.py | 35 ++--- .../opt/SolarFM/context/controller_data.py | 60 +++---- .../opt/SolarFM/context/mixins/__init__.py | 3 + .../context/mixins/exception_hook_mixin.py | 4 +- .../SolarFM/context/mixins/show_hide_mixin.py | 12 +- .../opt/SolarFM/context/mixins/ui/__init__.py | 3 + .../SolarFM/context/mixins/ui/pane_mixin.py | 12 +- .../SolarFM/context/mixins/ui/tab_mixin.py | 148 +++++++++--------- .../mixins/ui/widget_file_action_mixin.py | 133 ++++++++-------- .../SolarFM/context/mixins/ui/widget_mixin.py | 91 ++++++----- .../SolarFM/context/mixins/ui/window_mixin.py | 148 +++++++++--------- .../opt/SolarFM/context/signals/__init__.py | 3 + .../context/signals/ipc_signals_mixin.py | 2 +- .../context/signals/keyboard_signals_mixin.py | 140 ++++++++--------- .../ipc_server_mixin.py => ipc_server.py} | 33 +++- .../opt/SolarFM/plugins/plugins.py | 38 ++--- .../opt/SolarFM/shellfm/__init__.py | 3 + .../opt/SolarFM/shellfm/windows/__init__.py | 3 + .../opt/SolarFM/shellfm/windows/controller.py | 38 ++--- .../SolarFM/shellfm/windows/tabs/__init__.py | 3 + .../shellfm/windows/tabs/icons/__init__.py | 3 + .../windows/{views => tabs}/icons/icon.py | 0 .../windows/tabs/icons/mixins/__init__.py | 3 + .../icons/mixins/desktopiconmixin.py | 0 .../icons/mixins/videoiconmixin.py | 0 .../icons/mixins/xdg/BaseDirectory.py | 0 .../icons/mixins/xdg/Config.py | 0 .../icons/mixins/xdg/DesktopEntry.py | 0 .../icons/mixins/xdg/Exceptions.py | 0 .../icons/mixins/xdg/IconTheme.py | 0 .../icons/mixins/xdg/IniFile.py | 0 .../icons/mixins/xdg/Locale.py | 0 .../{views => tabs}/icons/mixins/xdg/Menu.py | 0 .../icons/mixins/xdg/MenuEditor.py | 0 .../{views => tabs}/icons/mixins/xdg/Mime.py | 0 .../icons/mixins/xdg/RecentFiles.py | 0 .../icons/mixins/xdg/__init__.py | 0 .../{views => tabs}/icons/mixins/xdg/util.py | 0 .../shellfm/windows/{views => tabs}/path.py | 0 .../windows/{views/view.py => tabs/tab.py} | 2 +- .../shellfm/windows/tabs/utils/__init__.py | 3 + .../{views => tabs}/utils/filehandler.py | 0 .../windows/{views => tabs}/utils/launcher.py | 0 .../windows/{views => tabs}/utils/settings.py | 0 .../SolarFM/shellfm/windows/views/__init__.py | 0 .../shellfm/windows/views/icons/__init__.py | 0 .../windows/views/icons/mixins/__init__.py | 0 .../shellfm/windows/views/utils/__init__.py | 0 .../opt/SolarFM/shellfm/windows/window.py | 54 +++---- .../opt/SolarFM/trasher/__init__.py | 3 + .../opt/SolarFM/trasher/trash.py | 0 .../opt/SolarFM/trasher/xdgtrash.py | 0 .../opt/SolarFM/utils/__init__.py | 3 + .../opt/SolarFM/utils/settings.py | 75 +++++---- .../solarfm-0.0.1/SolarFM/solarfm/__init__.py | 3 + .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 4 +- .../solarfm-0.0.1/SolarFM/solarfm/app.py | 55 +++++++ .../solarfm/context/mixins/__init__.py | 3 + .../solarfm/context/mixins/ui/__init__.py | 3 + .../solarfm/context/signals/__init__.py | 3 + .../SolarFM/solarfm/shellfm/__init__.py | 3 + .../solarfm/shellfm/windows/__init__.py | 3 + .../solarfm/shellfm/windows/tabs/__init__.py | 3 + .../shellfm/windows/tabs/icons/__init__.py | 3 + .../windows/tabs/icons/mixins/__init__.py | 3 + .../shellfm/windows/tabs/utils/__init__.py | 3 + .../SolarFM/solarfm/trasher/__init__.py | 3 + 71 files changed, 637 insertions(+), 592 deletions(-) rename src/{versions/solarfm-0.0.1/SolarFM/solarfm/main.py => debs/solarfm-0-0-1-x64/opt/SolarFM/app.py} (98%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/{context/ipc_server_mixin.py => ipc_server.py} (62%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/__init__.py create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/__init__.py rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/icon.py (100%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/__init__.py rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/desktopiconmixin.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/videoiconmixin.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/BaseDirectory.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/Config.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/DesktopEntry.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/Exceptions.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/IconTheme.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/IniFile.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/Locale.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/Menu.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/MenuEditor.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/Mime.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/RecentFiles.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/__init__.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/icons/mixins/xdg/util.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/path.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views/view.py => tabs/tab.py} (99%) create mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/__init__.py rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/utils/filehandler.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/utils/launcher.py (100%) rename src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/{views => tabs}/utils/settings.py (100%) delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/__init__.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/__init__.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/__init__.py delete mode 100644 src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/__init__.py mode change 100644 => 100755 src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/__init__.py mode change 100644 => 100755 src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py mode change 100644 => 100755 src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/xdgtrash.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__builtins__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__builtins__.py index 137213e..02c8d57 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__builtins__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__builtins__.py @@ -4,15 +4,17 @@ import builtins # Lib imports # Application imports -from context.ipc_server_mixin import IPCServerMixin +from ipc_server import IPCServer -class Builtins(IPCServerMixin): +class EventSystem(IPCServer): """ Inheret IPCServerMixin. Create an pub/sub systems. """ def __init__(self): + super(EventSystem, self).__init__() + # NOTE: The format used is list of [type, target, (data,)] Where: # type is useful context for control flow, # target is the method to call, @@ -20,11 +22,7 @@ class Builtins(IPCServerMixin): # Where data may be any kind of data self._gui_events = [] self._module_events = [] - self.is_ipc_alive = False - self.ipc_authkey = b'solarfm-ipc' - self.ipc_address = '127.0.0.1' - self.ipc_port = 4848 - self.ipc_timeout = 15.0 + # Makeshift fake "events" type system FIFO @@ -70,7 +68,7 @@ class Builtins(IPCServerMixin): # NOTE: Just reminding myself we can add to builtins two different ways... # __builtins__.update({"event_system": Builtins()}) builtins.app_name = "SolarFM" -builtins.event_system = Builtins() +builtins.event_system = EventSystem() builtins.event_sleep_time = 0.2 builtins.debug = False builtins.trace_debug = False diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py index 40d42d5..5416f23 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__init__.py @@ -1,56 +1,3 @@ -# Python imports -import os, inspect, time - -# Lib imports - -# Application imports -from utils.settings import Settings -from context.controller import Controller -from __builtins__ import Builtins - - - - -class Main(Builtins): - """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ - - def __init__(self, args, unknownargs): - if not debug: - event_system.create_ipc_server() - - time.sleep(0.2) - if not trace_debug and not debug: - if not event_system.is_ipc_alive: - if unknownargs: - for arg in unknownargs: - if os.path.isdir(arg): - message = f"FILE|{arg}" - event_system.send_ipc_message(message) - - if args.new_tab and os.path.isdir(args.new_tab): - message = f"FILE|{args.new_tab}" - event_system.send_ipc_message(message) - - raise Exception("IPC Server Exists: Will send path(s) to it and close...") - - - settings = Settings() - settings.create_window() - - controller = Controller(args, unknownargs, settings) - if not controller: - raise Exception("Controller exited and doesn't exist...") - - # Gets the methods from the classes and sets to handler. - # Then, builder connects to any signals it needs. - classes = [controller] - handlers = {} - for c in classes: - methods = None - try: - methods = inspect.getmembers(c, predicate=inspect.ismethod) - handlers.update(methods) - except Exception as e: - print(repr(e)) - - settings.builder.connect_signals(handlers) +""" +Base module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py index ba575d0..22b79dd 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/__main__.py @@ -15,7 +15,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from __init__ import Main +from app import Application if __name__ == "__main__": @@ -35,7 +35,7 @@ if __name__ == "__main__": # Read arguments (If any...) args, unknownargs = parser.parse_known_args() - Main(args, unknownargs) + Application(args, unknownargs) Gtk.main() except Exception as e: traceback.print_exc() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/app.py similarity index 98% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/app.py index ecd2d03..3e092c9 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/main.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/app.py @@ -11,7 +11,7 @@ from __builtins__ import EventSystem -class Main(EventSystem): +class Application(EventSystem): """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ def __init__(self, args, unknownargs): diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py index 3c620f4..fc7fa7c 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py @@ -50,7 +50,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def tear_down(self, widget=None, eve=None): event_system.send_ipc_message("close server") - self.window_controller.save_state() + self.fm_controller.save_state() time.sleep(event_sleep_time) Gtk.main_quit() @@ -75,21 +75,21 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def handle_gui_event_and_set_message(self, type, target, parameters): method = getattr(self.__class__, f"{target}") data = method(*(self, *parameters)) - self.plugins.set_message_on_plugin(type, data) + self.plugins.send_message_to_plugin(type, data) def open_terminal(self, widget=None, eve=None): - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - dir = view.get_current_directory() - view.execute(f"{view.terminal_app}", dir) + 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.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) save_load_dialog = self.builder.get_object("save_load_dialog") if action == "save_session": - self.window_controller.save_state() + self.fm_controller.save_state() return elif action == "save_session_as": save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) @@ -98,16 +98,16 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi else: raise Exception(f"Unknown action given: {action}") - save_load_dialog.set_current_folder(view.get_current_directory()) + save_load_dialog.set_current_folder(tab.get_current_directory()) save_load_dialog.set_current_name("session.json") response = save_load_dialog.run() if response == Gtk.ResponseType.OK: - if action == "save_session": + if action == "save_session_as": path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" - self.window_controller.save_state(path) + self.fm_controller.save_state(path) elif action == "load_session": path = f"{save_load_dialog.get_file().get_path()}" - session_json = self.window_controller.load_state(path) + session_json = self.fm_controller.load_state(path) self.load_session(session_json) if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): pass @@ -118,13 +118,13 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi if debug: print(f"Session Data: {session_json}") - self.ctrlDown = False - self.shiftDown = False - self.altDown = False + self.ctrl_down = False + self.shift_down = False + self.alt_down = False for notebook in self.notebooks: self.clear_children(notebook) - self.window_controller.unload_views_and_windows() + self.fm_controller.unload_tabs_and_windows() self.generate_windows(session_json) gc.collect() @@ -165,7 +165,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.restore_trash_files() if action == "empty_trash": self.empty_trash() - if action == "create": self.show_new_file_menu() if action in ["save_session", "save_session_as", "load_session"]: diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller_data.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller_data.py index 6d0a849..28a09e8 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller_data.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller_data.py @@ -17,9 +17,9 @@ class Controller_Data: def setup_controller_data(self, _settings): self.trashman = XDGTrash() - self.window_controller = WindowController() + self.fm_controller = WindowController() self.plugins = Plugins(_settings) - self.state = self.window_controller.load_state() + self.state = self.fm_controller.load_state() self.trashman.regenerate() self.settings = _settings @@ -31,8 +31,8 @@ class Controller_Data: self.window2 = self.builder.get_object("window_2") self.window3 = self.builder.get_object("window_3") self.window4 = self.builder.get_object("window_4") - self.message_widget = self.builder.get_object("message_widget") - self.message_view = self.builder.get_object("message_view") + self.message_popup_widget = self.builder.get_object("message_popup_widget") + self.message_text_view = self.builder.get_object("message_text_view") self.message_buffer = self.builder.get_object("message_buffer") self.arc_command_buffer = self.builder.get_object("arc_command_buffer") @@ -78,32 +78,32 @@ class Controller_Data: 'xz -cz %N > %O' ] - self.notebooks = [self.window1, self.window2, self.window3, self.window4] - self.selected_files = [] - self.to_copy_files = [] - self.to_cut_files = [] - self.soft_update_lock = {} + self.notebooks = [self.window1, self.window2, self.window3, self.window4] + self.selected_files = [] + self.to_copy_files = [] + self.to_cut_files = [] + self.soft_update_lock = {} - self.single_click_open = False - self.is_pane1_hidden = False - self.is_pane2_hidden = False - self.is_pane3_hidden = False - self.is_pane4_hidden = False + self.single_click_open = False + self.is_pane1_hidden = False + self.is_pane2_hidden = False + self.is_pane3_hidden = False + self.is_pane4_hidden = False self.override_drop_dest = None self.is_searching = False - self.search_iconview = None - self.search_view = None + self.search_icon_grid = None + self.search_tab = None - self.skip_edit = False - self.cancel_edit = False - self.ctrlDown = False - self.shiftDown = False - self.altDown = False + self.skip_edit = False + self.cancel_edit = False + self.ctrl_down = False + self.shift_down = False + self.alt_down = False - self.success = "#88cc27" - self.warning = "#ffa800" - self.error = "#ff0000" + self.success_color = self.settings.get_success_color() + self.warning_color = self.settings.get_warning_color() + self.error_color = self.settings.get_error_color() sys.excepthook = self.custom_except_hook self.window.connect("delete-event", self.tear_down) @@ -117,13 +117,13 @@ class Controller_Data: a (obj): self Returns: - wid, tid, view, iconview, store + wid, tid, tab, icon_grid, store ''' - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - iconview = self.builder.get_object(f"{wid}|{tid}|iconview") - store = iconview.get_model() - return wid, tid, view, iconview, store + 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 def clear_console(self): diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/__init__.py index e69de29..ded9abc 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/__init__.py @@ -0,0 +1,3 @@ +""" +Mixins module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/exception_hook_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/exception_hook_mixin.py index dbaa065..08d29cb 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/exception_hook_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/exception_hook_mixin.py @@ -27,14 +27,14 @@ class ExceptionHookMixin: def display_message(self, type, text, seconds=None): self.message_buffer.insert_at_cursor(text) - self.message_widget.popup() + self.message_popup_widget.popup() if seconds: self.hide_message_timeout(seconds) @threaded def hide_message_timeout(self, seconds=3): time.sleep(seconds) - GLib.idle_add(self.message_widget.popdown) + GLib.idle_add(self.message_popup_widget.popdown) def save_debug_alerts(self, widget=None, eve=None): start_itr, end_itr = self.message_buffer.get_bounds() diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/show_hide_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/show_hide_mixin.py index d73d098..2e7a4fd 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/show_hide_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/show_hide_mixin.py @@ -11,7 +11,7 @@ from gi.repository import Gtk, Gdk class ShowHideMixin: def show_messages_popup(self, type, text, seconds=None): - self.message_widget.popup() + self.message_popup_widget.popup() def stop_file_searching(self, widget=None, eve=None): self.is_searching = False @@ -48,7 +48,7 @@ class ShowHideMixin: def show_about_page(self, widget=None, eve=None): about_page = self.builder.get_object("about_page") response = about_page.run() - if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): + if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: self.hide_about_page() def hide_about_page(self, widget=None, eve=None): @@ -56,11 +56,11 @@ class ShowHideMixin: def show_archiver_dialogue(self, widget=None, eve=None): - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) archiver_dialogue = self.builder.get_object("archiver_dialogue") archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) - archiver_dialogue.set_current_folder(view.get_current_directory()) + archiver_dialogue.set_current_folder(tab.get_current_directory()) archiver_dialogue.set_current_name("arc.7z") response = archiver_dialogue.run() @@ -137,7 +137,7 @@ class ShowHideMixin: def hide_edit_file_menu_enter_key(self, widget=None, eve=None): keyname = Gdk.keyval_name(eve.keyval).lower() - if "return" in keyname or "enter" in keyname: + if keyname in ["return", "enter"]: self.builder.get_object("edit_file_menu").hide() def hide_edit_file_menu_skip(self, widget=None, eve=None): diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/__init__.py index e69de29..991f9a2 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/__init__.py @@ -0,0 +1,3 @@ +""" +UI module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py index accd2c4..3d2c888 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py @@ -39,8 +39,6 @@ class PaneMixin: def toggle_notebook_pane(self, widget, eve=None): name = widget.get_name() pane_index = int(name[-1]) - pane = None - master_pane = self.builder.get_object("pane_master") pane = self.builder.get_object("pane_top") if pane_index in [1, 2] else self.builder.get_object("pane_bottom") @@ -50,16 +48,12 @@ class PaneMixin: self._save_state(state, pane_index) return - child = None - if pane_index in [1, 3]: - child = pane.get_child1() - elif pane_index in [2, 4]: - child = pane.get_child2() + child = pane.get_child1() if pane_index in [1, 3] else pane.get_child2() self.toggle_pane(child) self._save_state(state, pane_index) def _save_state(self, state, pane_index): - window = self.window_controller.get_window_by_index(pane_index - 1) + window = self.fm_controller.get_window_by_index(pane_index - 1) window.set_is_hidden(state) - self.window_controller.save_state() + self.fm_controller.save_state() diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py index bfa78cc..9ce3953 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py @@ -18,128 +18,128 @@ class TabMixin(WidgetMixin): def create_tab(self, wid, path=None): notebook = self.builder.get_object(f"window_{wid}") path_entry = self.builder.get_object(f"path_entry") - view = self.window_controller.add_view_for_window_by_nickname(f"window_{wid}") - view.logger = self.logger + tab = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}") + tab.logger = self.logger - view.set_wid(wid) - if path: view.set_path(path) + tab.set_wid(wid) + if path: tab.set_path(path) - tab = self.create_tab_widget(view) - scroll, store = self.create_grid_iconview_widget(view, wid) - # scroll, store = self.create_grid_treeview_widget(view, wid) - index = notebook.append_page(scroll, tab) + tab_widget = self.create_tab_widget(tab) + scroll, store = self.create_icon_grid_widget(tab, wid) + # TODO: Fix global logic to make the below work too + # scroll, store = self.create_icon_tree_widget(tab, wid) + index = notebook.append_page(scroll, tab_widget) - self.window_controller.set__wid_and_tid(wid, view.get_id()) - path_entry.set_text(view.get_current_directory()) + self.fm_controller.set__wid_and_tid(wid, tab.get_id()) + path_entry.set_text(tab.get_current_directory()) notebook.show_all() notebook.set_current_page(index) ctx = notebook.get_style_context() ctx.add_class("notebook-unselected-focus") notebook.set_tab_reorderable(scroll, True) - self.load_store(view, store) + self.load_store(tab, store) self.set_window_title() - self.set_file_watcher(view) + self.set_file_watcher(tab) def close_tab(self, button, eve=None): notebook = button.get_parent().get_parent() - tid = self.get_id_from_tab_box(button.get_parent()) wid = int(notebook.get_name()[-1]) + tid = self.get_id_from_tab_box(button.get_parent()) scroll = self.builder.get_object(f"{wid}|{tid}") page = notebook.page_num(scroll) - view = self.get_fm_window(wid).get_view_by_id(tid) - watcher = view.get_dir_watcher() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + watcher = tab.get_dir_watcher() watcher.cancel() - self.get_fm_window(wid).delete_view_by_id(tid) + self.get_fm_window(wid).delete_tab_by_id(tid) notebook.remove_page(page) - self.window_controller.save_state() + self.fm_controller.save_state() self.set_window_title() def on_tab_reorder(self, child, page_num, new_index): wid, tid = page_num.get_name().split("|") window = self.get_fm_window(wid) - view = None + tab = None - for i, view in enumerate(window.get_all_views()): - if view.get_id() == tid: - _view = window.get_view_by_id(tid) - watcher = _view.get_dir_watcher() + for i, tab in enumerate(window.get_all_tabs()): + if tab.get_id() == tid: + _tab = window.get_tab_by_id(tid) + watcher = _tab.get_dir_watcher() watcher.cancel() - window.get_all_views().insert(new_index, window.get_all_views().pop(i)) + window.get_all_tabs().insert(new_index, window.get_all_tabs().pop(i)) - view = window.get_view_by_id(tid) - self.set_file_watcher(view) - self.window_controller.save_state() + tab = window.get_tab_by_id(tid) + self.set_file_watcher(tab) + self.fm_controller.save_state() def on_tab_switch_update(self, notebook, content=None, index=None): self.selected_files.clear() wid, tid = content.get_children()[0].get_name().split("|") - self.window_controller.set__wid_and_tid(wid, tid) + self.fm_controller.set__wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() def get_id_from_tab_box(self, tab_box): - tid = tab_box.get_children()[2] - return tid.get_text() + return tab_box.get_children()[2].get_text() - def get_tab_label(self, notebook, iconview): - return notebook.get_tab_label(iconview.get_parent()).get_children()[0] + def get_tab_label(self, notebook, icon_grid): + return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0] - def get_tab_close(self, notebook, iconview): - return notebook.get_tab_label(iconview.get_parent()).get_children()[1] + def get_tab_close(self, notebook, icon_grid): + return notebook.get_tab_label(icon_grid.get_parent()).get_children()[1] - def get_tab_iconview_from_notebook(self, notebook): + def get_tab_icon_grid_from_notebook(self, notebook): return notebook.get_children()[1].get_children()[0] def refresh_tab(data=None): - wid, tid, view, iconview, store = self.get_current_state() - view.load_directory() - self.load_store(view, store) + wid, tid, tab, icon_grid, store = self.get_current_state() + tab.load_directory() + self.load_store(tab, store) - def update_view(self, tab_label, view, store, wid, tid): - self.load_store(view, store) + def update_tab(self, tab_label, tab, store, wid, tid): + self.load_store(tab, store) self.set_path_text(wid, tid) - char_width = len(view.get_end_of_path()) + char_width = len(tab.get_end_of_path()) tab_label.set_width_chars(char_width) - tab_label.set_label(view.get_end_of_path()) + tab_label.set_label(tab.get_end_of_path()) self.set_window_title() - self.set_file_watcher(view) - self.window_controller.save_state() + self.set_file_watcher(tab) + self.fm_controller.save_state() def do_action_from_bar_controls(self, widget, eve=None): action = widget.get_name() - wid, tid = self.window_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") - view = self.get_fm_window(wid).get_view_by_id(tid) + tab = self.get_fm_window(wid).get_tab_by_id(tid) - if action == "go_up": - view.pop_from_path() - if action == "go_home": - view.set_to_home() - if action == "refresh_view": - view.load_directory() if action == "create_tab": - dir = view.get_current_directory() + dir = tab.get_current_directory() self.create_tab(wid, dir) - self.window_controller.save_state() + self.fm_controller.save_state() return + if action == "go_up": + tab.pop_from_path() + if action == "go_home": + tab.set_to_home() + if action == "refresh_tab": + tab.load_directory() if action == "path_entry": focused_obj = self.window.get_focus() - dir = f"{view.get_current_directory()}/" + dir = f"{tab.get_current_directory()}/" path = widget.get_text() if isinstance(focused_obj, Gtk.Entry): - button_box = self.path_menu.get_children()[0].get_children()[0].get_children()[0] - query = widget.get_text().replace(dir, "") - files = view.get_files() + view.get_hidden() + path_menu_buttons = self.builder.get_object("path_menu_buttons") + query = widget.get_text().replace(dir, "") + files = tab.get_files() + tab.get_hidden() - self.clear_children(button_box) + self.clear_children(path_menu_buttons) show_path_menu = False for file, hash in files: if os.path.isdir(f"{dir}{file}"): @@ -147,7 +147,7 @@ class TabMixin(WidgetMixin): button = Gtk.Button(label=file) button.show() button.connect("clicked", self.set_path_entry) - button_box.add(button) + path_menu_buttons.add(button) show_path_menu = True if not show_path_menu: @@ -160,11 +160,10 @@ class TabMixin(WidgetMixin): if path.endswith(".") or path == dir: return - traversed = view.set_path(path) - if not traversed: + if not tab.set_path(path): return - self.update_view(tab_label, view, store, wid, tid) + self.update_tab(tab_label, tab, store, wid, tid) try: widget.grab_focus_without_selecting() @@ -173,8 +172,8 @@ class TabMixin(WidgetMixin): pass def set_path_entry(self, button=None, eve=None): - wid, tid, view, iconview, store = self.get_current_state() - path = f"{view.get_current_directory()}/{button.get_label()}" + wid, tid, tab, icon_grid, store = self.get_current_state() + path = f"{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() @@ -182,23 +181,22 @@ class TabMixin(WidgetMixin): self.path_menu.popdown() def keyboard_close_tab(self): - wid, tid = self.window_controller.get_active_wid_and_tid() + 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) - view = self.get_fm_window(wid).get_view_by_id(tid) - watcher = view.get_dir_watcher() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + watcher = tab.get_dir_watcher() watcher.cancel() - self.get_fm_window(wid).delete_view_by_id(tid) + self.get_fm_window(wid).delete_tab_by_id(tid) notebook.remove_page(page) - self.window_controller.save_state() + self.fm_controller.save_state() self.set_window_title() - # File control events def show_hide_hidden_files(self): - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - view.set_hiding_hidden(not view.is_hiding_hidden()) - view.load_directory() - self.builder.get_object("refresh_view").released() + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + tab.set_hiding_hidden(not tab.is_hiding_hidden()) + tab.load_directory() + self.builder.get_object("refresh_tab").released() diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_file_action_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_file_action_mixin.py index eda4c7f..1a84305 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_file_action_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_file_action_mixin.py @@ -40,29 +40,24 @@ class WidgetFileActionMixin: return size - def set_file_watcher(self, view): - if view.get_dir_watcher(): - watcher = view.get_dir_watcher() + def set_file_watcher(self, tab): + if tab.get_dir_watcher(): + watcher = tab.get_dir_watcher() watcher.cancel() if debug: print(f"Watcher Is Cancelled: {watcher.is_cancelled()}") - cur_dir = view.get_current_directory() - # Temp updating too much with current events we are checking for. - # Seems to cause invalid iter errors in WidbetMixin > update_store - if cur_dir == "/tmp": - watcher = None - return + cur_dir = tab.get_current_directory() dir_watcher = Gio.File.new_for_path(cur_dir) \ .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) - wid = view.get_wid() - tid = view.get_id() + wid = tab.get_wid() + tid = tab.get_id() dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",)) - view.set_dir_watcher(dir_watcher) + tab.set_dir_watcher(dir_watcher) - # NOTE: Too lazy to impliment a proper update handler and so just regen store and update view. + # NOTE: Too lazy to impliment a proper update handler and so just regen store and update tab. # Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None): if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, @@ -72,46 +67,46 @@ class WidgetFileActionMixin: print(eve_type) if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: - self.update_on_end_soft_lock(data[0]) + self.update_on_soft_lock_end(data[0]) elif data[0] in self.soft_update_lock.keys(): self.soft_update_lock[data[0]]["last_update_time"] = time.time() else: self.soft_lock_countdown(data[0]) @threaded - def soft_lock_countdown(self, tab): - self.soft_update_lock[tab] = { "last_update_time": time.time()} + def soft_lock_countdown(self, tab_widget): + self.soft_update_lock[tab_widget] = { "last_update_time": time.time()} lock = True while lock: time.sleep(0.6) - last_update_time = self.soft_update_lock[tab]["last_update_time"] + last_update_time = self.soft_update_lock[tab_widget]["last_update_time"] current_time = time.time() if (current_time - last_update_time) > 0.6: lock = False - self.soft_update_lock.pop(tab, None) - GLib.idle_add(self.update_on_end_soft_lock, *(tab,)) + self.soft_update_lock.pop(tab_widget, None) + GLib.idle_add(self.update_on_soft_lock_end, *(tab_widget,)) - def update_on_end_soft_lock(self, tab): - wid, tid = tab.split("|") + def update_on_soft_lock_end(self, tab_widget): + wid, tid = tab_widget.split("|") notebook = self.builder.get_object(f"window_{wid}") - view = self.get_fm_window(wid).get_view_by_id(tid) - iconview = self.builder.get_object(f"{wid}|{tid}|iconview") - store = iconview.get_model() - _store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{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() + _store, tab_widget_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") - view.load_directory() - self.load_store(view, store) + tab.load_directory() + self.load_store(tab, store) - tab_label.set_label(view.get_end_of_path()) + tab_widget_label.set_label(tab.get_end_of_path()) - _wid, _tid, _view, _iconview, _store = self.get_current_state() + _wid, _tid, _tab, _icon_grid, _store = self.get_current_state() if [wid, tid] in [_wid, _tid]: - self.set_bottom_labels(view) + self.set_bottom_labels(tab) def popup_search_files(self, wid, keyname): @@ -123,43 +118,43 @@ class WidgetFileActionMixin: def do_file_search(self, widget, eve=None): query = widget.get_text() - self.search_iconview.unselect_all() - for i, file in enumerate(self.search_view.get_files()): + self.search_icon_grid.unselect_all() + for i, file in enumerate(self.search_tab.get_files()): if query and query in file[0].lower(): path = Gtk.TreePath().new_from_indices([i]) - self.search_iconview.select_path(path) + self.search_icon_grid.select_path(path) - items = self.search_iconview.get_selected_items() + items = self.search_icon_grid.get_selected_items() if len(items) == 1: - self.search_iconview.scroll_to_path(items[0], True, 0.5, 0.5) + self.search_icon_grid.scroll_to_path(items[0], True, 0.5, 0.5) def open_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) for file in uris: - view.open_file_locally(file) + tab.open_file_locally(file) def open_with_files(self, appchooser_widget): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() app_info = appchooser_widget.get_app_info() uris = self.format_to_uris(store, wid, tid, self.selected_files) - view.app_chooser_exec(app_info, uris) + tab.app_chooser_exec(app_info, uris) def execute_files(self, in_terminal=False): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() paths = self.format_to_uris(store, wid, tid, self.selected_files, True) - current_dir = view.get_current_directory() + current_dir = tab.get_current_directory() command = None for path in paths: - command = f"exec '{path}'" if not in_terminal else f"{view.terminal_app} -e '{path}'" - view.execute(command, start_dir=view.get_current_directory(), use_os_system=False) + 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) def archive_files(self, archiver_dialogue): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() paths = self.format_to_uris(store, wid, tid, self.selected_files, True) save_target = archiver_dialogue.get_filename(); @@ -167,14 +162,14 @@ class WidgetFileActionMixin: 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"{view.terminal_app} -e '{pre_command}'" + command = f"{tab.terminal_app} -e '{pre_command}'" - view.execute(command, start_dir=None, use_os_system=True) + 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, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) for uri in uris: @@ -191,7 +186,7 @@ class WidgetFileActionMixin: break rname_to = rename_input.get_text().strip() - target = f"{view.get_current_directory()}/{rname_to}" + target = f"{tab.get_current_directory()}/{rname_to}" self.handle_files([uri], "rename", target) @@ -201,27 +196,27 @@ class WidgetFileActionMixin: self.selected_files.clear() def cut_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) self.to_cut_files = uris def copy_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) self.to_copy_files = uris def paste_files(self): - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - target = f"{view.get_current_directory()}" + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + target = f"{tab.get_current_directory()}" - if len(self.to_copy_files) > 0: + if self.to_copy_files: self.handle_files(self.to_copy_files, "copy", target) - elif len(self.to_cut_files) > 0: + elif self.to_cut_files: self.handle_files(self.to_cut_files, "move", target) def delete_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) response = None @@ -236,7 +231,7 @@ class WidgetFileActionMixin: type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - view.delete_file( file.get_path() ) + tab.delete_file( file.get_path() ) else: file.delete(cancellable=None) else: @@ -244,13 +239,13 @@ class WidgetFileActionMixin: def trash_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) for uri in uris: self.trashman.trash(uri, False) def restore_trash_files(self): - wid, tid, view, iconview, store = self.get_current_state() + wid, tid, tab, icon_grid, store = self.get_current_state() uris = self.format_to_uris(store, wid, tid, self.selected_files, True) for uri in uris: self.trashman.restore(filename=uri.split("/")[-1], verbose=False) @@ -264,9 +259,9 @@ class WidgetFileActionMixin: file_name = fname_field.get_text().strip() type = self.builder.get_object("context_menu_type_toggle").get_state() - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - target = f"{view.get_current_directory()}" + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + target = f"{tab.get_current_directory()}" if file_name: path = f"{target}/{file_name}" @@ -331,9 +326,9 @@ class WidgetFileActionMixin: type = _file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) - view.delete_file( _file.get_path() ) + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + tab.delete_file( _file.get_path() ) else: _file.delete(cancellable=None) @@ -358,16 +353,16 @@ class WidgetFileActionMixin: type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - wid, tid = self.window_controller.get_active_wid_and_tid() - view = self.get_fm_window(wid).get_view_by_id(tid) + wid, tid = self.fm_controller.get_active_wid_and_tid() + tab = self.get_fm_window(wid).get_tab_by_id(tid) fPath = file.get_path() tPath = target.get_path() state = True if action == "copy": - view.copy_file(fPath, tPath) + tab.copy_file(fPath, tPath) if action == "move" or action == "rename": - view.move_file(fPath, tPath) + tab.move_file(fPath, tPath) else: if action == "copy": file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py index 84ecac0..6f496f4 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py @@ -22,29 +22,29 @@ def threaded(fn): class WidgetMixin: """docstring for WidgetMixin""" - def load_store(self, view, store, save_state=False): + def load_store(self, tab, store, save_state=False): store.clear() - dir = view.get_current_directory() - files = view.get_files() + dir = tab.get_current_directory() + files = tab.get_files() for i, file in enumerate(files): store.append([None, file[0]]) - self.create_icon(i, view, store, dir, file[0]) + self.create_icon(i, tab, store, dir, file[0]) # NOTE: Not likely called often from here but it could be useful if save_state: - self.window_controller.save_state() + self.fm_controller.save_state() @threaded - def create_icon(self, i, view, store, dir, file): - icon = view.create_icon(dir, file) + def create_icon(self, i, tab, store, dir, file): + icon = tab.create_icon(dir, file) fpath = f"{dir}/{file}" - GLib.idle_add(self.update_store, (i, store, icon, view, fpath,)) + GLib.idle_add(self.update_store, (i, store, icon, tab, fpath,)) # NOTE: Might need to keep an eye on this throwing invalid iters when too # many updates are happening to a folder. Example: /tmp def update_store(self, item): - i, store, icon, view, fpath = item + i, store, icon, tab, fpath = item itr = None try: @@ -60,12 +60,12 @@ class WidgetMixin: return if not icon: - icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0]) + icon = self.get_system_thumbnail(fpath, tab.SYS_ICON_WH[0]) if not icon: if fpath.endswith(".gif"): icon = GdkPixbuf.PixbufAnimation.get_static_image(fpath) else: - icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON) + icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) store.set_value(itr, 0, icon) @@ -90,31 +90,29 @@ class WidgetMixin: return None - - - def create_tab_widget(self, view): - tab = Gtk.ButtonBox() + def create_tab_widget(self, tab): + tab_widget = Gtk.ButtonBox() label = Gtk.Label() tid = Gtk.Label() close = Gtk.Button() icon = Gtk.Image(stock=Gtk.STOCK_CLOSE) - label.set_label(f"{view.get_end_of_path()}") - label.set_width_chars(len(view.get_end_of_path())) + label.set_label(f"{tab.get_end_of_path()}") + label.set_width_chars(len(tab.get_end_of_path())) label.set_xalign(0.0) - tid.set_label(f"{view.get_id()}") + tid.set_label(f"{tab.get_id()}") close.add(icon) - tab.add(label) - tab.add(close) - tab.add(tid) + tab_widget.add(label) + tab_widget.add(close) + tab_widget.add(tid) close.connect("released", self.close_tab) - tab.show_all() + tab_widget.show_all() tid.hide() - return tab + return tab_widget - def create_grid_iconview_widget(self, view, wid): + def create_icon_grid_widget(self, tab, wid): scroll = Gtk.ScrolledWindow() grid = Gtk.IconView() store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) @@ -134,11 +132,11 @@ class WidgetMixin: grid.set_column_spacing(18) grid.connect("button_release_event", self.grid_icon_single_click) - grid.connect("item-activated", self.grid_icon_double_click) - grid.connect("selection-changed", self.grid_set_selected_items) - grid.connect("drag-data-get", self.grid_on_drag_set) - grid.connect("drag-data-received", self.grid_on_drag_data_received) - grid.connect("drag-motion", self.grid_on_drag_motion) + grid.connect("item-activated", self.grid_icon_double_click) + grid.connect("selection-changed", self.grid_set_selected_items) + grid.connect("drag-data-get", self.grid_on_drag_set) + grid.connect("drag-data-received", self.grid_on_drag_data_received) + grid.connect("drag-motion", self.grid_on_drag_motion) URI_TARGET_TYPE = 80 @@ -150,17 +148,16 @@ class WidgetMixin: grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{view.get_id()}") - scroll.set_name(f"{wid}|{view.get_id()}") - self.builder.expose_object(f"{wid}|{view.get_id()}|iconview", grid) - self.builder.expose_object(f"{wid}|{view.get_id()}", scroll) + grid.set_name(f"{wid}|{tab.get_id()}") + scroll.set_name(f"{wid}|{tab.get_id()}") + self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) + self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) return scroll, store - def create_grid_treeview_widget(self, view, wid): + def create_icon_tree_widget(self, tab, wid): scroll = Gtk.ScrolledWindow() grid = Gtk.TreeView() - store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) - # store = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str) + store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) column = Gtk.TreeViewColumn("Icons") icon = Gtk.CellRendererPixbuf() name = Gtk.CellRendererText() @@ -184,10 +181,10 @@ class WidgetMixin: grid.set_enable_tree_lines(False) grid.connect("button_release_event", self.grid_icon_single_click) - grid.connect("row-activated", self.grid_icon_double_click) - grid.connect("drag-data-get", self.grid_on_drag_set) - grid.connect("drag-data-received", self.grid_on_drag_data_received) - grid.connect("drag-motion", self.grid_on_drag_motion) + grid.connect("row-activated", self.grid_icon_double_click) + grid.connect("drag-data-get", self.grid_on_drag_set) + grid.connect("drag-data-received", self.grid_on_drag_data_received) + grid.connect("drag-motion", self.grid_on_drag_motion) URI_TARGET_TYPE = 80 uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) @@ -199,23 +196,23 @@ class WidgetMixin: grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{view.get_id()}") - scroll.set_name(f"{wid}|{view.get_id()}") + grid.set_name(f"{wid}|{tab.get_id()}") + scroll.set_name(f"{wid}|{tab.get_id()}") grid.columns_autosize() - self.builder.expose_object(f"{wid}|{view.get_id()}", scroll) + self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) return scroll, store def get_store_and_label_from_notebook(self, notebook, _name): - icon_view = None + icon_grid = None tab_label = None store = None for obj in notebook.get_children(): - icon_view = obj.get_children()[0] - name = icon_view.get_name() + icon_grid = obj.get_children()[0] + name = icon_grid.get_name() if name == _name: - store = icon_view.get_model() + store = icon_grid.get_model() tab_label = notebook.get_tab_label(obj).get_children()[0] return store, tab_label diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py index ee1ad8b..b1e3878 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py @@ -10,9 +10,6 @@ from gi.repository import Gdk, Gio # Application imports from .tab_mixin import TabMixin -from .widget_mixin import WidgetMixin - - class WindowMixin(TabMixin): @@ -22,47 +19,46 @@ class WindowMixin(TabMixin): if session_json: for j, value in enumerate(session_json): i = j + 1 - isHidden = True if value[0]["window"]["isHidden"] == "True" else False - object = self.builder.get_object(f"tggl_notebook_{i}") - views = value[0]["window"]["views"] - self.window_controller.create_window() - object.set_active(True) + notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}") + is_hidden = True if value[0]["window"]["isHidden"] == "True" else False + tabs = value[0]["window"]["tabs"] + self.fm_controller.create_window() + notebook_tggl_button.set_active(True) - for view in views: - self.create_new_view_notebook(None, i, view) + for tab in tabs: + self.create_new_tab_notebook(None, i, tab) - if isHidden: - self.toggle_notebook_pane(object) + if is_hidden: + self.toggle_notebook_pane(notebook_tggl_button) try: if not self.is_pane4_hidden: - icon_view = self.window4.get_children()[1].get_children()[0] - icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid = self.window4.get_children()[1].get_children()[0] elif not self.is_pane3_hidden: - icon_view = self.window3.get_children()[1].get_children()[0] - icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid = self.window3.get_children()[1].get_children()[0] elif not self.is_pane2_hidden: - icon_view = self.window2.get_children()[1].get_children()[0] - icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid = self.window2.get_children()[1].get_children()[0] elif not self.is_pane1_hidden: - icon_view = self.window1.get_children()[1].get_children()[0] - icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid = self.window1.get_children()[1].get_children()[0] + + icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) + icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) except Exception as e: print("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n") print(repr(e)) else: for j in range(0, 4): i = j + 1 - self.window_controller.create_window() - self.create_new_view_notebook(None, i, None) + self.fm_controller.create_window() + self.create_new_tab_notebook(None, i, None) def get_fm_window(self, wid): - return self.window_controller.get_window_by_nickname(f"window_{wid}") + return self.fm_controller.get_window_by_nickname(f"window_{wid}") def format_to_uris(self, store, wid, tid, treePaths, use_just_path=False): - view = self.get_fm_window(wid).get_view_by_id(tid) - dir = view.get_current_directory() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + dir = tab.get_current_directory() uris = [] for path in treePaths: @@ -80,10 +76,10 @@ class WindowMixin(TabMixin): return uris - def set_bottom_labels(self, view): - _wid, _tid, _view, icon_view, store = self.get_current_state() - selected_files = icon_view.get_selected_items() - current_directory = view.get_current_directory() + def set_bottom_labels(self, tab): + _wid, _tid, _tab, icon_grid, store = self.get_current_state() + selected_files = 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) formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) ) @@ -98,8 +94,8 @@ class WindowMixin(TabMixin): # If something selected self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}") - self.bottom_path_label.set_label(view.get_current_directory()) - if len(selected_files) > 0: + self.bottom_path_label.set_label(tab.get_current_directory()) + if selected_files: uris = self.format_to_uris(store, _wid, _tid, selected_files, True) combined_size = 0 for uri in uris: @@ -115,29 +111,29 @@ class WindowMixin(TabMixin): formatted_size = self.sizeof_fmt(combined_size) - if view.get_hidden(): - self.bottom_path_label.set_label(f" {len(uris)} / {view.get_files_count()} ({formatted_size})") + if tab.is_hiding_hidden(): + self.bottom_path_label.set_label(f" {len(uris)} / {tab.get_files_count()} ({formatted_size})") else: - self.bottom_path_label.set_label(f" {len(uris)} / {view.get_not_hidden_count()} ({formatted_size})") + self.bottom_path_label.set_label(f" {len(uris)} / {tab.get_not_hidden_count()} ({formatted_size})") return # If nothing selected - if view.get_hidden(): - if view.get_hidden_count() > 0: - self.bottom_file_count_label.set_label(f"{view.get_not_hidden_count()} visible ({view.get_hidden_count()} hidden)") + if tab.get_hidden(): + if tab.get_hidden_count() > 0: + self.bottom_file_count_label.set_label(f"{tab.get_not_hidden_count()} visible ({tab.get_hidden_count()} hidden)") else: - self.bottom_file_count_label.set_label(f"{view.get_files_count()} items") + self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items") else: - self.bottom_file_count_label.set_label(f"{view.get_files_count()} items") + self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items") def set_window_title(self): - wid, tid = self.window_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") - view = self.get_fm_window(wid).get_view_by_id(tid) - dir = view.get_current_directory() + tab = self.get_fm_window(wid).get_tab_by_id(tid) + dir = tab.get_current_directory() for _notebook in self.notebooks: ctx = _notebook.get_style_context() @@ -149,73 +145,73 @@ class WindowMixin(TabMixin): ctx.add_class("notebook-selected-focus") self.window.set_title(f"SolarFM ~ {dir}") - self.set_bottom_labels(view) + self.set_bottom_labels(tab) def set_path_text(self, wid, tid): path_entry = self.builder.get_object("path_entry") - view = self.get_fm_window(wid).get_view_by_id(tid) - path_entry.set_text(view.get_current_directory()) + tab = self.get_fm_window(wid).get_tab_by_id(tid) + path_entry.set_text(tab.get_current_directory()) - def grid_set_selected_items(self, iconview): - self.selected_files = iconview.get_selected_items() + def grid_set_selected_items(self, icons_grid): + self.selected_files = icons_grid.get_selected_items() - def grid_cursor_toggled(self, iconview): + def grid_cursor_toggled(self, icons_grid): print("wat...") - def grid_icon_single_click(self, iconview, eve): + def grid_icon_single_click(self, icons_grid, eve): try: self.path_menu.popdown() - wid, tid = iconview.get_name().split("|") - self.window_controller.set__wid_and_tid(wid, tid) + wid, tid = icons_grid.get_name().split("|") + self.fm_controller.set__wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1: # l-click if self.single_click_open: # FIXME: need to find a way to pass the model index - self.grid_icon_double_click(iconview) + self.grid_icon_double_click(icons_grid) elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click self.show_context_menu() except Exception as e: print(repr(e)) - self.display_message(self.error, f"{repr(e)}") + self.display_message(self.error_color, f"{repr(e)}") - def grid_icon_double_click(self, iconview, item, data=None): + def grid_icon_double_click(self, icons_grid, item, data=None): try: - if self.ctrlDown and self.shiftDown: + if self.ctrl_down and self.shift_down: self.unset_keys_and_data() self.execute_files(in_terminal=True) return - elif self.ctrlDown: + elif self.ctrl_down: self.unset_keys_and_data() self.execute_files() return - wid, tid, view, _iconview, store = self.get_current_state() + wid, tid, tab, _icons_grid, store = self.get_current_state() notebook = self.builder.get_object(f"window_{wid}") - tab_label = self.get_tab_label(notebook, iconview) + tab_label = self.get_tab_label(notebook, icons_grid) fileName = store[item][1] - dir = view.get_current_directory() + dir = tab.get_current_directory() file = f"{dir}/{fileName}" if isdir(file): - view.set_path(file) - self.update_view(tab_label, view, store, wid, tid) + tab.set_path(file) + self.update_tab(tab_label, tab, store, wid, tid) else: self.open_files() except Exception as e: - self.display_message(self.error, f"{repr(e)}") + self.display_message(self.error_color, f"{repr(e)}") - def grid_on_drag_set(self, iconview, drag_context, data, info, time): - action = iconview.get_name() + def grid_on_drag_set(self, icons_grid, drag_context, data, info, time): + action = icons_grid.get_name() wid, tid = action.split("|") - store = iconview.get_model() - treePaths = iconview.get_selected_items() + store = icons_grid.get_model() + treePaths = icons_grid.get_selected_items() # NOTE: Need URIs as URI format for DnD to work. Will strip 'file://' # further down call chain when doing internal fm stuff. uris = self.format_to_uris(store, wid, tid, treePaths) @@ -224,30 +220,30 @@ class WindowMixin(TabMixin): data.set_uris(uris) data.set_text(uris_text, -1) - def grid_on_drag_motion(self, iconview, drag_context, x, y, data): - current = '|'.join(self.window_controller.get_active_wid_and_tid()) - target = iconview.get_name() + def grid_on_drag_motion(self, icons_grid, drag_context, x, y, data): + current = '|'.join(self.fm_controller.get_active_wid_and_tid()) + target = icons_grid.get_name() wid, tid = target.split("|") - store = iconview.get_model() - treePath = iconview.get_drag_dest_item().path + store = icons_grid.get_model() + treePath = icons_grid.get_drag_dest_item().path if treePath: uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "") self.override_drop_dest = uri if isdir(uri) else None if target not in current: - self.window_controller.set__wid_and_tid(wid, tid) + self.fm_controller.set__wid_and_tid(wid, tid) def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): if info == 80: - wid, tid = self.window_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") - view = self.get_fm_window(wid).get_view_by_id(tid) + tab = self.get_fm_window(wid).get_tab_by_id(tid) uris = data.get_uris() - dest = f"{view.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest + dest = f"{tab.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest if len(uris) == 0: uris = data.get_text().split("\n") @@ -256,5 +252,5 @@ class WindowMixin(TabMixin): self.move_files(uris, dest) - def create_new_view_notebook(self, widget=None, wid=None, path=None): + def create_new_tab_notebook(self, widget=None, wid=None, path=None): self.create_tab(wid, path) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/__init__.py index e69de29..c2a7368 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/__init__.py @@ -0,0 +1,3 @@ +""" +Signals module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/ipc_signals_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/ipc_signals_mixin.py index 3f46614..64b86dc 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/ipc_signals_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/ipc_signals_mixin.py @@ -13,7 +13,7 @@ class IPCSignalsMixin: print(message) def handle_file_from_ipc(self, path): - wid, tid = self.window_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") if notebook.is_visible(): self.create_tab(wid, path) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py index a801365..16ed902 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py @@ -17,20 +17,20 @@ class KeyboardSignalsMixin: """ KeyboardSignalsMixin keyboard hooks controller. """ def unset_keys_and_data(self, widget=None, eve=None): - self.ctrlDown = False - self.shiftDown = False - self.altDown = False + 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): keyname = Gdk.keyval_name(user_data.keyval).lower() - if "control" in keyname or "alt" in keyname or "shift" in keyname: + if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: if "control" in keyname: - self.ctrlDown = True + self.ctrl_down = True if "shift" in keyname: - self.shiftDown = True + self.shift_down = True if "alt" in keyname: - self.altDown = True + self.alt_down = True # NOTE: Yes, this should actually be mapped to some key controller setting # file or something. Sue me. @@ -39,84 +39,56 @@ class KeyboardSignalsMixin: if debug: print(f"global_key_release_controller > key > {keyname}") - if "control" in keyname or "alt" in keyname or "shift" in keyname: + if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: if "control" in keyname: - self.ctrlDown = False + self.ctrl_down = False if "shift" in keyname: - self.shiftDown = False + self.shift_down = False if "alt" in keyname: - self.altDown = False + self.alt_down = False - - if self.ctrlDown and self.shiftDown and keyname == "t": + 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() - if re.fullmatch(valid_keyvalue_pat, keyname): - if not self.is_searching and not self.ctrlDown \ - and not self.shiftDown and not self.altDown: - focused_obj = self.window.get_focus() - if isinstance(focused_obj, Gtk.IconView): - self.is_searching = True - wid, tid, self.search_view, self.search_iconview, store = self.get_current_state() - self.unset_keys_and_data() - self.popup_search_files(wid, keyname) - return - - - if (self.ctrlDown and keyname in ["1", "kp_1"]): - self.builder.get_object("tggl_notebook_1").released() - if (self.ctrlDown and keyname in ["2", "kp_2"]): - self.builder.get_object("tggl_notebook_2").released() - if (self.ctrlDown and keyname in ["3", "kp_3"]): - self.builder.get_object("tggl_notebook_3").released() - if (self.ctrlDown and keyname in ["4", "kp_4"]): - self.builder.get_object("tggl_notebook_4").released() - - if self.ctrlDown and keyname == "q": - self.tear_down() - if (self.ctrlDown and keyname == "slash") or keyname == "home": - self.builder.get_object("go_home").released() - if (self.ctrlDown and keyname == "r") or keyname == "f5": - self.builder.get_object("refresh_view").released() - if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"): - self.builder.get_object("go_up").released() - if self.ctrlDown and keyname == "l": - self.unset_keys_and_data() - self.builder.get_object("path_entry").grab_focus() - if self.ctrlDown and keyname == "t": - self.builder.get_object("create_tab").released() - if self.ctrlDown and keyname == "o": - self.unset_keys_and_data() - self.open_files() - if self.ctrlDown and keyname == "w": - self.keyboard_close_tab() - if self.ctrlDown and keyname == "h": - self.show_hide_hidden_files() - if (self.ctrlDown and keyname == "e"): - self.unset_keys_and_data() - self.rename_files() - if self.ctrlDown and keyname == "c": - self.copy_files() - self.to_cut_files.clear() - if self.ctrlDown and keyname == "x": - self.to_copy_files.clear() - self.cut_files() - if self.ctrlDown and keyname == "v": - self.paste_files() - if self.ctrlDown and keyname == "n": - self.unset_keys_and_data() - self.show_new_file_menu() - - - - if keyname in ["alt_l", "alt_r"]: - top_main_menubar = self.builder.get_object("top_main_menubar") - if top_main_menubar.is_visible(): - top_main_menubar.hide() - else: - top_main_menubar.show() if keyname == "delete": self.unset_keys_and_data() self.delete_files() @@ -126,3 +98,17 @@ class KeyboardSignalsMixin: 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 \ + 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 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/ipc_server_mixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/ipc_server.py similarity index 62% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/ipc_server_mixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/ipc_server.py index ff12d4a..0972d86 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/ipc_server_mixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/ipc_server.py @@ -1,5 +1,5 @@ # Python imports -import threading, time +import os, threading, time from multiprocessing.connection import Listener, Client # Lib imports @@ -15,12 +15,32 @@ def threaded(fn): -class IPCServerMixin: +class IPCServer: """ Create a listener so that other SolarFM instances send requests back to existing instance. """ + def __init__(self, conn_type="socket"): + self.is_ipc_alive = False + self._conn_type = conn_type + self.ipc_authkey = b'solarfm-ipc' + self.ipc_timeout = 15.0 + + if conn_type == "socket": + self.ipc_address = '/tmp/solarfm-ipc.sock' + else: + self.ipc_address = '127.0.0.1' + self.ipc_port = 4848 + @threaded def create_ipc_server(self): - listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + if self._conn_type == "socket": + if os.path.exists(self.ipc_address): + return + + listener = Listener(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) + else: + listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + + self.is_ipc_alive = True while True: conn = listener.accept() @@ -58,7 +78,12 @@ class IPCServerMixin: def send_ipc_message(self, message="Empty Data..."): try: - conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + if self._conn_type == "socket": + conn = Client(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) + else: + conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + + conn.send(message) conn.send('close connection') except Exception as e: diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py index dcfa61b..49230f5 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py @@ -13,8 +13,6 @@ from gi.repository import Gtk, Gio class Plugin: name = None module = None - gtk_socket_id = None - gtk_socket = None reference = None @@ -23,8 +21,7 @@ class Plugins: def __init__(self, settings): self._settings = settings - self._plugin_list_widget = self._settings.get_builder().get_object("plugin_list") - self._plugin_list_socket = self._settings.get_builder().get_object("plugin_socket") + self._builder = self._settings.get_builder() self._plugins_path = self._settings.get_plugins_path() self._plugins_dir_watcher = None self._plugin_collection = [] @@ -56,26 +53,18 @@ class Plugins: if isdir(path): os.chdir(path) - gtk_socket = Gtk.Socket().new() - self._plugin_list_socket.add(gtk_socket) - # NOTE: Must get ID after adding socket to window. Else issues.... - gtk_socket_id = gtk_socket.get_id() - sys.path.insert(0, path) - spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) + spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) + app = importlib.util.module_from_spec(spec) + spec.loader.exec_module(app) - ref = module.Main(gtk_socket_id, event_system) - plugin = Plugin() - plugin.name = ref.get_plugin_name() - plugin.module = path - plugin.gtk_socket_id = gtk_socket_id - plugin.gtk_socket = gtk_socket - plugin.reference = ref + plugin_reference = app.Plugin(self._builder, event_system) + plugin = Plugin() + plugin.name = plugin_reference.get_plugin_name() + plugin.module = path + plugin.reference = plugin_reference self._plugin_collection.append(plugin) - gtk_socket.show_all() except Exception as e: print("Malformed plugin! Not loading!") traceback.print_exc() @@ -84,14 +73,9 @@ class Plugins: def reload_plugins(self, file=None): - print(f"Reloading plugins...") - # if self._plugin_collection: - # to_unload = [] - # for dir in self._plugin_collection: - # if not os.path.isdir(os.path.join(self._plugins_path, dir)): - # to_unload.append(dir) + print(f"Reloading plugins... stub.") - def set_message_on_plugin(self, type, data): + def send_message_to_plugin(self, type, data): print("Trying to send message to plugin...") for plugin in self._plugin_collection: if type in plugin.name: diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py index e69de29..8a30fad 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/__init__.py @@ -0,0 +1,3 @@ +""" +Root of ShellFM +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py index e69de29..2463c54 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/__init__.py @@ -0,0 +1,3 @@ +""" +Window module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/controller.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/controller.py index 67457b1..60c3379 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/controller.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/controller.py @@ -40,20 +40,20 @@ class WindowController: return window - def add_view_for_window(self, win_id): + def add_tab_for_window(self, win_id): for window in self._windows: if window.get_id() == win_id: - return window.create_view() + return window.create_tab() - def add_view_for_window_by_name(self, name): + def add_tab_for_window_by_name(self, name): for window in self._windows: if window.get_name() == name: - return window.create_view() + return window.create_tab() - def add_view_for_window_by_nickname(self, nickname): + def add_tab_for_window_by_nickname(self, nickname): for window in self._windows: if window.get_nickname() == nickname: - return window.create_view() + return window.create_tab() def pop_window(self): self._windows.pop() @@ -116,33 +116,33 @@ class WindowController: print(f"Name: {window.get_name()}") print(f"Nickname: {window.get_nickname()}") print(f"Is Hidden: {window.is_hidden()}") - print(f"View Count: {window.get_views_count()}") + print(f"Tab Count: {window.get_tabs_count()}") print("\n-------------------------\n") - def list_files_from_views_of_window(self, win_id): + def list_files_from_tabs_of_window(self, win_id): for window in self._windows: if window.get_id() == win_id: - window.list_files_from_views() + window.list_files_from_tabs() break - def get_views_count(self, win_id): + def get_tabs_count(self, win_id): for window in self._windows: if window.get_id() == win_id: - return window.get_views_count() + return window.get_tabs_count() - def get_views_from_window(self, win_id): + def get_tabs_from_window(self, win_id): for window in self._windows: if window.get_id() == win_id: - return window.get_all_views() + return window.get_all_tabs() - def unload_views_and_windows(self): + def unload_tabs_and_windows(self): for window in self._windows: - window.get_all_views().clear() + window.get_all_tabs().clear() self._windows.clear() @@ -153,9 +153,9 @@ class WindowController: if len(self._windows) > 0: windows = [] for window in self._windows: - views = [] - for view in window.get_all_views(): - views.append(view.get_current_directory()) + tabs = [] + for tab in window.get_all_tabs(): + tabs.append(tab.get_current_directory()) windows.append( [ @@ -165,7 +165,7 @@ class WindowController: "Name": window.get_name(), "Nickname": window.get_nickname(), "isHidden": f"{window.is_hidden()}", - 'views': views + 'tabs': tabs } } ] diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/__init__.py new file mode 100644 index 0000000..c4d8aba --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/__init__.py @@ -0,0 +1,3 @@ +""" +Tabs module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/__init__.py new file mode 100644 index 0000000..14bb42f --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/__init__.py @@ -0,0 +1,3 @@ +""" +Icons module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/icon.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/icon.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/icon.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/icon.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/__init__.py new file mode 100644 index 0000000..d5bc819 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/__init__.py @@ -0,0 +1,3 @@ +""" +Icons mixins module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/desktopiconmixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/desktopiconmixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/videoiconmixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/videoiconmixin.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/videoiconmixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/videoiconmixin.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/BaseDirectory.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/BaseDirectory.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/BaseDirectory.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Config.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Config.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Config.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Config.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/DesktopEntry.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/DesktopEntry.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/DesktopEntry.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Exceptions.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Exceptions.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Exceptions.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Exceptions.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/IconTheme.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/IconTheme.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/IconTheme.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/IconTheme.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/IniFile.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/IniFile.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/IniFile.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/IniFile.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Locale.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Locale.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Locale.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Locale.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Menu.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Menu.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Menu.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Menu.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/MenuEditor.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/MenuEditor.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/MenuEditor.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Mime.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Mime.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/Mime.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/Mime.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/RecentFiles.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/RecentFiles.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/RecentFiles.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/__init__.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/__init__.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/__init__.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/util.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/util.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/xdg/util.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/mixins/xdg/util.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/path.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/path.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/path.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/path.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/view.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/tab.py similarity index 99% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/view.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/tab.py index 5676659..70acaed 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/view.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/tab.py @@ -18,7 +18,7 @@ from .icons.icon import Icon from .path import Path -class View(Settings, FileHandler, Launcher, Icon, Path): +class Tab(Settings, FileHandler, Launcher, Icon, Path): def __init__(self): self.logger = None self._id_length = 10 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/__init__.py new file mode 100644 index 0000000..aad0ae1 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/__init__.py @@ -0,0 +1,3 @@ +""" +Utils module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/filehandler.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/filehandler.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/filehandler.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/filehandler.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/launcher.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/launcher.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/launcher.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/launcher.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/settings.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/settings.py similarity index 100% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/settings.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/utils/settings.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/icons/mixins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/views/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py index 58d8414..9a09233 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py @@ -6,7 +6,7 @@ from random import randint # Application imports -from .views.view import View +from .tabs.tab import Tab class Window: @@ -16,45 +16,40 @@ class Window: self._name = "" self._nickname = "" self._isHidden = False - self._views = [] + self._tabs = [] self._generate_id() self._set_name() - def create_view(self): - view = View() - self._views.append(view) - return view + def create_tab(self): + tab = Tab() + self._tabs.append(tab) + return tab - def pop_view(self): - self._views.pop() + def pop_tab(self): + self._tabs.pop() - def delete_view_by_id(self, vid): - for view in self._views: - if view.get_id() == vid: - self._views.remove(view) + def delete_tab_by_id(self, vid): + for tab in self._tabs: + if tab.get_id() == vid: + self._tabs.remove(tab) break - def get_view_by_id(self, vid): - for view in self._views: - if view.get_id() == vid: - return view + def get_tab_by_id(self, vid): + for tab in self._tabs: + if tab.get_id() == vid: + return tab - def get_view_by_index(self, index): - return self._views[index] + def get_tab_by_index(self, index): + return self._tabs[index] - def get_views_count(self): - return len(self._views) - - def get_all_views(self): - return self._views - - def list_files_from_views(self): - for view in self._views: - print(view.get_files()) + def get_tabs_count(self): + return len(self._tabs) + def get_all_tabs(self): + return self._tabs def get_id(self): return self._id @@ -68,7 +63,9 @@ class Window: def is_hidden(self): return self._isHidden - + def list_files_from_tabs(self): + for tab in self._tabs: + print(tab.get_files()) def set_nickname(self, nickname): @@ -80,6 +77,7 @@ class Window: def _set_name(self): self._name = "window_" + self.get_id() + def _random_with_N_digits(self, n): range_start = 10**(n-1) range_end = (10**n)-1 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/__init__.py old mode 100644 new mode 100755 index e69de29..5c16e50 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/__init__.py @@ -0,0 +1,3 @@ +""" +Trasher module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py old mode 100644 new mode 100755 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/xdgtrash.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/xdgtrash.py old mode 100644 new mode 100755 diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py index e69de29..a8e5edd 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/__init__.py @@ -0,0 +1,3 @@ +""" + Utils module +""" diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py index a02f0ce..df454c7 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py @@ -7,8 +7,8 @@ import gi, cairo gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') -from gi.repository import Gtk as gtk -from gi.repository import Gdk as gdk +from gi.repository import Gtk +from gi.repository import Gdk # Application imports @@ -17,36 +17,39 @@ from .logger import Logger class Settings: def __init__(self): - self.builder = gtk.Builder() + self._SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + self._USER_HOME = path.expanduser('~') + self._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}" + self._PLUGINS_PATH = f"{self._CONFIG_PATH}/plugins" + self._USR_SOLARFM = f"/usr/share/{app_name.lower()}" - self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) - self.USER_HOME = path.expanduser('~') - self.CONFIG_PATH = f"{self.USER_HOME}/.config/{app_name.lower()}" - self.PLUGINS_PATH = f"{self.CONFIG_PATH}/plugins" - self.USR_SOLARFM = f"/usr/share/{app_name.lower()}" + self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css" + self._WINDOWS_GLADE = f"{self._CONFIG_PATH}/Main_Window.glade" + self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" + self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" - self.CSS_FILE = f"{self.CONFIG_PATH}/stylesheet.css" - self.WINDOWS_GLADE = f"{self.CONFIG_PATH}/Main_Window.glade" - self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons" - self.WINDOW_ICON = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png" - self.main_window = None + if not os.path.exists(self._CONFIG_PATH): + os.mkdir(self._CONFIG_PATH) + if not os.path.exists(self._PLUGINS_PATH): + os.mkdir(self._PLUGINS_PATH) - if not os.path.exists(self.CONFIG_PATH): - os.mkdir(self.CONFIG_PATH) - if not os.path.exists(self.PLUGINS_PATH): - os.mkdir(self.PLUGINS_PATH) + if not os.path.exists(self._WINDOWS_GLADE): + self._WINDOWS_GLADE = f"{self._USR_SOLARFM}/Main_Window.glade" + if not os.path.exists(self._CSS_FILE): + self._CSS_FILE = f"{self._USR_SOLARFM}/stylesheet.css" + if not os.path.exists(self._WINDOW_ICON): + self._WINDOW_ICON = f"{self._USR_SOLARFM}/icons/{app_name.lower()}.png" + if not os.path.exists(self._DEFAULT_ICONS): + self._DEFAULT_ICONS = f"{self._USR_SOLARFM}/icons" - if not os.path.exists(self.WINDOWS_GLADE): - self.WINDOWS_GLADE = f"{self.USR_SOLARFM}/Main_Window.glade" - if not os.path.exists(self.CSS_FILE): - self.CSS_FILE = f"{self.USR_SOLARFM}/stylesheet.css" - if not os.path.exists(self.WINDOW_ICON): - self.WINDOW_ICON = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png" - if not os.path.exists(self.DEFAULT_ICONS): - self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons" + self._success_color = "#88cc27" + self._warning_color = "#ffa800" + self._error_color = "#ff0000" - self.logger = Logger(self.CONFIG_PATH).get_logger() - self.builder.add_from_file(self.WINDOWS_GLADE) + self.main_window = None + self.logger = Logger(self._CONFIG_PATH).get_logger() + self.builder = Gtk.Builder() + self.builder.add_from_file(self._WINDOWS_GLADE) @@ -56,7 +59,7 @@ class Settings: self._set_window_data() def _set_window_data(self): - self.main_window.set_icon_from_file(self.WINDOW_ICON) + self.main_window.set_icon_from_file(self._WINDOW_ICON) screen = self.main_window.get_screen() visual = screen.get_rgba_visual() @@ -66,11 +69,11 @@ class Settings: self.main_window.connect("draw", self._area_draw) # bind css file - cssProvider = gtk.CssProvider() - cssProvider.load_from_path(self.CSS_FILE) - screen = gdk.Screen.get_default() - styleContext = gtk.StyleContext() - styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER) + cssProvider = Gtk.CssProvider() + cssProvider.load_from_path(self._CSS_FILE) + screen = Gdk.Screen.get_default() + styleContext = Gtk.StyleContext() + styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) def _area_draw(self, widget, cr): cr.set_source_rgba(0, 0, 0, 0.54) @@ -92,4 +95,8 @@ class Settings: def get_builder(self): return self.builder def get_logger(self): return self.logger 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 + + def get_success_color(self): return self._success_color + def get_warning_color(self): return self._warning_color + def get_error_color(self): return self._error_color diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py index e69de29..5416f23 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py @@ -0,0 +1,3 @@ +""" +Base module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index 0941597..22b79dd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -15,7 +15,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from main import Main +from app import Application if __name__ == "__main__": @@ -35,7 +35,7 @@ if __name__ == "__main__": # Read arguments (If any...) args, unknownargs = parser.parse_known_args() - Main(args, unknownargs) + Application(args, unknownargs) Gtk.main() except Exception as e: traceback.print_exc() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py new file mode 100644 index 0000000..3e092c9 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -0,0 +1,55 @@ +# Python imports +import os, inspect, time + +# Lib imports + +# Application imports +from utils.settings import Settings +from context.controller import Controller +from __builtins__ import EventSystem + + + + +class Application(EventSystem): + """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ + + def __init__(self, args, unknownargs): + if not trace_debug: + event_system.create_ipc_server() + time.sleep(0.1) + + if not event_system.is_ipc_alive: + if unknownargs: + for arg in unknownargs: + if os.path.isdir(arg): + message = f"FILE|{arg}" + event_system.send_ipc_message(message) + + if args.new_tab and os.path.isdir(args.new_tab): + message = f"FILE|{args.new_tab}" + event_system.send_ipc_message(message) + + raise Exception("IPC Server Exists: Will send path(s) to it and close...") + + + settings = Settings() + settings.create_window() + + controller = Controller(args, unknownargs, settings) + if not controller: + raise Exception("Controller exited and doesn't exist...") + + # Gets the methods from the classes and sets to handler. + # Then, builder connects to any signals it needs. + classes = [controller] + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + print(repr(e)) + + settings.builder.connect_signals(handlers) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py index e69de29..ded9abc 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py @@ -0,0 +1,3 @@ +""" +Mixins module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py index e69de29..991f9a2 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py @@ -0,0 +1,3 @@ +""" +UI module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py index e69de29..c2a7368 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py @@ -0,0 +1,3 @@ +""" +Signals module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py index e69de29..8a30fad 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py @@ -0,0 +1,3 @@ +""" +Root of ShellFM +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/__init__.py index e69de29..2463c54 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/__init__.py @@ -0,0 +1,3 @@ +""" +Window module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/__init__.py index e69de29..c4d8aba 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/__init__.py @@ -0,0 +1,3 @@ +""" +Tabs module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/__init__.py index e69de29..14bb42f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/__init__.py @@ -0,0 +1,3 @@ +""" +Icons module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/__init__.py index e69de29..d5bc819 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/__init__.py @@ -0,0 +1,3 @@ +""" +Icons mixins module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/__init__.py index e69de29..aad0ae1 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/__init__.py @@ -0,0 +1,3 @@ +""" +Utils module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/__init__.py index e69de29..5c16e50 100755 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/__init__.py @@ -0,0 +1,3 @@ +""" +Trasher module +""" -- 2.27.0 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 24/41] 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 -- 2.27.0 From 45ca8abbdd31a9092dec74dfd942a65c7ee5dc49 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 7 Mar 2022 17:52:43 -0600 Subject: [PATCH 25/41] Finalized keybinding inferastructure --- plugins/youtube_download/__main__.py | 4 +- .../solarfm-0.0.1/SolarFM/solarfm/app.py | 2 +- .../solarfm/context/mixins/ui/window_mixin.py | 2 +- .../SolarFM/solarfm/utils/keybindings.py | 5 +- .../SolarFM/solarfm/utils/settings.py | 79 +++++++------------ .../usr/share/solarfm/key-bindings.json | 23 ++++++ 6 files changed, 58 insertions(+), 57 deletions(-) create mode 100644 user_config/usr/share/solarfm/key-bindings.json diff --git a/plugins/youtube_download/__main__.py b/plugins/youtube_download/__main__.py index 5482b09..f264fcf 100644 --- a/plugins/youtube_download/__main__.py +++ b/plugins/youtube_download/__main__.py @@ -39,8 +39,8 @@ class Plugin: self._run_timeout() if self._message: - wid, tid, view, iconview, store = self._message - subprocess.Popen([f'{self.SCRIPT_PTH}/download.sh' , view.get_current_directory()]) + state = self._message + subprocess.Popen([f'{self.SCRIPT_PTH}/download.sh' , state.tab.get_current_directory()]) self._message = None diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py index 3e092c9..383af1f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -52,4 +52,4 @@ class Application(EventSystem): except Exception as e: print(repr(e)) - settings.builder.connect_signals(handlers) + settings.get_builder().connect_signals(handlers) 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 b94a561..18556b6 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 @@ -122,7 +122,7 @@ class WindowMixin(TabMixin): return # If nothing selected - if tab.get_hidden(): + if tab.is_hiding_hidden(): if tab.get_hidden_count() > 0: self.bottom_file_count_label.set_label(f"{tab.get_not_hidden_count()} visible ({tab.get_hidden_count()} hidden)") else: 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 index fe192e7..a7bbbf6 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py @@ -53,9 +53,12 @@ class Keybindings: self._masks = 0 for action, bindings in list(self.keys.items()): - if not isinstance(bindings, tuple): + if isinstance(bindings, list): + bindings = (*bindings,) + elif not isinstance(bindings, tuple): bindings = (bindings,) + for binding in bindings: if not binding or binding == "None": continue 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 187e005..0750213 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 @@ -1,5 +1,5 @@ # Python imports -import os +import os, json from os import path # Gtk imports @@ -18,36 +18,6 @@ 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: def __init__(self): self._SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) @@ -57,7 +27,8 @@ class Settings: self._USR_SOLARFM = f"/usr/share/{app_name.lower()}" self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css" - self._WINDOWS_GLADE = f"{self._CONFIG_PATH}/Main_Window.glade" + self._GLADE_FILE = f"{self._CONFIG_PATH}/Main_Window.glade" + self._KEY_BINDINGS = f"{self._CONFIG_PATH}/key-bindings.json" self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" @@ -66,8 +37,10 @@ class Settings: if not os.path.exists(self._PLUGINS_PATH): os.mkdir(self._PLUGINS_PATH) - if not os.path.exists(self._WINDOWS_GLADE): - self._WINDOWS_GLADE = f"{self._USR_SOLARFM}/Main_Window.glade" + if not os.path.exists(self._GLADE_FILE): + self._GLADE_FILE = f"{self._USR_SOLARFM}/Main_Window.glade" + if not os.path.exists(self._KEY_BINDINGS): + self._KEY_BINDINGS = f"{self._USR_SOLARFM}/key-bindings.json" if not os.path.exists(self._CSS_FILE): self._CSS_FILE = f"{self._USR_SOLARFM}/stylesheet.css" if not os.path.exists(self._WINDOW_ICON): @@ -79,29 +52,31 @@ 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) + self._keybindings = Keybindings() + with open(self._KEY_BINDINGS) as file: + keybindings = json.load(file)["keybindings"] + self._keybindings.configure(keybindings) + self._main_window = None + self._logger = Logger(self._CONFIG_PATH).get_logger() + self._builder = Gtk.Builder() + self._builder.add_from_file(self._GLADE_FILE) def create_window(self): # Get window and connect signals - self.main_window = self.builder.get_object("Main_Window") + self._main_window = self._builder.get_object("Main_Window") self._set_window_data() def _set_window_data(self): - self.main_window.set_icon_from_file(self._WINDOW_ICON) - screen = self.main_window.get_screen() + self._main_window.set_icon_from_file(self._WINDOW_ICON) + screen = self._main_window.get_screen() visual = screen.get_rgba_visual() if visual != None and screen.is_composited(): - self.main_window.set_visual(visual) - self.main_window.set_app_paintable(True) - self.main_window.connect("draw", self._area_draw) + self._main_window.set_visual(visual) + self._main_window.set_app_paintable(True) + self._main_window.connect("draw", self._area_draw) # bind css file cssProvider = Gtk.CssProvider() @@ -117,7 +92,7 @@ class Settings: cr.set_operator(cairo.OPERATOR_OVER) def get_monitor_data(self): - screen = self.builder.get_object("Main_Window").get_screen() + screen = self._builder.get_object("Main_Window").get_screen() monitors = [] for m in range(screen.get_n_monitors()): monitors.append(screen.get_monitor_geometry(m)) @@ -127,11 +102,11 @@ class Settings: return monitors - 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 + 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 def get_success_color(self): return self._success_color def get_warning_color(self): return self._warning_color diff --git a/user_config/usr/share/solarfm/key-bindings.json b/user_config/usr/share/solarfm/key-bindings.json new file mode 100644 index 0000000..487bc02 --- /dev/null +++ b/user_config/usr/share/solarfm/key-bindings.json @@ -0,0 +1,23 @@ +{ + "keybindings": { + "help" : "F1", + "rename_files" : ["F2", "e"], + "open_terminal" : "F4", + "refresh_tab" : ["F5", "r"], + "delete_files" : "Delete", + "tggl_top_main_menubar" : "", + "trash_files" : "t", + "tear_down" : "q", + "go_up" : "Up", + "go_home" : "slash", + "grab_focus_path_entry" : "l", + "open_files" : "o", + "show_hide_hidden_files" : "h", + "keyboard_create_tab" : "t", + "keyboard_close_tab" : "w", + "keyboard_copy_files" : "c", + "keyboard_cut_files" : "x", + "paste_files" : "v", + "show_new_file_menu" : "n" + } +} -- 2.27.0 From c3f637b5fdfc404ebe85da9632524844fd7f3951 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 7 Mar 2022 19:18:55 -0600 Subject: [PATCH 26/41] Changed keyboard control events --- .../SolarFM/solarfm/__builtins__.py | 2 +- .../SolarFM/solarfm/context/controller.py | 4 +--- .../solarfm/context/controller_data.py | 23 +++++++++---------- .../solarfm/context/mixins/ui/tab_mixin.py | 9 +++++--- .../mixins/ui/widget_file_action_mixin.py | 2 ++ .../solarfm/context/mixins/ui/window_mixin.py | 4 ++-- .../context/signals/keyboard_signals_mixin.py | 14 +---------- .../solarfm/shellfm/windows/controller.py | 4 ++-- .../SolarFM/solarfm/{ => utils}/ipc_server.py | 0 .../usr/share/solarfm/key-bindings.json | 15 +++++++----- 10 files changed, 35 insertions(+), 42 deletions(-) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{ => utils}/ipc_server.py (100%) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 02c8d57..9f24355 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -4,7 +4,7 @@ import builtins # Lib imports # Application imports -from ipc_server import IPCServer +from utils.ipc_server import IPCServer 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 52285b2..705ae3e 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 @@ -28,7 +28,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.setup_controller_data(_settings) self.window.show() - self.generate_windows(self.state) + self.generate_windows(self.fm_controller_data) self.plugins.launch_plugins() if debug: @@ -140,10 +140,8 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi if action == "rename": self.rename_files() if action == "cut": - self.to_copy_files.clear() self.cut_files() if action == "copy": - self.to_cut_files.clear() self.copy_files() if action == "paste": self.paste_files() 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 9e06eec..f4a9345 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 @@ -22,15 +22,15 @@ 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.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 = XDGTrash() + self.fm_controller = WindowController() + self.plugins = Plugins(_settings) + self.fm_controller_data = self.fm_controller.get_state_from_file() self.trashman.regenerate() self.window = self.settings.get_main_window() @@ -127,11 +127,10 @@ class Controller_Data: Returns: state (obj): State ''' - state = State() - wid, tid = self.fm_controller.get_active_wid_and_tid() + state = State() 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.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid) + state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid") state.store = state.icon_grid.get_model() return state 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 910f3f2..85f8c2c 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 @@ -15,7 +15,10 @@ from .widget_mixin import WidgetMixin class TabMixin(WidgetMixin): """docstring for TabMixin""" - def create_tab(self, wid, path=None): + def create_tab(self, wid=None, path=None): + if not wid: + wid, _tid = self.fm_controller.get_active_wid_and_tid() + notebook = self.builder.get_object(f"window_{wid}") path_entry = self.builder.get_object(f"path_entry") tab = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}") @@ -30,7 +33,7 @@ class TabMixin(WidgetMixin): # scroll, store = self.create_icon_tree_widget(tab, wid) index = notebook.append_page(scroll, tab_widget) - self.fm_controller.set__wid_and_tid(wid, tab.get_id()) + self.fm_controller.set_wid_and_tid(wid, tab.get_id()) path_entry.set_text(tab.get_current_directory()) notebook.show_all() notebook.set_current_page(index) @@ -79,7 +82,7 @@ class TabMixin(WidgetMixin): def on_tab_switch_update(self, notebook, content=None, index=None): self.selected_files.clear() wid, tid = content.get_children()[0].get_name().split("|") - self.fm_controller.set__wid_and_tid(wid, tid) + self.fm_controller.set_wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() 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 0a2b8f3..dea712b 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 @@ -191,11 +191,13 @@ class WidgetFileActionMixin: self.selected_files.clear() def cut_files(self): + self.to_copy_files.clear() 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): + self.to_cut_files.clear() 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 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 18556b6..5482dd2 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 @@ -165,7 +165,7 @@ class WindowMixin(TabMixin): try: self.path_menu.popdown() wid, tid = icons_grid.get_name().split("|") - self.fm_controller.set__wid_and_tid(wid, tid) + self.fm_controller.set_wid_and_tid(wid, tid) self.set_path_text(wid, tid) self.set_window_title() @@ -235,7 +235,7 @@ class WindowMixin(TabMixin): self.override_drop_dest = uri if isdir(uri) else None if target not in current: - self.fm_controller.set__wid_and_tid(wid, tid) + self.fm_controller.set_wid_and_tid(wid, tid) def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): 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 c50b848..bc2f762 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,7 +16,7 @@ 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. + # 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 @@ -72,10 +72,6 @@ class KeyboardSignalsMixin: 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}") @@ -89,11 +85,3 @@ class KeyboardSignalsMixin: 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/shellfm/windows/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py index 60c3379..abec786 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py @@ -26,7 +26,7 @@ class WindowController: self._windows = [] - def set__wid_and_tid(self, wid, tid): + def set_wid_and_tid(self, wid, tid): self._active_window_id = str(wid) self._active_tab_id = str(tid) @@ -176,7 +176,7 @@ class WindowController: else: raise Exception("Window data corrupted! Can not save session!") - def load_state(self, session_file = None): + def get_state_from_file(self, session_file = None): if not session_file: session_file = self._session_file diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/ipc_server.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py diff --git a/user_config/usr/share/solarfm/key-bindings.json b/user_config/usr/share/solarfm/key-bindings.json index 487bc02..a8c6506 100644 --- a/user_config/usr/share/solarfm/key-bindings.json +++ b/user_config/usr/share/solarfm/key-bindings.json @@ -1,10 +1,13 @@ { "keybindings": { "help" : "F1", - "rename_files" : ["F2", "e"], + "rename_files" : ["F2", + "e"], "open_terminal" : "F4", - "refresh_tab" : ["F5", "r"], - "delete_files" : "Delete", + "refresh_tab" : ["F5", + "r"], + "delete_files" : ["Delete", + "d"], "tggl_top_main_menubar" : "", "trash_files" : "t", "tear_down" : "q", @@ -13,10 +16,10 @@ "grab_focus_path_entry" : "l", "open_files" : "o", "show_hide_hidden_files" : "h", - "keyboard_create_tab" : "t", + "create_tab" : "t", "keyboard_close_tab" : "w", - "keyboard_copy_files" : "c", - "keyboard_cut_files" : "x", + "copy_files" : "c", + "cut_files" : "x", "paste_files" : "v", "show_new_file_menu" : "n" } -- 2.27.0 From 51ac26048c618ae9d3605c1591815d452267fb04 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 13 Mar 2022 17:32:37 -0500 Subject: [PATCH 27/41] improved thubnailing logic, fixed new tab logic --- .../SolarFM/solarfm/context/controller.py | 2 +- .../solarfm/context/controller_data.py | 4 +- .../solarfm/context/mixins/ui/tab_mixin.py | 13 +++++-- .../solarfm/context/mixins/ui/widget_mixin.py | 38 +++++++------------ .../solarfm/context/mixins/ui/window_mixin.py | 2 +- 5 files changed, 27 insertions(+), 32 deletions(-) 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 705ae3e..77043aa 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 @@ -101,7 +101,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.fm_controller.save_state(path) elif action == "load_session": path = f"{save_load_dialog.get_file().get_path()}" - session_json = self.fm_controller.load_state(path) + session_json = self.fm_controller.get_state_from_file(path) self.load_session(session_json) if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): pass 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 f4a9345..fe4be1e 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 @@ -56,8 +56,8 @@ class Controller_Data: self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label") self.bottom_path_label = self.builder.get_object("bottom_path_label") - self.trash_files_path = GLib.get_user_data_dir() + "/Trash/files" - self.trash_info_path = GLib.get_user_data_dir() + "/Trash/info" + self.trash_files_path = f"{GLib.get_user_data_dir()}/Trash/files" + self.trash_info_path = f"{GLib.get_user_data_dir()}/Trash/info" # In compress commands: # %n: First selected filename/dir to archive 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 85f8c2c..819ae43 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 @@ -15,9 +15,9 @@ from .widget_mixin import WidgetMixin class TabMixin(WidgetMixin): """docstring for TabMixin""" - def create_tab(self, wid=None, path=None): + def create_tab(self, wid=None, tid=None, path=None): if not wid: - wid, _tid = self.fm_controller.get_active_wid_and_tid() + wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") path_entry = self.builder.get_object(f"path_entry") @@ -25,7 +25,12 @@ class TabMixin(WidgetMixin): tab.logger = self.logger tab.set_wid(wid) - if path: tab.set_path(path) + if not path: + if wid and tid: + _tab = self.get_fm_window(wid).get_tab_by_id(tid) + tab.set_path(_tab.get_current_directory()) + else: + tab.set_path(path) tab_widget = self.create_tab_widget(tab) scroll, store = self.create_icon_grid_widget(tab, wid) @@ -123,7 +128,7 @@ class TabMixin(WidgetMixin): if action == "create_tab": dir = tab.get_current_directory() - self.create_tab(wid, dir) + self.create_tab(wid, None, dir) self.fm_controller.save_state() return if action == "go_up": diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py index 6f496f4..2fa3d0c 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py @@ -27,8 +27,14 @@ class WidgetMixin: dir = tab.get_current_directory() files = tab.get_files() - for i, file in enumerate(files): + # NOTE: We insure all indecies exist before calling threads that update + # icon positions. In addition, adding the name allows us to see + # the "file" during long or heavy number of icon updates. + for file in files: store.append([None, file[0]]) + + # Now we update as fast as possible the icons. + for i, file in enumerate(files): self.create_icon(i, tab, store, dir, file[0]) # NOTE: Not likely called often from here but it could be useful @@ -37,27 +43,12 @@ class WidgetMixin: @threaded def create_icon(self, i, tab, store, dir, file): - icon = tab.create_icon(dir, file) + icon = tab.create_icon(dir, file) + GLib.idle_add(self.update_store, *(i, store, icon, tab, dir, file,)) + + def update_store(self, i, store, icon, tab, dir, file): fpath = f"{dir}/{file}" - GLib.idle_add(self.update_store, (i, store, icon, tab, fpath,)) - - # NOTE: Might need to keep an eye on this throwing invalid iters when too - # many updates are happening to a folder. Example: /tmp - def update_store(self, item): - i, store, icon, tab, fpath = item - itr = None - - try: - itr = store.get_iter(i) - except Exception as e: - try: - time.sleep(0.2) - itr = store.get_iter(i) - except Exception as e: - print(":Invalid Itr detected: (Potential race condition...)") - print(f"Index Requested: {i}") - print(f"Store Size: {len(store)}") - return + itr = store.get_iter(i) if not icon: icon = self.get_system_thumbnail(fpath, tab.SYS_ICON_WH[0]) @@ -69,7 +60,6 @@ class WidgetMixin: store.set_value(itr, 0, icon) - def get_system_thumbnail(self, filename, size): try: if os.path.exists(filename): @@ -115,7 +105,7 @@ class WidgetMixin: def create_icon_grid_widget(self, tab, wid): scroll = Gtk.ScrolledWindow() grid = Gtk.IconView() - store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) + store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) grid.set_model(store) grid.set_pixbuf_column(0) @@ -157,7 +147,7 @@ class WidgetMixin: def create_icon_tree_widget(self, tab, wid): scroll = Gtk.ScrolledWindow() grid = Gtk.TreeView() - store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) + store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) column = Gtk.TreeViewColumn("Icons") icon = Gtk.CellRendererPixbuf() name = Gtk.CellRendererText() 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 5482dd2..1ff7379 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 @@ -256,4 +256,4 @@ class WindowMixin(TabMixin): def create_new_tab_notebook(self, widget=None, wid=None, path=None): - self.create_tab(wid, path) + self.create_tab(wid, None, path) -- 2.27.0 From 09a85abb79a682edcfae15c9fac9ed1c5df5c2e4 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 13 Mar 2022 18:21:06 -0500 Subject: [PATCH 28/41] Fixed initial loaded start tab, cleanup --- .../SolarFM/solarfm/context/mixins/ui/window_mixin.py | 8 ++++---- .../SolarFM/solarfm/shellfm/windows/window.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) 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 1ff7379..b94bdca 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 @@ -36,13 +36,13 @@ class WindowMixin(TabMixin): try: if not self.is_pane4_hidden: - icon_grid = self.window4.get_children()[1].get_children()[0] + icon_grid = self.window4.get_children()[-1].get_children()[0] elif not self.is_pane3_hidden: - icon_grid = self.window3.get_children()[1].get_children()[0] + icon_grid = self.window3.get_children()[-1].get_children()[0] elif not self.is_pane2_hidden: - icon_grid = self.window2.get_children()[1].get_children()[0] + icon_grid = self.window2.get_children()[-1].get_children()[0] elif not self.is_pane1_hidden: - icon_grid = self.window1.get_children()[1].get_children()[0] + icon_grid = self.window1.get_children()[-1].get_children()[0] icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py index 9a09233..ec61cd6 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py @@ -30,16 +30,16 @@ class Window: def pop_tab(self): self._tabs.pop() - def delete_tab_by_id(self, vid): + def delete_tab_by_id(self, tid): for tab in self._tabs: - if tab.get_id() == vid: + if tab.get_id() == tid: self._tabs.remove(tab) break - def get_tab_by_id(self, vid): + def get_tab_by_id(self, tid): for tab in self._tabs: - if tab.get_id() == vid: + if tab.get_id() == tid: return tab def get_tab_by_index(self, index): -- 2.27.0 From 1fc1609b0a38b857116b61919f7a21e3ea7aff93 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 13 Mar 2022 18:42:00 -0500 Subject: [PATCH 29/41] Renamed file, cleanup --- .../ui/{widget_mixin.py => grid_mixin.py} | 17 ++++------------- .../solarfm/context/mixins/ui/tab_mixin.py | 4 ++-- .../solarfm/shellfm/windows/tabs/icons/icon.py | 12 ++++++------ 3 files changed, 12 insertions(+), 21 deletions(-) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/{widget_mixin.py => grid_mixin.py} (92%) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py similarity index 92% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py index 2fa3d0c..c9135ee 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py @@ -19,7 +19,7 @@ def threaded(fn): -class WidgetMixin: +class GridMixin: """docstring for WidgetMixin""" def load_store(self, tab, store, save_state=False): @@ -27,14 +27,8 @@ class WidgetMixin: dir = tab.get_current_directory() files = tab.get_files() - # NOTE: We insure all indecies exist before calling threads that update - # icon positions. In addition, adding the name allows us to see - # the "file" during long or heavy number of icon updates. - for file in files: - store.append([None, file[0]]) - - # Now we update as fast as possible the icons. for i, file in enumerate(files): + store.append([None, file[0]]) self.create_icon(i, tab, store, dir, file[0]) # NOTE: Not likely called often from here but it could be useful @@ -48,15 +42,12 @@ class WidgetMixin: def update_store(self, i, store, icon, tab, dir, file): fpath = f"{dir}/{file}" - itr = store.get_iter(i) + itr = store.get_iter(i) if not icon: icon = self.get_system_thumbnail(fpath, tab.SYS_ICON_WH[0]) if not icon: - if fpath.endswith(".gif"): - icon = GdkPixbuf.PixbufAnimation.get_static_image(fpath) - else: - icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) + icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) store.set_value(itr, 0, icon) 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 819ae43..24e2b56 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 @@ -7,12 +7,12 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from .widget_mixin import WidgetMixin +from .grid_mixin import GridMixin -class TabMixin(WidgetMixin): +class TabMixin(GridMixin): """docstring for TabMixin""" def create_tab(self, wid=None, tid=None, path=None): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py index 6afa0e5..4ff9c4e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py @@ -59,12 +59,12 @@ class Icon(DesktopIconMixin, VideoIconMixin): def create_scaled_image(self, path, wxh): try: - if path.lower().endswith(".gif"): - return GdkPixbuf.PixbufAnimation.new_from_file(path) \ - .get_static_image() \ - .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) - else: - return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) + if path.lower().endswith(".gif"): + return GdkPixbuf.PixbufAnimation.new_from_file(path) \ + .get_static_image() \ + .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) + else: + return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) except Exception as e: print("Image Scaling Issue:") print( repr(e) ) -- 2.27.0 From 22c9fa301beb1fbe781da0fca4e13e63289f5771 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 13 Mar 2022 18:53:43 -0500 Subject: [PATCH 30/41] Fixed ipc new tab --- .../solarfm/context/signals/ipc_signals_mixin.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py index 64b86dc..3aeb267 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py @@ -13,17 +13,17 @@ class IPCSignalsMixin: print(message) def handle_file_from_ipc(self, path): - wid, tid = self.fm_controller.get_active_wid_and_tid() - notebook = self.builder.get_object(f"window_{wid}") + wid, tid = self.fm_controller.get_active_wid_and_tid() + notebook = self.builder.get_object(f"window_{wid}") if notebook.is_visible(): - self.create_tab(wid, path) + self.create_tab(wid, None, path) return if not self.is_pane4_hidden: - self.create_tab(4, path) + self.create_tab(4, None, path) elif not self.is_pane3_hidden: - self.create_tab(3, path) + self.create_tab(3, None, path) elif not self.is_pane2_hidden: - self.create_tab(2, path) + self.create_tab(2, None, path) elif not self.is_pane1_hidden: - self.create_tab(1, path) + self.create_tab(1, None, path) -- 2.27.0 From 2278cdc0c3f3cb244c84101cb90b6a9e00ef1ffa Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 17 Mar 2022 01:39:03 -0500 Subject: [PATCH 31/41] Dialog window changes, copy/paste changes, thread change --- .../solarfm/context/mixins/show_hide_mixin.py | 4 +- .../solarfm/context/mixins/ui/grid_mixin.py | 9 ++++- .../mixins/ui/widget_file_action_mixin.py | 9 +++-- .../usr/share/solarfm/Main_Window.glade | 38 ++++++++++++++----- 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py index 2e7a4fd..687a47a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py @@ -110,7 +110,9 @@ class ShowHideMixin: def show_new_file_menu(self, widget=None, eve=None): - self.builder.get_object("context_menu_fname").set_text("") + context_menu_fname = self.builder.get_object("context_menu_fname") + context_menu_fname.set_text("") + context_menu_fname.grab_focus() new_file_menu = self.builder.get_object("new_file_menu") response = new_file_menu.run() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py index c9135ee..696167e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py @@ -42,7 +42,14 @@ class GridMixin: def update_store(self, i, store, icon, tab, dir, file): fpath = f"{dir}/{file}" - itr = store.get_iter(i) + + loop = True + while loop: + try: + itr = store.get_iter(i) + loop = False + except: + pass if not icon: icon = self.get_system_thumbnail(fpath, tab.SYS_ICON_WH[0]) 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 dea712b..9a4013a 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 @@ -115,7 +115,7 @@ class WidgetFileActionMixin: entry.set_position(-1) def do_file_search(self, widget, eve=None): - query = widget.get_text() + query = widget.get_text().lower() self.search_icon_grid.unselect_all() for i, file in enumerate(self.search_tab.get_files()): if query and query in file[0].lower(): @@ -123,8 +123,8 @@ class WidgetFileActionMixin: self.search_icon_grid.select_path(path) items = self.search_icon_grid.get_selected_items() - if len(items) == 1: - self.search_icon_grid.scroll_to_path(items[0], True, 0.5, 0.5) + if len(items) > 0: + self.search_icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5) def open_files(self): @@ -290,6 +290,9 @@ class WidgetFileActionMixin: file = Gio.File.new_for_path(path) if _target_path: + if file.get_parent().get_path() == _target_path: + raise Exception("Parent dir of target and file locations are the same! Won't copy or move!") + if os.path.isdir(_target_path): info = file.query_info("standard::display-name", 0, cancellable=None) _target = f"{_target_path}/{info.get_display_name()}" diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index d4b201c..bd83627 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -623,20 +623,24 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False - popup False True - center + center-always True - splashscreen + normal True True + False False center False + 5 + 5 + 5 + 5 vertical 2 @@ -694,7 +698,13 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True True + New File/Dir Name... + True gtk-edit + False + False + False + False New File/Dir Name... @@ -795,15 +805,15 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 120 False - popup False True - center + center-always True - splashscreen + normal True True True + False False center @@ -1233,19 +1243,23 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False - popup False True - center + center-always True - splashscreen + normal True True + False False center False + 5 + 5 + 5 + 5 vertical 2 @@ -1336,7 +1350,13 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True True + Rename To: + True gtk-edit + False + False + False + False To: -- 2.27.0 From 0539fa41f02fe6def5fe187e7706e290aefd2244 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 24 Mar 2022 22:15:08 -0500 Subject: [PATCH 32/41] begin enforcing types better; glade updates --- .../SolarFM/solarfm/__builtins__.py | 18 +- .../solarfm/context/controller_data.py | 25 +- .../solarfm/context/mixins/ui/grid_mixin.py | 40 +- .../SolarFM/solarfm/plugins/plugins.py | 20 +- .../shellfm/windows/tabs/utils/settings.py | 4 +- .../SolarFM/solarfm/utils/ipc_server.py | 11 +- .../SolarFM/solarfm/utils/logger.py | 4 +- .../SolarFM/solarfm/utils/settings.py | 28 +- .../usr/share/solarfm/Main_Window.glade | 1331 +++++++++-------- 9 files changed, 740 insertions(+), 741 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 9f24355..bd0a892 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -26,41 +26,41 @@ class EventSystem(IPCServer): # Makeshift fake "events" type system FIFO - def _pop_gui_event(self): + def _pop_gui_event(self) -> None: if len(self._gui_events) > 0: return self._gui_events.pop(0) return None - def _pop_module_event(self): + def _pop_module_event(self) -> None: if len(self._module_events) > 0: return self._module_events.pop(0) return None - def push_gui_event(self, event): + def push_gui_event(self, eventevent: list) -> None: if len(event) == 3: self._gui_events.append(event) return None raise Exception("Invald event format! Please do: [type, target, (data,)]") - def push_module_event(self, event): + def push_module_event(self, event: list) -> None: if len(event) == 3: self._module_events.append(event) return None raise Exception("Invald event format! Please do: [type, target, (data,)]") - def read_gui_event(self): + def read_gui_event(self) -> list: return self._gui_events[0] - def read_module_event(self): + def read_module_event(self) -> list: return self._module_events[0] - def consume_gui_event(self): + def consume_gui_event(self) -> list: return self._pop_gui_event() - def consume_module_event(self): + def consume_module_event(self) -> list: return self._pop_module_event() @@ -70,5 +70,5 @@ class EventSystem(IPCServer): builtins.app_name = "SolarFM" builtins.event_system = EventSystem() builtins.event_sleep_time = 0.2 -builtins.debug = False builtins.trace_debug = False +builtins.debug = False 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 fe4be1e..12437a8 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 @@ -2,6 +2,7 @@ import sys, os, signal # Lib imports +import gi from gi.repository import GLib # Application imports @@ -11,17 +12,17 @@ from plugins.plugins import Plugins class State: - wid = None - tid = None - tab = None - icon_grid = None - store = None + wid: int = None + tid: int = None + tab: type = None + icon_grid: gi.overrides.Gtk.IconView = None + store: gi.overrides.Gtk.ListStore = 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): + def setup_controller_data(self, _settings: type) -> None: self.settings = _settings self.builder = self.settings.get_builder() self.logger = self.settings.get_logger() @@ -58,6 +59,7 @@ class Controller_Data: self.trash_files_path = f"{GLib.get_user_data_dir()}/Trash/files" self.trash_info_path = f"{GLib.get_user_data_dir()}/Trash/info" + self.icon_theme = self.settings.get_icon_theme() # In compress commands: # %n: First selected filename/dir to archive @@ -117,7 +119,7 @@ class Controller_Data: GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) - def get_current_state(self): + def get_current_state(self) -> State: ''' Returns the state info most useful for any given context and action intent. @@ -132,14 +134,15 @@ class Controller_Data: state.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid) state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid") state.store = state.icon_grid.get_model() + return state - def clear_console(self): + def clear_console(self) -> None: ''' Clears the terminal screen. ''' os.system('cls' if os.name == 'nt' else 'clear') - def call_method(self, _method_name, data = None): + def call_method(self, _method_name: str, data: type = None) -> type: ''' Calls a method from scope of class. @@ -156,11 +159,11 @@ class Controller_Data: method = getattr(self, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}") return method(data) if data else method() - def has_method(self, obj, name): + def has_method(self, obj, name) -> type: ''' Checks if a given method exists. ''' return callable(getattr(obj, name, None)) - def clear_children(self, widget): + def clear_children(self, widget: type) -> None: ''' Clear children of a gtk widget. ''' for child in widget.get_children(): widget.remove(child) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py index 696167e..a654c56 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py @@ -27,8 +27,10 @@ class GridMixin: dir = tab.get_current_directory() files = tab.get_files() - for i, file in enumerate(files): + for file in files: store.append([None, file[0]]) + + for i, file in enumerate(files): self.create_icon(i, tab, store, dir, file[0]) # NOTE: Not likely called often from here but it could be useful @@ -41,40 +43,30 @@ class GridMixin: GLib.idle_add(self.update_store, *(i, store, icon, tab, dir, file,)) def update_store(self, i, store, icon, tab, dir, file): - fpath = f"{dir}/{file}" - - loop = True - while loop: + while True: try: - itr = store.get_iter(i) - loop = False + itr = store.get_iter(i) + break except: pass if not icon: - icon = self.get_system_thumbnail(fpath, tab.SYS_ICON_WH[0]) - if not icon: - icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) + path = f"{dir}/{file}" + icon = self.get_system_thumbnail(path, tab.SYS_ICON_WH[0]) + + if not icon: + icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) store.set_value(itr, 0, icon) def get_system_thumbnail(self, filename, size): try: - if os.path.exists(filename): - gioFile = Gio.File.new_for_path(filename) - info = gioFile.query_info('standard::icon' , 0, Gio.Cancellable()) - icon = info.get_icon().get_names()[0] - iconTheme = Gtk.IconTheme.get_default() - iconData = iconTheme.lookup_icon(icon , size , 0) - if iconData: - iconPath = iconData.get_filename() - return GdkPixbuf.Pixbuf.new_from_file(iconPath) - else: - return None - else: - return None + gio_file = Gio.File.new_for_path(filename) + info = gio_file.query_info('standard::icon' , 0, None) + icon = info.get_icon().get_names()[0] + icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename() + return GdkPixbuf.Pixbuf.new_from_file(icon_path) except Exception as e: - print("System icon generation issue:") return None diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index 49230f5..c28817b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -11,15 +11,15 @@ from gi.repository import Gtk, Gio class Plugin: - name = None - module = None - reference = None + name: str = None + module: str = None + reference: type = None class Plugins: """Plugins controller""" - def __init__(self, settings): + def __init__(self, settings: type): self._settings = settings self._builder = self._settings.get_builder() self._plugins_path = self._settings.get_plugins_path() @@ -27,11 +27,11 @@ class Plugins: self._plugin_collection = [] - def launch_plugins(self): + def launch_plugins(self) -> None: self._set_plugins_watcher() self.load_plugins() - def _set_plugins_watcher(self): + def _set_plugins_watcher(self) -> None: self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \ .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ()) @@ -43,7 +43,7 @@ class Plugins: self.reload_plugins(file) # @threaded - def load_plugins(self, file=None): + def load_plugins(self, file: str = None) -> None: print(f"Loading plugins...") parent_path = os.getcwd() @@ -72,12 +72,12 @@ class Plugins: os.chdir(parent_path) - def reload_plugins(self, file=None): + def reload_plugins(self, file: str = None) -> None: print(f"Reloading plugins... stub.") - def send_message_to_plugin(self, type, data): + def send_message_to_plugin(self, target: str , data: type) -> None: print("Trying to send message to plugin...") for plugin in self._plugin_collection: - if type in plugin.name: + if target in plugin.name: print('Found plugin; posting message...') plugin.reference.set_message(data) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py index e4d9323..fb982af 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py @@ -59,7 +59,7 @@ class Settings: subpath = settings["base_of_home"] HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"] - go_past_home = True if settings["go_past_home"] == "" else settings["go_past_home"] + go_past_home = True if settings["go_past_home"] == "" else settings["go_past_home"] lock_folder = True if settings["lock_folder"] == "true" else False locked_folders = settings["locked_folders"].split("::::") mplayer_options = settings["mplayer_options"].split() @@ -76,7 +76,7 @@ class Settings: # Filters fvideos = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm') foffice = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf') - fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga') + fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga', '.webp') ftext = ('.txt', '.text', '.sh', '.cfg', '.conf') fmusic = ('.psf', '.mp3', '.ogg', '.flac', '.m4a') fpdf = ('.pdf') diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index 0972d86..27a0095 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -17,7 +17,7 @@ def threaded(fn): class IPCServer: """ Create a listener so that other SolarFM instances send requests back to existing instance. """ - def __init__(self, conn_type="socket"): + def __init__(self, conn_type: str = "socket"): self.is_ipc_alive = False self._conn_type = conn_type self.ipc_authkey = b'solarfm-ipc' @@ -31,7 +31,7 @@ class IPCServer: @threaded - def create_ipc_server(self): + def create_ipc_server(self) -> None: if self._conn_type == "socket": if os.path.exists(self.ipc_address): return @@ -44,7 +44,7 @@ class IPCServer: self.is_ipc_alive = True while True: conn = listener.accept() - start_time = time.time() + start_time = time.perf_counter() print(f"New Connection: {listener.last_accepted}") while True: @@ -69,14 +69,14 @@ class IPCServer: break # NOTE: Not perfect but insures we don't lock up the connection for too long. - end_time = time.time() + end_time = time.perf_counter() if (end - start) > self.ipc_timeout: conn.close() listener.close() - def send_ipc_message(self, message="Empty Data..."): + def send_ipc_message(self, message: str = "Empty Data...") -> None: try: if self._conn_type == "socket": conn = Client(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) @@ -86,5 +86,6 @@ class IPCServer: conn.send(message) conn.send('close connection') + conn.close() except Exception as e: print(repr(e)) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py index 06eed47..63db6e2 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py @@ -5,10 +5,10 @@ import os, logging class Logger: - def __init__(self, config_path): + def __init__(self, config_path: str): self._CONFIG_PATH = config_path - def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True): + def get_logger(self, loggerName: str = "NO_LOGGER_NAME_PASSED", createFile: bool = True) -> logging.Logger: """ Create a new logging object and return it. :note: 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 0750213..6d93e77 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 @@ -31,6 +31,7 @@ class Settings: self._KEY_BINDINGS = f"{self._CONFIG_PATH}/key-bindings.json" self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" + self._ICON_THEME = Gtk.IconTheme.get_default() if not os.path.exists(self._CONFIG_PATH): os.mkdir(self._CONFIG_PATH) @@ -63,12 +64,12 @@ class Settings: self._builder.add_from_file(self._GLADE_FILE) - def create_window(self): + def create_window(self) -> None: # Get window and connect signals self._main_window = self._builder.get_object("Main_Window") self._set_window_data() - def _set_window_data(self): + def _set_window_data(self) -> None: self._main_window.set_icon_from_file(self._WINDOW_ICON) screen = self._main_window.get_screen() visual = screen.get_rgba_visual() @@ -85,29 +86,28 @@ class Settings: styleContext = Gtk.StyleContext() styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) - def _area_draw(self, widget, cr): + def _area_draw(self, widget: Gtk.ApplicationWindow, cr: cairo.Context) -> None: cr.set_source_rgba(0, 0, 0, 0.54) cr.set_operator(cairo.OPERATOR_SOURCE) cr.paint() cr.set_operator(cairo.OPERATOR_OVER) - def get_monitor_data(self): + def get_monitor_data(self) -> list: screen = self._builder.get_object("Main_Window").get_screen() monitors = [] for m in range(screen.get_n_monitors()): monitors.append(screen.get_monitor_geometry(m)) - - for monitor in monitors: print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y)) return monitors - 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 + def get_main_window(self) -> Gtk.ApplicationWindow: return self._main_window + def get_builder(self) -> Gtk.Builder: return self._builder + def get_logger(self) -> Logger: return self._logger + def get_keybindings(self) -> Keybindings: return self._keybindings + def get_plugins_path(self) -> str: return self._PLUGINS_PATH + def get_icon_theme(self) -> str: return self._ICON_THEME - def get_success_color(self): return self._success_color - def get_warning_color(self): return self._warning_color - def get_error_color(self): return self._error_color + def get_success_color(self) -> str: return self._success_color + def get_warning_color(self) -> str: return self._warning_color + def get_error_color(self) -> str: return self._error_color diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index bd83627..065cf65 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -621,519 +621,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-new - - False - False - True - center-always - True - normal - True - True - False - False - center - - - - False - 5 - 5 - 5 - 5 - vertical - 2 - - - False - end - - - gtk-cancel - True - True - True - True - True - - - True - True - 0 - - - - - Create - create - True - True - True - Create File/Folder... - create_img - True - - - False - True - 1 - - - - - False - False - 0 - - - - - True - False - vertical - - - 500 - 26 - True - True - True - New File/Dir Name... - True - gtk-edit - False - False - False - False - New File/Dir Name... - - - False - True - 0 - - - - - True - False - 20 - vertical - True - - - True - False - - - True - False - 15 - Folder - - - - - - True - True - 0 - - - - - True - False - 15 - File - - - - - - True - True - 1 - - - - - False - False - 0 - - - - - True - True - File/Folder - True - - - False - False - 1 - - - - - False - True - 1 - - - - - False - True - 1 - - - - - - button9 - button10 - - True False gtk-execute - - 120 - False - False - True - center-always - True - normal - True - True - True - False - False - center - - - False - 5 - 5 - 5 - 5 - vertical - 2 - - - False - end - - - Overwrite - True - True - True - - - True - True - 0 - - - - - Overwrite (All) - True - True - True - - - True - True - 1 - - - - - Skip - True - True - True - - - True - True - 2 - - - - - Skip (All) - True - True - True - - - True - True - 3 - - - - - False - False - 0 - - - - - True - False - vertical - - - True - False - Filename already exists. Please rename or select an action. - 0.10000000149011612 - - - - - - False - True - 0 - - - - - True - False - 15 - 10 - - - True - False - Moving From: - - - False - True - 0 - - - - - True - False - - - True - True - 1 - - - - - False - True - 1 - - - - - True - False - 0 - - - True - True - 2 - - - - - True - False - 20 - 10 - - - True - False - Moving To: - - - False - True - 0 - - - - - True - False - - - True - True - 1 - - - - - False - True - 3 - - - - - True - False - 0 - - - True - True - 4 - - - - - True - False - 20 - - - True - False - Filename: - - - False - True - 0 - - - - - True - False - - - True - True - 1 - - - - - False - True - 5 - - - - - True - True - - - - False - True - 6 - - - - - True - False - 20 - top - start - - - - - - Rename - True - False - True - True - - - - True - True - 1 - True - - - - - Auto Rename - True - True - True - - - - True - True - 2 - True - - - - - Auto Rename All - True - True - True - - - - True - True - 3 - True - - - - - False - True - 7 - - - - - True - True - 1 - - - - - - button5 - button6 - button7 - button8 - - - True False @@ -1241,162 +733,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-media-forward - - False - False - True - center-always - True - normal - True - True - False - False - center - - - False - 5 - 5 - 5 - 5 - vertical - 2 - - - False - end - - - gtk-cancel - cancel_renames - True - True - True - True - - - True - True - 0 - - - - - Skip - skip_renames - True - True - True - skip_img - True - - - True - True - 1 - - - - - False - False - 0 - - - - - True - False - vertical - - - True - False - - - True - False - Rename: - - - False - True - 0 - - - - - True - False - - - True - True - 1 - - - - - False - True - 0 - - - - - 500 - 26 - True - True - True - Rename To: - True - gtk-edit - False - False - False - False - To: - - - - False - True - 1 - - - - - Rename - rename - True - True - True - rename_img - True - - - - False - True - 2 - - - - - True - True - 1 - - - - - - button2 - button1 - - True False @@ -2128,6 +1464,496 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + False + False + True + center-always + True + normal + True + True + False + False + center + Main_Window + + + False + 5 + 5 + 5 + 5 + vertical + 2 + + + False + end + + + gtk-cancel + cancel_renames + True + True + True + True + + + True + True + 0 + + + + + Skip + skip_renames + True + True + True + skip_img + True + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + + + True + False + Rename: + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + True + 0 + + + + + 500 + 26 + True + True + True + Rename To: + True + gtk-edit + False + False + False + False + To: + + + + False + True + 1 + + + + + Rename + rename + True + True + True + rename_img + True + + + + False + True + 2 + + + + + True + True + 1 + + + + + + button2 + button1 + + + + 120 + False + False + True + center-always + True + normal + True + True + True + False + False + center + Main_Window + + + False + 5 + 5 + 5 + 5 + vertical + 2 + + + False + end + + + Overwrite + True + True + True + + + True + True + 0 + + + + + Overwrite (All) + True + True + True + + + True + True + 1 + + + + + Skip + True + True + True + + + True + True + 2 + + + + + Skip (All) + True + True + True + + + True + True + 3 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + Filename already exists. Please rename or select an action. + 0.10000000149011612 + + + + + + False + True + 0 + + + + + True + False + 15 + 10 + + + True + False + Moving From: + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + 0 + + + True + True + 2 + + + + + True + False + 20 + 10 + + + True + False + Moving To: + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + True + 3 + + + + + True + False + 0 + + + True + True + 4 + + + + + True + False + 20 + + + True + False + Filename: + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + True + 5 + + + + + True + True + + + + False + True + 6 + + + + + True + False + 20 + top + start + + + + + + Rename + True + False + True + True + + + + True + True + 1 + True + + + + + Auto Rename + True + True + True + + + + True + True + 2 + True + + + + + Auto Rename All + True + True + True + + + + True + True + 3 + True + + + + + False + True + 7 + + + + + True + True + 1 + + + + + + button5 + button6 + button7 + button8 + + + 320 False @@ -2183,6 +2009,183 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + False + False + True + center-always + True + normal + True + True + False + False + center + Main_Window + + + + False + 5 + 5 + 5 + 5 + vertical + 2 + + + False + end + + + gtk-cancel + True + True + True + True + True + + + True + True + 0 + + + + + Create + create + True + True + True + Create File/Folder... + create_img + True + + + False + True + 1 + + + + + False + False + 0 + + + + + True + False + vertical + + + 500 + 26 + True + True + True + New File/Dir Name... + True + gtk-edit + False + False + False + False + New File/Dir Name... + + + False + True + 0 + + + + + True + False + 20 + vertical + True + + + True + False + + + True + False + 15 + Folder + + + + + + True + True + 0 + + + + + True + False + 15 + File + + + + + + True + True + 1 + + + + + False + False + 0 + + + + + True + True + File/Folder + True + + + False + False + 1 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + + button9 + button10 + + 240 420 -- 2.27.0 From 9442453d4378ca84b89233e9033c64a1aa9fd678 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 2 Apr 2022 23:23:33 -0500 Subject: [PATCH 33/41] Changed execute logic --- .../SolarFM/solarfm/__builtins__.py | 2 +- .../SolarFM/solarfm/context/controller.py | 4 ++-- .../mixins/ui/widget_file_action_mixin.py | 17 ++++++++++------- .../solarfm/context/mixins/ui/window_mixin.py | 5 +++-- .../shellfm/windows/tabs/utils/launcher.py | 17 +++++++---------- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index bd0a892..e690fe3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -37,7 +37,7 @@ class EventSystem(IPCServer): return None - def push_gui_event(self, eventevent: list) -> None: + def push_gui_event(self, event: list) -> None: if len(event) == 3: self._gui_events.append(event) return None 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 77043aa..5b40296 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 @@ -1,5 +1,5 @@ # Python imports -import os, gc, threading, time +import os, gc, threading, time, shlex # Lib imports import gi @@ -187,4 +187,4 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi 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) + tab.execute([f"{tab.terminal_app}"], start_dir=shlex.quote(dir)) 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 9a4013a..7421555 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 @@ -1,5 +1,5 @@ # Python imports -import os, time, threading +import os, time, threading, shlex # Lib imports import gi @@ -145,21 +145,24 @@ class WidgetFileActionMixin: current_dir = state.tab.get_current_directory() command = None for path in paths: - 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) + command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}" + state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory()) def archive_files(self, archiver_dialogue): state = self.get_current_state() - paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) + _paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) + paths = [] + for p in _paths: + paths.append(shlex.quote(p)) 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("%o", shlex.quote(save_target)) pre_command = pre_command.replace("%N", ' '.join(paths)) - command = f"{state.tab.terminal_app} -e '{pre_command}'" + command = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}" - state.tab.execute(command, start_dir=None, use_os_system=True) + state.tab.execute(shlex.split(command), start_dir=shlex.quote(state.tab.get_current_directory())) def rename_files(self): rename_label = self.builder.get_object("file_to_rename_label") 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 b94bdca..414e48c 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 @@ -1,7 +1,7 @@ # Python imports import copy -from os.path import isdir, isfile - +import traceback +from os.path import isdir # Lib imports import gi @@ -206,6 +206,7 @@ class WindowMixin(TabMixin): else: self.open_files() except Exception as e: + traceback.print_exc() self.display_message(self.error_color, f"{repr(e)}") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py index 22a14e3..d259963 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py @@ -1,6 +1,5 @@ # System import -import os, threading, subprocess - +import os, threading, subprocess, shlex # Lib imports @@ -41,19 +40,17 @@ class Launcher: else: command = ["xdg-open", file] - self.execute(command, use_shell=False) + self.execute(command) - def execute(self, command, start_dir=os.getenv("HOME"), use_os_system=None, use_shell=True): + def execute(self, command, start_dir=os.getenv("HOME"), use_shell=False): self.logger.debug(command) - if use_os_system: - os.system(command) - else: - subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=None, stderr=None, close_fds=True) + subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=None, stderr=None, close_fds=True) - def execute_and_return_thread_handler(self, command, start_dir=os.getenv("HOME"), use_shell=True): + # TODO: Return stdout and in handlers along with subprocess instead of sinking to null + def execute_and_return_thread_handler(self, command, start_dir=os.getenv("HOME"), use_shell=False): DEVNULL = open(os.devnull, 'w') - return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True) + return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=False, stdout=DEVNULL, stderr=DEVNULL, close_fds=False) @threaded def app_chooser_exec(self, app_info, uris): -- 2.27.0 From e4656c771aefa50ae2475e285c3952091481425c Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 10 Jun 2022 20:13:57 -0500 Subject: [PATCH 34/41] IPC updates, module rename and repath --- .../solarfm-0.0.1/SolarFM/solarfm/__init__.py | 2 +- .../solarfm-0.0.1/SolarFM/solarfm/app.py | 2 +- .../SolarFM/solarfm/context/__init__.py | 3 --- .../SolarFM/solarfm/core/__init__.py | 3 +++ .../solarfm/{context => core}/controller.py | 0 .../{context => core}/controller_data.py | 3 +++ .../{context => core}/mixins/__init__.py | 0 .../mixins/exception_hook_mixin.py | 0 .../mixins/show_hide_mixin.py | 0 .../{context => core}/mixins/ui/__init__.py | 0 .../{context => core}/mixins/ui/grid_mixin.py | 0 .../{context => core}/mixins/ui/pane_mixin.py | 0 .../{context => core}/mixins/ui/tab_mixin.py | 0 .../mixins/ui/widget_file_action_mixin.py | 0 .../mixins/ui/window_mixin.py | 0 .../{context => core}/mixins/ui_mixin.py | 0 .../{context => core}/signals/__init__.py | 0 .../signals/ipc_signals_mixin.py | 0 .../signals/keyboard_signals_mixin.py | 0 .../SolarFM/solarfm/utils/ipc_server.py | 21 +++++++++++++------ .../solarfm-0.0.1/SolarFM/tests/__init__.py | 3 +++ 21 files changed, 26 insertions(+), 11 deletions(-) delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/context/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/core/__init__.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/controller.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/controller_data.py (97%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/exception_hook_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/show_hide_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/ui/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/ui/grid_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/ui/pane_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/ui/tab_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/ui/widget_file_action_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/ui/window_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/mixins/ui_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/signals/__init__.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/signals/ipc_signals_mixin.py (100%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{context => core}/signals/keyboard_signals_mixin.py (100%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/tests/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py index 5416f23..cd8371b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__init__.py @@ -1,3 +1,3 @@ """ -Base module + Base module """ diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py index 383af1f..adc8fa4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -5,7 +5,7 @@ import os, inspect, time # Application imports from utils.settings import Settings -from context.controller import Controller +from core.controller import Controller from __builtins__ import EventSystem diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/__init__.py deleted file mode 100644 index 90cfadc..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" - Gtk Bound Signal Module -""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/__init__.py new file mode 100644 index 0000000..3641b89 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/__init__.py @@ -0,0 +1,3 @@ +""" + Core Module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py similarity index 97% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 12437a8..b94766b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -1,5 +1,6 @@ # Python imports import sys, os, signal +from dataclasses import dataclass # Lib imports import gi @@ -11,6 +12,7 @@ from shellfm.windows.controller import WindowController from plugins.plugins import Plugins +@dataclass(slots=True) class State: wid: int = None tid: int = None @@ -21,6 +23,7 @@ class State: class Controller_Data: """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """ + __slots__ = "settings", "builder", "logger", "keybindings", "trashman", "fm_controller", "window", "window1", "window2", "window3", "window4" def setup_controller_data(self, _settings: type) -> None: self.settings = _settings diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/__init__.py 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/core/mixins/exception_hook_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/exception_hook_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/show_hide_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/grid_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/pane_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/pane_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/pane_mixin.py 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/core/mixins/ui/tab_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/tab_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py 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/core/mixins/ui/widget_file_action_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/widget_file_action_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py 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/core/mixins/ui/window_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui/window_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/mixins/ui_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/__init__.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/__init__.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/__init__.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/ipc_signals_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/ipc_signals_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/ipc_signals_mixin.py 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/core/signals/keyboard_signals_mixin.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/context/signals/keyboard_signals_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index 27a0095..23d19b7 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -20,14 +20,20 @@ class IPCServer: def __init__(self, conn_type: str = "socket"): self.is_ipc_alive = False self._conn_type = conn_type + self.ipc_port = 4848 + self.ipc_address = '127.0.0.1' self.ipc_authkey = b'solarfm-ipc' self.ipc_timeout = 15.0 if conn_type == "socket": self.ipc_address = '/tmp/solarfm-ipc.sock' - else: - self.ipc_address = '127.0.0.1' - self.ipc_port = 4848 + elif conn_type == "full_network": + self.ipc_address = '0.0.0.0' + elif conn_type == "full_network_unsecured": + self.ipc_authkey = None + self.ipc_address = '0.0.0.0' + elif conn_type == "local_network_unsecured": + self.ipc_authkey = None @threaded @@ -37,8 +43,10 @@ class IPCServer: return listener = Listener(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) - else: + elif "unsecured" not in conn_type: listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + else: + listener = Listener((self.ipc_address, self.ipc_port)) self.is_ipc_alive = True @@ -80,9 +88,10 @@ class IPCServer: try: if self._conn_type == "socket": conn = Client(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) - else: + elif "unsecured" not in conn_type: conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) - + else: + conn = Client((self.ipc_address, self.ipc_port)) conn.send(message) conn.send('close connection') diff --git a/src/versions/solarfm-0.0.1/SolarFM/tests/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/tests/__init__.py new file mode 100644 index 0000000..fa1889d --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/tests/__init__.py @@ -0,0 +1,3 @@ +""" + Tests Module +""" -- 2.27.0 From ee086f67f496ad50dd5802626ed3d5194fde5f92 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 14 Jun 2022 17:12:25 -0500 Subject: [PATCH 35/41] IPC changes, further externalizing settings --- .../solarfm-0.0.1/SolarFM/solarfm/app.py | 2 +- .../solarfm/core/mixins/ui/grid_mixin.py | 2 +- .../shellfm/windows/tabs/icons/icon.py | 4 +- .../tabs/icons/mixins/desktopiconmixin.py | 12 +-- .../shellfm/windows/tabs/utils/launcher.py | 2 + .../shellfm/windows/tabs/utils/settings.py | 90 +++++++--------- .../SolarFM/solarfm/utils/ipc_server.py | 100 +++++++++--------- 7 files changed, 99 insertions(+), 113 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py index adc8fa4..aefcd5e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -16,7 +16,7 @@ class Application(EventSystem): def __init__(self, args, unknownargs): if not trace_debug: - event_system.create_ipc_server() + event_system.create_ipc_listener() time.sleep(0.1) if not event_system.is_ipc_alive: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py index a654c56..11721c8 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -52,7 +52,7 @@ class GridMixin: if not icon: path = f"{dir}/{file}" - icon = self.get_system_thumbnail(path, tab.SYS_ICON_WH[0]) + icon = self.get_system_thumbnail(path, tab.sys_icon_wh[0]) if not icon: icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py index 4ff9c4e..a503f61 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py @@ -30,7 +30,7 @@ class Icon(DesktopIconMixin, VideoIconMixin): if file.lower().endswith(self.fvideos): # Video icon thumbnl = self.create_thumbnail(dir, file) elif file.lower().endswith(self.fimages): # Image Icon - thumbnl = self.create_scaled_image(full_path, self.VIDEO_ICON_WH) + thumbnl = self.create_scaled_image(full_path, self.video_icon_wh) elif full_path.lower().endswith( ('.desktop',) ): # .desktop file parsing thumbnl = self.parse_desktop_files(full_path) @@ -46,7 +46,7 @@ class Icon(DesktopIconMixin, VideoIconMixin): if isfile(hash_img_pth) == False: self.generate_video_thumbnail(full_path, hash_img_pth) - thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) + thumbnl = self.create_scaled_image(hash_img_pth, self.video_icon_wh) if thumbnl == None: # If no icon whatsoever, return internal default thumbnl = GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py index 9f5ed2e..3f69ea3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py @@ -18,23 +18,23 @@ class DesktopIconMixin: if "steam" in icon: name = xdgObj.getName() file_hash = hashlib.sha256(str.encode(name)).hexdigest() - hash_img_pth = self.STEAM_ICONS_PTH + "/" + file_hash + ".jpg" + hash_img_pth = f"{self.STEAM_ICONS_PTH}/{file_hash}.jpg" if isfile(hash_img_pth) == True: # Use video sizes since headers are bigger - return self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) + return self.create_scaled_image(hash_img_pth, self.video_icon_wh) exec_str = xdgObj.getExec() parts = exec_str.split("steam://rungameid/") id = parts[len(parts) - 1] - imageLink = self.STEAM_BASE_URL + id + "/header.jpg" + imageLink = f"{self.STEAM_CDN_URL}{id}/header.jpg" proc = subprocess.Popen(["wget", "-O", hash_img_pth, imageLink]) proc.wait() # Use video thumbnail sizes since headers are bigger - return self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) + return self.create_scaled_image(hash_img_pth, self.video_icon_wh) elif os.path.exists(icon): - return self.create_scaled_image(icon, self.SYS_ICON_WH) + return self.create_scaled_image(icon, self.sys_icon_wh) else: alt_icon_path = "" @@ -43,7 +43,7 @@ class DesktopIconMixin: if alt_icon_path != "": break - return self.create_scaled_image(alt_icon_path, self.SYS_ICON_WH) + return self.create_scaled_image(alt_icon_path, self.sys_icon_wh) except Exception as e: print(".desktop icon generation issue:") print( repr(e) ) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py index d259963..7ad1d23 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py @@ -31,6 +31,8 @@ class Launcher: command = [self.music_app, file] elif lowerName.endswith(self.foffice): command = [self.office_app, file] + elif lowerName.endswith(self.fcode): + command = [self.code_app, file] elif lowerName.endswith(self.ftext): command = [self.text_app, file] elif lowerName.endswith(self.fpdf): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py index fb982af..928bf6a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py @@ -19,68 +19,16 @@ class Settings: CONFIG_FILE = f"{CONFIG_PATH}/settings.json" HIDE_HIDDEN_FILES = True - GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1) + GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1) DEFAULT_ICONS = f"{CONFIG_PATH}/icons" DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png" FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder - STEAM_BASE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/" ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", f"{USER_HOME}/.icons" ,] BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails" # Used for thumbnail generation ABS_THUMBS_PTH = f"{BASE_THUMBS_PTH}/normal" # Used for thumbnail generation STEAM_ICONS_PTH = f"{BASE_THUMBS_PTH}/steam_icons" - CONTAINER_ICON_WH = [128, 128] - VIDEO_ICON_WH = [128, 64] - SYS_ICON_WH = [56, 56] - - # CONTAINER_ICON_WH = [128, 128] - # VIDEO_ICON_WH = [96, 48] - # SYS_ICON_WH = [96, 96] - - subpath = "" - go_past_home = None - lock_folder = None - locked_folders = None - mplayer_options = None - music_app = None - media_app = None - image_app = None - office_app = None - pdf_app = None - text_app = None - file_manager_app = None - remux_folder_max_disk_usage = None - - if path.isfile(CONFIG_FILE): - with open(CONFIG_FILE) as infile: - settings = json.load(infile)["settings"] - - subpath = settings["base_of_home"] - HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False - FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"] - go_past_home = True if settings["go_past_home"] == "" else settings["go_past_home"] - lock_folder = True if settings["lock_folder"] == "true" else False - locked_folders = settings["locked_folders"].split("::::") - mplayer_options = settings["mplayer_options"].split() - music_app = settings["music_app"] - media_app = settings["media_app"] - image_app = settings["image_app"] - office_app = settings["office_app"] - pdf_app = settings["pdf_app"] - text_app = settings["text_app"] - file_manager_app = settings["file_manager_app"] - terminal_app = settings["terminal_app"] - remux_folder_max_disk_usage = settings["remux_folder_max_disk_usage"] - - # Filters - fvideos = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm') - foffice = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf') - fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga', '.webp') - ftext = ('.txt', '.text', '.sh', '.cfg', '.conf') - fmusic = ('.psf', '.mp3', '.ogg', '.flac', '.m4a') - fpdf = ('.pdf') - # Dir structure check if not path.isdir(REMUX_FOLDER): @@ -98,3 +46,39 @@ class Settings: if not os.path.exists(DEFAULT_ICONS): DEFAULT_ICONS = f"{USR_SOLARFM}/icons" DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png" + + with open(CONFIG_FILE) as f: + settings = json.load(f) + config = settings["config"] + + subpath = config["base_of_home"] + STEAM_CDN_URL = config["steam_cdn_url"] + FFMPG_THUMBNLR = FFMPG_THUMBNLR if config["thumbnailer_path"] == "" else config["thumbnailer_path"] + HIDE_HIDDEN_FILES = True if config["hide_hidden_files"] == "true" else False + go_past_home = True if config["go_past_home"] == "" else config["go_past_home"] + lock_folder = True if config["lock_folder"] == "true" else False + locked_folders = config["locked_folders"].split("::::") + mplayer_options = config["mplayer_options"].split() + music_app = config["music_app"] + media_app = config["media_app"] + image_app = config["image_app"] + office_app = config["office_app"] + pdf_app = config["pdf_app"] + code_app = config["code_app"] + text_app = config["text_app"] + terminal_app = config["terminal_app"] + container_icon_wh = config["container_icon_wh"] + video_icon_wh = config["video_icon_wh"] + sys_icon_wh = config["sys_icon_wh"] + file_manager_app = config["file_manager_app"] + remux_folder_max_disk_usage = config["remux_folder_max_disk_usage"] + + # Filters + filters = settings["filters"] + fcode = tuple(filters["code"]) + fvideos = tuple(filters["videos"]) + foffice = tuple(filters["office"]) + fimages = tuple(filters["images"]) + ftext = tuple(filters["text"]) + fmusic = tuple(filters["music"]) + fpdf = tuple(filters["pdf"]) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index 23d19b7..5ad037d 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -17,84 +17,84 @@ def threaded(fn): class IPCServer: """ Create a listener so that other SolarFM instances send requests back to existing instance. """ - def __init__(self, conn_type: str = "socket"): - self.is_ipc_alive = False - self._conn_type = conn_type - self.ipc_port = 4848 - self.ipc_address = '127.0.0.1' - self.ipc_authkey = b'solarfm-ipc' - self.ipc_timeout = 15.0 + def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"): + self.is_ipc_alive = False + self._ipc_port = 4848 + self._ipc_address = ipc_address + self._conn_type = conn_type + self._ipc_authkey = b'solarfm-ipc' + self._ipc_timeout = 15.0 if conn_type == "socket": - self.ipc_address = '/tmp/solarfm-ipc.sock' + self._ipc_address = '/tmp/solarfm-ipc.sock' elif conn_type == "full_network": - self.ipc_address = '0.0.0.0' + self._ipc_address = '0.0.0.0' elif conn_type == "full_network_unsecured": - self.ipc_authkey = None - self.ipc_address = '0.0.0.0' + self._ipc_authkey = None + self._ipc_address = '0.0.0.0' elif conn_type == "local_network_unsecured": - self.ipc_authkey = None + self._ipc_authkey = None @threaded - def create_ipc_server(self) -> None: + def create_ipc_listener(self) -> None: if self._conn_type == "socket": - if os.path.exists(self.ipc_address): + if os.path.exists(self._ipc_address): return - listener = Listener(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) - elif "unsecured" not in conn_type: - listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) + elif "unsecured" not in self._conn_type: + listener = Listener((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey) else: - listener = Listener((self.ipc_address, self.ipc_port)) + listener = Listener((self._ipc_address, self._ipc_port)) self.is_ipc_alive = True while True: conn = listener.accept() start_time = time.perf_counter() - - print(f"New Connection: {listener.last_accepted}") - while True: - msg = conn.recv() - if debug: - print(msg) - - if "FILE|" in msg: - file = msg.split("FILE|")[1].strip() - if file: - event_system.push_gui_event([None, "handle_file_from_ipc", (file,)]) - - conn.close() - break - - - if msg == 'close connection': - conn.close() - break - if msg == 'close server': - conn.close() - break - - # NOTE: Not perfect but insures we don't lock up the connection for too long. - end_time = time.perf_counter() - if (end - start) > self.ipc_timeout: - conn.close() + self.handle_message(conn, start_time) listener.close() + def handle_message(self, conn, start_time) -> None: + while True: + msg = conn.recv() + if debug: + print(msg) + + if "FILE|" in msg: + file = msg.split("FILE|")[1].strip() + if file: + event_system.push_gui_event([None, "handle_file_from_ipc", (file,)]) + + conn.close() + break + + + if msg in ['close connection', 'close server']: + conn.close() + break + + # NOTE: Not perfect but insures we don't lock up the connection for too long. + end_time = time.perf_counter() + if (end_time - start_time) > self._ipc_timeout: + conn.close() + break + def send_ipc_message(self, message: str = "Empty Data...") -> None: try: if self._conn_type == "socket": - conn = Client(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) - elif "unsecured" not in conn_type: - conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) + conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) + elif "unsecured" not in self._conn_type: + conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey) else: - conn = Client((self.ipc_address, self.ipc_port)) + conn = Client((self._ipc_address, self._ipc_port)) conn.send(message) - conn.send('close connection') conn.close() + except ConnectionRefusedError as e: + print("Connection refused...") except Exception as e: print(repr(e)) -- 2.27.0 From 793621745ac460874c77afd9c4b3038364d8d65e Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 14 Jun 2022 23:03:04 -0500 Subject: [PATCH 36/41] Update logging, update event system, update plugin structure --- plugins/template/__main__.py | 69 ++++++++++++----- plugins/youtube_download/__main__.py | 77 +++++++++++++------ .../SolarFM/solarfm/__builtins__.py | 20 ++--- .../solarfm-0.0.1/SolarFM/solarfm/app.py | 2 +- .../SolarFM/solarfm/core/controller.py | 32 ++++---- .../mixins/ui/widget_file_action_mixin.py | 13 ++-- .../SolarFM/solarfm/plugins/plugins.py | 12 ++- .../shellfm/windows/tabs/utils/launcher.py | 2 +- .../SolarFM/solarfm/utils/logger.py | 38 ++++----- .../SolarFM/solarfm/utils/settings.py | 2 +- 10 files changed, 164 insertions(+), 103 deletions(-) diff --git a/plugins/template/__main__.py b/plugins/template/__main__.py index ac0f74f..8264621 100644 --- a/plugins/template/__main__.py +++ b/plugins/template/__main__.py @@ -1,5 +1,5 @@ # Python imports -import sys, threading, subprocess, time +import os, threading, subprocess, time # Gtk imports import gi @@ -9,45 +9,74 @@ from gi.repository import Gtk # Application imports +# NOTE: Threads will not die with parent's destruction def threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() return wrapper +# NOTE: Insure threads die with parent's destruction +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + class Plugin: def __init__(self, builder, event_system): - self._plugin_name = "Example Plugin" - self._builder = builder - self._event_system = event_system - self._message = None - self._time_out = 5 + self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + self._plugin_name = "Example Plugin" + self._plugin_author = "John Doe" + self._plugin_version = "0.0.1" - button = Gtk.Button(label=self._plugin_name) - button.connect("button-release-event", self._do_action) + self._builder = builder + self._event_system = event_system + self._event_sleep_time = .5 + self._event_message = None - plugin_list = self._builder.get_object("plugin_socket") + button = Gtk.Button(label=self._plugin_name) + button.connect("button-release-event", self.send_message) + + # self._module_event_observer() # NOTE: Enable if you want the plugin to watch for events sent to it + + plugin_list = self._builder.get_object("plugin_socket") plugin_list.add(button) plugin_list.show_all() - @threaded - def _do_action(self, widget=None, eve=None): + def send_message(self, widget=None, eve=None): message = "Hello, World!" - self._event_system.push_gui_event(["some_type", "display_message", ("warning", message, None)]) + self._event_system.push_gui_event([self._plugin_name, "display_message", ("warning", message, None)]) - def set_message(self, data): - self._message = data - def get_plugin_name(self): return self._plugin_name + def get_plugin_author(self): + return self._plugin_author + + def get_plugin_version(self): + return self._plugin_version + def get_socket_id(self): return self._socket_id - def _run_timeout(self): - timeout = 0 - while not self._message and timeout < self._time_out: - time.sleep(1) - timeout += 1 + @daemon_threaded + def _module_event_observer(self): + while True: + time.sleep(self._event_sleep_time) + event = self._event_system.read_module_event() + if event: + try: + if event[0] is self._plugin_name: + target_id, method_target, data = self._event_system.consume_module_event() + + if not method_target: + self._event_message = data + else: + method = getattr(self.__class__, f"{method_target}") + data = method(*(self, *parameters)) + except Exception as e: + print(repr(e)) diff --git a/plugins/youtube_download/__main__.py b/plugins/youtube_download/__main__.py index f264fcf..bc81cbb 100644 --- a/plugins/youtube_download/__main__.py +++ b/plugins/youtube_download/__main__.py @@ -1,5 +1,5 @@ # Python imports -import os, sys, threading, subprocess, time +import os, threading, subprocess, time # Gtk imports import gi @@ -9,49 +9,78 @@ from gi.repository import Gtk # Application imports +# NOTE: Threads will not die with parent's destruction def threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() return wrapper +# NOTE: Insure threads die with parent's destruction +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + class Plugin: - def __init__(self, builder, event_system): - self._plugin_name = "Youtube Download" - self._builder = builder - self._event_system = event_system - self._message = None - self._time_out = 5 + def __init__(self, fm_builder, fm_event_system): + self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + self._plugin_name = "Youtube Download" + self._plugin_author = "ITDominator" + self._plugin_version = "0.0.1" - self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + self._fm_builder = fm_builder + self._fm_event_system = fm_event_system + self._event_sleep_time = .5 + self._fm_event_message = None - button = Gtk.Button(label=self._plugin_name) + + button = Gtk.Button(label=self._plugin_name) button.connect("button-release-event", self._do_download) - plugin_list = self._builder.get_object("plugin_socket") + self._module_event_observer() + + plugin_list = self._fm_builder.get_object("plugin_socket") plugin_list.add(button) plugin_list.show_all() + @daemon_threaded + def _module_event_observer(self): + while True: + time.sleep(self._event_sleep_time) + event = self._fm_event_system.read_module_event() + if event: + try: + if event[0] is self._plugin_name: + target_id, method_target, data = self._fm_event_system.consume_module_event() + + if not method_target: + self._fm_event_message = data + else: + method = getattr(self.__class__, f"{method_target}") + data = method(*(self, *parameters)) + except Exception as e: + print(repr(e)) + @threaded def _do_download(self, widget=None, eve=None): - self._event_system.push_gui_event([self._plugin_name, "get_current_state", ()]) - self._run_timeout() + self._fm_event_system.push_gui_event([self._plugin_name, "get_current_state", ()]) + while not self._fm_event_message: + pass - if self._message: - state = self._message - subprocess.Popen([f'{self.SCRIPT_PTH}/download.sh' , state.tab.get_current_directory()]) - self._message = None + state = self._fm_event_message + subprocess.Popen([f'{self.SCRIPT_PTH}/download.sh' , state.tab.get_current_directory()]) + self._fm_event_message = None - def set_message(self, data): - self._message = data - def get_plugin_name(self): return self._plugin_name - def _run_timeout(self): - timeout = 0 - while not self._message and timeout < self._time_out: - time.sleep(1) - timeout += 1 + def get_plugin_author(self): + return self._plugin_author + + def get_plugin_version(self): + return self._plugin_version diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index e690fe3..ae1c305 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -15,17 +15,17 @@ class EventSystem(IPCServer): def __init__(self): super(EventSystem, self).__init__() - # NOTE: The format used is list of [type, target, (data,)] Where: - # type is useful context for control flow, - # target is the method to call, - # data is the method parameters to give + # NOTE: The format used is list of ['who', target, (data,)] Where: + # who is the sender or target ID and is used for context and control flow, + # method_target is the method to call, + # data is the method parameters OR message data to give # Where data may be any kind of data self._gui_events = [] self._module_events = [] - # Makeshift fake "events" type system FIFO + # Makeshift "events" system FIFO def _pop_gui_event(self) -> None: if len(self._gui_events) > 0: return self._gui_events.pop(0) @@ -42,20 +42,20 @@ class EventSystem(IPCServer): self._gui_events.append(event) return None - raise Exception("Invald event format! Please do: [type, target, (data,)]") + raise Exception("Invald event format! Please do: ['sender_id': str, method_target: method, (data,): any]") def push_module_event(self, event: list) -> None: if len(event) == 3: self._module_events.append(event) return None - raise Exception("Invald event format! Please do: [type, target, (data,)]") + raise Exception("Invald event format! Please do: ['target_id': str, method_target: method, (data,): any]") def read_gui_event(self) -> list: - return self._gui_events[0] + return self._gui_events[0] if self._gui_events else None def read_module_event(self) -> list: - return self._module_events[0] + return self._module_events[0] if self._module_events else None def consume_gui_event(self) -> list: return self._pop_gui_event() @@ -69,6 +69,6 @@ class EventSystem(IPCServer): # __builtins__.update({"event_system": Builtins()}) builtins.app_name = "SolarFM" builtins.event_system = EventSystem() -builtins.event_sleep_time = 0.2 +builtins.event_sleep_time = 0.05 builtins.trace_debug = False builtins.debug = False diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py index aefcd5e..bedf202 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -17,7 +17,7 @@ class Application(EventSystem): def __init__(self, args, unknownargs): if not trace_debug: event_system.create_ipc_listener() - time.sleep(0.1) + time.sleep(0.05) if not event_system.is_ipc_alive: if unknownargs: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 5b40296..4e0491a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -1,5 +1,5 @@ # Python imports -import os, gc, threading, time, shlex +import os, gc, threading, time # Lib imports import gi @@ -14,7 +14,14 @@ from .signals.keyboard_signals_mixin import KeyboardSignalsMixin from .controller_data import Controller_Data +# NOTE: Threads will not die with parent's destruction def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Insure threads die with parent's destruction +def daemon_threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() return wrapper @@ -62,20 +69,20 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi event = event_system.consume_gui_event() if event: try: - type, target, data = event - if type: - method = getattr(self.__class__, "handle_gui_event_and_set_message") - GLib.idle_add(method, *(self, type, target, data)) + sender_id, method_target, parameters = event + if sender_id: + method = getattr(self.__class__, "handle_gui_event_and_return_message") + GLib.idle_add(method, *(self, sender_id, method_target, parameters)) else: - method = getattr(self.__class__, target) - GLib.idle_add(method, *(self, *data,)) + method = getattr(self.__class__, method_target) + GLib.idle_add(method, *(self, *parameters,)) except Exception as e: print(repr(e)) - def handle_gui_event_and_set_message(self, type, target, parameters): - method = getattr(self.__class__, f"{target}") + def handle_gui_event_and_return_message(self, sender, method_target, parameters): + method = getattr(self.__class__, f"{method_target}") data = method(*(self, *parameters)) - self.plugins.send_message_to_plugin(type, data) + event_system.push_module_event([sender, None, data]) def save_load_session(self, action="save_session"): wid, tid = self.fm_controller.get_active_wid_and_tid() @@ -110,7 +117,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def load_session(self, session_json): if debug: - print(f"Session Data: {session_json}") + self.logger.debug(f"Session Data: {session_json}") self.ctrl_down = False self.shift_down = False @@ -186,5 +193,4 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi 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}"], start_dir=shlex.quote(dir)) + tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory()) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py index 7421555..84005de 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py @@ -45,7 +45,7 @@ class WidgetFileActionMixin: watcher = tab.get_dir_watcher() watcher.cancel() if debug: - print(f"Watcher Is Cancelled: {watcher.is_cancelled()}") + self.logger.debug(f"Watcher Is Cancelled: {watcher.is_cancelled()}") cur_dir = tab.get_current_directory() @@ -64,7 +64,7 @@ class WidgetFileActionMixin: Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: if debug: - print(eve_type) + self.logger.debug(eve_type) if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: self.update_on_soft_lock_end(data[0]) @@ -85,7 +85,6 @@ class WidgetFileActionMixin: if (current_time - last_update_time) > 0.6: lock = False - self.soft_update_lock.pop(tab_widget, None) GLib.idle_add(self.update_on_soft_lock_end, *(tab_widget,)) @@ -427,10 +426,10 @@ class WidgetFileActionMixin: start = "-copy" if debug: - print(f"Path: {full_path}") - print(f"Base Path: {base_path}") - print(f'Name: {file_name}') - print(f"Extension: {extension}") + self.logger.debug(f"Path: {full_path}") + self.logger.debug(f"Base Path: {base_path}") + self.logger.debug(f'Name: {file_name}') + self.logger.debug(f"Extension: {extension}") i = 2 while target.query_exists(): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index c28817b..92cf234 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -12,6 +12,8 @@ from gi.repository import Gtk, Gio class Plugin: name: str = None + author: str = None + version: str = None module: str = None reference: type = None @@ -61,6 +63,9 @@ class Plugins: plugin_reference = app.Plugin(self._builder, event_system) plugin = Plugin() plugin.name = plugin_reference.get_plugin_name() + plugin.author = plugin_reference.get_plugin_author() + plugin.version = plugin_reference.get_plugin_version() + plugin.module = path plugin.reference = plugin_reference @@ -74,10 +79,3 @@ class Plugins: def reload_plugins(self, file: str = None) -> None: print(f"Reloading plugins... stub.") - - def send_message_to_plugin(self, target: str , data: type) -> None: - print("Trying to send message to plugin...") - for plugin in self._plugin_collection: - if target in plugin.name: - print('Found plugin; posting message...') - plugin.reference.set_message(data) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py index 7ad1d23..eab19cd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py @@ -59,7 +59,7 @@ class Launcher: app_info.launch_uris_async(uris) def remux_video(self, hash, file): - remux_vid_pth = self.REMUX_FOLDER + "/" + hash + ".mp4" + remux_vid_pth = "{self.REMUX_FOLDER}/{hash}.mp4" self.logger.debug(remux_vid_pth) if not os.path.isfile(remux_vid_pth): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py index 63db6e2..c33444f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py @@ -5,39 +5,39 @@ import os, logging class Logger: - def __init__(self, config_path: str): - self._CONFIG_PATH = config_path - - def get_logger(self, loggerName: str = "NO_LOGGER_NAME_PASSED", createFile: bool = True) -> logging.Logger: - """ - Create a new logging object and return it. - :note: - NOSET # Don't know the actual log level of this... (defaulting or literally none?) - Log Levels (From least to most) + """ + Create a new logging object and return it. + :note: + NOSET # Don't know the actual log level of this... (defaulting or literally none?) + Log Levels (From least to most) Type Value CRITICAL 50 ERROR 40 WARNING 30 INFO 20 DEBUG 10 - :param loggerName: Sets the name of the logger object. (Used in log lines) - :param createFile: Whether we create a log file or just pump to terminal + :param loggerName: Sets the name of the logger object. (Used in log lines) + :param createFile: Whether we create a log file or just pump to terminal - :return: the logging object we created - """ + :return: the logging object we created + """ - globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels - chLogLevel = logging.CRITICAL # Prety musch the only one we change ever - fhLogLevel = logging.DEBUG + def __init__(self, config_path: str, _ch_log_lvl = logging.CRITICAL, _fh_log_lvl = logging.INFO): + self._CONFIG_PATH = config_path + self.global_lvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels + self.ch_log_lvl = _ch_log_lvl # Prety much the only one we ever change + self.fh_log_lvl = _fh_log_lvl + + def get_logger(self, loggerName: str = "NO_LOGGER_NAME_PASSED", createFile: bool = True) -> logging.Logger: log = logging.getLogger(loggerName) - log.setLevel(globalLogLvl) + log.setLevel(self.global_lvl) # Set our log output styles fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S') cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s') ch = logging.StreamHandler() - ch.setLevel(level=chLogLevel) + ch.setLevel(level=self.ch_log_lvl) ch.setFormatter(cFormatter) log.addHandler(ch) @@ -49,7 +49,7 @@ class Logger: os.mkdir(folder) fh = logging.FileHandler(file) - fh.setLevel(level=fhLogLevel) + fh.setLevel(level=self.fh_log_lvl) fh.setFormatter(fFormatter) log.addHandler(fh) 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 6d93e77..80bbc01 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 @@ -59,7 +59,7 @@ class Settings: self._keybindings.configure(keybindings) self._main_window = None - self._logger = Logger(self._CONFIG_PATH).get_logger() + self._logger = Logger(self._CONFIG_PATH, _fh_log_lvl=20).get_logger() self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) -- 2.27.0 From 111c5358766f5c8a2f30be97602cfb0ef1444b06 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 18 Jun 2022 22:27:17 -0500 Subject: [PATCH 37/41] Small fixes --- plugins/template/__main__.py | 17 ++++++----- plugins/youtube_download/__main__.py | 28 +++++++++---------- .../SolarFM/solarfm/core/controller.py | 2 +- .../solarfm/core/mixins/ui/grid_mixin.py | 8 +----- .../usr/share/solarfm/Main_Window.glade | 8 +++--- 5 files changed, 28 insertions(+), 35 deletions(-) diff --git a/plugins/template/__main__.py b/plugins/template/__main__.py index 8264621..110ce10 100644 --- a/plugins/template/__main__.py +++ b/plugins/template/__main__.py @@ -9,13 +9,13 @@ from gi.repository import Gtk # Application imports -# NOTE: Threads will not die with parent's destruction +# NOTE: Threads WILL NOT die with parent's destruction. def threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() return wrapper -# NOTE: Insure threads die with parent's destruction +# NOTE: Threads WILL die with parent's destruction. def daemon_threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() @@ -39,18 +39,11 @@ class Plugin: button = Gtk.Button(label=self._plugin_name) button.connect("button-release-event", self.send_message) - # self._module_event_observer() # NOTE: Enable if you want the plugin to watch for events sent to it - plugin_list = self._builder.get_object("plugin_socket") plugin_list.add(button) plugin_list.show_all() - def send_message(self, widget=None, eve=None): - message = "Hello, World!" - self._event_system.push_gui_event([self._plugin_name, "display_message", ("warning", message, None)]) - - def get_plugin_name(self): return self._plugin_name @@ -63,6 +56,12 @@ class Plugin: def get_socket_id(self): return self._socket_id + + def send_message(self, widget=None, eve=None): + message = "Hello, World!" + self._event_system.push_gui_event([self._plugin_name, "display_message", ("warning", message, None)]) + + @daemon_threaded def _module_event_observer(self): while True: diff --git a/plugins/youtube_download/__main__.py b/plugins/youtube_download/__main__.py index bc81cbb..06d7e06 100644 --- a/plugins/youtube_download/__main__.py +++ b/plugins/youtube_download/__main__.py @@ -9,13 +9,13 @@ from gi.repository import Gtk # Application imports -# NOTE: Threads will not die with parent's destruction +# NOTE: Threads WILL NOT die with parent's destruction. def threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() return wrapper -# NOTE: Insure threads die with parent's destruction +# NOTE: Threads WILL die with parent's destruction. def daemon_threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() @@ -36,16 +36,26 @@ class Plugin: self._event_sleep_time = .5 self._fm_event_message = None + self._module_event_observer() button = Gtk.Button(label=self._plugin_name) button.connect("button-release-event", self._do_download) - self._module_event_observer() - plugin_list = self._fm_builder.get_object("plugin_socket") plugin_list.add(button) plugin_list.show_all() + + def get_plugin_name(self): + return self._plugin_name + + def get_plugin_author(self): + return self._plugin_author + + def get_plugin_version(self): + return self._plugin_version + + @daemon_threaded def _module_event_observer(self): while True: @@ -74,13 +84,3 @@ class Plugin: state = self._fm_event_message subprocess.Popen([f'{self.SCRIPT_PTH}/download.sh' , state.tab.get_current_directory()]) self._fm_event_message = None - - - def get_plugin_name(self): - return self._plugin_name - - def get_plugin_author(self): - return self._plugin_author - - def get_plugin_version(self): - return self._plugin_version diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 4e0491a..deb4493 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -62,7 +62,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi Gtk.main_quit() - @threaded + @daemon_threaded def gui_event_observer(self): while True: time.sleep(event_sleep_time) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py index 11721c8..8d4d065 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -43,13 +43,6 @@ class GridMixin: GLib.idle_add(self.update_store, *(i, store, icon, tab, dir, file,)) def update_store(self, i, store, icon, tab, dir, file): - while True: - try: - itr = store.get_iter(i) - break - except: - pass - if not icon: path = f"{dir}/{file}" icon = self.get_system_thumbnail(path, tab.sys_icon_wh[0]) @@ -57,6 +50,7 @@ class GridMixin: if not icon: icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) + itr = store.get_iter(i) store.set_value(itr, 0, icon) def get_system_thumbnail(self, filename, size): diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 065cf65..56fdb37 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1266,7 +1266,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False - True + False @@ -1304,7 +1304,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False - True + False
@@ -1356,7 +1356,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
False - True + False
@@ -1393,7 +1393,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
False - True + False
-- 2.27.0 From 8f64066049f71dbec3a5b59b88d3383880bc34ef Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 6 Jul 2022 23:19:41 -0500 Subject: [PATCH 38/41] Restructured plugin system and examples --- plugins/README.md | 58 ++++++++++++ plugins/README.txt | 2 - plugins/template/{__main__.py => plugin.py} | 49 +++++----- .../{__main__.py => plugin.py} | 50 +++++------ .../solarfm-0.0.1/SolarFM/debugger.sh | 18 ++++ .../solarfm-0.0.1/SolarFM/solarfm/app.py | 2 +- .../solarfm/core/mixins/show_hide_mixin.py | 8 +- .../mixins/ui/widget_file_action_mixin.py | 22 +++-- .../core/signals/keyboard_signals_mixin.py | 1 - .../SolarFM/solarfm/plugins/plugins.py | 89 +++++++++++++++---- .../SolarFM/solarfm/utils/settings.py | 4 +- 11 files changed, 213 insertions(+), 90 deletions(-) create mode 100644 plugins/README.md delete mode 100644 plugins/README.txt rename plugins/template/{__main__.py => plugin.py} (62%) rename plugins/youtube_download/{__main__.py => plugin.py} (62%) create mode 100755 src/versions/solarfm-0.0.1/SolarFM/debugger.sh diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..6c3ef9e --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,58 @@ +### Note +Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' permissions data but don't have to if not directly interacted with. +Plugins must have a run method defined; though, you do not need to necessarily do anything within it. The run method implies that the passed in event system or other data is ready for the plugin to use. + + +### Manifest +``` +class Manifest: + path: str = os.path.dirname(os.path.realpath(__file__)) + name: str = "Example Plugin" + author: str = "John Doe" + version: str = "0.0.1" + support: str = "" + permissions: {} = { + 'ui_target': "plugin_control_list", + 'pass_fm_events': "true" + + } +``` + + +### Permissions +``` +permissions: {} = { + 'ui_target': "plugin_control_list", + 'ui_target_id': "" # Only needed if using "other" in "ui_target". See below for predefined "ui_target" options... + 'pass_fm_events': "true" # If empty or undefined will be ignored. +} +``` + +UI Targets: +
    +
  • main_Window
  • +
  • main_menu_bar
  • +
  • path_menu_bar
  • +
  • plugin_control_list
  • +
  • window_(1-4)
  • +
  • context_menu
  • +
  • other
  • +
+ +### Methods +``` +# Must define and return a widget if "ui_target" is defined. +def get_ui_element(self): + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._do_download) + return button + +# Must define in plugin if "pass_fm_events" is set to "true" string. +def set_fm_event_system(self, fm_event_system): + self._fm_event_system = fm_event_system + +# Must define regardless if needed. Can just pass if plugin does stuff in its __init__ +def run(self): + self._module_event_observer() + +``` diff --git a/plugins/README.txt b/plugins/README.txt deleted file mode 100644 index 4173ddd..0000000 --- a/plugins/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -### Note -Copy the example and rename it to your desired name. The Main class and passed in arguments are required. You don't necessarily need to use the passed in socket_id or event_system. diff --git a/plugins/template/__main__.py b/plugins/template/plugin.py similarity index 62% rename from plugins/template/__main__.py rename to plugins/template/plugin.py index 110ce10..f991579 100644 --- a/plugins/template/__main__.py +++ b/plugins/template/plugin.py @@ -24,42 +24,41 @@ def daemon_threaded(fn): -class Plugin: - def __init__(self, builder, event_system): - self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) - self._plugin_name = "Example Plugin" - self._plugin_author = "John Doe" - self._plugin_version = "0.0.1" +class Manifest: + path: str = os.path.dirname(os.path.realpath(__file__)) + name: str = "Example Plugin" + author: str = "John Doe" + version: str = "0.0.1" + support: str = "" + permissions: {} = { + 'ui_target': "plugin_control_list", + 'pass_fm_events': "true" - self._builder = builder + } + + +class Plugin(Manifest): + def __init__(self): self._event_system = event_system self._event_sleep_time = .5 self._event_message = None - button = Gtk.Button(label=self._plugin_name) + + def get_ui_element(self): + button = Gtk.Button(label=self.name) button.connect("button-release-event", self.send_message) + return button - plugin_list = self._builder.get_object("plugin_socket") - plugin_list.add(button) - plugin_list.show_all() + def set_fm_event_system(self, fm_event_system): + self.event_system = fm_event_system - - def get_plugin_name(self): - return self._plugin_name - - def get_plugin_author(self): - return self._plugin_author - - def get_plugin_version(self): - return self._plugin_version - - def get_socket_id(self): - return self._socket_id + def run(self): + self._module_event_observer() def send_message(self, widget=None, eve=None): message = "Hello, World!" - self._event_system.push_gui_event([self._plugin_name, "display_message", ("warning", message, None)]) + self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)]) @daemon_threaded @@ -69,7 +68,7 @@ class Plugin: event = self._event_system.read_module_event() if event: try: - if event[0] is self._plugin_name: + if event[0] is self.name: target_id, method_target, data = self._event_system.consume_module_event() if not method_target: diff --git a/plugins/youtube_download/__main__.py b/plugins/youtube_download/plugin.py similarity index 62% rename from plugins/youtube_download/__main__.py rename to plugins/youtube_download/plugin.py index 06d7e06..1407471 100644 --- a/plugins/youtube_download/__main__.py +++ b/plugins/youtube_download/plugin.py @@ -24,36 +24,36 @@ def daemon_threaded(fn): -class Plugin: - def __init__(self, fm_builder, fm_event_system): - self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) - self._plugin_name = "Youtube Download" - self._plugin_author = "ITDominator" - self._plugin_version = "0.0.1" +class Manifest: + path: str = os.path.dirname(os.path.realpath(__file__)) + name: str = "Youtube Download" + author: str = "ITDominator" + version: str = "0.0.1" + support: str = "" + permissions: {} = { + 'ui_target': "plugin_control_list", + 'pass_fm_events': "true" - self._fm_builder = fm_builder - self._fm_event_system = fm_event_system + } + + +class Plugin(Manifest): + def __init__(self): + self._fm_event_system = None self._event_sleep_time = .5 self._fm_event_message = None - self._module_event_observer() - button = Gtk.Button(label=self._plugin_name) + def get_ui_element(self): + button = Gtk.Button(label=self.name) button.connect("button-release-event", self._do_download) + return button - plugin_list = self._fm_builder.get_object("plugin_socket") - plugin_list.add(button) - plugin_list.show_all() + def set_fm_event_system(self, fm_event_system): + self._fm_event_system = fm_event_system - - def get_plugin_name(self): - return self._plugin_name - - def get_plugin_author(self): - return self._plugin_author - - def get_plugin_version(self): - return self._plugin_version + def run(self): + self._module_event_observer() @daemon_threaded @@ -63,7 +63,7 @@ class Plugin: event = self._fm_event_system.read_module_event() if event: try: - if event[0] is self._plugin_name: + if event[0] is self.name: target_id, method_target, data = self._fm_event_system.consume_module_event() if not method_target: @@ -77,10 +77,10 @@ class Plugin: @threaded def _do_download(self, widget=None, eve=None): - self._fm_event_system.push_gui_event([self._plugin_name, "get_current_state", ()]) + self._fm_event_system.push_gui_event([self.name, "get_current_state", ()]) while not self._fm_event_message: pass state = self._fm_event_message - subprocess.Popen([f'{self.SCRIPT_PTH}/download.sh' , state.tab.get_current_directory()]) + subprocess.Popen([f'{self.path}/download.sh' , state.tab.get_current_directory()]) self._fm_event_message = None diff --git a/src/versions/solarfm-0.0.1/SolarFM/debugger.sh b/src/versions/solarfm-0.0.1/SolarFM/debugger.sh new file mode 100755 index 0000000..db15691 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/debugger.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# . CONFIG.sh + +# set -o xtrace ## To debug scripts +# set -o errexit ## To exit on error +# set -o errunset ## To exit if a variable is referenced but not set + + +function main() { + SCRIPTPATH="$( cd "$(dirname "")" >/dev/null 2>&1 ; pwd -P )" + cd "${SCRIPTPATH}" + echo "Working Dir: " $(pwd) + + source '/home/abaddon/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/activate' + python -m pudb $(pwd)/solarfm/__main__.py; bash +} +main "$@"; diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py index bedf202..bc414b5 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -30,7 +30,7 @@ class Application(EventSystem): message = f"FILE|{args.new_tab}" event_system.send_ipc_message(message) - raise Exception("IPC Server Exists: Will send path(s) to it and close...") + raise Exception("IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/solarfm-ipc.sock") settings = Settings() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py index 687a47a..cf77ba0 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py @@ -97,16 +97,16 @@ class ShowHideMixin: def show_plugins_popup(self, widget=None, eve=None): - self.builder.get_object("plugin_list").popup() + self.builder.get_object("plugin_controls").popup() def hide_plugins_popup(self, widget=None, eve=None): - self.builder.get_object("plugin_list").hide() + self.builder.get_object("plugin_controls").hide() def show_context_menu(self, widget=None, eve=None): - self.builder.get_object("context_menu").run() + self.builder.get_object("context_menu_popup").run() def hide_context_menu(self, widget=None, eve=None): - self.builder.get_object("context_menu").hide() + self.builder.get_object("context_menu_popup").hide() def show_new_file_menu(self, widget=None, eve=None): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py index 84005de..9fc5a83 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py @@ -63,15 +63,15 @@ class WidgetFileActionMixin: if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: - if debug: - self.logger.debug(eve_type) + if debug: + self.logger.debug(eve_type) - if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: - self.update_on_soft_lock_end(data[0]) - elif data[0] in self.soft_update_lock.keys(): - self.soft_update_lock[data[0]]["last_update_time"] = time.time() - else: - self.soft_lock_countdown(data[0]) + if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: + self.update_on_soft_lock_end(data[0]) + elif data[0] in self.soft_update_lock.keys(): + self.soft_update_lock[data[0]]["last_update_time"] = time.time() + else: + self.soft_lock_countdown(data[0]) @threaded def soft_lock_countdown(self, tab_widget): @@ -149,10 +149,7 @@ class WidgetFileActionMixin: def archive_files(self, archiver_dialogue): state = self.get_current_state() - _paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) - paths = [] - for p in _paths: - paths.append(shlex.quote(p)) + paths = [shlex.quote(p) for p in 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() @@ -175,6 +172,7 @@ class WidgetFileActionMixin: rename_input.set_text(entry) self.show_edit_file_menu(rename_input) + if self.skip_edit: self.skip_edit = False continue diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py index bc2f762..4cb0108 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py @@ -44,7 +44,6 @@ class KeyboardSignalsMixin: if "alt" in keyname: self.alt_down = False - mapping = self.keybindings.lookup(event) if mapping: getattr(self, mapping)() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index 92cf234..44c5e63 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -11,13 +11,16 @@ from gi.repository import Gtk, Gio class Plugin: + path: str = None name: str = None author: str = None version: str = None - module: str = None + suppoert: str = None + permissions:{} = None reference: type = None + class Plugins: """Plugins controller""" @@ -44,7 +47,6 @@ class Plugins: Gio.FileMonitorEvent.MOVED_OUT]: self.reload_plugins(file) - # @threaded def load_plugins(self, file: str = None) -> None: print(f"Loading plugins...") parent_path = os.getcwd() @@ -53,23 +55,11 @@ class Plugins: try: path = join(self._plugins_path, file) if isdir(path): - os.chdir(path) + module = self.load_plugin_module(path, file) + plugin = self.collect_info(module, path) + loading_data = self.parse_permissions(plugin) - sys.path.insert(0, path) - spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) - app = importlib.util.module_from_spec(spec) - spec.loader.exec_module(app) - - plugin_reference = app.Plugin(self._builder, event_system) - plugin = Plugin() - plugin.name = plugin_reference.get_plugin_name() - plugin.author = plugin_reference.get_plugin_author() - plugin.version = plugin_reference.get_plugin_version() - - plugin.module = path - plugin.reference = plugin_reference - - self._plugin_collection.append(plugin) + self.execute_plugin(module, plugin, loading_data) except Exception as e: print("Malformed plugin! Not loading!") traceback.print_exc() @@ -77,5 +67,68 @@ class Plugins: os.chdir(parent_path) + def load_plugin_module(self, path, file): + os.chdir(path) + sys.path.insert(0, path) + spec = importlib.util.spec_from_file_location(file, join(path, "plugin.py")) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + return module + + def collect_info(self, module, path) -> Plugin: + plugin = Plugin() + plugin.path = module.Manifest.path + plugin.name = module.Manifest.name + plugin.author = module.Manifest.author + plugin.version = module.Manifest.version + plugin.support = module.Manifest.support + plugin.permissions = module.Manifest.permissions + + return plugin + + def parse_permissions(self, plugin): + loading_data = {} + permissions = plugin.permissions + keys = permissions.keys() + + if "ui_target" in keys: + if permissions["ui_target"] in [ + "none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list", + "context_menu", "window_1", "window_2", "window_3", "window_4" + ]: + if permissions["ui_target"] == "other": + if "ui_target_id" in keys: + loading_data["ui_target"] = self._builder.get_object(permissions["ui_target_id"]) + if loading_data["ui_target"] == None: + raise Exception('Invalid "ui_target_id" given in permissions. Must have one if setting "ui_target" to "other"...') + else: + raise Exception('Invalid "ui_target_id" given in permissions. Must have one if setting "ui_target" to "other"...') + else: + loading_data["ui_target"] = self._builder.get_object(permissions["ui_target"]) + else: + raise Exception('Unknown "ui_target" given in permissions.') + + + if "pass_fm_events" in keys: + if permissions["pass_fm_events"] in ["true"]: + loading_data["pass_fm_events"] = True + + return loading_data + + def execute_plugin(self, module: type, plugin: Plugin, loading_data: []): + plugin.reference = module.Plugin() + keys = loading_data.keys() + + if "ui_target" in keys: + loading_data["ui_target"].add(plugin.reference.get_ui_element()) + loading_data["ui_target"].show_all() + + if "pass_fm_events" in keys: + plugin.reference.set_fm_event_system(event_system) + + plugin.reference.run() + self._plugin_collection.append(plugin) + def reload_plugins(self, file: str = None) -> None: print(f"Reloading plugins... stub.") 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 80bbc01..3826e3d 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 @@ -66,7 +66,7 @@ class Settings: def create_window(self) -> None: # Get window and connect signals - self._main_window = self._builder.get_object("Main_Window") + self._main_window = self._builder.get_object("main_window") self._set_window_data() def _set_window_data(self) -> None: @@ -93,7 +93,7 @@ class Settings: cr.set_operator(cairo.OPERATOR_OVER) def get_monitor_data(self) -> list: - screen = self._builder.get_object("Main_Window").get_screen() + screen = self._builder.get_object("main_window").get_screen() monitors = [] for m in range(screen.get_n_monitors()): monitors.append(screen.get_monitor_geometry(m)) -- 2.27.0 From bcc04dda3c2978548bcd7769c73e968f976eb852 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 7 Jul 2022 12:51:51 -0500 Subject: [PATCH 39/41] Plugin permission additions --- plugins/README.md | 9 ++-- plugins/template/plugin.py | 13 +++-- plugins/youtube_download/plugin.py | 7 ++- .../SolarFM/solarfm/core/controller.py | 4 ++ .../solarfm/core/mixins/ui/grid_mixin.py | 47 ++++++++++++++++++- .../solarfm/core/mixins/ui/tab_mixin.py | 4 +- .../core/signals/keyboard_signals_mixin.py | 10 +++- .../SolarFM/solarfm/plugins/plugins.py | 11 ++++- .../SolarFM/solarfm/utils/keybindings.py | 11 +++++ 9 files changed, 99 insertions(+), 17 deletions(-) diff --git a/plugins/README.md b/plugins/README.md index 6c3ef9e..9495829 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -3,7 +3,7 @@ Copy the example and rename it to your desired name. Plugins define a ui target Plugins must have a run method defined; though, you do not need to necessarily do anything within it. The run method implies that the passed in event system or other data is ready for the plugin to use. -### Manifest +### Manifest Example (All are required.) ``` class Manifest: path: str = os.path.dirname(os.path.realpath(__file__)) @@ -23,8 +23,11 @@ class Manifest: ``` permissions: {} = { 'ui_target': "plugin_control_list", - 'ui_target_id': "" # Only needed if using "other" in "ui_target". See below for predefined "ui_target" options... - 'pass_fm_events': "true" # If empty or undefined will be ignored. + 'ui_target_id': "" # Only needed if using "other" in "ui_target". See below for predefined "ui_target" options... + 'pass_fm_events': "true" # If empty or not present will be ignored. + 'bind_keys': [f"{name}||send_message:f"], + f"{name}||do_save:s"] # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right. + } ``` diff --git a/plugins/template/plugin.py b/plugins/template/plugin.py index f991579..e514ec5 100644 --- a/plugins/template/plugin.py +++ b/plugins/template/plugin.py @@ -32,8 +32,8 @@ class Manifest: support: str = "" permissions: {} = { 'ui_target': "plugin_control_list", - 'pass_fm_events': "true" - + 'pass_fm_events': "true", + 'bind_keys': [f"{name}||send_message:f"] } @@ -58,6 +58,7 @@ class Plugin(Manifest): def send_message(self, widget=None, eve=None): message = "Hello, World!" + print("here") self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)]) @@ -68,13 +69,17 @@ class Plugin(Manifest): event = self._event_system.read_module_event() if event: try: - if event[0] is self.name: + if event[0] == self.name: target_id, method_target, data = self._event_system.consume_module_event() if not method_target: self._event_message = data else: method = getattr(self.__class__, f"{method_target}") - data = method(*(self, *parameters)) + if data: + data = method(*(self, *data)) + else: + method(*(self,)) except Exception as e: + print("ewww here") print(repr(e)) diff --git a/plugins/youtube_download/plugin.py b/plugins/youtube_download/plugin.py index 1407471..e183c4f 100644 --- a/plugins/youtube_download/plugin.py +++ b/plugins/youtube_download/plugin.py @@ -63,14 +63,17 @@ class Plugin(Manifest): event = self._fm_event_system.read_module_event() if event: try: - if event[0] is self.name: + if event[0] == self.name: target_id, method_target, data = self._fm_event_system.consume_module_event() if not method_target: self._fm_event_message = data else: method = getattr(self.__class__, f"{method_target}") - data = method(*(self, *parameters)) + if data: + data = method(*(self, *data)) + else: + method(*(self,)) except Exception as e: print(repr(e)) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index deb4493..3b7ba50 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -84,6 +84,10 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi data = method(*(self, *parameters)) event_system.push_module_event([sender, None, data]) + def handle_plugin_key_event(self, sender, method_target, parameters=()): + event_system.push_module_event([sender, method_target, parameters]) + + 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) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py index 8d4d065..01ff051 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -19,6 +19,39 @@ def threaded(fn): +# NOTE: Consider trying to use Gtk.TreeView with css that turns it into a grid... +# Can possibly use this to dynamicly load icons instead... +class Icon(Gtk.HBox): + def __init__(self, tab, dir, file): + super(Icon, self).__init__() + + self.load_icon(tab, dir, file) + + @threaded + def load_icon(self, tab, dir, file): + icon = tab.create_icon(dir, file) + + if not icon: + path = f"{dir}/{file}" + icon = self.get_system_thumbnail(path, tab.sys_icon_wh[0]) + + if not icon: + icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) + + self.add(Gtk.Image.new_from_pixbuf(icon)) + self.show_all() + + def get_system_thumbnail(self, file, size): + try: + gio_file = Gio.File.new_for_path(file) + info = gio_file.query_info('standard::icon' , 0, None) + icon = info.get_icon().get_names()[0] + icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename() + return GdkPixbuf.Pixbuf.new_from_file(icon_path) + except Exception as e: + return None + + class GridMixin: """docstring for WidgetMixin""" @@ -86,6 +119,15 @@ class GridMixin: tid.hide() return tab_widget + def create_scroll_and_store(self, tab, wid, use_tree_view=False): + if not use_tree_view: + scroll, store = self.create_icon_grid_widget(tab, wid) + else: + # TODO: Fix global logic to make the below work too + scroll, store = self.create_icon_tree_widget(tab, wid) + + return scroll, store + def create_icon_grid_widget(self, tab, wid): scroll = Gtk.ScrolledWindow() grid = Gtk.IconView() @@ -112,7 +154,6 @@ class GridMixin: grid.connect("drag-data-received", self.grid_on_drag_data_received) grid.connect("drag-motion", self.grid_on_drag_motion) - URI_TARGET_TYPE = 80 uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) targets = [ uri_target ] @@ -167,12 +208,14 @@ class GridMixin: grid.enable_model_drag_dest(targets, action) grid.enable_model_drag_source(0, targets, action) - grid.show_all() scroll.add(grid) grid.set_name(f"{wid}|{tab.get_id()}") scroll.set_name(f"{wid}|{tab.get_id()}") + self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) + self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) grid.columns_autosize() + self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) return scroll, store diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py index 24e2b56..539f840 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py @@ -33,9 +33,7 @@ class TabMixin(GridMixin): tab.set_path(path) tab_widget = self.create_tab_widget(tab) - scroll, store = self.create_icon_grid_widget(tab, wid) - # TODO: Fix global logic to make the below work too - # scroll, store = self.create_icon_tree_widget(tab, wid) + scroll, store = self.create_scroll_and_store(tab, wid) index = notebook.append_page(scroll, tab_widget) self.fm_controller.set_wid_and_tid(wid, tab.get_id()) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py index 4cb0108..e5f70e8 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py @@ -46,8 +46,14 @@ class KeyboardSignalsMixin: mapping = self.keybindings.lookup(event) if mapping: - getattr(self, mapping)() - return True + try: + # See if in filemanager scope + getattr(self, mapping)() + return True + except Exception: + # Must be plugins scope or we forgot to add method to file manager scope + sender, method_target = mapping.split("||") + self.handle_plugin_key_event(sender, method_target) else: if debug: print(f"on_global_key_release_controller > key > {keyname}") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index 44c5e63..0a81be1 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -15,7 +15,7 @@ class Plugin: name: str = None author: str = None version: str = None - suppoert: str = None + support: str = None permissions:{} = None reference: type = None @@ -28,6 +28,8 @@ class Plugins: self._settings = settings self._builder = self._settings.get_builder() self._plugins_path = self._settings.get_plugins_path() + self._keybindings = self._settings.get_keybindings() + self._plugins_dir_watcher = None self._plugin_collection = [] @@ -114,6 +116,10 @@ class Plugins: if permissions["pass_fm_events"] in ["true"]: loading_data["pass_fm_events"] = True + if "bind_keys" in keys: + if isinstance(permissions["bind_keys"], list): + loading_data["bind_keys"] = permissions["bind_keys"] + return loading_data def execute_plugin(self, module: type, plugin: Plugin, loading_data: []): @@ -127,6 +133,9 @@ class Plugins: if "pass_fm_events" in keys: plugin.reference.set_fm_event_system(event_system) + if "bind_keys" in keys: + self._keybindings.append_bindings(loading_data["bind_keys"]) + plugin.reference.run() self._plugin_collection.append(plugin) 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 index a7bbbf6..f1bf0bc 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py @@ -42,6 +42,17 @@ class Keybindings: self.keymap = Gdk.Keymap.get_default() self.configure({}) + def print_keys(self): + print(self.keys) + + def append_bindings(self, combos): + """Accept new binding(s) and reload""" + for item in combos: + method, keys = item.split(":") + self.keys[method] = keys + + self.reload() + def configure(self, bindings): """Accept new bindings and reconfigure with them""" self.keys = bindings -- 2.27.0 From b1bf8785c60cf2359455c8b0692ead79cf9e6f20 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 16 Jul 2022 02:33:25 -0500 Subject: [PATCH 40/41] Perliminary properties viewer plugin --- plugins/README.md | 20 +- plugins/file_properties/file_properties.glade | 685 ++++++++++++++++++ plugins/file_properties/plugin.py | 263 +++++++ plugins/template/plugin.py | 21 +- plugins/youtube_download/plugin.py | 51 +- .../SolarFM/solarfm/core/controller_data.py | 15 + .../SolarFM/solarfm/plugins/plugins.py | 72 +- 7 files changed, 1050 insertions(+), 77 deletions(-) create mode 100644 plugins/file_properties/file_properties.glade create mode 100644 plugins/file_properties/plugin.py diff --git a/plugins/README.md b/plugins/README.md index 9495829..2602c94 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -1,17 +1,17 @@ ### Note -Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' permissions data but don't have to if not directly interacted with. +Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' requests data but don't have to if not directly interacted with. Plugins must have a run method defined; though, you do not need to necessarily do anything within it. The run method implies that the passed in event system or other data is ready for the plugin to use. -### Manifest Example (All are required.) +### Manifest Example (All are required even if empty.) ``` class Manifest: - path: str = os.path.dirname(os.path.realpath(__file__)) - name: str = "Example Plugin" - author: str = "John Doe" - version: str = "0.0.1" - support: str = "" - permissions: {} = { + path: str = os.path.dirname(os.path.realpath(__file__)) + name: str = "Example Plugin" + author: str = "John Doe" + version: str = "0.0.1" + support: str = "" + requests: {} = { 'ui_target': "plugin_control_list", 'pass_fm_events': "true" @@ -19,9 +19,9 @@ class Manifest: ``` -### Permissions +### Requests ``` -permissions: {} = { +requests: {} = { 'ui_target': "plugin_control_list", 'ui_target_id': "" # Only needed if using "other" in "ui_target". See below for predefined "ui_target" options... 'pass_fm_events': "true" # If empty or not present will be ignored. diff --git a/plugins/file_properties/file_properties.glade b/plugins/file_properties/file_properties.glade new file mode 100644 index 0000000..7390e9e --- /dev/null +++ b/plugins/file_properties/file_properties.glade @@ -0,0 +1,685 @@ + + + + + + False + 6 + File Properties + True + center-on-parent + 420 + True + dialog + True + True + center + + + + True + False + 12 + + + True + False + end + + + gtk-cancel + True + True + True + False + True + + + True + True + 0 + + + + + gtk-ok + True + True + True + False + True + + + True + True + 1 + + + + + False + False + end + 0 + + + + + True + True + 6 + + + True + False + 6 + 6 + 12 + + + True + False + 4 + 7 + 2 + 12 + 6 + + + True + False + <b>File _Name:</b> + True + True + file_name + 0 + + + GTK_FILL + + + + + + True + True + + + 1 + 2 + + + + + + True + False + <b>_Location:</b> + True + True + file_location + 0 + + + 1 + 2 + GTK_FILL + + + + + + True + True + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + True + False + <b>Link _Target:</b> + True + True + file_target + 0 + + + 2 + 3 + GTK_FILL + + + + + + True + True + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + + True + False + <b>Type:</b> + True + True + 0 + 0 + + + 3 + 4 + GTK_FILL + GTK_FILL + + + + + True + True + True + end + 0 + 0 + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + + True + False + <b>Size:</b> + True + True + 0 + + + 4 + 5 + GTK_FILL + + + + + + True + True + True + 0 + + + 1 + 2 + 4 + 5 + GTK_FILL + + + + + + True + False + <b>_Modified:</b> + True + True + 0 + + + 5 + 6 + GTK_FILL + + + + + + True + False + <b>_Accessed:</b> + True + True + 0 + + + 6 + 7 + GTK_FILL + + + + + + True + True + + + 1 + 2 + 5 + 6 + GTK_FILL + + + + + + True + True + + + 1 + 2 + 6 + 7 + GTK_FILL + + + + + + + + + + True + False + _Info + True + + + False + + + + + True + False + 6 + 6 + 12 + + + True + False + 6 + + + True + False + 2 + 2 + 2 + 12 + 6 + + + True + False + <b>_Owner:</b> + True + True + file_owner + 0 + + + GTK_FILL + + + + + + True + False + <b>_Group:</b> + True + True + file_group + 0 + + + 1 + 2 + GTK_FILL + + + + + + True + True + + + 1 + 2 + + + + + + True + True + + + 1 + 2 + 1 + 2 + + + + + + False + False + 0 + + + + + True + False + + + False + False + 1 + + + + + True + False + 4 + 3 + 5 + 12 + 6 + + + True + False + <b>Owner:</b> + True + True + 0 + + + GTK_FILL + + + + + + True + False + <b>Group:</b> + True + True + 0 + + + 1 + 2 + GTK_FILL + + + + + + True + False + <b>Other:</b> + True + True + 0 + + + 2 + 3 + GTK_FILL + + + + + + Read + True + True + False + 2 + True + True + + + 1 + 2 + GTK_FILL + + + + + + Read + True + True + False + 2 + True + True + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + Read + True + True + False + 2 + True + True + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + + Write + True + True + False + 2 + True + True + + + 2 + 3 + GTK_FILL + + + + + + Write + True + True + False + 2 + True + True + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Write + True + True + False + 2 + True + True + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Execute + True + True + False + 2 + True + True + + + 3 + 4 + GTK_FILL + + + + + + Execute + True + True + False + 2 + True + True + + + 3 + 4 + 1 + 2 + GTK_FILL + + + + + + Execute + True + True + False + 2 + True + True + + + 3 + 4 + 2 + 3 + GTK_FILL + + + + + + True + False + + + 4 + 5 + 3 + GTK_FILL + GTK_FILL + + + + + False + True + 2 + + + + + + + 1 + + + + + True + False + _Permissions + True + + + 1 + False + + + + + False + True + 2 + + + + + + cancel_button + ok_button + + + diff --git a/plugins/file_properties/plugin.py b/plugins/file_properties/plugin.py new file mode 100644 index 0000000..72c74a1 --- /dev/null +++ b/plugins/file_properties/plugin.py @@ -0,0 +1,263 @@ +# Python imports +import os, threading, subprocess, time +from datetime import datetime + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib, Gio + +# Application imports + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +class Manifest: + path: str = os.path.dirname(os.path.realpath(__file__)) + name: str = "Properties" + author: str = "ITDominator" + version: str = "0.0.1" + support: str = "" + requests: {} = { + 'ui_target': "context_menu", + 'pass_fm_events': "true" + } + +class Properties: + file_uri: str = None + file_name: str = None + file_location: str = None + file_target: str = None + mime_type: str = None + file_size: str = None + mtime: int = None + atime: int = None + file_owner: str = None + file_group: str = None + chmod_stat: str = None + + +class Plugin(Manifest): + def __init__(self): + self._GLADE_FILE = f"{self.path}/file_properties.glade" + self._builder = None + self._properties_dialog = None + + self._event_system = None + self._event_sleep_time = .5 + self._event_message = None + + self._file_name = None + self._file_location = None + self._file_target = None + self._mime_type = None + self._file_size = None + self._mtime = None + self._atime = None + self._file_owner = None + self._file_group = None + + self._chmod_map: {} = { + "7": "rwx", + "6": "rw", + "5": "rx", + "4": "r", + "3": "wx", + "2": "w", + "1": "x", + "0": "" + } + + self._chmod_map_counter: {} = { + "rwx": "7", + "rw": "6", + "rx": "5", + "r": "4", + "wx": "3", + "w": "2", + "x": "1", + "": "0" + } + + + def get_ui_element(self): + self._builder = Gtk.Builder() + self._builder.add_from_file(self._GLADE_FILE) + + self._properties_dialog = self._builder.get_object("file_properties_dialog") + self._file_name = self._builder.get_object("file_name") + self._file_location = self._builder.get_object("file_location") + self._file_target = self._builder.get_object("file_target") + self._mime_type = self._builder.get_object("mime_type") + self._file_size = self._builder.get_object("file_size") + self._mtime = self._builder.get_object("mtime") + self._atime = self._builder.get_object("atime") + self._file_owner = self._builder.get_object("file_owner") + self._file_group = self._builder.get_object("file_group") + + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_properties_page) + return button + + def set_fm_event_system(self, fm_event_system): + self._event_system = fm_event_system + + def run(self): + self._module_event_observer() + + + + + @threaded + def _show_properties_page(self, widget=None, eve=None): + self._event_system.push_gui_event([self.name, "get_current_state", ()]) + self.wait_for_fm_message() + + state = self._event_message + self._event_message = None + + GLib.idle_add(self._process_changes, (state)) + + def _process_changes(self, state): + if len(state.selected_files) == 1: + uri = state.selected_files[0] + path = state.tab.get_current_directory() + + + properties = self._set_ui_data(uri, path) + response = self._properties_dialog.run() + if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: + self._properties_dialog.hide() + + self._update_file(properties) + self._properties_dialog.hide() + + + def _update_file(self, properties): + chmod_stat = self._get_check_boxes() + + if chmod_stat is not properties.chmod_stat: + try: + print("\nNew chmod flags...") + print(f"Old: {''.join(properties.chmod_stat)}") + print(f"New: {chmod_stat}") + # self._guest_fs.chmod(int(chmod_stat), properties.file_uri) + except Exception as e: + print(f"Couldn't chmod\nFile: {properties.file_uri}") + print( repr(e) ) + + + def _set_ui_data(self, uri, path): + properties = Properties() + file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::*,owner::*,time::access,time::changed", + flags=Gio.FileQueryInfoFlags.NONE, + cancellable=None) + + is_symlink = file_info.get_attribute_as_string("standard::is-symlink") + properties.file_uri = uri + properties.file_target = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink else "" + properties.file_name = file_info.get_display_name() + properties.file_location = path + properties.mime_type = file_info.get_content_type() + properties.file_size = self._sizeof_fmt(file_info.get_size()) + properties.mtime = datetime.fromtimestamp( int(file_info.get_attribute_as_string("time::changed")) ).strftime("%A, %B %d, %Y %I:%M:%S") + properties.atime = datetime.fromtimestamp( int(file_info.get_attribute_as_string("time::access")) ).strftime("%A, %B %d, %Y %I:%M:%S") + properties.file_owner = file_info.get_attribute_as_string("owner::user") + properties.file_group = file_info.get_attribute_as_string("owner::group") + + # NOTE: Read = 4, Write = 2, Exec = 1 + command = ["stat", "-c", "%a", uri] + with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: + properties.chmod_stat = list(proc.stdout.read().decode("UTF-8").strip()) + owner = self._chmod_map[f"{properties.chmod_stat[0]}"] + group = self._chmod_map[f"{properties.chmod_stat[1]}"] + others = self._chmod_map[f"{properties.chmod_stat[2]}"] + + self._reset_check_boxes() + self._set_check_boxes([["owner", owner], ["group", group], ["others", others]]) + + self._file_name.set_text(properties.file_name) + self._file_location.set_text(properties.file_location) + self._file_target.set_text(properties.file_target) + self._mime_type.set_label(properties.mime_type) + self._file_size.set_label(properties.file_size) + self._mtime.set_text(properties.mtime) + self._atime.set_text(properties.atime) + self._file_owner.set_text(properties.file_owner) + self._file_group.set_text(properties.file_group) + + return properties + + + + + def _get_check_boxes(self): + perms = [[], [], []] + + for i, target in enumerate(["owner", "group", "others"]): + for type in ["r", "w", "x"]: + is_active = self._builder.get_object(f"{target}_{type}").get_active() + if is_active: + perms[i].append(type) + + digits = [] + for perm in perms: + digits.append(self._chmod_map_counter[ ''.join(perm) ]) + + return ''.join(digits) + + def _set_check_boxes(self, targets): + for name, target in targets: + for type in list(target): + obj = f"{name}_{type}" + self._builder.get_object(obj).set_active(True) + + def _reset_check_boxes(self): + for target in ["owner", "group", "others"]: + for type in ["r", "w", "x"]: + self._builder.get_object(f"{target}_{type}").set_active(False) + + def _sizeof_fmt(self, num, suffix="B"): + for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: + if abs(num) < 1024.0: + return f"{num:3.1f} {unit}{suffix}" + num /= 1024.0 + return f"{num:.1f} Yi{suffix}" + + def wait_for_fm_message(self): + while not self._event_message: + pass + + @daemon_threaded + def _module_event_observer(self): + while True: + time.sleep(self._event_sleep_time) + event = self._event_system.read_module_event() + if event: + try: + if event[0] == self.name: + target_id, method_target, data = self._event_system.consume_module_event() + + if not method_target: + self._event_message = data + else: + method = getattr(self.__class__, f"{method_target}") + if data: + data = method(*(self, *data)) + else: + method(*(self,)) + except Exception as e: + print(repr(e)) diff --git a/plugins/template/plugin.py b/plugins/template/plugin.py index e514ec5..294aadc 100644 --- a/plugins/template/plugin.py +++ b/plugins/template/plugin.py @@ -25,12 +25,12 @@ def daemon_threaded(fn): class Manifest: - path: str = os.path.dirname(os.path.realpath(__file__)) - name: str = "Example Plugin" - author: str = "John Doe" - version: str = "0.0.1" - support: str = "" - permissions: {} = { + path: str = os.path.dirname(os.path.realpath(__file__)) + name: str = "Example Plugin" + author: str = "John Doe" + version: str = "0.0.1" + support: str = "" + requests: {} = { 'ui_target': "plugin_control_list", 'pass_fm_events': "true", 'bind_keys': [f"{name}||send_message:f"] @@ -39,7 +39,7 @@ class Manifest: class Plugin(Manifest): def __init__(self): - self._event_system = event_system + self._event_system = None self._event_sleep_time = .5 self._event_message = None @@ -50,7 +50,7 @@ class Plugin(Manifest): return button def set_fm_event_system(self, fm_event_system): - self.event_system = fm_event_system + self._event_system = fm_event_system def run(self): self._module_event_observer() @@ -62,6 +62,10 @@ class Plugin(Manifest): self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)]) + def wait_for_fm_message(self): + while not self._event_message: + pass + @daemon_threaded def _module_event_observer(self): while True: @@ -81,5 +85,4 @@ class Plugin(Manifest): else: method(*(self,)) except Exception as e: - print("ewww here") print(repr(e)) diff --git a/plugins/youtube_download/plugin.py b/plugins/youtube_download/plugin.py index e183c4f..a7a6c44 100644 --- a/plugins/youtube_download/plugin.py +++ b/plugins/youtube_download/plugin.py @@ -25,12 +25,12 @@ def daemon_threaded(fn): class Manifest: - path: str = os.path.dirname(os.path.realpath(__file__)) - name: str = "Youtube Download" - author: str = "ITDominator" - version: str = "0.0.1" - support: str = "" - permissions: {} = { + path: str = os.path.dirname(os.path.realpath(__file__)) + name: str = "Youtube Download" + author: str = "ITDominator" + version: str = "0.0.1" + support: str = "" + requests: {} = { 'ui_target': "plugin_control_list", 'pass_fm_events': "true" @@ -39,9 +39,9 @@ class Manifest: class Plugin(Manifest): def __init__(self): - self._fm_event_system = None - self._event_sleep_time = .5 - self._fm_event_message = None + self._event_system = None + self._event_sleep_time = .5 + self._event_message = None def get_ui_element(self): @@ -50,24 +50,38 @@ class Plugin(Manifest): return button def set_fm_event_system(self, fm_event_system): - self._fm_event_system = fm_event_system + self._event_system = fm_event_system def run(self): self._module_event_observer() + @threaded + def _do_download(self, widget=None, eve=None): + self._event_system.push_gui_event([self.name, "get_current_state", ()]) + self.wait_for_fm_message() + + state = self._event_message + subprocess.Popen([f'{self.path}/download.sh' , state.tab.get_current_directory()]) + self._event_message = None + + + def wait_for_fm_message(self): + while not self._event_message: + pass + @daemon_threaded def _module_event_observer(self): while True: time.sleep(self._event_sleep_time) - event = self._fm_event_system.read_module_event() + event = self._event_system.read_module_event() if event: try: if event[0] == self.name: - target_id, method_target, data = self._fm_event_system.consume_module_event() + target_id, method_target, data = self._event_system.consume_module_event() if not method_target: - self._fm_event_message = data + self._event_message = data else: method = getattr(self.__class__, f"{method_target}") if data: @@ -76,14 +90,3 @@ class Plugin(Manifest): method(*(self,)) except Exception as e: print(repr(e)) - - - @threaded - def _do_download(self, widget=None, eve=None): - self._fm_event_system.push_gui_event([self.name, "get_current_state", ()]) - while not self._fm_event_message: - pass - - state = self._fm_event_message - subprocess.Popen([f'{self.path}/download.sh' , state.tab.get_current_directory()]) - self._fm_event_message = None diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index b94766b..6a787f4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -19,6 +19,9 @@ class State: tab: type = None icon_grid: gi.overrides.Gtk.IconView = None store: gi.overrides.Gtk.ListStore = None + selected_files: [] = None + to_copy_files: [] = None + to_cut_files: [] = None class Controller_Data: @@ -138,6 +141,18 @@ class Controller_Data: state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid") state.store = state.icon_grid.get_model() + + selected_files = state.icon_grid.get_selected_items() + # if self.selected_files: + if selected_files: + state.selected_files = self.format_to_uris(state.store, state.wid, state.tid, selected_files, True) + + # if self.to_copy_files: + # state.to_copy_files = self.format_to_uris(state.store, state.wid, state.tid, self.to_copy_files, True) + # + # if self.to_cut_files: + # state.to_cut_files = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True) + return state diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index 0a81be1..2d547b4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -16,7 +16,7 @@ class Plugin: author: str = None version: str = None support: str = None - permissions:{} = None + requests:{} = None reference: type = None @@ -53,72 +53,76 @@ class Plugins: print(f"Loading plugins...") parent_path = os.getcwd() - for file in os.listdir(self._plugins_path): + for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]: try: - path = join(self._plugins_path, file) - if isdir(path): - module = self.load_plugin_module(path, file) - plugin = self.collect_info(module, path) - loading_data = self.parse_permissions(plugin) + target = join(path, "plugin.py") + if not os.path.exists(target): + raise Exception("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...") - self.execute_plugin(module, plugin, loading_data) + module = self.load_plugin_module(path, folder, target) + plugin = self.collect_info(module, path) + loading_data = self.parse_requests(plugin) + + self.execute_plugin(module, plugin, loading_data) except Exception as e: - print("Malformed plugin! Not loading!") + print(f"Malformed Plugin: Not loading -->: '{folder}' !") traceback.print_exc() + # if debug: + # traceback.print_exc() os.chdir(parent_path) - def load_plugin_module(self, path, file): + def load_plugin_module(self, path, folder, target): os.chdir(path) sys.path.insert(0, path) - spec = importlib.util.spec_from_file_location(file, join(path, "plugin.py")) + spec = importlib.util.spec_from_file_location(folder, target) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def collect_info(self, module, path) -> Plugin: - plugin = Plugin() - plugin.path = module.Manifest.path - plugin.name = module.Manifest.name - plugin.author = module.Manifest.author - plugin.version = module.Manifest.version - plugin.support = module.Manifest.support - plugin.permissions = module.Manifest.permissions + plugin = Plugin() + plugin.path = module.Manifest.path + plugin.name = module.Manifest.name + plugin.author = module.Manifest.author + plugin.version = module.Manifest.version + plugin.support = module.Manifest.support + plugin.requests = module.Manifest.requests return plugin - def parse_permissions(self, plugin): + def parse_requests(self, plugin): loading_data = {} - permissions = plugin.permissions - keys = permissions.keys() + requests = plugin.requests + keys = requests.keys() if "ui_target" in keys: - if permissions["ui_target"] in [ - "none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list", - "context_menu", "window_1", "window_2", "window_3", "window_4" - ]: - if permissions["ui_target"] == "other": + if requests["ui_target"] in [ + "none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list", + "context_menu", "window_1", "window_2", "window_3", "window_4" + ]: + if requests["ui_target"] == "other": if "ui_target_id" in keys: - loading_data["ui_target"] = self._builder.get_object(permissions["ui_target_id"]) + loading_data["ui_target"] = self._builder.get_object(requests["ui_target_id"]) if loading_data["ui_target"] == None: - raise Exception('Invalid "ui_target_id" given in permissions. Must have one if setting "ui_target" to "other"...') + raise Exception('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...') else: - raise Exception('Invalid "ui_target_id" given in permissions. Must have one if setting "ui_target" to "other"...') + raise Exception('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...') else: - loading_data["ui_target"] = self._builder.get_object(permissions["ui_target"]) + loading_data["ui_target"] = self._builder.get_object(requests["ui_target"]) else: - raise Exception('Unknown "ui_target" given in permissions.') + raise Exception('Unknown "ui_target" given in requests.') if "pass_fm_events" in keys: - if permissions["pass_fm_events"] in ["true"]: + if requests["pass_fm_events"] in ["true"]: loading_data["pass_fm_events"] = True if "bind_keys" in keys: - if isinstance(permissions["bind_keys"], list): - loading_data["bind_keys"] = permissions["bind_keys"] + if isinstance(requests["bind_keys"], list): + loading_data["bind_keys"] = requests["bind_keys"] return loading_data -- 2.27.0 From 92d8069f3a0c26f6c766dbe67f948eda80df2901 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 16 Jul 2022 13:29:19 -0500 Subject: [PATCH 41/41] Implimented chmod, chown logic --- plugins/file_properties/plugin.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/file_properties/plugin.py b/plugins/file_properties/plugin.py index 72c74a1..9236370 100644 --- a/plugins/file_properties/plugin.py +++ b/plugins/file_properties/plugin.py @@ -1,5 +1,5 @@ # Python imports -import os, threading, subprocess, time +import os, threading, subprocess, time, pwd, grp from datetime import datetime # Gtk imports @@ -154,7 +154,27 @@ class Plugin(Manifest): print("\nNew chmod flags...") print(f"Old: {''.join(properties.chmod_stat)}") print(f"New: {chmod_stat}") - # self._guest_fs.chmod(int(chmod_stat), properties.file_uri) + + command = ["chmod", f"{chmod_stat}", properties.file_uri] + with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: + result = proc.stdout.read().decode("UTF-8").strip() + print(result) + except Exception as e: + print(f"Couldn't chmod\nFile: {properties.file_uri}") + print( repr(e) ) + + + owner = self._file_owner.get_text() + group = self._file_group.get_text() + if owner is not properties.file_owner or group is not properties.file_group: + try: + print("\nNew owner/group flags...") + print(f"Old:\n\tOwner: {properties.file_owner}\n\tGroup: {properties.file_group}") + print(f"New:\n\tOwner: {owner}\n\tGroup: {group}") + + uid = pwd.getpwnam(owner).pw_uid + gid = grp.getgrnam(group).gr_gid + os.chown(properties.file_uri, uid, gid) except Exception as e: print(f"Couldn't chmod\nFile: {properties.file_uri}") print( repr(e) ) -- 2.27.0