From 36441aecac6c61279a5a600716844d6ca216bffb Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 15 Apr 2023 23:41:06 -0500 Subject: [PATCH] Further FileWidget enhancements --- src/core/controller.py | 20 +++- src/core/widgets/dialogs/appchooser_widget.py | 61 ++++++++++ src/core/widgets/drag_area_widget.py | 4 +- .../widget_selector/widget_selector_grid.py | 7 +- .../widgets/widget_selector/widgets/file.py | 108 ++++++++++++++++-- src/utils/launcher.py | 41 +++++++ src/utils/settings/settings.py | 4 + .../usr/share/coherence/stylesheet.css | 6 + .../coherence/ui_widgets/appchooser_ui.glade | 75 ++++++++++++ 9 files changed, 313 insertions(+), 13 deletions(-) create mode 100644 src/core/widgets/dialogs/appchooser_widget.py create mode 100644 src/utils/launcher.py create mode 100644 user_config/usr/share/coherence/ui_widgets/appchooser_ui.glade diff --git a/src/core/controller.py b/src/core/controller.py index 912f6d5..d46cc37 100644 --- a/src/core/controller.py +++ b/src/core/controller.py @@ -10,6 +10,8 @@ from gi.repository import Gdk from gi.repository import GLib # Application imports +from utils.launcher import Launcher + from .mixins.signals_mixins import SignalsMixins from .controller_data import ControllerData from .query_controller import QueryController @@ -20,7 +22,7 @@ from .widgets.sections.sections_widget import Sections -class Controller(SignalsMixins, ControllerData): +class Controller(Launcher, SignalsMixins, ControllerData): def __init__(self, args, unknownargs): self.setup_controller_data() @@ -53,6 +55,8 @@ class Controller(SignalsMixins, ControllerData): self.window.connect("focus-out-event", self.unset_keys_and_data) self.window.connect("key-press-event", self.on_global_key_press_controller) self.window.connect("key-release-event", self.on_global_key_release_controller) + event_system.subscribe("open_files", self.open_files) + event_system.subscribe("open_with_files", self.open_with_files) def _subscribe_to_events(self): event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc) @@ -90,3 +94,17 @@ class Controller(SignalsMixins, ControllerData): _section = os.path.join(path, section) manifest = os.path.join(_section, "MANIFEST") event_system.emit("create_section_view", (None, None, manifest)) + + def open_files(self, uris): + for file in uris: + self.open_file_locally(file) + + def open_with_files(self, app_info): + # state = event_system.emit_and_await("get_current_state") + # uris = state.uris_raw + + # if not state.uris_raw: + # uris = [f"file://{state.tab.get_current_directory()}"] + # + # state.tab.app_chooser_exec(app_info, uris) + ... diff --git a/src/core/widgets/dialogs/appchooser_widget.py b/src/core/widgets/dialogs/appchooser_widget.py new file mode 100644 index 0000000..d9e8155 --- /dev/null +++ b/src/core/widgets/dialogs/appchooser_widget.py @@ -0,0 +1,61 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + + + +class AppchooserWidget: + """docstring for AppchooserWidget.""" + + def __init__(self): + super(AppchooserWidget, self).__init__() + + _GLADE_FILE = f"{settings.get_ui_widgets_path()}/appchooser_ui.glade" + self._builder = Gtk.Builder() + self._builder.add_from_file(_GLADE_FILE) + + self._setup_styling() + self._setup_signals() + self._load_widgets() + + + def _setup_styling(self): + ... + + def _setup_signals(self): + event_system.subscribe("show_appchooser_menu", self.show_appchooser_menu) + event_system.subscribe("hide_appchooser_menu", self.hide_appchooser_menu) + event_system.subscribe("run_appchooser_launch", self.run_appchooser_launch) + settings.register_signals_to_builder([self,], self._builder) + + def _load_widgets(self): + builder = settings.get_builder() + + self._appchooser_menu = self._builder.get_object("appchooser_menu") + self._appchooser_widget = self._builder.get_object("appchooser_widget") + + builder.expose_object(f"appchooser_menu", self._appchooser_menu) + builder.expose_object(f"appchooser_widget", self._appchooser_widget) + + + def show_appchooser_menu(self, widget=None, eve=None): + response = self._appchooser_menu.run() + if response == Gtk.ResponseType.OK: + app_info = self._appchooser_widget.get_app_info() + event_system.emit("open_with_files", app_info) + self.hide_appchooser_menu() + if response == Gtk.ResponseType.CANCEL: + self.hide_appchooser_menu() + + + def hide_appchooser_menu(self, widget=None, eve=None): + self._appchooser_menu.hide() + + def run_appchooser_launch(self, widget=None, eve=None): + self._appchooser_menu.response(Gtk.ResponseType.OK) diff --git a/src/core/widgets/drag_area_widget.py b/src/core/widgets/drag_area_widget.py index da16b7b..be02982 100644 --- a/src/core/widgets/drag_area_widget.py +++ b/src/core/widgets/drag_area_widget.py @@ -103,7 +103,9 @@ class DragArea(Gtk.Fixed): for uri in uris: event_system.emit("set_widget_type", "FileWidget") dynamic_widget = self.add_or_select_widget(x = _x, y = _y) - dynamic_widget.set_file(uri) + dynamic_widget.get_body().set_file_path(uri) + dynamic_widget.save() + _y += 85 def _move_callback(self, widget = None, x = None, y = None): diff --git a/src/core/widgets/widget_selector/widget_selector_grid.py b/src/core/widgets/widget_selector/widget_selector_grid.py index d5343e9..1e6415b 100644 --- a/src/core/widgets/widget_selector/widget_selector_grid.py +++ b/src/core/widgets/widget_selector/widget_selector_grid.py @@ -52,9 +52,12 @@ class WidgetSelectorGrid(Gtk.Grid): self._register_widget_type(globals()[widget]) def _register_widget_type(self, Widget): - widget = Widget() - selection = Gtk.EventBox() + widget = Widget() + ctx = widget.get_style_context() + + ctx.add_class("selection-widget") + selection.set_above_child(True) selection.add(widget) selection.connect("button-release-event", self._set_widget_type_eve) diff --git a/src/core/widgets/widget_selector/widgets/file.py b/src/core/widgets/widget_selector/widgets/file.py index 42edf20..eb337d1 100644 --- a/src/core/widgets/widget_selector/widgets/file.py +++ b/src/core/widgets/widget_selector/widgets/file.py @@ -10,17 +10,63 @@ from gi.repository import Gio from utils.widget_save_load_controller import WidgetSaveLoadController -class FileWidget(WidgetSaveLoadController, Gtk.Box): - def __init__(self): - super(FileWidget, self).__init__() - self._file_path = None +class FileWidgetException(Exception): + ... + + +class FileChooser(Gtk.Dialog): + """docstring for FileChooser.""" + + def __init__(self): + super(FileChooser, self).__init__() + + self._file_chooser_widget = None self._setup_styling() self._setup_signals() self._subscribe_to_events() self._load_widgets() + self.show_all() + + + def _setup_styling(self): + ... + + def _setup_signals(self): + ... + + def _subscribe_to_events(self): + ... + + def _load_widgets(self): + hbox = self.get_content_area() + self._file_chooser_widget = Gtk.FileChooserWidget() + self._file_chooser_widget.set_select_multiple(True) + + hbox.add(self._file_chooser_widget) + self.add_button("Cancel", 1) + self.add_button("Select", 0) + + def get_file_chooser_widget(self): + return self._file_chooser_widget + + +class FileWidget(WidgetSaveLoadController, Gtk.Box): + def __init__(self): + super(FileWidget, self).__init__() + + self._file_path = None + self._file_name = "No File Selected..." + self.label = None + + self._setup_styling() + self._setup_signals() + self._subscribe_to_events() + self._load_widgets() + self.set_file_path() + event_system.emit("register_to_query_controller", (self, self.get_query_data)) self.show_all() @@ -30,7 +76,7 @@ class FileWidget(WidgetSaveLoadController, Gtk.Box): return widget def _setup_styling(self): - self.set_orientation(0) + self.set_orientation(1) def _setup_signals(self): self.connect("key-release-event", self._key_released) @@ -39,8 +85,20 @@ class FileWidget(WidgetSaveLoadController, Gtk.Box): ... def _load_widgets(self): - image = Gtk.Image(stock=Gtk.STOCK_MEDIA_PLAY ) - self.add(image) + box = Gtk.Box() + eve_box = Gtk.EventBox() + image = Gtk.Image(stock = Gtk.STOCK_FILE) + self.label = Gtk.Label(self._file_name) + + box.set_orientation(1) + eve_box.connect('button-press-event', self._clicked) + + box.add(image) + box.add(self.label) + eve_box.add(box) + self.add(eve_box) + eve_box.show_all() + def _key_released(self, widget = None, eve = None): if eve.type == 9: @@ -48,6 +106,20 @@ class FileWidget(WidgetSaveLoadController, Gtk.Box): # pass ... + def _clicked(self, widget = None, eve = None): + if eve.button == 1 and eve.type == 5: # NOTE: Left dbl click + event_system.emit("open_files", ( [self.get_file_path()], ) ) + return + + if eve.button == 3 and eve.type == 4: # NOTE: Right click + try: + self.set_current_file() + self.get_parent().save_needed = True + except FileWidgetException as e: + logger.debug(e) + + return + def get_query_data(self): return self.get_file_name() @@ -64,12 +136,17 @@ class FileWidget(WidgetSaveLoadController, Gtk.Box): self.set_file_path( self.save_collection["data"] ) - def set_file_path(self, path): + def set_file_path(self, path = settings.get_home_path()): try: self._file_path = Gio.File.new_for_uri(path) - except Exception as e: + if not self._file_path.get_path(): + raise FileWidgetException("Not URI based path...") + except FileWidgetException as e: self._file_path = Gio.File.new_for_path(path) + self._file_name = self.get_file_name() + self.label.set_label(self._file_name) + def get_file(self): return self._file_path @@ -83,3 +160,16 @@ class FileWidget(WidgetSaveLoadController, Gtk.Box): def get_file_name(self): info = self._file_path.query_info("standard::*", 0, cancellable = None) return info.get_display_name() + + def set_current_file(self): + dlg = FileChooser() + response = dlg.run() + if response == 0: + widget = dlg.get_file_chooser_widget() + uris = widget.get_uris() + if len(uris) == 1: + uri = uris[0] + + self.set_file_path(uri) + + dlg.destroy() diff --git a/src/utils/launcher.py b/src/utils/launcher.py new file mode 100644 index 0000000..afc4f8c --- /dev/null +++ b/src/utils/launcher.py @@ -0,0 +1,41 @@ +# Python imports +import os +import subprocess + +# Lib imports + +# Apoplication imports + + +class CoherenceLauncherException(Exception): + ... + + + +class Launcher: + def open_file_locally(self, file): + command = ["xdg-open", file] + self.execute(command) + + + def execute(self, command, start_dir=os.getenv("HOME"), use_shell=False): + try: + logger.debug(command) + subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=None, stderr=None, close_fds=True) + except CoherenceLauncherException as e: + logger.error(f"Couldn't execute: {command}") + logger.error(e) + + # TODO: Return std(out/in/err) 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): + try: + DEVNULL = open(os.devnull, 'w') + return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=False, stdout=DEVNULL, stderr=DEVNULL, close_fds=False) + except CoherenceLauncherException as e: + logger.error(f"Couldn't execute and return thread: {command}") + logger.error(e) + return None + + @threaded + def app_chooser_exec(self, app_info, uris): + app_info.launch_uris_async(uris) diff --git a/src/utils/settings/settings.py b/src/utils/settings/settings.py index c9dd4a1..15c2022 100644 --- a/src/utils/settings/settings.py +++ b/src/utils/settings/settings.py @@ -33,6 +33,7 @@ class Settings(StartCheckMixin): self._KEY_BINDINGS_FILE = f"{self._HOME_CONFIG_PATH}/key-bindings.json" self._PID_FILE = f"{self._HOME_CONFIG_PATH}/{app_name.lower()}.pid" self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" + self._UI_WIDEGTS_PATH = f"{self._HOME_CONFIG_PATH}/ui_widgets" if not os.path.exists(self._HOME_CONFIG_PATH): os.mkdir(self._HOME_CONFIG_PATH) @@ -68,6 +69,8 @@ class Settings(StartCheckMixin): self._WINDOW_ICON = f"{self._USR_PATH}/icons/{app_name.lower()}.png" if not os.path.exists(self._WINDOW_ICON): raise MissingConfigError("Unable to find the application icon.") + if not os.path.exists(self._UI_WIDEGTS_PATH): + self._UI_WIDEGTS_PATH = f"{self._USR_PATH}/ui_widgets" with open(self._KEY_BINDINGS_FILE) as file: @@ -201,6 +204,7 @@ class Settings(StartCheckMixin): def get_window_icon(self) -> str: return self._WINDOW_ICON def get_home_path(self) -> str: return self._USER_HOME def get_notebooks_path(self) -> str: return self._NOTEBOOKS_PATH + def get_ui_widgets_path(self) -> str: return self._UI_WIDEGTS_PATH # Filter returns def get_office_filter(self) -> tuple: return tuple(self._settings["filters"]["office"]) diff --git a/user_config/usr/share/coherence/stylesheet.css b/user_config/usr/share/coherence/stylesheet.css index 39d2f87..3865d61 100644 --- a/user_config/usr/share/coherence/stylesheet.css +++ b/user_config/usr/share/coherence/stylesheet.css @@ -62,6 +62,12 @@ notebook > header > tabs > tab:checked { +.selection-widget { + background-color: rgba(45, 45, 45, 0.84); + color: rgba(0, 0, 0, 0.5); + +} + .dynamic-header-widget { margin-bottom: 5px; } diff --git a/user_config/usr/share/coherence/ui_widgets/appchooser_ui.glade b/user_config/usr/share/coherence/ui_widgets/appchooser_ui.glade new file mode 100644 index 0000000..6959306 --- /dev/null +++ b/user_config/usr/share/coherence/ui_widgets/appchooser_ui.glade @@ -0,0 +1,75 @@ + + + + + + False + mouse + splashscreen + south + + + + False + vertical + 2 + + + False + end + + + gtk-cancel + True + True + True + True + + + True + True + 0 + + + + + Select + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + False + True + + + + False + True + 1 + + + + + + button31 + appchooser_select_btn + + +