diff --git a/.gitignore b/.gitignore index f8b73e7..a7059ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.idea/ +*.zip + # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index d95b54d..c61ddf6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# SolarFM - # SolarFM SolarFM is a Gtk+ Python file manager. @@ -14,6 +12,11 @@ sudo apt-get install python3.8 wget python3-setproctitle python3-gi ffmpegthumbn # TODO # Images diff --git a/bin/solarfm-0-0-1-x64.deb b/bin/solarfm-0-0-1-x64.deb index 744a9c4..f09cae7 100644 Binary files a/bin/solarfm-0-0-1-x64.deb and b/bin/solarfm-0-0-1-x64.deb differ diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..2602c94 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,61 @@ +### Note +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 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 = "" + requests: {} = { + 'ui_target': "plugin_control_list", + 'pass_fm_events': "true" + + } +``` + + +### Requests +``` +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. + '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. + +} +``` + +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/example/__main__.py b/plugins/example/__main__.py deleted file mode 100644 index 004f660..0000000 --- a/plugins/example/__main__.py +++ /dev/null @@ -1,37 +0,0 @@ -# Python imports -import sys, traceback, threading, inspect, os, 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=True).start() - return wrapper - - -class Main: - def __init__(self, socket_id, event_system): - 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 - - self._event_system.push_gui_event(["some_type", "display_message", ("warning", str(i), None)]) - i += 1 - - time.sleep(1) 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..9236370 --- /dev/null +++ b/plugins/file_properties/plugin.py @@ -0,0 +1,283 @@ +# Python imports +import os, threading, subprocess, time, pwd, grp +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}") + + 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) ) + + + 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 new file mode 100644 index 0000000..294aadc --- /dev/null +++ b/plugins/template/plugin.py @@ -0,0 +1,88 @@ +# Python imports +import os, threading, subprocess, time + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +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: 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 = "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"] + } + + +class Plugin(Manifest): + def __init__(self): + self._event_system = None + self._event_sleep_time = .5 + self._event_message = None + + + def get_ui_element(self): + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self.send_message) + return button + + def set_fm_event_system(self, fm_event_system): + self._event_system = fm_event_system + + def run(self): + self._module_event_observer() + + + 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)]) + + + 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/youtube_download/download.sh b/plugins/youtube_download/download.sh new file mode 100755 index 0000000..dc6df21 --- /dev/null +++ b/plugins/youtube_download/download.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() { + 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}" +} +main "$@"; diff --git a/plugins/youtube_download/plugin.py b/plugins/youtube_download/plugin.py new file mode 100644 index 0000000..a7a6c44 --- /dev/null +++ b/plugins/youtube_download/plugin.py @@ -0,0 +1,92 @@ +# Python imports +import os, threading, subprocess, time + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +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: 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 = "Youtube Download" + author: str = "ITDominator" + version: str = "0.0.1" + support: str = "" + requests: {} = { + 'ui_target': "plugin_control_list", + 'pass_fm_events': "true" + + } + + +class Plugin(Manifest): + def __init__(self): + self._event_system = None + self._event_sleep_time = .5 + self._event_message = None + + + def get_ui_element(self): + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._do_download) + 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 _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._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/src/debs/clear_pycache_dirs.sh b/src/debs/clear_pycache_dirs.sh deleted file mode 100644 index 7987a3d..0000000 --- a/src/debs/clear_pycache_dirs.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# 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() { - find . -name "__pycache__" -exec rm -rf $1 {} \; - find . -name "*.pyc" -exec rm -rf $1 {} \; -} -main 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 3ac749f..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,24 +4,25 @@ import builtins # Lib imports # Application imports -from signal_classes import IPCServerMixin +from ipc_server import IPCServer -class Builtins(IPCServerMixin): - """Docstring for __builtins__ extender""" +class EventSystem(IPCServer): + """ Inheret IPCServerMixin. Create an pub/sub systems. """ def __init__(self): - # NOTE: The format used is list of [type, target, data] + 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 # Where data may be any kind of data self._gui_events = [] - self._fm_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 + self._module_events = [] + # Makeshift fake "events" type system FIFO @@ -30,9 +31,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 @@ -41,33 +42,33 @@ 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.event_system = Builtins() +builtins.app_name = "SolarFM" +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 a3de649..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,54 +1,3 @@ -# Python imports -import os, inspect, time - -# Lib imports - -# Application imports -from utils import Settings -from signal_classes import Controller -from __builtins__ import Builtins - - - - -class Main(Builtins): - def __init__(self, args, unknownargs): - if not debug: - event_system.create_ipc_server() - - time.sleep(0.2) - if not trace_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 66870d2..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,10 +15,12 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from __init__ import Main +from app import Application if __name__ == "__main__": + """ Set process title, get arguments, and create GTK main thread. """ + try: # import web_pdb # web_pdb.set_trace() @@ -33,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/debs/solarfm-0-0-1-x64/opt/SolarFM/app.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/app.py new file mode 100644 index 0000000..3e092c9 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/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/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/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py new file mode 100644 index 0000000..fc7fa7c --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py @@ -0,0 +1,171 @@ +# 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.fm_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.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) + save_load_dialog = self.builder.get_object("save_load_dialog") + + if action == "save_session": + self.fm_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(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_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": + path = f"{save_load_dialog.get_file().get_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 + + save_load_dialog.hide() + + def load_session(self, session_json): + if debug: + print(f"Session Data: {session_json}") + + self.ctrl_down = False + self.shift_down = False + self.alt_down = False + for notebook in self.notebooks: + self.clear_children(notebook) + + self.fm_controller.unload_tabs_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/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 52% 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 fe97040..28a09e8 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 @@ -1,26 +1,25 @@ # Python imports -import signal +import sys, os, signal # Lib imports from gi.repository import GLib # Application imports from trasher.xdgtrash import XDGTrash -from shellfm import WindowController -from plugins 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() - 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 @@ -32,18 +31,19 @@ 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") + 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") @@ -78,32 +78,80 @@ 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.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.success = "#88cc27" - self.warning = "#ffa800" - self.error = "#ff0000" + self.skip_edit = False + self.cancel_edit = False + self.ctrl_down = False + self.shift_down = False + self.alt_down = False + 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) 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, tab, icon_grid, 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): + ''' 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/context/mixins/__init__.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/__init__.py new file mode 100644 index 0000000..ded9abc --- /dev/null +++ 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 new file mode 100644 index 0000000..08d29cb --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/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_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_popup_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/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 88% 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 index 0f896fc..2e7a4fd 100644 --- 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 @@ -4,14 +4,14 @@ 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 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_data() - 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() @@ -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() @@ -131,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 new file mode 100644 index 0000000..991f9a2 --- /dev/null +++ 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/signal_classes/mixins/ui/PaneMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py similarity index 84% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/PaneMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/pane_mixin.py index 235736d..3d2c888 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/PaneMixin.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.isHidden = state - self.window_controller.save_state() + window = self.fm_controller.get_window_by_index(pane_index - 1) + window.set_is_hidden(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 new file mode 100644 index 0000000..9ce3953 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/tab_mixin.py @@ -0,0 +1,202 @@ +# Python imports +import os + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from .widget_mixin import WidgetMixin + + + + +class TabMixin(WidgetMixin): + """docstring for TabMixin""" + + def create_tab(self, wid, path=None): + 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}") + tab.logger = self.logger + + tab.set_wid(wid) + if path: 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) + index = notebook.append_page(scroll, tab_widget) + + 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(tab, store) + self.set_window_title() + self.set_file_watcher(tab) + + + + + def close_tab(self, button, eve=None): + notebook = button.get_parent().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) + 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 on_tab_reorder(self, child, page_num, new_index): + wid, tid = page_num.get_name().split("|") + window = self.get_fm_window(wid) + tab = None + + 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_tabs().insert(new_index, window.get_all_tabs().pop(i)) + + 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.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): + return tab_box.get_children()[2].get_text() + + 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, icon_grid): + return notebook.get_tab_label(icon_grid.get_parent()).get_children()[1] + + def get_tab_icon_grid_from_notebook(self, notebook): + 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) + + def update_tab(self, tab_label, tab, store, wid, tid): + self.load_store(tab, store) + self.set_path_text(wid, tid) + + char_width = len(tab.get_end_of_path()) + tab_label.set_width_chars(char_width) + tab_label.set_label(tab.get_end_of_path()) + self.set_window_title() + 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.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}") + tab = self.get_fm_window(wid).get_tab_by_id(tid) + + if action == "create_tab": + dir = tab.get_current_directory() + self.create_tab(wid, dir) + 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"{tab.get_current_directory()}/" + path = widget.get_text() + + if isinstance(focused_obj, Gtk.Entry): + 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(path_menu_buttons) + show_path_menu = False + for file, hash in files: + if os.path.isdir(f"{dir}{file}"): + if query.lower() in file.lower(): + button = Gtk.Button(label=file) + button.show() + button.connect("clicked", self.set_path_entry) + path_menu_buttons.add(button) + show_path_menu = True + + if not show_path_menu: + self.path_menu.popdown() + else: + self.path_menu.popup() + widget.grab_focus_without_selecting() + widget.set_position(-1) + + if path.endswith(".") or path == dir: + return + + if not tab.set_path(path): + return + + self.update_tab(tab_label, tab, store, wid, tid) + + try: + widget.grab_focus_without_selecting() + widget.set_position(-1) + except Exception as e: + 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()}" + 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) + 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/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 73% 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 1969125..1a84305 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 @@ -1,5 +1,5 @@ # Python imports -import os +import os, time, threading # Lib imports import gi @@ -9,9 +9,15 @@ 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: @@ -34,47 +40,73 @@ 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_tab_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 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, 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_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]) - 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_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_widget]["last_update_time"] + current_time = time.time() + 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,)) + + + def update_on_soft_lock_end(self, tab_widget): + wid, tid = tab_widget.split("|") + notebook = self.builder.get_object(f"window_{wid}") + 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}") + + tab.load_directory() + 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]: + self.set_bottom_labels(tab) def popup_search_files(self, wid, keyname): @@ -86,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.files): - if query and query in file.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(): 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(); @@ -130,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: @@ -154,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) @@ -164,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_data() - 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 @@ -199,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: @@ -207,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) @@ -227,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_data() - 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}" @@ -294,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_data() - 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) @@ -321,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_data() - 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/controller/mixins/ui/WidgetMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/widget_mixin.py similarity index 68% 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 349cba7..6f496f4 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,29 +20,31 @@ def threaded(fn): class WidgetMixin: - def load_store(self, view, store, save_state=False): + """docstring for WidgetMixin""" + + 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: @@ -58,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) @@ -88,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.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) @@ -132,13 +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("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) - 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.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}|{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.id}") - scroll.set_name(f"{wid}|{view.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.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/controller/mixins/ui/WindowMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/mixins/ui/window_mixin.py similarity index 55% 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 17e4be3..b1e3878 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,58 +9,56 @@ gi.require_version('Gdk', '3.0') from gi.repository import Gdk, Gio # Application imports -from . import TabMixin, WidgetMixin - - +from .tab_mixin import TabMixin 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}") - 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: @@ -78,10 +76,10 @@ class WindowMixin(TabMixin): return uris - def set_bottom_labels(self, view): - _wid, _tid, _view, iconview, store = self.get_current_state() - selected_files = iconview.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")) ) @@ -96,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: @@ -113,29 +111,29 @@ class WindowMixin(TabMixin): formatted_size = self.sizeof_fmt(combined_size) - if view.hide_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.hide_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_data() + 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() @@ -146,72 +144,74 @@ class WindowMixin(TabMixin): ctx.remove_class("notebook-unselected-focus") ctx.add_class("notebook-selected-focus") - self.window.set_title("SolarFM ~ " + dir) - self.set_bottom_labels(view) + self.window.set_title(f"SolarFM ~ {dir}") + 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_active_data(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) @@ -220,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_data()) - 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_active_data(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_data() + 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") @@ -252,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/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..c2a7368 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/__init__.py @@ -0,0 +1,3 @@ +""" +Signals module +""" 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 81% 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 74c1ea5..64b86dc 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,12 +6,14 @@ 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_data() + 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 new file mode 100644 index 0000000..16ed902 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/signals/keyboard_signals_mixin.py @@ -0,0 +1,114 @@ +# Python imports +import re + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk, Gdk + +# Application imports + + +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.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 keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: + if "control" in keyname: + self.ctrl_down = True + if "shift" in keyname: + self.shift_down = True + 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}") + + if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: + if "control" in keyname: + self.ctrl_down = False + if "shift" in keyname: + self.shift_down = False + 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() + + 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: + 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/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/ipc_server.py similarity index 53% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/ipc_server.py index a689101..0972d86 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/IPCServerMixin.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/ipc_server.py @@ -1,5 +1,5 @@ # Python imports -import threading, socket, time +import os, threading, time from multiprocessing.connection import Listener, Client # Lib imports @@ -15,11 +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() @@ -34,7 +55,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 @@ -47,7 +68,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() @@ -57,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/__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/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py new file mode 100644 index 0000000..49230f5 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py @@ -0,0 +1,83 @@ +# 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 + reference = None + + +class Plugins: + """Plugins controller""" + + def __init__(self, settings): + self._settings = settings + self._builder = self._settings.get_builder() + 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) + + 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.module = path + plugin.reference = plugin_reference + + self._plugin_collection.append(plugin) + 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... stub.") + + 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: + print('Found plugin; posting message...') + plugin.reference.set_message(data) 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..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 @@ -1 +1,3 @@ -from .windows import WindowController +""" +Root of ShellFM +""" 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..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 @@ -1,2 +1,3 @@ -from .Window import Window -from .WindowController import WindowController +""" +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 new file mode 100644 index 0000000..60c3379 --- /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_tab_for_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.create_tab() + + def add_tab_for_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + return window.create_tab() + + def add_tab_for_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + return window.create_tab() + + 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"Tab Count: {window.get_tabs_count()}") + print("\n-------------------------\n") + + + + 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_tabs() + break + + def get_tabs_count(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_tabs_count() + + def get_tabs_from_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_all_tabs() + + + + + def unload_tabs_and_windows(self): + for window in self._windows: + window.get_all_tabs().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: + tabs = [] + for tab in window.get_all_tabs(): + tabs.append(tab.get_current_directory()) + + windows.append( + [ + { + 'window':{ + "ID": window.get_id(), + "Name": window.get_name(), + "Nickname": window.get_nickname(), + "isHidden": f"{window.is_hidden()}", + 'tabs': tabs + } + } + ] + ) + + 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/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/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/Icon.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/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/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/icons/icon.py index 3c14d2f..6afa0e5 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/icons/Icon.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/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/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/view/icons/mixins/DesktopIconMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/path.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/View.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/tab.py similarity index 50% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/View.py rename to src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/tab.py index 594dee1..70acaed 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/View.py +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/tab.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): +class Tab(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/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/view/utils/FileHandler.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/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/tabs/utils/settings.py 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/window.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py new file mode 100644 index 0000000..9a09233 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/window.py @@ -0,0 +1,87 @@ +# Python imports +from random import randint + + +# Lib imports + + +# Application imports +from .tabs.tab import Tab + + +class Window: + def __init__(self): + self._id_length = 10 + self._id = "" + self._name = "" + self._nickname = "" + self._isHidden = False + self._tabs = [] + + self._generate_id() + self._set_name() + + + def create_tab(self): + tab = Tab() + self._tabs.append(tab) + return tab + + def pop_tab(self): + self._tabs.pop() + + def delete_tab_by_id(self, vid): + for tab in self._tabs: + if tab.get_id() == vid: + self._tabs.remove(tab) + break + + + def get_tab_by_id(self, vid): + for tab in self._tabs: + if tab.get_id() == vid: + return tab + + def get_tab_by_index(self, index): + return self._tabs[index] + + def get_tabs_count(self): + return len(self._tabs) + + def get_all_tabs(self): + return self._tabs + + 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 list_files_from_tabs(self): + for tab in self._tabs: + print(tab.get_files()) + + + 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/Controller_Data.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller_Data.py deleted file mode 100644 index 95d8008..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/Controller_Data.py +++ /dev/null @@ -1,109 +0,0 @@ -# Python imports -import signal - -# Lib imports -from gi.repository import GLib - -# Application imports -from shellfm import WindowController -from trasher.xdgtrash import XDGTrash -from . import Plugins - - - - -class Controller_Data: - def has_method(self, o, name): - return callable(getattr(o, name, None)) - - def setup_controller_data(self, _settings): - self.trashman = XDGTrash() - self.window_controller = WindowController() - self.plugins = Plugins(_settings) - self.state = self.window_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") - 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_buffer = self.builder.get_object("message_buffer") - self.arc_command_buffer = self.builder.get_object("arc_command_buffer") - - 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.bottom_size_label = self.builder.get_object("bottom_size_label") - 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" - - # In compress commands: - # %n: First selected filename/dir to archive - # %N: All selected filenames/dirs to archive, or (with %O) a single filename - # %o: Resulting single archive file - # %O: Resulting archive per source file/directory (use changes %N meaning) - # - # In extract commands: - # %x: Archive file to extract - # %g: Unique extraction target filename with optional subfolder - # %G: Unique extraction target filename, never with subfolder - # - # In list commands: - # %x: Archive to list - # - # Plus standard bash variables are accepted. - self.arc_commands = [ '$(which 7za || echo 7zr) a %o %N', - 'zip -r %o %N', - 'rar a -r %o %N', - 'tar -cvf %o %N', - 'tar -cvjf %o %N', - 'tar -cvzf %o %N', - 'tar -cvJf %o %N', - 'gzip -c %N > %O', - '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.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.skip_edit = False - self.cancel_edit = False - self.ctrlDown = False - self.shiftDown = False - self.altDown = False - - self.success = "#88cc27" - self.warning = "#ffa800" - self.error = "#ff0000" - - - self.window.connect("delete-event", self.tear_down) - GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/IPCServerMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/IPCServerMixin.py deleted file mode 100644 index be92ace..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/IPCServerMixin.py +++ /dev/null @@ -1,64 +0,0 @@ -# Python imports -import threading, socket, time -from multiprocessing.connection import Listener, Client - -# Lib imports - -# Application imports - - -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - - - -class IPCServerMixin: - - @threaded - def create_ipc_server(self): - listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) - self.is_ipc_alive = True - while True: - conn = listener.accept() - start_time = time.time() - - 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(["create_tab_from_ipc", None, 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 lockup the connection for too long. - end_time = time.time() - if (end - start) > self.ipc_timeout: - conn.close() - - listener.close() - - - def send_ipc_message(self, message="Empty Data..."): - try: - conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) - conn.send(message) - conn.send('close connection') - except Exception as e: - print(repr(e)) 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/KeyboardSignalsMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/KeyboardSignalsMixin.py deleted file mode 100644 index b397027..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/KeyboardSignalsMixin.py +++ /dev/null @@ -1,120 +0,0 @@ -# Python imports -import re - -# Lib imports -import gi -gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk - -# Application imports - - -valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") - - -class KeyboardSignalsMixin: - def unset_keys_and_data(self, widget=None, eve=None): - self.ctrlDown = False - self.shiftDown = False - self.altDown = 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 "control" in keyname: - self.ctrlDown = True - if "shift" in keyname: - self.shiftDown = True - if "alt" in keyname: - self.altDown = 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}") - - if "control" in keyname or "alt" in keyname or "shift" in keyname: - if "control" in keyname: - self.ctrlDown = False - if "shift" in keyname: - self.shiftDown = False - if "alt" in keyname: - self.altDown = False - - - if self.ctrlDown and self.shiftDown and keyname == "t": - 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: - 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.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.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.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() - if self.ctrlDown and keyname == "c": - self.to_cut_files.clear() - self.copy_files() - 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.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.delete_files() - if keyname == "f2": - 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) 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/TabMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/TabMixin.py deleted file mode 100644 index 51960dd..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/TabMixin.py +++ /dev/null @@ -1,223 +0,0 @@ -# Python imports -import os - -# Lib imports -import gi -gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk - -# Application imports -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") - view = self.window_controller.add_view_for_window_by_nickname(f"window_{wid}") - view.logger = self.logger - - view.set_wid(wid) - if path: view.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) - - self.window_controller.set_active_data(wid, view.get_tab_id()) - path_entry.set_text(view.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.set_window_title() - self.set_file_watcher(view) - - - - - def close_tab(self, button, eve=None): - notebook = button.get_parent().get_parent() - tid = self.get_tab_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) - view = self.get_fm_window(wid).get_view_by_id(tid) - watcher = view.get_dir_watcher() - - watcher.cancel() - self.get_fm_window(wid).delete_view_by_id(tid) - notebook.remove_page(page) - self.window_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 - - for i, view in enumerate(window.views): - if view.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)) - - view = window.get_view_by_id(tid) - self.set_file_watcher(view) - self.window_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_active_data(wid, tid) - self.set_path_text(wid, tid) - self.set_window_title() - - def get_tab_id_from_tab_box(self, tab_box): - tid = tab_box.get_children()[2] - return tid.get_text() - - def get_tab_label(self, notebook, iconview): - return notebook.get_tab_label(iconview.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_iconview_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) - - def update_view(self, tab_label, view, store, wid, tid): - self.load_store(view, store) - self.set_path_text(wid, tid) - - char_width = len(view.get_end_of_path()) - tab_label.set_width_chars(char_width) - tab_label.set_label(view.get_end_of_path()) - self.set_window_title() - self.set_file_watcher(view) - self.window_controller.save_state() - - def do_action_from_bar_controls(self, widget, eve=None): - action = widget.get_name() - wid, tid = self.window_controller.get_active_data() - 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) - - 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() - self.create_tab(wid, dir) - self.window_controller.save_state() - return - if action == "path_entry": - focused_obj = self.window.get_focus() - dir = f"{view.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.files + view.hidden - - self.clear_children(button_box) - show_path_menu = False - for file in files: - if os.path.isdir(f"{dir}{file}"): - if query.lower() in file.lower(): - button = Gtk.Button(label=file) - button.show() - button.connect("clicked", self.set_path_entry) - button_box.add(button) - show_path_menu = True - - if not show_path_menu: - self.path_menu.popdown() - else: - self.path_menu.popup() - widget.grab_focus_without_selecting() - widget.set_position(-1) - - if path.endswith(".") or path == dir: - return - - traversed = view.set_path(path) - if not traversed: - return - - self.update_view(tab_label, view, store, wid, tid) - - try: - widget.grab_focus_without_selecting() - widget.set_position(-1) - except Exception as e: - 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()}" - 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.window_controller.get_active_data() - 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() - watcher.cancel() - - self.get_fm_window(wid).delete_view_by_id(tid) - notebook.remove_page(page) - self.window_controller.save_state() - self.set_window_title() - - # File control events - def show_hide_hidden_files(self): - wid, tid = self.window_controller.get_active_data() - view = self.get_fm_window(wid).get_view_by_id(tid) - view.hide_hidden = not view.hide_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/WidgetMixin.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetMixin.py deleted file mode 100644 index 349cba7..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WidgetMixin.py +++ /dev/null @@ -1,221 +0,0 @@ -# Python imports -import os, threading, subprocess, time - -# Lib imports -import gi - -gi.require_version("Gtk", "3.0") -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf - -# Application imports - - -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - - - -class WidgetMixin: - def load_store(self, view, store, save_state=False): - store.clear() - dir = view.get_current_directory() - files = view.get_files() - - for i, file in enumerate(files): - store.append([None, file[0]]) - self.create_icon(i, view, store, dir, file[0]) - - # NOTE: Not likely called often from here but it could be useful - if save_state: - self.window_controller.save_state() - - @threaded - def create_icon(self, i, view, store, dir, file): - icon = view.create_icon(dir, file) - fpath = f"{dir}/{file}" - GLib.idle_add(self.update_store, (i, store, icon, view, 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 - 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 - - if not icon: - icon = self.get_system_thumbnail(fpath, view.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) - - 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 - except Exception as e: - print("System icon generation issue:") - return None - - - - - def create_tab_widget(self, view): - tab = 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_xalign(0.0) - tid.set_label(f"{view.id}") - - close.add(icon) - tab.add(label) - tab.add(close) - tab.add(tid) - - close.connect("released", self.close_tab) - tab.show_all() - tid.hide() - return tab - - def create_grid_iconview_widget(self, view, wid): - scroll = Gtk.ScrolledWindow() - grid = Gtk.IconView() - store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) - - grid.set_model(store) - grid.set_pixbuf_column(0) - grid.set_text_column(1) - - grid.set_item_orientation(1) - grid.set_selection_mode(3) - grid.set_item_width(96) - grid.set_item_padding(8) - grid.set_margin(12) - grid.set_row_spacing(18) - grid.set_columns(-1) - grid.set_spacing(12) - 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("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) - 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 ] - action = Gdk.DragAction.COPY - 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}|{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) - return scroll, store - - def create_grid_treeview_widget(self, view, 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) - column = Gtk.TreeViewColumn("Icons") - icon = Gtk.CellRendererPixbuf() - name = Gtk.CellRendererText() - selec = grid.get_selection() - - grid.set_model(store) - selec.set_mode(3) - column.pack_start(icon, False) - column.pack_start(name, True) - column.add_attribute(icon, "pixbuf", 0) - column.add_attribute(name, "text", 1) - column.set_expand(False) - column.set_sizing(2) - column.set_min_width(120) - column.set_max_width(74) - - grid.append_column(column) - grid.set_search_column(1) - grid.set_rubber_banding(True) - grid.set_headers_visible(False) - 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) - - URI_TARGET_TYPE = 80 - uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) - targets = [ uri_target ] - action = Gdk.DragAction.COPY - 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}|{view.id}") - scroll.set_name(f"{wid}|{view.id}") - grid.columns_autosize() - self.builder.expose_object(f"{wid}|{view.id}", scroll) - return scroll, store - - - def get_store_and_label_from_notebook(self, notebook, _name): - icon_view = None - tab_label = None - store = None - - for obj in notebook.get_children(): - icon_view = obj.get_children()[0] - name = icon_view.get_name() - if name == _name: - store = icon_view.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/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/__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 index be29701..4210f9c --- 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/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/Settings.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Settings.py deleted file mode 100644 index 380210a..0000000 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/Settings.py +++ /dev/null @@ -1,95 +0,0 @@ -# Python imports -import os -from os import path - -# Gtk imports -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 - - -# Application imports -from . 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.cssFile = 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.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.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) - - - - def create_window(self): - # Get window and connect signals - 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() - 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) - - # bind css file - cssProvider = gtk.CssProvider() - cssProvider.load_from_path(self.cssFile) - 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) - cr.set_operator(cairo.OPERATOR_SOURCE) - cr.paint() - cr.set_operator(cairo.OPERATOR_OVER) - - def get_monitor_data(self): - 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_main_window(self): return self.main_window - def get_plugins_path(self): return self.PLUGINS_PATH 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..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 @@ -1,6 +1,3 @@ """ 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/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py new file mode 100644 index 0000000..df454c7 --- /dev/null +++ b/src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py @@ -0,0 +1,102 @@ +# Python imports +import os +from os import path + +# Gtk imports +import gi, cairo +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') + +from gi.repository import Gtk +from gi.repository import Gdk + + +# Application imports +from .logger import Logger + + +class Settings: + def __init__(self): + 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" + + 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" + + self._success_color = "#88cc27" + self._warning_color = "#ffa800" + self._error_color = "#ff0000" + + self.main_window = None + self.logger = Logger(self._CONFIG_PATH).get_logger() + self.builder = Gtk.Builder() + self.builder.add_from_file(self._WINDOWS_GLADE) + + + + def create_window(self): + # Get window and connect signals + 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() + 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) + + # 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) + + def _area_draw(self, widget, cr): + 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): + 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_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 + def get_error_color(self): return self._error_color 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/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 42391a5..ae1c305 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -4,73 +4,71 @@ import builtins # Lib imports # Application imports -from controller import IPCServerMixin +from utils.ipc_server import IPCServer -class Builtins(IPCServerMixin): - """Docstring for __builtins__ extender""" +class EventSystem(IPCServer): + """ Inheret IPCServerMixin. Create an pub/sub systems. """ def __init__(self): - # 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 + super(EventSystem, self).__init__() + + # 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._fm_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 + self._module_events = [] - # Makeshift fake "events" type system FIFO - def _pop_gui_event(self): + + # Makeshift "events" system FIFO + def _pop_gui_event(self) -> None: if len(self._gui_events) > 0: 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) -> 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, event: list) -> None: if len(event) == 3: 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_fm_event(self, event): + def push_module_event(self, event: list) -> None: 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: ['target_id': str, method_target: method, (data,): any]") - def read_gui_event(self): - return self._gui_events[0] + def read_gui_event(self) -> list: + return self._gui_events[0] if self._gui_events else None - def read_fm_event(self): - return self._fm_events[0] + def read_module_event(self) -> list: + return self._module_events[0] if self._module_events else None - def consume_gui_event(self): + def consume_gui_event(self) -> list: return self._pop_gui_event() - def consume_fm_event(self): - return self._pop_fm_event() + def consume_module_event(self) -> list: + 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.event_system = Builtins() -builtins.event_sleep_time = 0.2 -builtins.debug = False +builtins.event_system = EventSystem() +builtins.event_sleep_time = 0.05 builtins.trace_debug = False +builtins.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 cb48f8c..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,54 +1,3 @@ -# Python imports -import os, inspect, time - -# Lib imports - -# Application imports -from utils import Settings -from controller import Controller -from __builtins__ import Builtins - - - - -class Main(Builtins): - def __init__(self, args, unknownargs): - if not debug: - event_system.create_ipc_server() - - time.sleep(0.2) - if not trace_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/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index 66870d2..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,10 +15,12 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from __init__ import Main +from app import Application if __name__ == "__main__": + """ Set process title, get arguments, and create GTK main thread. """ + try: # import web_pdb # web_pdb.set_trace() @@ -33,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..bc414b5 --- /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 core.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_listener() + time.sleep(0.05) + + 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...\nNote: If no fm exists, remove /tmp/solarfm-ipc.sock") + + + 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.get_builder().connect_signals(handlers) 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 deleted file mode 100644 index ecedf6b..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/Controller.py +++ /dev/null @@ -1,162 +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 import UIMixin -from .signals import IPCSignalsMixin, 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(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, 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__, target) - 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.ctrlDown = False 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 d4205b8..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .ShowHideMixin import ShowHideMixin -from .UIMixin import UIMixin 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 deleted file mode 100644 index d6e57fb..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/TabMixin.py +++ /dev/null @@ -1,205 +0,0 @@ -# Python imports -import os - -# Lib imports -import gi -gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk - -# Application imports -from . import WidgetMixin - - - - -class TabMixin(WidgetMixin): - """docstring for TabMixin""" - - 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 - - view.set_wid(wid) - if path: view.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) - - self.window_controller.set_active_data(wid, view.get_tab_id()) - path_entry.set_text(view.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.set_window_title() - self.set_file_watcher(view) - - - - - def close_tab(self, button, eve=None): - notebook = button.get_parent().get_parent() - tid = self.get_tab_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) - view = self.get_fm_window(wid).get_view_by_id(tid) - watcher = view.get_dir_watcher() - - watcher.cancel() - self.get_fm_window(wid).delete_view_by_id(tid) - notebook.remove_page(page) - self.window_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 - - for i, view in enumerate(window.views): - if view.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)) - - view = window.get_view_by_id(tid) - self.set_file_watcher(view) - self.window_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_active_data(wid, tid) - self.set_path_text(wid, tid) - self.set_window_title() - - def get_tab_id_from_tab_box(self, tab_box): - tid = tab_box.get_children()[2] - return tid.get_text() - - def get_tab_label(self, notebook, iconview): - return notebook.get_tab_label(iconview.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_iconview_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) - - def update_view(self, tab_label, view, store, wid, tid): - self.load_store(view, store) - self.set_path_text(wid, tid) - - char_width = len(view.get_end_of_path()) - tab_label.set_width_chars(char_width) - tab_label.set_label(view.get_end_of_path()) - self.set_window_title() - self.set_file_watcher(view) - self.window_controller.save_state() - - def do_action_from_bar_controls(self, widget, eve=None): - action = widget.get_name() - wid, tid = self.window_controller.get_active_data() - 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) - - 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() - self.create_tab(wid, dir) - self.window_controller.save_state() - return - if action == "path_entry": - focused_obj = self.window.get_focus() - dir = f"{view.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.files + view.hidden - - self.clear_children(button_box) - show_path_menu = False - for file in files: - if os.path.isdir(f"{dir}{file}"): - if query.lower() in file.lower(): - button = Gtk.Button(label=file) - button.show() - button.connect("clicked", self.set_path_entry) - button_box.add(button) - show_path_menu = True - - if not show_path_menu: - self.path_menu.popdown() - else: - self.path_menu.popup() - widget.grab_focus_without_selecting() - widget.set_position(-1) - - if path.endswith(".") or path == dir: - return - - traversed = view.set_path(path) - if not traversed: - return - - self.update_view(tab_label, view, store, wid, tid) - - try: - widget.grab_focus_without_selecting() - widget.set_position(-1) - except Exception as e: - 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()}" - 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.window_controller.get_active_data() - 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() - watcher.cancel() - - self.get_fm_window(wid).delete_view_by_id(tid) - notebook.remove_page(page) - self.window_controller.save_state() - self.set_window_title() - - # File control events - def show_hide_hidden_files(self): - wid, tid = self.window_controller.get_active_data() - view = self.get_fm_window(wid).get_view_by_id(tid) - view.hide_hidden = not view.hide_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/__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/KeyboardSignalsMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py deleted file mode 100644 index b397027..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/signals/KeyboardSignalsMixin.py +++ /dev/null @@ -1,120 +0,0 @@ -# Python imports -import re - -# Lib imports -import gi -gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk - -# Application imports - - -valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") - - -class KeyboardSignalsMixin: - def unset_keys_and_data(self, widget=None, eve=None): - self.ctrlDown = False - self.shiftDown = False - self.altDown = 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 "control" in keyname: - self.ctrlDown = True - if "shift" in keyname: - self.shiftDown = True - if "alt" in keyname: - self.altDown = 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}") - - if "control" in keyname or "alt" in keyname or "shift" in keyname: - if "control" in keyname: - self.ctrlDown = False - if "shift" in keyname: - self.shiftDown = False - if "alt" in keyname: - self.altDown = False - - - if self.ctrlDown and self.shiftDown and keyname == "t": - 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: - 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.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.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.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() - if self.ctrlDown and keyname == "c": - self.to_cut_files.clear() - self.copy_files() - 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.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.delete_files() - if keyname == "f2": - 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) 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/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/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py new file mode 100644 index 0000000..3b7ba50 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -0,0 +1,200 @@ +# 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 + + +# 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 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.fm_controller_data) + 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.fm_controller.save_state() + time.sleep(event_sleep_time) + Gtk.main_quit() + + + @daemon_threaded + def gui_event_observer(self): + while True: + time.sleep(event_sleep_time) + event = event_system.consume_gui_event() + if event: + try: + 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__, method_target) + GLib.idle_add(method, *(self, *parameters,)) + except Exception as e: + print(repr(e)) + + def handle_gui_event_and_return_message(self, sender, method_target, parameters): + method = getattr(self.__class__, f"{method_target}") + 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) + save_load_dialog = self.builder.get_object("save_load_dialog") + + if action == "save_session": + self.fm_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(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_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": + path = f"{save_load_dialog.get_file().get_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 + + save_load_dialog.hide() + + def load_session(self, session_json): + if debug: + self.logger.debug(f"Session Data: {session_json}") + + self.ctrl_down = False + self.shift_down = False + self.alt_down = False + for notebook in self.notebooks: + self.clear_children(notebook) + + self.fm_controller.unload_tabs_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.cut_files() + if action == "copy": + 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) + + + + + + + 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) + tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory()) 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 new file mode 100644 index 0000000..6a787f4 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -0,0 +1,187 @@ +# Python imports +import sys, os, signal +from dataclasses import dataclass + +# Lib imports +import gi +from gi.repository import GLib + +# Application imports +from trasher.xdgtrash import XDGTrash +from shellfm.windows.controller import WindowController +from plugins.plugins import Plugins + + +@dataclass(slots=True) +class State: + wid: int = None + tid: int = None + 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: + """ 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 + 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.fm_controller_data = self.fm_controller.get_state_from_file() + self.trashman.regenerate() + + self.window = self.settings.get_main_window() + self.window1 = self.builder.get_object("window_1") + 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_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") + + 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.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") + self.bottom_path_label = self.builder.get_object("bottom_path_label") + + 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 + # %N: All selected filenames/dirs to archive, or (with %O) a single filename + # %o: Resulting single archive file + # %O: Resulting archive per source file/directory (use changes %N meaning) + # + # In extract commands: + # %x: Archive file to extract + # %g: Unique extraction target filename with optional subfolder + # %G: Unique extraction target filename, never with subfolder + # + # In list commands: + # %x: Archive to list + # + # Plus standard bash variables are accepted. + self.arc_commands = [ '$(which 7za || echo 7zr) a %o %N', + 'zip -r %o %N', + 'rar a -r %o %N', + 'tar -cvf %o %N', + 'tar -cvjf %o %N', + 'tar -cvzf %o %N', + 'tar -cvJf %o %N', + 'gzip -c %N > %O', + '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.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_icon_grid = None + self.search_tab = None + + self.skip_edit = False + self.cancel_edit = False + self.ctrl_down = False + self.shift_down = False + self.alt_down = False + + 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) + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) + + + def get_current_state(self) -> State: + ''' + Returns the state info most useful for any given context and action intent. + + Parameters: + a (obj): self + + Returns: + state (obj): State + ''' + state = State() + state.wid, state.tid = self.fm_controller.get_active_wid_and_tid() + 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() + + + 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 + + + def clear_console(self) -> None: + ''' Clears the terminal screen. ''' + os.system('cls' if os.name == 'nt' else 'clear') + + def call_method(self, _method_name: str, data: type = None) -> type: + ''' + 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) -> type: + ''' Checks if a given method exists. ''' + return callable(getattr(obj, name, None)) + + 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/core/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/__init__.py new file mode 100644 index 0000000..ded9abc --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/__init__.py @@ -0,0 +1,3 @@ +""" +Mixins module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py new file mode 100644 index 0000000..81a0b95 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/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_color, data) + + def display_message(self, type, text, seconds=None): + self.message_buffer.insert_at_cursor(text) + 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_popup_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/core/mixins/show_hide_mixin.py similarity index 78% 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/core/mixins/show_hide_mixin.py index 49b29d8..cf77ba0 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/core/mixins/show_hide_mixin.py @@ -4,19 +4,18 @@ 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 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 - def show_exists_page(self, widget=None, eve=None): response = self.file_exists_dialog.run() self.file_exists_dialog.hide() @@ -49,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): @@ -57,11 +56,11 @@ class ShowHideMixin: def show_archiver_dialogue(self, widget=None, eve=None): - wid, tid = self.window_controller.get_active_data() - 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() @@ -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,20 +96,34 @@ class ShowHideMixin: dialog.response(Gtk.ResponseType.OK) + def show_plugins_popup(self, widget=None, eve=None): + self.builder.get_object("plugin_controls").popup() + + def hide_plugins_popup(self, widget=None, eve=None): + 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): - self.builder.get_object("new_file_menu").run() + 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() + 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() @@ -124,7 +139,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/core/mixins/ui/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/__init__.py new file mode 100644 index 0000000..991f9a2 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/__init__.py @@ -0,0 +1,3 @@ +""" +UI module +""" 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 new file mode 100644 index 0000000..01ff051 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -0,0 +1,235 @@ +# Python imports +import os, threading, subprocess, time + +# Lib imports +import gi + +gi.require_version("Gtk", "3.0") +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf + +# Application imports + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +# 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""" + + def load_store(self, tab, store, save_state=False): + store.clear() + dir = tab.get_current_directory() + files = tab.get_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 + if save_state: + self.fm_controller.save_state() + + @threaded + def create_icon(self, i, tab, store, 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): + 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) + + itr = store.get_iter(i) + store.set_value(itr, 0, icon) + + def get_system_thumbnail(self, filename, size): + try: + 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: + return None + + + 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"{tab.get_end_of_path()}") + label.set_width_chars(len(tab.get_end_of_path())) + label.set_xalign(0.0) + tid.set_label(f"{tab.get_id()}") + + close.add(icon) + tab_widget.add(label) + tab_widget.add(close) + tab_widget.add(tid) + + close.connect("released", self.close_tab) + tab_widget.show_all() + 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() + store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) + + grid.set_model(store) + grid.set_pixbuf_column(0) + grid.set_text_column(1) + + grid.set_item_orientation(1) + grid.set_selection_mode(3) + grid.set_item_width(96) + grid.set_item_padding(8) + grid.set_margin(12) + grid.set_row_spacing(18) + grid.set_columns(-1) + grid.set_spacing(12) + 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) + + URI_TARGET_TYPE = 80 + uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) + targets = [ uri_target ] + action = Gdk.DragAction.COPY + 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) + return scroll, store + + 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 or None) + column = Gtk.TreeViewColumn("Icons") + icon = Gtk.CellRendererPixbuf() + name = Gtk.CellRendererText() + selec = grid.get_selection() + + grid.set_model(store) + selec.set_mode(3) + column.pack_start(icon, False) + column.pack_start(name, True) + column.add_attribute(icon, "pixbuf", 0) + column.add_attribute(name, "text", 1) + column.set_expand(False) + column.set_sizing(2) + column.set_min_width(120) + column.set_max_width(74) + + grid.append_column(column) + grid.set_search_column(1) + grid.set_rubber_banding(True) + grid.set_headers_visible(False) + 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) + + URI_TARGET_TYPE = 80 + uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) + targets = [ uri_target ] + action = Gdk.DragAction.COPY + 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 + + + def get_store_and_label_from_notebook(self, notebook, _name): + icon_grid = None + tab_label = None + store = None + + for obj in notebook.get_children(): + icon_grid = obj.get_children()[0] + name = icon_grid.get_name() + if name == _name: + 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/controller/mixins/ui/PaneMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/pane_mixin.py similarity index 84% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/pane_mixin.py index 235736d..3d2c888 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/controller/mixins/ui/PaneMixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/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.isHidden = state - self.window_controller.save_state() + window = self.fm_controller.get_window_by_index(pane_index - 1) + window.set_is_hidden(state) + self.fm_controller.save_state() 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 new file mode 100644 index 0000000..539f840 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py @@ -0,0 +1,194 @@ +# Python imports +import os + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from .grid_mixin import GridMixin + + + + +class TabMixin(GridMixin): + """docstring for TabMixin""" + + def create_tab(self, wid=None, tid=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}") + tab.logger = self.logger + + tab.set_wid(wid) + 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_scroll_and_store(tab, wid) + index = notebook.append_page(scroll, tab_widget) + + 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(tab, store) + self.set_window_title() + self.set_file_watcher(tab) + + + + + def close_tab(self, button, eve=None): + notebook = button.get_parent().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) + 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 on_tab_reorder(self, child, page_num, new_index): + wid, tid = page_num.get_name().split("|") + window = self.get_fm_window(wid) + tab = None + + 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_tabs().insert(new_index, window.get_all_tabs().pop(i)) + + 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.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): + return tab_box.get_children()[2].get_text() + + 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, icon_grid): + return notebook.get_tab_label(icon_grid.get_parent()).get_children()[1] + + def get_tab_icon_grid_from_notebook(self, notebook): + return notebook.get_children()[1].get_children()[0] + + def refresh_tab(data=None): + 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) + self.set_path_text(wid, tid) + + char_width = len(tab.get_end_of_path()) + tab_label.set_width_chars(char_width) + tab_label.set_label(tab.get_end_of_path()) + self.set_window_title() + 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.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}") + tab = self.get_fm_window(wid).get_tab_by_id(tid) + + if action == "create_tab": + dir = tab.get_current_directory() + self.create_tab(wid, None, dir) + 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"{tab.get_current_directory()}/" + path = widget.get_text() + + if isinstance(focused_obj, Gtk.Entry): + 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(path_menu_buttons) + show_path_menu = False + for file, hash in files: + if os.path.isdir(f"{dir}{file}"): + if query.lower() in file.lower(): + button = Gtk.Button(label=file) + button.show() + button.connect("clicked", self.set_path_entry) + path_menu_buttons.add(button) + show_path_menu = True + + if not show_path_menu: + self.path_menu.popdown() + else: + self.path_menu.popup() + widget.grab_focus_without_selecting() + widget.set_position(-1) + + if path.endswith(".") or path == dir: + return + + if not tab.set_path(path): + return + + self.update_tab(tab_label, tab, store, wid, tid) + + try: + widget.grab_focus_without_selecting() + widget.set_position(-1) + except Exception as e: + pass + + def set_path_entry(self, button=None, eve=None): + 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 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) + 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/signal_classes/mixins/ui/WidgetFileActionMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py similarity index 52% 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/core/mixins/ui/widget_file_action_mixin.py index 06ba422..9fc5a83 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/core/mixins/ui/widget_file_action_mixin.py @@ -1,17 +1,23 @@ # Python imports -import os +import os, time, threading, shlex # 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: @@ -34,47 +40,70 @@ 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()}") + self.logger.debug(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_tab_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 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, Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, - Gio.FileMonitorEvent.MOVED_OUT]: - if debug: - print(eve_type) + Gio.FileMonitorEvent.MOVED_OUT]: + if debug: + self.logger.debug(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_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]) - 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_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_widget]["last_update_time"] + current_time = time.time() + 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,)) + + + def update_on_soft_lock_end(self, tab_widget): + wid, tid = tab_widget.split("|") + notebook = self.builder.get_object(f"window_{wid}") + 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}") + + tab.load_directory() + self.load_store(tab, store) + + tab_widget_label.set_label(tab.get_end_of_path()) + state = self.get_current_state() + if [wid, tid] in [state.wid, state.tid]: + self.set_bottom_labels(tab) def popup_search_files(self, wid, keyname): @@ -85,60 +114,57 @@ class WidgetFileActionMixin: entry.set_position(-1) 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(): + 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(): 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() - if len(items) == 1: - self.search_iconview.scroll_to_path(items[0], True, 0.5, 0.5) + items = self.search_icon_grid.get_selected_items() + if len(items) > 0: + self.search_icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5) def open_files(self): - wid, tid, view, iconview, 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: - view.open_file_locally(file) + state.tab.open_file_locally(file) def open_with_files(self, appchooser_widget): - wid, tid, view, iconview, 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) - - view.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, view, iconview, store = self.get_current_state() - paths = self.format_to_uris(store, wid, tid, self.selected_files, True) - current_dir = view.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"{view.terminal_app} -e '{path}'" - view.execute(command, start_dir=view.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): - wid, tid, view, iconview, store = self.get_current_state() - paths = self.format_to_uris(store, wid, tid, self.selected_files, True) + state = self.get_current_state() + 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() 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"{view.terminal_app} -e '{pre_command}'" + command = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}" - view.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") rename_input = self.builder.get_object("new_rename_fname") - wid, tid, view, iconview, 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] @@ -146,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 @@ -154,7 +181,7 @@ class WidgetFileActionMixin: break rname_to = rename_input.get_text().strip() - target = f"{view.get_current_directory()}/{rname_to}" + target = f"{state.tab.get_current_directory()}/{rname_to}" self.handle_files([uri], "rename", target) @@ -164,28 +191,30 @@ class WidgetFileActionMixin: self.selected_files.clear() def cut_files(self): - wid, tid, view, iconview, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) + 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): - wid, tid, view, iconview, store = self.get_current_state() - uris = self.format_to_uris(store, wid, tid, self.selected_files, True) + 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 def paste_files(self): - wid, tid = self.window_controller.get_active_data() - 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() - 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)?") @@ -199,7 +228,7 @@ class WidgetFileActionMixin: type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: - view.delete_file( file.get_path() ) + state.tab.delete_file( file.get_path() ) else: file.delete(cancellable=None) else: @@ -207,14 +236,14 @@ class WidgetFileActionMixin: def trash_files(self): - wid, tid, view, iconview, 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, view, iconview, 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) @@ -227,9 +256,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_data() - 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}" @@ -239,12 +268,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): @@ -261,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()}" @@ -273,8 +305,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,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_data() - 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) @@ -322,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_data() - 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) @@ -344,6 +375,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() @@ -354,10 +424,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/debs/solarfm-0-0-1-x64/opt/SolarFM/signal_classes/mixins/ui/WindowMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py similarity index 51% 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/core/mixins/ui/window_mixin.py index 17e4be3..414e48c 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/core/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 @@ -9,58 +9,59 @@ gi.require_version('Gdk', '3.0') from gi.repository import Gdk, Gio # Application imports -from . import TabMixin, WidgetMixin - - +from .tab_mixin import TabMixin 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}") - 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) + if tabs: + for tab in tabs: + self.create_new_tab_notebook(None, i, tab) + else: + self.create_new_tab_notebook(None, i, None) - 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: @@ -78,10 +79,10 @@ class WindowMixin(TabMixin): return uris - def set_bottom_labels(self, view): - _wid, _tid, _view, iconview, store = self.get_current_state() - selected_files = iconview.get_selected_items() - current_directory = view.get_current_directory() + def set_bottom_labels(self, tab): + 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) formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) ) @@ -96,9 +97,9 @@ 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: - uris = self.format_to_uris(store, _wid, _tid, selected_files, True) + self.bottom_path_label.set_label(tab.get_current_directory()) + if selected_files: + uris = self.format_to_uris(state.store, state.wid, state.tid, selected_files, True) combined_size = 0 for uri in uris: try: @@ -113,29 +114,29 @@ class WindowMixin(TabMixin): formatted_size = self.sizeof_fmt(combined_size) - if view.hide_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.hide_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.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: - 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_data() + 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() @@ -146,72 +147,75 @@ class WindowMixin(TabMixin): ctx.remove_class("notebook-unselected-focus") ctx.add_class("notebook-selected-focus") - self.window.set_title("SolarFM ~ " + dir) - self.set_bottom_labels(view) + self.window.set_title(f"SolarFM ~ {dir}") + 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_active_data(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() - notebook = self.builder.get_object(f"window_{wid}") - tab_label = self.get_tab_label(notebook, iconview) + 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 = view.get_current_directory() + fileName = state.store[item][1] + dir = state.tab.get_current_directory() file = f"{dir}/{fileName}" if isdir(file): - view.set_path(file) - self.update_view(tab_label, view, 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: - self.display_message(self.error, f"{repr(e)}") + traceback.print_exc() + 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) @@ -220,30 +224,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_data()) - 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_active_data(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_data() + 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") @@ -252,5 +256,5 @@ class WindowMixin(TabMixin): self.move_files(uris, dest) - def create_new_view_notebook(self, widget=None, wid=None, path=None): - self.create_tab(wid, path) + def create_new_tab_notebook(self, widget=None, wid=None, path=None): + self.create_tab(wid, None, path) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py new file mode 100644 index 0000000..d127b28 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/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/core/signals/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/__init__.py new file mode 100644 index 0000000..c2a7368 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/__init__.py @@ -0,0 +1,3 @@ +""" +Signals module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/ipc_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/ipc_signals_mixin.py new file mode 100644 index 0000000..3aeb267 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/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.fm_controller.get_active_wid_and_tid() + notebook = self.builder.get_object(f"window_{wid}") + if notebook.is_visible(): + self.create_tab(wid, None, path) + return + + if not self.is_pane4_hidden: + self.create_tab(4, None, path) + elif not self.is_pane3_hidden: + self.create_tab(3, None, path) + elif not self.is_pane2_hidden: + self.create_tab(2, None, path) + elif not self.is_pane1_hidden: + self.create_tab(1, None, path) 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 new file mode 100644 index 0000000..e5f70e8 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py @@ -0,0 +1,92 @@ +# Python imports +import re + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk, Gdk + +# Application imports + + +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 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: + self.ctrl_down = True + if "shift" in keyname: + self.shift_down = True + if "alt" in keyname: + self.alt_down = True + + 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 + if "shift" in keyname: + self.shift_down = False + if "alt" in keyname: + self.alt_down = False + + mapping = self.keybindings.lookup(event) + if mapping: + 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}") + + 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 + 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_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() 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 deleted file mode 100644 index 6458eb1..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/Plugins.py +++ /dev/null @@ -1,63 +0,0 @@ -# Python imports -import os, importlib -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 Plugins: - """docstring for Plugins""" - def __init__(self, settings): - self._settings = settings - self._plugins_path = self._settings.get_plugins_path() - 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() - - - 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) - - 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) - self._plugin_collection.append([file, module]) - - spec.loader.exec_module(module) - module.Main(self.gtk_socket_id, event_system) - - 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) 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..2d547b4 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -0,0 +1,147 @@ +# 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: + path: str = None + name: str = None + author: str = None + version: str = None + support: str = None + requests:{} = None + reference: type = None + + + +class Plugins: + """Plugins controller""" + + def __init__(self, settings: type): + 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 = [] + + + def launch_plugins(self) -> None: + self._set_plugins_watcher() + self.load_plugins() + + 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, ()) + + 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) + + def load_plugins(self, file: str = None) -> None: + print(f"Loading plugins...") + parent_path = os.getcwd() + + 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: + target = join(path, "plugin.py") + if not os.path.exists(target): + raise Exception("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...") + + 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(f"Malformed Plugin: Not loading -->: '{folder}' !") + traceback.print_exc() + # if debug: + # traceback.print_exc() + + os.chdir(parent_path) + + + def load_plugin_module(self, path, folder, target): + os.chdir(path) + sys.path.insert(0, path) + 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.requests = module.Manifest.requests + + return plugin + + def parse_requests(self, plugin): + loading_data = {} + requests = plugin.requests + keys = requests.keys() + + if "ui_target" in keys: + 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(requests["ui_target_id"]) + if loading_data["ui_target"] == None: + 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 requests. Must have one if setting "ui_target" to "other"...') + else: + loading_data["ui_target"] = self._builder.get_object(requests["ui_target"]) + else: + raise Exception('Unknown "ui_target" given in requests.') + + + if "pass_fm_events" in keys: + if requests["pass_fm_events"] in ["true"]: + loading_data["pass_fm_events"] = True + + if "bind_keys" in keys: + if isinstance(requests["bind_keys"], list): + loading_data["bind_keys"] = requests["bind_keys"] + + 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) + + if "bind_keys" in keys: + self._keybindings.append_bindings(loading_data["bind_keys"]) + + 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/shellfm/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/__init__.py index 0c8b591..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 @@ -1 +1,3 @@ -from .windows import WindowController +""" +Root of ShellFM +""" 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 7f068e7..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/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/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..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 @@ -1,2 +1,3 @@ -from .Window import Window -from .WindowController import WindowController +""" +Window module +""" 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..abec786 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/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_tab_for_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.create_tab() + + def add_tab_for_window_by_name(self, name): + for window in self._windows: + if window.get_name() == name: + return window.create_tab() + + def add_tab_for_window_by_nickname(self, nickname): + for window in self._windows: + if window.get_nickname() == nickname: + return window.create_tab() + + 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"Tab Count: {window.get_tabs_count()}") + print("\n-------------------------\n") + + + + 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_tabs() + break + + def get_tabs_count(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_tabs_count() + + def get_tabs_from_window(self, win_id): + for window in self._windows: + if window.get_id() == win_id: + return window.get_all_tabs() + + + + + def unload_tabs_and_windows(self): + for window in self._windows: + window.get_all_tabs().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: + tabs = [] + for tab in window.get_all_tabs(): + tabs.append(tab.get_current_directory()) + + windows.append( + [ + { + 'window':{ + "ID": window.get_id(), + "Name": window.get_name(), + "Nickname": window.get_nickname(), + "isHidden": f"{window.is_hidden()}", + 'tabs': tabs + } + } + ] + ) + + 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 get_state_from_file(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/tabs/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/__init__.py new file mode 100644 index 0000000..c4d8aba --- /dev/null +++ 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 new file mode 100644 index 0000000..14bb42f --- /dev/null +++ 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/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/Icon.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py similarity index 80% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/Icon.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py index 3c14d2f..a503f61 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/icons/Icon.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/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): @@ -27,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) @@ -43,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") @@ -56,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) ) 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 new file mode 100644 index 0000000..d5bc819 --- /dev/null +++ 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/view/icons/mixins/DesktopIconMixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py similarity index 84% 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/tabs/icons/mixins/desktopiconmixin.py index 2d3c30b..3f69ea3 100644 --- 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/tabs/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 @@ -21,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 = "" @@ -46,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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/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/view/Path.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/path.py diff --git a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/View.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py similarity index 50% rename from src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/View.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py index 594dee1..70acaed 100644 --- a/src/debs/solarfm-0-0-1-x64/opt/SolarFM/shellfm/windows/view/View.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.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): +class Tab(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/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 new file mode 100644 index 0000000..aad0ae1 --- /dev/null +++ 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/shellfm/windows/view/utils/FileHandler.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/filehandler.py similarity index 98% 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/tabs/utils/filehandler.py index d0f7396..105782a 100644 --- 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/tabs/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/view/utils/Launcher.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py similarity index 85% 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/tabs/utils/launcher.py index 22a14e3..eab19cd 100644 --- 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/tabs/utils/launcher.py @@ -1,6 +1,5 @@ # System import -import os, threading, subprocess - +import os, threading, subprocess, shlex # Lib imports @@ -32,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): @@ -41,26 +42,24 @@ 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): 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/shellfm/windows/tabs/utils/settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py new file mode 100644 index 0000000..928bf6a --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py @@ -0,0 +1,84 @@ +# System import +import json +import os +from os import path + +# Lib imports + + +# Apoplication imports + + + +class Settings: + logger = None + + USR_SOLARFM = "/usr/share/solarfm" + USER_HOME = path.expanduser('~') + CONFIG_PATH = f"{USER_HOME}/.config/solarfm" + CONFIG_FILE = f"{CONFIG_PATH}/settings.json" + HIDE_HIDDEN_FILES = True + + 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 + + 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" + + # Dir structure check + if not path.isdir(REMUX_FOLDER): + os.mkdir(REMUX_FOLDER) + + if not path.isdir(BASE_THUMBS_PTH): + os.mkdir(BASE_THUMBS_PTH) + + if not path.isdir(ABS_THUMBS_PTH): + os.mkdir(ABS_THUMBS_PTH) + + if not path.isdir(STEAM_ICONS_PTH): + os.mkdir(STEAM_ICONS_PTH) + + 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/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/Settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/Settings.py deleted file mode 100644 index e4d9323..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/view/utils/Settings.py +++ /dev/null @@ -1,100 +0,0 @@ -# System import -import json -import os -from os import path - -# Lib imports - - -# Apoplication imports - - - -class Settings: - logger = None - - USR_SOLARFM = "/usr/share/solarfm" - USER_HOME = path.expanduser('~') - CONFIG_PATH = f"{USER_HOME}/.config/solarfm" - CONFIG_FILE = f"{CONFIG_PATH}/settings.json" - HIDE_HIDDEN_FILES = True - - 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') - ftext = ('.txt', '.text', '.sh', '.cfg', '.conf') - fmusic = ('.psf', '.mp3', '.ogg', '.flac', '.m4a') - fpdf = ('.pdf') - - - # Dir structure check - if not path.isdir(REMUX_FOLDER): - os.mkdir(REMUX_FOLDER) - - if not path.isdir(BASE_THUMBS_PTH): - os.mkdir(BASE_THUMBS_PTH) - - if not path.isdir(ABS_THUMBS_PTH): - os.mkdir(ABS_THUMBS_PTH) - - if not path.isdir(STEAM_ICONS_PTH): - os.mkdir(STEAM_ICONS_PTH) - - if not os.path.exists(DEFAULT_ICONS): - DEFAULT_ICONS = f"{USR_SOLARFM}/icons" - DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png" 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/window.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py new file mode 100644 index 0000000..ec61cd6 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py @@ -0,0 +1,87 @@ +# Python imports +from random import randint + + +# Lib imports + + +# Application imports +from .tabs.tab import Tab + + +class Window: + def __init__(self): + self._id_length = 10 + self._id = "" + self._name = "" + self._nickname = "" + self._isHidden = False + self._tabs = [] + + self._generate_id() + self._set_name() + + + def create_tab(self): + tab = Tab() + self._tabs.append(tab) + return tab + + def pop_tab(self): + self._tabs.pop() + + def delete_tab_by_id(self, tid): + for tab in self._tabs: + if tab.get_id() == tid: + self._tabs.remove(tab) + break + + + def get_tab_by_id(self, tid): + for tab in self._tabs: + if tab.get_id() == tid: + return tab + + def get_tab_by_index(self, index): + return self._tabs[index] + + def get_tabs_count(self): + return len(self._tabs) + + def get_all_tabs(self): + return self._tabs + + 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 list_files_from_tabs(self): + for tab in self._tabs: + print(tab.get_files()) + + + 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/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 +""" 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 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 deleted file mode 100644 index d6a42ee..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/Settings.py +++ /dev/null @@ -1,95 +0,0 @@ -# Python imports -import os -from os import path - -# Gtk imports -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 - - -# Application imports -from . 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.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.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) - - - - def create_window(self): - # Get window and connect signals - 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() - 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) - - # 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) - - def _area_draw(self, widget, cr): - 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): - 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_main_window(self): return self.main_window - def get_plugins_path(self): return self.PLUGINS_PATH 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..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 @@ -1,6 +1,3 @@ """ Utils module """ - -from .Logger import Logger -from .Settings import Settings 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 new file mode 100644 index 0000000..5ad037d --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -0,0 +1,100 @@ +# Python imports +import os, threading, time +from multiprocessing.connection import Listener, Client + +# Lib imports + +# Application imports + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +class IPCServer: + """ Create a listener so that other SolarFM instances send requests back to existing instance. """ + 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' + 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 + def create_ipc_listener(self) -> None: + 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) + 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)) + + + self.is_ipc_alive = True + while True: + conn = listener.accept() + start_time = time.perf_counter() + 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 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.send(message) + conn.close() + except ConnectionRefusedError as e: + print("Connection refused...") + except Exception as e: + print(repr(e)) 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..f1bf0bc --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/keybindings.py @@ -0,0 +1,138 @@ +# 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 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 + 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 isinstance(bindings, list): + bindings = (*bindings,) + elif 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/Logger.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py similarity index 50% 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 index 06eed47..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): - self._CONFIG_PATH = config_path - - def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True): - """ - 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 new file mode 100644 index 0000000..3826e3d --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py @@ -0,0 +1,113 @@ +# Python imports +import os, json +from os import path + +# Gtk imports +import gi, cairo +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') + +from gi.repository import Gtk +from gi.repository import Gdk + + +# Application imports +from .logger import Logger +from .keybindings import Keybindings + + + + +class Settings: + def __init__(self): + 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._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" + self._ICON_THEME = Gtk.IconTheme.get_default() + + 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._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): + 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._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, _fh_log_lvl=20).get_logger() + self._builder = Gtk.Builder() + self._builder.add_from_file(self._GLADE_FILE) + + + 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) -> None: + 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) + + # 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) + + 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) -> 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)) + print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y)) + + return monitors + + 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) -> 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/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 +""" diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index e760dc6..56fdb37 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -616,24 +616,62 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-save-as - + True False gtk-new - + + True False - popup - False - True - center - True - splashscreen - True - True - False - center - + gtk-execute + + + True + False + gtk-save-as + + + True + False + gtk-file + + + True + False + gtk-justify-center + + + True + False + gtk-save + + + True + False + gtk-execute + + + + True + False + gtk-open + + + True + False + gtk-edit + 3 + + + True + False + gtk-edit + + + False + dialog + True False @@ -644,15 +682,459 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False end - - Create - create + + gtk-cancel + True True True True - Create File/Folder... - createImage + True + + + True + True + 0 + + + + + gtk-ok + True + True + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + + + + + button11 + button12 + + + + True + False + gtk-media-forward + + + True + False + gtk-apply + 3 + + + True + False + gtk-apply + 3 + + + True + False + gtk-apply + 3 + + + True + False + gtk-apply + 3 + + + 800 + 600 + False + center + 1670 + 830 + center + + + + + + True + False + vertical + top + + + True + False + + + True + False + + + True + False + _File + True + + + True + False + + + gtk-new + create + True + False + New File/Folder... + True + True + + + + + + gtk-open + open + True + False + Open... + True + True + + + + + + True + False + + + + + Terminal + True + False + image5 + False + + + + + + True + 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 + + + + + + + + + + True + False + Debug + + + True + False + + + Show Errors + True + False + image3 + False + + + + + + + + + + gtk-quit + True + False + True + True + + + + + + + + + + True + False + _Edit + True + + + True + False + + + gtk-cut + cut + True + False + True + True + + + + + + gtk-copy + copy + True + False + True + True + + + + + + gtk-paste + paste + True + False + True + True + + + + + + gtk-delete + delete + True + False + True + True + + + + + + + + + + True + False + _Help + True + + + True + False + + + gtk-about + True + False + True + True + + + + + + + + + + True + True + 0 + + + + + True + False + 5 + start + + + Plugins + True + True + True + + + + True + True + 0 + + + + + tggl_notebook_1 + True + True + True + tggl_notebook_1_img + True + + + + True + True + 1 + + + + + tggl_notebook_2 + True + True + True + tggl_notebook_2_img + True + + + + True + True + 2 + + + + + tggl_notebook_3 + True + True + True + tggl_notebook_3_img + True + + + + True + True + 3 + + + + + tggl_notebook_4 + True + True + True + tggl_notebook_4_img + True + + + + True + True + 4 + + + + + False + True + 1 + + + + + + True + False + False + False + False + False + + + True + True + 2 + + + + + False + True + 0 + + + + + True + False + + + gtk-home + go_home + True + True + True + True True + False @@ -661,13 +1143,377 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - - gtk-cancel + + gtk-add + create_tab True True True True True + + + + False + True + 1 + + + + + gtk-go-up + go_up + True + True + True + True + True + + + + False + True + 2 + + + + + path_entry + True + True + True + Path... + + + + True + True + 3 + + + + + gtk-refresh + refresh_view + True + True + True + True + True + + + + False + True + 4 + + + + + False + True + 1 + + + + + True + True + True + True + vertical + True + + + True + True + 5 + True + True + True + + + notebook1 + True + True + True + 5 + 5 + 5 + 5 + False + True + + + + + + + + + + + + + + + + + + + + + + + False + False + + + + + notebook2 + True + True + True + 5 + 5 + 5 + 5 + False + True + + + + + + + + + + + + + + + + + + + + + + + False + False + + + + + True + True + + + + + True + True + 5 + True + True + True + + + notebook3 + True + True + True + 5 + 5 + 5 + 5 + False + True + + + + + + + + + + + + + + + + + + + + + + + False + False + + + + + notebook4 + True + True + True + 5 + 5 + 5 + False + True + + + + + + + + + + + + + + + + + + + + + + + False + False + + + + + True + True + + + + + True + True + 2 + + + + + True + False + 10 + 10 + 10 + 10 + 6 + 6 + 15 + top + + + True + False + + + False + True + 0 + + + + + True + False + + + False + True + 1 + + + + + True + False + + + False + True + 2 + + + + + False + True + 3 + + + + + + + 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 @@ -688,14 +1534,32 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False vertical - - 500 - 26 + True - True - True - gtk-edit - New File/Dir Name... + False + + + True + False + Rename: + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + False @@ -704,68 +1568,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - + + 500 + 26 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 - - + True + True + Rename To: + True + gtk-edit + False + False + False + False + To: + False @@ -773,9 +1590,26 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 1 + + + Rename + rename + True + True + True + rename_img + True + + + + False + True + 2 + + - False + True True 1 @@ -783,29 +1617,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - button10 - button9 + button2 + button1 - - True - False - gtk-execute - 120 False - popup False True - center + center-always True - splashscreen + normal True True True + False False center + Main_Window False @@ -1124,842 +1954,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - - True - False - gtk-justify-center - - - - True - False - gtk-open - - - True - False - gtk-edit - 3 - - - True - False - gtk-edit - - - True - False - gtk-media-forward - - - False - popup - False - True - center - True - splashscreen - True - True - False - center - - - False - vertical - 2 - - - False - end - - - Skip - skip_renames - True - True - True - skip_img - True - - - True - True - 0 - - - - - gtk-cancel - cancel_renames - True - True - True - 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 - gtk-edit - To: - - - - False - True - 1 - - - - - Rename - rename - True - True - True - rename_img - True - - - - False - True - 2 - - - - - True - True - 1 - - - - - - button1 - button2 - - - - True - False - gtk-apply - 3 - - - True - False - gtk-apply - 3 - - - True - False - gtk-apply - 3 - - - True - False - gtk-apply - 3 - - - 800 - 600 - False - center - 1670 - 830 - center - - - - - - True - False - vertical - top - - - True - False - - - True - False - - - True - False - _File - True - - - True - False - - - gtk-new - create - True - False - New File/Folder... - True - True - - - - - - gtk-open - open - True - False - Open... - True - True - - - - - - True - False - - - - - gtk-quit - True - False - True - True - - - - - - - - - - True - False - _Edit - True - - - True - False - - - gtk-cut - cut - True - False - True - True - - - - - - gtk-copy - copy - True - False - True - True - - - - - - gtk-paste - paste - True - False - True - True - - - - - - gtk-delete - delete - True - False - True - True - - - - - - - - - - True - False - _Help - True - - - True - False - - - gtk-about - True - False - True - True - - - - - - - - - - True - False - Debug - - - True - False - - - Show Errors - True - False - image1 - False - - - - - - - - - - True - True - 0 - - - - - True - False - 5 - start - - - tggl_notebook_1 - True - True - True - tggl_notebook_1_img - True - - - - True - True - 0 - - - - - tggl_notebook_2 - True - True - True - tggl_notebook_2_img - True - - - - True - True - 1 - - - - - tggl_notebook_3 - True - True - True - tggl_notebook_3_img - True - - - - True - True - 2 - - - - - tggl_notebook_4 - True - True - True - tggl_notebook_4_img - True - - - - True - True - 3 - - - - - False - True - 1 - - - - - - True - False - False - False - False - False - - - True - True - 2 - - - - - False - True - 0 - - - - - True - False - - - gtk-home - go_home - True - True - True - True - True - - - - False - True - 0 - - - - - gtk-refresh - refresh_view - True - True - True - True - True - - - - False - True - 1 - - - - - gtk-go-up - go_up - True - True - True - True - True - - - - False - True - 2 - - - - - path_entry - True - True - True - Path... - - - - True - True - 3 - - - - - gtk-add - create_tab - True - True - True - True - True - - - - False - True - 4 - - - - - False - True - 1 - - - - - True - True - True - True - vertical - True - - - True - True - 5 - True - True - True - - - notebook1 - True - True - True - 5 - 5 - 5 - 5 - False - True - - - - - - - - - - - - - - - - - - - - - - - True - True - - - - - notebook2 - True - True - True - 5 - 5 - 5 - 5 - False - True - - - - - - - - - - - - - - - - - - - - - - - True - True - - - - - True - True - - - - - True - True - 5 - True - True - True - - - notebook3 - True - True - True - 5 - 5 - 5 - 5 - False - True - - - - - - - - - - - - - - - - - - - - - - - True - True - - - - - notebook4 - True - True - True - 5 - 5 - 5 - False - True - - - - - - - - - - - - - - - - - - - - - - - True - True - - - - - True - True - - - - - True - True - 2 - - - - - True - False - 10 - 10 - 10 - 10 - 6 - 6 - 15 - top - - - True - False - - - False - True - 0 - - - - - True - False - - - False - True - 1 - - - - - True - False - - - False - True - 2 - - - - - False - True - 3 - - - - - - + 320 False True - top_main_menubar + app_menu_bar bottom @@ -1990,7 +1989,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe in False - + message_view True True @@ -2010,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 @@ -2028,7 +2204,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True False - + True False vertical @@ -2043,6 +2219,20 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + False + plugins_buttoin + + + True + False + vertical + + + + + + False 5 @@ -2247,6 +2437,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 @@ -2255,7 +2463,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True Rename... - 20 rename_img2 True @@ -2263,7 +2470,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 4 + 5 @@ -2281,7 +2488,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 5 + 6 @@ -2299,7 +2506,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 6 + 7 @@ -2317,7 +2524,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False True - 7 + 8 @@ -2332,25 +2539,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True - - False - True - 8 - - - - - gtk-delete - delete - True - True - True - Delete... - 20 - True - True - - False True @@ -2382,7 +2570,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True Empty Trash... - 20 @@ -2399,6 +2586,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True Move to Trash... + 20 trash_img True @@ -2427,6 +2615,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 13 + + + gtk-delete + delete + True + True + True + Delete... + 20 + True + True + + + + False + True + 14 + + False 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..a8c6506 --- /dev/null +++ b/user_config/usr/share/solarfm/key-bindings.json @@ -0,0 +1,26 @@ +{ + "keybindings": { + "help" : "F1", + "rename_files" : ["F2", + "e"], + "open_terminal" : "F4", + "refresh_tab" : ["F5", + "r"], + "delete_files" : ["Delete", + "d"], + "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", + "create_tab" : "t", + "keyboard_close_tab" : "w", + "copy_files" : "c", + "cut_files" : "x", + "paste_files" : "v", + "show_new_file_menu" : "n" + } +} 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 0000000..1a403ae Binary files /dev/null and b/user_config/usr/share/solarfm/solarfm-64x64.png differ diff --git a/user_config/usr/share/solarfm/solarfm.png b/user_config/usr/share/solarfm/solarfm.png new file mode 100644 index 0000000..1a403ae Binary files /dev/null and b/user_config/usr/share/solarfm/solarfm.png differ