WIP implimentig new files widget, updated settings, broke keybindings
This commit is contained in:
3
src/versions/solarfm-0.0.1/solarfm/core/__init__.py
Normal file
3
src/versions/solarfm-0.0.1/solarfm/core/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Core Module
|
||||
"""
|
185
src/versions/solarfm-0.0.1/solarfm/core/controller.py
Normal file
185
src/versions/solarfm-0.0.1/solarfm/core/controller.py
Normal file
@@ -0,0 +1,185 @@
|
||||
# Python imports
|
||||
import os
|
||||
import time
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from .controller_data import Controller_Data
|
||||
from .fs_actions.file_system_actions import FileSystemActions
|
||||
from .mixins.signals_mixins import SignalsMixins
|
||||
|
||||
from .widgets.dialogs.about_widget import AboutWidget
|
||||
from .widgets.dialogs.appchooser_widget import AppchooserWidget
|
||||
from .widgets.dialogs.file_exists_widget import FileExistsWidget
|
||||
from .widgets.dialogs.new_file_widget import NewFileWidget
|
||||
from .widgets.dialogs.message_widget import MessageWidget
|
||||
from .widgets.dialogs.rename_widget import RenameWidget
|
||||
from .widgets.dialogs.save_load_widget import SaveLoadWidget
|
||||
|
||||
from .widgets.popups.message_popup_widget import MessagePopupWidget
|
||||
from .widgets.popups.path_menu_popup_widget import PathMenuPopupWidget
|
||||
from .widgets.popups.plugins_popup_widget import PluginsPopupWidget
|
||||
from .widgets.popups.io_popup_widget import IOPopupWidget
|
||||
|
||||
from .widgets.context_menu_widget import ContextMenuWidget
|
||||
|
||||
from .ui_mixin import UIMixin
|
||||
|
||||
|
||||
|
||||
|
||||
class Controller(UIMixin, SignalsMixins, Controller_Data):
|
||||
""" Controller coordinates the mixins and is somewhat the root hub of it all. """
|
||||
|
||||
def __init__(self, args, unknownargs):
|
||||
self.setup_controller_data()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.generate_windows(self.fm_controller_data)
|
||||
|
||||
if args.no_plugins == "false":
|
||||
self.plugins.launch_plugins()
|
||||
|
||||
for arg in unknownargs + [args.new_tab,]:
|
||||
if os.path.isdir(arg):
|
||||
message = f"FILE|{arg}"
|
||||
event_system.emit("post_file_to_ipc", message)
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
FileSystemActions()
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
|
||||
event_system.subscribe("generate_windows", self.generate_windows)
|
||||
event_system.subscribe("clear_notebooks", self.clear_notebooks)
|
||||
event_system.subscribe("get_current_state", self.get_current_state)
|
||||
event_system.subscribe("go_to_path", self.go_to_path)
|
||||
event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls)
|
||||
event_system.subscribe("set_clipboard_data", self.set_clipboard_data)
|
||||
|
||||
def _load_glade_file(self):
|
||||
self.builder = Gtk.Builder()
|
||||
self.builder.add_from_file(settings.get_glade_file())
|
||||
self.builder.expose_object("main_window", self.window)
|
||||
|
||||
self.core_widget = self.builder.get_object("core_widget")
|
||||
|
||||
settings.set_builder(self.builder)
|
||||
settings.register_signals_to_builder([self,])
|
||||
|
||||
def get_core_widget(self):
|
||||
return self.core_widget
|
||||
|
||||
|
||||
# NOTE: Really we will move these to the UI/(New) Window 'base' controller
|
||||
# after we're done cleaning and refactoring to use fewer mixins.
|
||||
def _load_widgets(self):
|
||||
IOPopupWidget()
|
||||
MessagePopupWidget()
|
||||
PathMenuPopupWidget()
|
||||
PluginsPopupWidget()
|
||||
|
||||
AboutWidget()
|
||||
AppchooserWidget()
|
||||
ContextMenuWidget()
|
||||
NewFileWidget()
|
||||
RenameWidget()
|
||||
FileExistsWidget()
|
||||
SaveLoadWidget()
|
||||
self.message_dialog = MessageWidget()
|
||||
|
||||
|
||||
def reload_plugins(self, widget=None, eve=None):
|
||||
self.plugins.reload_plugins()
|
||||
|
||||
|
||||
def do_action_from_menu_controls(self, _action=None, eve=None):
|
||||
if not _action:
|
||||
return
|
||||
|
||||
if not isinstance(_action, str):
|
||||
action = _action.get_name()
|
||||
else:
|
||||
action = _action
|
||||
|
||||
event_system.emit("hide_context_menu")
|
||||
event_system.emit("hide_new_file_menu")
|
||||
event_system.emit("hide_rename_file_menu")
|
||||
|
||||
if action == "open":
|
||||
event_system.emit("open_files")
|
||||
if action == "open_with":
|
||||
event_system.emit("show_appchooser_menu")
|
||||
if action == "execute":
|
||||
event_system.emit("execute_files")
|
||||
if action == "execute_in_terminal":
|
||||
event_system.emit("execute_files", (True,))
|
||||
if action == "rename":
|
||||
event_system.emit("rename_files")
|
||||
if action == "cut":
|
||||
event_system.emit("cut_files")
|
||||
if action == "copy":
|
||||
event_system.emit("copy_files")
|
||||
if action == "copy_name":
|
||||
event_system.emit("copy_name")
|
||||
if action == "paste":
|
||||
event_system.emit("paste_files")
|
||||
if action == "create":
|
||||
event_system.emit("create_files")
|
||||
if action in ["save_session", "save_session_as", "load_session"]:
|
||||
event_system.emit("save_load_session", (action))
|
||||
|
||||
if action == "about_page":
|
||||
event_system.emit("show_about_page")
|
||||
if action == "io_popup":
|
||||
event_system.emit("show_io_popup")
|
||||
if action == "plugins_popup":
|
||||
event_system.emit("show_plugins_popup")
|
||||
if action == "messages_popup":
|
||||
event_system.emit("show_messages_popup")
|
||||
if action == "tear_down":
|
||||
event_system.emit("tear_down")
|
||||
|
||||
|
||||
@endpoint_registry.register(rule="go_home")
|
||||
def go_home(self, widget=None, eve=None):
|
||||
self.builder.get_object("go_home").released()
|
||||
|
||||
@endpoint_registry.register(rule="refresh_tab")
|
||||
def refresh_tab(self, widget=None, eve=None):
|
||||
self.builder.get_object("refresh_tab").released()
|
||||
|
||||
@endpoint_registry.register(rule="go_up")
|
||||
def go_up(self, widget=None, eve=None):
|
||||
self.builder.get_object("go_up").released()
|
||||
|
||||
@endpoint_registry.register(rule="grab_focus_path_entry")
|
||||
def grab_focus_path_entry(self, widget=None, eve=None):
|
||||
self.builder.get_object("path_entry").grab_focus()
|
||||
|
||||
@endpoint_registry.register(rule="tggl_top_main_menubar")
|
||||
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()
|
||||
|
||||
@endpoint_registry.register(rule="open_terminal")
|
||||
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())
|
||||
|
||||
def go_to_path(self, path: str):
|
||||
self.path_entry.set_text(path)
|
185
src/versions/solarfm-0.0.1/solarfm/core/controller_data.py
Normal file
185
src/versions/solarfm-0.0.1/solarfm/core/controller_data.py
Normal file
@@ -0,0 +1,185 @@
|
||||
# Python imports
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from shellfm.windows.controller import WindowController
|
||||
from plugins.plugins_controller import PluginsController
|
||||
|
||||
# from factories.split_view_widget import SplitViewWidget
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class State:
|
||||
fm_controller: any = None
|
||||
notebooks: any = None
|
||||
wid: int = None
|
||||
tid: int = None
|
||||
tab: type = None
|
||||
icon_grid: gi.overrides.Gtk.IconView = None
|
||||
store: gi.overrides.Gtk.ListStore = None
|
||||
uris: [] = None
|
||||
uris_raw: [] = None
|
||||
selected_files: [] = None
|
||||
to_copy_files: [] = None
|
||||
to_cut_files: [] = None
|
||||
message_dialog: type = 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) -> None:
|
||||
self.window = settings.get_main_window()
|
||||
self.builder = None
|
||||
self.core_widget = None
|
||||
|
||||
self._load_glade_file()
|
||||
self.fm_controller = WindowController()
|
||||
self.plugins = PluginsController()
|
||||
self.fm_controller_data = self.fm_controller.get_state_from_file()
|
||||
|
||||
|
||||
# self.pane_master = self.builder.get_object("pane_master")
|
||||
# self.pane_master.pack1(SplitViewWidget(), True, True)
|
||||
# self.pane_master.pack2(SplitViewWidget(), True, True)
|
||||
|
||||
|
||||
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.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.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.dnd_left_primed = 0
|
||||
|
||||
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.ctrl_down = False
|
||||
self.shift_down = False
|
||||
self.alt_down = False
|
||||
|
||||
# sys.excepthook = self.custom_except_hook
|
||||
if settings.is_debug():
|
||||
self.window.set_interactive_debugging(True)
|
||||
|
||||
|
||||
def custom_except_hook(self, exc_type, exc_value, exc_traceback):
|
||||
if issubclass(exc_type, KeyboardInterrupt):
|
||||
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||
return
|
||||
|
||||
logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
|
||||
|
||||
|
||||
|
||||
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.fm_controller = self.fm_controller
|
||||
state.notebooks = self.notebooks
|
||||
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()
|
||||
state.message_dialog = self.message_dialog
|
||||
|
||||
selected_files = state.icon_grid.get_selected_items()
|
||||
if selected_files:
|
||||
state.uris = self.format_to_uris(state.store, state.wid, state.tid, selected_files, True)
|
||||
state.uris_raw = self.format_to_uris(state.store, state.wid, state.tid, selected_files)
|
||||
|
||||
state.selected_files = event_system.emit_and_await("get_selected_files")
|
||||
|
||||
# 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)
|
||||
|
||||
event_system.emit("update_state_info_plugins", state)
|
||||
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_notebooks(self) -> None:
|
||||
self.ctrl_down = False
|
||||
self.shift_down = False
|
||||
self.alt_down = False
|
||||
|
||||
for notebook in self.notebooks:
|
||||
self.clear_children(notebook)
|
||||
|
||||
def clear_children(self, widget: type) -> None:
|
||||
''' Clear children of a gtk widget. '''
|
||||
for child in widget.get_children():
|
||||
widget.remove(child)
|
||||
|
||||
def get_clipboard_data(self) -> str:
|
||||
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE)
|
||||
retcode = proc.wait()
|
||||
data = proc.stdout.read()
|
||||
return data.decode("utf-8").strip()
|
||||
|
||||
def set_clipboard_data(self, data: type) -> None:
|
||||
proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin=subprocess.PIPE)
|
||||
proc.stdin.write(data.encode("utf-8"))
|
||||
proc.stdin.close()
|
||||
retcode = proc.wait()
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
FS Actions Module
|
||||
"""
|
@@ -0,0 +1,70 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class CRUDMixin:
|
||||
"""docstring for CRUDMixin"""
|
||||
|
||||
def move_files(self, files, target):
|
||||
self.handle_files(files, "move", target)
|
||||
|
||||
def paste_files(self):
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
target = f"{state.tab.get_current_directory()}"
|
||||
|
||||
if self._to_copy_files:
|
||||
self.handle_files(self._to_copy_files, "copy", target)
|
||||
elif self._to_cut_files:
|
||||
self.handle_files(self._to_cut_files, "move", target)
|
||||
|
||||
def create_files(self):
|
||||
fname_field = self._builder.get_object("new_fname_field")
|
||||
cancel_creation = event_system.emit_and_await("show_new_file_menu", fname_field)
|
||||
|
||||
if cancel_creation:
|
||||
event_system.emit("hide_new_file_menu")
|
||||
return
|
||||
|
||||
file_name = fname_field.get_text().strip()
|
||||
type = self._builder.get_object("new_file_toggle_type").get_state()
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
target = f"{state.tab.get_current_directory()}"
|
||||
|
||||
if file_name:
|
||||
path = f"{target}/{file_name}"
|
||||
|
||||
if type == True: # Create File
|
||||
self.handle_files([path], "create_file")
|
||||
else: # Create Folder
|
||||
self.handle_files([path], "create_dir")
|
||||
|
||||
event_system.emit("hide_new_file_menu")
|
||||
|
||||
def rename_files(self):
|
||||
rename_label = self._builder.get_object("file_to_rename_label")
|
||||
rename_input = self._builder.get_object("rename_fname")
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
|
||||
for uri in state.uris:
|
||||
entry = uri.split("/")[-1]
|
||||
rename_label.set_label(entry)
|
||||
rename_input.set_text(entry)
|
||||
|
||||
response = event_system.emit_and_await("show_rename_file_menu", rename_input)
|
||||
if response == "skip_edit":
|
||||
continue
|
||||
if response == "cancel_edit":
|
||||
break
|
||||
|
||||
rname_to = rename_input.get_text().strip()
|
||||
if rname_to:
|
||||
target = f"{state.tab.get_current_directory()}/{rname_to}"
|
||||
self.handle_files([uri], "rename", target)
|
||||
|
||||
event_system.emit("hide_rename_file_menu")
|
||||
event_system.emit_and_await("get_selected_files").clear()
|
@@ -0,0 +1,105 @@
|
||||
# Python imports
|
||||
import shlex
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .crud_mixin import CRUDMixin
|
||||
from .handler_mixin import HandlerMixin
|
||||
|
||||
|
||||
|
||||
|
||||
class FileSystemActions(HandlerMixin, CRUDMixin):
|
||||
"""docstring for FileSystemActions"""
|
||||
|
||||
def __init__(self):
|
||||
super(FileSystemActions, self).__init__()
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self._selected_files = []
|
||||
self._to_copy_files = []
|
||||
self._to_cut_files = []
|
||||
|
||||
self._builder = settings.get_builder()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("set_selected_files", self.set_selected_files)
|
||||
event_system.subscribe("get_selected_files", self.get_selected_files)
|
||||
|
||||
event_system.subscribe("open_files", self.open_files)
|
||||
event_system.subscribe("open_with_files", self.open_with_files)
|
||||
event_system.subscribe("execute_files", self.execute_files)
|
||||
|
||||
event_system.subscribe("cut_files", self.cut_files)
|
||||
event_system.subscribe("copy_files", self.copy_files)
|
||||
event_system.subscribe("paste_files", self.paste_files)
|
||||
event_system.subscribe("move_files", self.move_files)
|
||||
event_system.subscribe("copy_name", self.copy_name)
|
||||
event_system.subscribe("create_files", self.create_files)
|
||||
event_system.subscribe("rename_files", self.rename_files)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
|
||||
def set_selected_files(self, selected_files: []):
|
||||
self._selected_files = selected_files
|
||||
|
||||
def get_selected_files(self):
|
||||
return self._selected_files
|
||||
|
||||
|
||||
def cut_files(self):
|
||||
self._to_copy_files.clear()
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
self._to_cut_files = state.uris
|
||||
|
||||
def copy_files(self):
|
||||
self._to_cut_files.clear()
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
self._to_copy_files = state.uris
|
||||
|
||||
def copy_name(self):
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
if len(state.uris) == 1:
|
||||
file_name = state.uris[0].split("/")[-1]
|
||||
event_system.emit("set_clipboard_data", (file_name,))
|
||||
|
||||
def copy_path(self):
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
dir = state.tab.get_current_directory()
|
||||
event_system.emit("set_clipboard_data", (file_name,))
|
||||
|
||||
def open_files(self):
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
for file in state.uris:
|
||||
state.tab.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)
|
||||
|
||||
|
||||
def execute_files(self, in_terminal=False):
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
current_dir = state.tab.get_current_directory()
|
||||
command = None
|
||||
for path in state.uris:
|
||||
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())
|
@@ -0,0 +1,162 @@
|
||||
# Python imports
|
||||
import os
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from ..widgets.io_widget import IOWidget
|
||||
|
||||
|
||||
|
||||
|
||||
class HandlerMixin:
|
||||
"""docstring for HandlerMixin"""
|
||||
|
||||
# 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):
|
||||
target = None
|
||||
_file = None
|
||||
response = None
|
||||
overwrite_all = False
|
||||
rename_auto_all = False
|
||||
|
||||
for path in paths:
|
||||
try:
|
||||
if "file://" in path:
|
||||
path = path.split("file://")[1]
|
||||
|
||||
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()}"
|
||||
_file = Gio.File.new_for_path(_target)
|
||||
else:
|
||||
_file = Gio.File.new_for_path(_target_path)
|
||||
else:
|
||||
_file = Gio.File.new_for_path(path)
|
||||
|
||||
|
||||
if _file.query_exists():
|
||||
if not overwrite_all and not rename_auto_all:
|
||||
event_system.emit("setup_exists_data", (file, _file))
|
||||
response = event_system.emit_and_await("show_exists_page")
|
||||
|
||||
if response == "overwrite_all":
|
||||
overwrite_all = True
|
||||
if response == "rename_auto_all":
|
||||
rename_auto_all = True
|
||||
|
||||
if response == "rename":
|
||||
base_path = _file.get_parent().get_path()
|
||||
new_name = self._builder.get_object("exists_file_field").get_text().strip()
|
||||
rfPath = f"{base_path}/{new_name}"
|
||||
_file = Gio.File.new_for_path(rfPath)
|
||||
|
||||
if response == "rename_auto" or rename_auto_all:
|
||||
_file = self.rename_proc(_file)
|
||||
|
||||
if response == "overwrite" or overwrite_all:
|
||||
type = _file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
|
||||
|
||||
if type == Gio.FileType.DIRECTORY:
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
state.tab.delete_file( _file.get_path() )
|
||||
else:
|
||||
_file.delete(cancellable=None)
|
||||
|
||||
if response == "skip":
|
||||
continue
|
||||
if response == "skip_all":
|
||||
break
|
||||
|
||||
if _target_path:
|
||||
target = _file
|
||||
else:
|
||||
file = _file
|
||||
|
||||
|
||||
if action == "create_file":
|
||||
file.create(flags=Gio.FileCreateFlags.NONE, cancellable=None)
|
||||
continue
|
||||
if action == "create_dir":
|
||||
file.make_directory(cancellable=None)
|
||||
continue
|
||||
|
||||
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
|
||||
if type == Gio.FileType.DIRECTORY:
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
tab = state.tab
|
||||
fPath = file.get_path()
|
||||
tPath = target.get_path()
|
||||
state = True
|
||||
|
||||
if action == "copy":
|
||||
tab.copy_file(fPath, tPath)
|
||||
if action == "move" or action == "rename":
|
||||
tab.move_file(fPath, tPath)
|
||||
else:
|
||||
io_widget = IOWidget(action, file)
|
||||
|
||||
if action == "copy":
|
||||
file.copy_async(destination=target,
|
||||
flags=Gio.FileCopyFlags.BACKUP,
|
||||
io_priority=98,
|
||||
cancellable=io_widget.cancle_eve,
|
||||
progress_callback=io_widget.update_progress,
|
||||
callback=io_widget.finish_callback)
|
||||
|
||||
self._builder.get_object("io_list").add(io_widget)
|
||||
if action == "move" or action == "rename":
|
||||
file.move_async(destination=target,
|
||||
flags=Gio.FileCopyFlags.BACKUP,
|
||||
io_priority=98,
|
||||
cancellable=io_widget.cancle_eve,
|
||||
progress_callback=None,
|
||||
# NOTE: progress_callback here causes seg fault when set
|
||||
callback=io_widget.finish_callback)
|
||||
|
||||
self._builder.get_object("io_list").add(io_widget)
|
||||
|
||||
except GObject.GError as e:
|
||||
raise OSError(e)
|
||||
|
||||
self._builder.get_object("exists_file_rename_bttn").set_sensitive(False)
|
||||
|
||||
def rename_proc(self, gio_file):
|
||||
full_path = gio_file.get_path()
|
||||
base_path = gio_file.get_parent().get_path()
|
||||
file_name = os.path.splitext(gio_file.get_basename())[0]
|
||||
extension = os.path.splitext(full_path)[-1]
|
||||
target = Gio.File.new_for_path(full_path)
|
||||
start = "-copy"
|
||||
|
||||
if settings.is_debug():
|
||||
logger.debug(f"Path: {full_path}")
|
||||
logger.debug(f"Base Path: {base_path}")
|
||||
logger.debug(f'Name: {file_name}')
|
||||
logger.debug(f"Extension: {extension}")
|
||||
|
||||
i = 2
|
||||
while target.query_exists():
|
||||
try:
|
||||
value = file_name[(file_name.find(start)+len(start)):]
|
||||
int(value)
|
||||
file_name = file_name.split(start)[0]
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
target = Gio.File.new_for_path(f"{base_path}/{file_name}-copy{i}{extension}")
|
||||
i += 1
|
||||
|
||||
return target
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Mixins module
|
||||
"""
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Signals module
|
||||
"""
|
@@ -0,0 +1,105 @@
|
||||
# Python imports
|
||||
import time
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gio
|
||||
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class FileActionSignalsMixin:
|
||||
"""docstring for FileActionSignalsMixin"""
|
||||
|
||||
def set_file_watcher(self, tab):
|
||||
if tab.get_dir_watcher():
|
||||
watcher = tab.get_dir_watcher()
|
||||
watcher.cancel()
|
||||
if settings.is_debug():
|
||||
logger.debug(f"Watcher Is Cancelled: {watcher.is_cancelled()}")
|
||||
|
||||
cur_dir = tab.get_current_directory()
|
||||
|
||||
dir_watcher = Gio.File.new_for_path(cur_dir) \
|
||||
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
|
||||
|
||||
wid = tab.get_wid()
|
||||
tid = tab.get_id()
|
||||
dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",))
|
||||
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 settings.is_debug():
|
||||
logger.debug(eve_type)
|
||||
|
||||
if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]:
|
||||
self.update_on_soft_lock_end(data[0])
|
||||
elif data[0] in self.soft_update_lock.keys():
|
||||
self.soft_update_lock[data[0]]["last_update_time"] = time.time()
|
||||
else:
|
||||
self.soft_lock_countdown(data[0])
|
||||
|
||||
@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 do_file_search(self, widget, eve=None):
|
||||
if not self.ctrl_down and not self.shift_down and not self.alt_down:
|
||||
target = widget.get_name()
|
||||
notebook = self.builder.get_object(target)
|
||||
page = notebook.get_current_page()
|
||||
nth_page = notebook.get_nth_page(page)
|
||||
icon_grid = nth_page.get_children()[0]
|
||||
|
||||
wid, tid = icon_grid.get_name().split("|")
|
||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||
query = widget.get_text().lower()
|
||||
|
||||
icon_grid.unselect_all()
|
||||
for i, file in enumerate(tab.get_files()):
|
||||
if query and query in file[0].lower():
|
||||
path = Gtk.TreePath().new_from_indices([i])
|
||||
icon_grid.select_path(path)
|
||||
|
||||
items = icon_grid.get_selected_items()
|
||||
if len(items) > 0:
|
||||
icon_grid.scroll_to_path(items[0], False, 0.5, 0.5)
|
@@ -0,0 +1,35 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IPCSignalsMixin:
|
||||
""" IPCSignalsMixin handle messages from another starting solarfm process. """
|
||||
|
||||
def print_to_console(self, message=None):
|
||||
print(message)
|
||||
|
||||
def handle_file_from_ipc(self, path):
|
||||
window = self.builder.get_object("main_window")
|
||||
window.deiconify()
|
||||
window.show()
|
||||
window.present()
|
||||
|
||||
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)
|
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
from gi.repository import 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
|
||||
|
||||
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 = keybindings.lookup(event)
|
||||
if mapping:
|
||||
# See if in filemanager scope
|
||||
try:
|
||||
getattr(self, mapping)()
|
||||
return True
|
||||
except Exception:
|
||||
# Must be plugins scope, event call, OR we forgot to add method to file manager scope
|
||||
if "||" in mapping:
|
||||
sender, eve_type = mapping.split("||")
|
||||
else:
|
||||
sender = ""
|
||||
eve_type = mapping
|
||||
|
||||
self.handle_plugin_key_event(sender, eve_type)
|
||||
else:
|
||||
logger.debug(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()
|
||||
|
||||
def handle_plugin_key_event(self, sender, eve_type):
|
||||
event_system.emit(eve_type)
|
||||
|
||||
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)
|
||||
if not trace_debug:
|
||||
self.fm_controller.save_state()
|
||||
self.set_window_title()
|
@@ -0,0 +1,14 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from .signals.file_action_signals_mixin import FileActionSignalsMixin
|
||||
from .signals.ipc_signals_mixin import IPCSignalsMixin
|
||||
from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class SignalsMixins(FileActionSignalsMixin, KeyboardSignalsMixin, IPCSignalsMixin):
|
||||
...
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
UI module
|
||||
"""
|
105
src/versions/solarfm-0.0.1/solarfm/core/mixins/ui/grid_mixin.py
Normal file
105
src/versions/solarfm-0.0.1/solarfm/core/mixins/ui/grid_mixin.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from ...widgets.tab_header_widget import TabHeaderWidget
|
||||
from ...widgets.icon_grid_widget import IconGridWidget
|
||||
from ...widgets.icon_tree_widget import IconTreeWidget
|
||||
|
||||
|
||||
|
||||
|
||||
class GridMixin:
|
||||
"""docstring for GridMixin"""
|
||||
|
||||
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]])
|
||||
|
||||
Gtk.main_iteration()
|
||||
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 and not trace_debug:
|
||||
self.fm_controller.save_state()
|
||||
|
||||
|
||||
@daemon_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,))
|
||||
|
||||
def update_store(self, i, store, icon):
|
||||
itr = store.get_iter(i)
|
||||
store.set_value(itr, 0, icon)
|
||||
|
||||
def create_tab_widget(self, tab):
|
||||
return TabHeaderWidget(tab, self.close_tab)
|
||||
|
||||
def create_scroll_and_store(self, tab, wid, use_tree_view=False):
|
||||
scroll = Gtk.ScrolledWindow()
|
||||
|
||||
if not use_tree_view:
|
||||
grid = self.create_icon_grid_widget()
|
||||
else:
|
||||
# TODO: Fix global logic to make the below work too
|
||||
grid = self.create_icon_tree_widget()
|
||||
|
||||
scroll.add(grid)
|
||||
scroll.set_name(f"{wid}|{tab.get_id()}")
|
||||
grid.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, grid.get_store()
|
||||
|
||||
def create_icon_grid_widget(self):
|
||||
grid = IconGridWidget()
|
||||
grid._setup_additional_signals(
|
||||
self.grid_icon_single_click,
|
||||
self.grid_icon_double_click,
|
||||
self.grid_set_selected_items,
|
||||
self.grid_on_drag_set,
|
||||
self.grid_on_drag_data_received,
|
||||
self.grid_on_drag_motion
|
||||
)
|
||||
|
||||
return grid
|
||||
|
||||
def create_icon_tree_widget(self):
|
||||
grid = IconTreeWidget()
|
||||
grid._setup_additional_signals(
|
||||
self.grid_icon_single_click,
|
||||
self.grid_icon_double_click,
|
||||
self.grid_on_drag_set,
|
||||
self.grid_on_drag_data_received,
|
||||
self.grid_on_drag_motion
|
||||
)
|
||||
|
||||
grid.columns_autosize()
|
||||
return grid
|
||||
|
||||
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
|
@@ -0,0 +1,60 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
# TODO: Should rewrite to try and support more windows more naturally
|
||||
class PaneMixin:
|
||||
"""docstring for PaneMixin"""
|
||||
|
||||
def toggle_pane(self, child):
|
||||
if child.is_visible():
|
||||
child.hide()
|
||||
else:
|
||||
child.show()
|
||||
|
||||
def run_flag_toggle(self, pane_index):
|
||||
tggl_button = self.builder.get_object(f"tggl_notebook_{pane_index}")
|
||||
if pane_index == 1:
|
||||
self.is_pane1_hidden = not self.is_pane1_hidden
|
||||
tggl_button.set_active(not self.is_pane1_hidden)
|
||||
return self.is_pane1_hidden
|
||||
elif pane_index == 2:
|
||||
self.is_pane2_hidden = not self.is_pane2_hidden
|
||||
tggl_button.set_active(not self.is_pane2_hidden)
|
||||
return self.is_pane2_hidden
|
||||
elif pane_index == 3:
|
||||
self.is_pane3_hidden = not self.is_pane3_hidden
|
||||
tggl_button.set_active(not self.is_pane3_hidden)
|
||||
return self.is_pane3_hidden
|
||||
elif pane_index == 4:
|
||||
self.is_pane4_hidden = not self.is_pane4_hidden
|
||||
tggl_button.set_active(not self.is_pane4_hidden)
|
||||
return self.is_pane4_hidden
|
||||
|
||||
def toggle_notebook_pane(self, widget, eve=None):
|
||||
name = widget.get_name()
|
||||
pane_index = int(name[-1])
|
||||
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")
|
||||
|
||||
state = self.run_flag_toggle(pane_index)
|
||||
if self.is_pane1_hidden and self.is_pane2_hidden and self.is_pane3_hidden and self.is_pane4_hidden:
|
||||
state = self.run_flag_toggle(pane_index)
|
||||
self._save_state(state, pane_index)
|
||||
return
|
||||
|
||||
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.fm_controller.get_window_by_index(pane_index - 1)
|
||||
window.set_is_hidden(state)
|
||||
if not settings.is_trace_debug():
|
||||
self.fm_controller.save_state()
|
204
src/versions/solarfm-0.0.1/solarfm/core/mixins/ui/tab_mixin.py
Normal file
204
src/versions/solarfm-0.0.1/solarfm/core/mixins/ui/tab_mixin.py
Normal file
@@ -0,0 +1,204 @@
|
||||
# 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 = 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)
|
||||
notebook.set_tab_detachable(scroll, True)
|
||||
|
||||
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)
|
||||
if not settings.is_trace_debug():
|
||||
self.fm_controller.save_state()
|
||||
self.set_window_title()
|
||||
|
||||
# NOTE: Not actually getting called even tho set in the glade file...
|
||||
def on_tab_dnded(self, notebook, page, x, y):
|
||||
...
|
||||
|
||||
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)
|
||||
if not settings.is_trace_debug():
|
||||
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)
|
||||
if not settings.is_trace_debug():
|
||||
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)
|
||||
if not settings.is_trace_debug():
|
||||
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, size 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:
|
||||
event_system.emit("hide_path_menu")
|
||||
else:
|
||||
event_system.emit("show_path_menu")
|
||||
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)
|
||||
event_system.emit("hide_path_menu")
|
||||
|
||||
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()
|
@@ -0,0 +1,292 @@
|
||||
# Python imports
|
||||
import copy
|
||||
import traceback
|
||||
from os.path import isdir
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from .tab_mixin import TabMixin
|
||||
|
||||
|
||||
class WindowException(Exception):
|
||||
...
|
||||
|
||||
|
||||
class WindowMixin(TabMixin):
|
||||
"""docstring for WindowMixin"""
|
||||
|
||||
def generate_windows(self, session_json = None):
|
||||
# for session in session_json:
|
||||
# nickname = session["window"]["Nickname"]
|
||||
# tabs = session["window"]["tabs"]
|
||||
# isHidden = True if session["window"]["isHidden"] == "True" else False
|
||||
# event_system.emit("load-window-state", (nickname, tabs))
|
||||
|
||||
if session_json:
|
||||
for j, value in enumerate(session_json):
|
||||
i = j + 1
|
||||
notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}")
|
||||
is_hidden = True if value["window"]["isHidden"] == "True" else False
|
||||
tabs = value["window"]["tabs"]
|
||||
self.fm_controller.create_window()
|
||||
notebook_tggl_button.set_active(True)
|
||||
|
||||
if tabs:
|
||||
for tab in tabs:
|
||||
self.create_new_tab_notebook(None, i, tab)
|
||||
else:
|
||||
self.create_new_tab_notebook(None, i, None)
|
||||
|
||||
if is_hidden:
|
||||
self.toggle_notebook_pane(notebook_tggl_button)
|
||||
|
||||
try:
|
||||
if not self.is_pane4_hidden:
|
||||
icon_grid = self.window4.get_children()[-1].get_children()[0]
|
||||
elif not self.is_pane3_hidden:
|
||||
icon_grid = self.window3.get_children()[-1].get_children()[0]
|
||||
elif not self.is_pane2_hidden:
|
||||
icon_grid = self.window2.get_children()[-1].get_children()[0]
|
||||
elif not self.is_pane1_hidden:
|
||||
icon_grid = self.window1.get_children()[-1].get_children()[0]
|
||||
|
||||
icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE))
|
||||
icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE))
|
||||
except WindowException as e:
|
||||
logger.info("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n")
|
||||
logger.debug(repr(e))
|
||||
else:
|
||||
for j in range(0, 4):
|
||||
i = j + 1
|
||||
self.fm_controller.create_window()
|
||||
self.create_new_tab_notebook(None, i, None)
|
||||
|
||||
|
||||
def get_fm_window(self, 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):
|
||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||
dir = tab.get_current_directory()
|
||||
uris = []
|
||||
|
||||
for path in treePaths:
|
||||
itr = store.get_iter(path)
|
||||
file = store.get(itr, 1)[0]
|
||||
fpath = ""
|
||||
|
||||
if not use_just_path:
|
||||
fpath = f"file://{dir}/{file}"
|
||||
else:
|
||||
fpath = f"{dir}/{file}"
|
||||
|
||||
uris.append(fpath)
|
||||
|
||||
return uris
|
||||
|
||||
|
||||
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 = sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
|
||||
formatted_mount_size = sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
|
||||
|
||||
# NOTE: Hides empty trash and other desired buttons based on context.
|
||||
if settings.get_trash_files_path() == current_directory:
|
||||
event_system.emit("show_trash_buttons")
|
||||
else:
|
||||
event_system.emit("hide_trash_buttons")
|
||||
|
||||
# If something selected
|
||||
self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}")
|
||||
self.bottom_path_label.set_label(tab.get_current_directory())
|
||||
if selected_files:
|
||||
uris = self.format_to_uris(state.store, state.wid, state.tid, selected_files, True)
|
||||
combined_size = 0
|
||||
for uri in uris:
|
||||
try:
|
||||
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
|
||||
flags=Gio.FileQueryInfoFlags.NONE,
|
||||
cancellable=None)
|
||||
file_size = file_info.get_size()
|
||||
combined_size += file_size
|
||||
except WindowException as e:
|
||||
logger.debug(repr(e))
|
||||
|
||||
formatted_size = sizeof_fmt(combined_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)} / {tab.get_not_hidden_count()} ({formatted_size})")
|
||||
|
||||
return
|
||||
|
||||
# If nothing selected
|
||||
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"{tab.get_files_count()} items")
|
||||
else:
|
||||
self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items")
|
||||
|
||||
|
||||
|
||||
def set_window_title(self):
|
||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||
notebook = self.builder.get_object(f"window_{wid}")
|
||||
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()
|
||||
ctx.remove_class("notebook-selected-focus")
|
||||
ctx.add_class("notebook-unselected-focus")
|
||||
|
||||
ctx = notebook.get_style_context()
|
||||
ctx.remove_class("notebook-unselected-focus")
|
||||
ctx.add_class("notebook-selected-focus")
|
||||
|
||||
self.window.set_title(f"{app_name} ~ {dir}")
|
||||
self.set_bottom_labels(tab)
|
||||
|
||||
def set_path_text(self, wid, tid):
|
||||
path_entry = self.builder.get_object("path_entry")
|
||||
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, icons_grid):
|
||||
new_items = icons_grid.get_selected_items()
|
||||
items_size = len(new_items)
|
||||
selected_items = event_system.emit_and_await("get_selected_files")
|
||||
|
||||
if items_size == 1:
|
||||
# NOTE: If already in selection, likely dnd else not so wont readd
|
||||
if new_items[0] in selected_items:
|
||||
self.dnd_left_primed += 1
|
||||
# NOTE: If in selection but trying to just select an already selected item.
|
||||
if self.dnd_left_primed > 1:
|
||||
self.dnd_left_primed = 0
|
||||
selected_items.clear()
|
||||
|
||||
# NOTE: Likely trying dnd, just readd to selection the former set.
|
||||
# Prevents losing highlighting of grid selected.
|
||||
for path in selected_items:
|
||||
icons_grid.select_path(path)
|
||||
|
||||
if items_size > 0:
|
||||
event_system.emit("set_selected_files", (new_items,))
|
||||
else:
|
||||
self.dnd_left_primed = 0
|
||||
selected_items.clear()
|
||||
|
||||
def grid_icon_single_click(self, icons_grid, eve):
|
||||
try:
|
||||
event_system.emit("hide_path_menu")
|
||||
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.ctrl_down:
|
||||
self.dnd_left_primed = 0
|
||||
|
||||
if self.single_click_open: # FIXME: need to find a way to pass the model index
|
||||
self.grid_icon_double_click(icons_grid)
|
||||
elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
|
||||
event_system.emit("show_context_menu")
|
||||
|
||||
except WindowException as e:
|
||||
logger.info(repr(e))
|
||||
self.display_message(settings.get_error_color(), f"{repr(e)}")
|
||||
|
||||
def grid_icon_double_click(self, icons_grid, item, data=None):
|
||||
try:
|
||||
if self.ctrl_down and self.shift_down:
|
||||
self.unset_keys_and_data()
|
||||
self.execute_files(in_terminal=True)
|
||||
return
|
||||
elif self.ctrl_down:
|
||||
self.unset_keys_and_data()
|
||||
self.execute_files()
|
||||
return
|
||||
|
||||
|
||||
state = self.get_current_state()
|
||||
notebook = self.builder.get_object(f"window_{state.wid}")
|
||||
tab_label = self.get_tab_label(notebook, icons_grid)
|
||||
|
||||
fileName = state.store[item][1]
|
||||
dir = state.tab.get_current_directory()
|
||||
file = f"{dir}/{fileName}"
|
||||
|
||||
if isdir(file):
|
||||
state.tab.set_path(file)
|
||||
self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid)
|
||||
else:
|
||||
event_system.emit("open_files")
|
||||
except WindowException as e:
|
||||
traceback.print_exc()
|
||||
self.display_message(settings.get_error_color(), f"{repr(e)}")
|
||||
|
||||
|
||||
|
||||
def grid_on_drag_set(self, icons_grid, drag_context, data, info, time):
|
||||
action = icons_grid.get_name()
|
||||
wid, tid = action.split("|")
|
||||
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)
|
||||
uris_text = '\n'.join(uris)
|
||||
|
||||
data.set_uris(uris)
|
||||
data.set_text(uris_text, -1)
|
||||
|
||||
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 = icons_grid.get_model()
|
||||
path_at_loc = None
|
||||
|
||||
try:
|
||||
path_at_loc = icons_grid.get_item_at_pos(x, y)[0]
|
||||
highlighted_item_path = icons_grid.get_drag_dest_item().path
|
||||
if path_at_loc and path_at_loc == highlighted_item_path:
|
||||
uri = self.format_to_uris(store, wid, tid, highlighted_item_path)[0].replace("file://", "")
|
||||
self.override_drop_dest = uri if isdir(uri) else None
|
||||
except Exception as e:
|
||||
...
|
||||
|
||||
if target not in current:
|
||||
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:
|
||||
uris = data.get_uris()
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
dest = f"{state.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")
|
||||
|
||||
from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1])
|
||||
if from_uri != dest:
|
||||
event_system.emit("move_files", (uris, dest))
|
||||
|
||||
|
||||
def create_new_tab_notebook(self, widget=None, wid=None, path=None):
|
||||
self.create_tab(wid, None, path)
|
13
src/versions/solarfm-0.0.1/solarfm/core/ui_mixin.py
Normal file
13
src/versions/solarfm-0.0.1/solarfm/core/ui_mixin.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Python imports
|
||||
|
||||
# Gtk imports
|
||||
|
||||
# Application imports
|
||||
from .mixins.ui.pane_mixin import PaneMixin
|
||||
from .mixins.ui.window_mixin import WindowMixin
|
||||
|
||||
|
||||
|
||||
|
||||
class UIMixin(PaneMixin, WindowMixin):
|
||||
...
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Widgets module
|
||||
"""
|
@@ -0,0 +1,104 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class ContextMenuWidget(Gtk.Menu):
|
||||
"""docstring for ContextMenuWidget"""
|
||||
|
||||
def __init__(self):
|
||||
super(ContextMenuWidget, self).__init__()
|
||||
self.builder = settings.get_builder()
|
||||
self._builder = Gtk.Builder()
|
||||
self._context_menu_data = settings.get_context_menu_data()
|
||||
self._window = settings.get_main_window()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_context_menu", self.show_context_menu)
|
||||
event_system.subscribe("hide_context_menu", self.hide_context_menu)
|
||||
|
||||
classes = [self]
|
||||
handlers = {}
|
||||
for c in classes:
|
||||
methods = None
|
||||
try:
|
||||
methods = inspect.getmembers(c, predicate=inspect.ismethod)
|
||||
handlers.update(methods)
|
||||
except Exception as e:
|
||||
logger.debug(repr(e))
|
||||
|
||||
self._builder.connect_signals(handlers)
|
||||
|
||||
def _load_widgets(self):
|
||||
self.build_context_menu()
|
||||
|
||||
|
||||
def make_submenu(self, name, data, keys):
|
||||
menu = Gtk.Menu()
|
||||
menu_item = Gtk.MenuItem(name)
|
||||
|
||||
for key in keys:
|
||||
if isinstance(data, dict):
|
||||
entry = self.make_menu_item(key, data[key])
|
||||
elif isinstance(data, list):
|
||||
entry = self.make_menu_item(key, data)
|
||||
else:
|
||||
continue
|
||||
|
||||
menu.append(entry)
|
||||
|
||||
menu_item.set_submenu(menu)
|
||||
return menu_item
|
||||
|
||||
def make_menu_item(self, name, data) -> Gtk.MenuItem:
|
||||
if isinstance(data, dict):
|
||||
return self.make_submenu(name, data, data.keys())
|
||||
elif isinstance(data, list):
|
||||
entry = Gtk.ImageMenuItem(name)
|
||||
icon = getattr(Gtk, f"{data[0]}")
|
||||
entry.set_image( Gtk.Image(stock=icon) )
|
||||
entry.set_always_show_image(True)
|
||||
entry.connect("activate", self._emit, (data[1]))
|
||||
return entry
|
||||
|
||||
def build_context_menu(self) -> None:
|
||||
data = self._context_menu_data
|
||||
dkeys = data.keys()
|
||||
plugins_entry = None
|
||||
|
||||
for dkey in dkeys:
|
||||
entry = self.make_menu_item(dkey, data[dkey])
|
||||
self.append(entry)
|
||||
if dkey == "Plugins":
|
||||
plugins_entry = entry
|
||||
|
||||
self.attach_to_widget(self._window, None)
|
||||
self.show_all()
|
||||
self.builder.expose_object("context_menu", self)
|
||||
if plugins_entry:
|
||||
self.builder.expose_object("context_menu_plugins", plugins_entry.get_submenu())
|
||||
|
||||
def _emit(self, menu_item, type):
|
||||
event_system.emit("do_action_from_menu_controls", type)
|
||||
|
||||
def show_context_menu(self, widget=None, eve=None):
|
||||
self.builder.get_object("context_menu").popup_at_pointer(None)
|
||||
|
||||
def hide_context_menu(self, widget=None, eve=None):
|
||||
self.builder.get_object("context_menu").popdown()
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Dialogs module
|
||||
"""
|
@@ -0,0 +1,61 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class AboutWidget:
|
||||
"""docstring for AboutWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(AboutWidget, self).__init__()
|
||||
_GLADE_FILE = f"{settings.get_ui_widgets_path()}/about_ui.glade"
|
||||
builder = settings.get_builder()
|
||||
self._builder = Gtk.Builder()
|
||||
|
||||
self._builder.add_from_file(_GLADE_FILE)
|
||||
self.about_page = self._builder.get_object("about_page")
|
||||
|
||||
builder.expose_object(f"about_page", self.about_page)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_about_page", self.show_about_page)
|
||||
event_system.subscribe("hide_about_page", self.hide_about_page)
|
||||
|
||||
classes = [self]
|
||||
handlers = {}
|
||||
for c in classes:
|
||||
methods = None
|
||||
try:
|
||||
methods = inspect.getmembers(c, predicate=inspect.ismethod)
|
||||
handlers.update(methods)
|
||||
except Exception as e:
|
||||
logger.debug(repr(e))
|
||||
|
||||
self._builder.connect_signals(handlers)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def show_about_page(self, widget=None, eve=None):
|
||||
response = self.about_page.run()
|
||||
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
|
||||
self.hide_about_page()
|
||||
|
||||
def hide_about_page(self, widget=None, eve=None):
|
||||
self.about_page.hide()
|
@@ -0,0 +1,72 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# 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"
|
||||
builder = settings.get_builder()
|
||||
self._builder = Gtk.Builder()
|
||||
|
||||
self._builder.add_from_file(_GLADE_FILE)
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
classes = [self]
|
||||
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))
|
||||
|
||||
self._builder.connect_signals(handlers)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
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)
|
@@ -0,0 +1,143 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class FileExistsWidget:
|
||||
"""docstring for FileExistsWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(FileExistsWidget, self).__init__()
|
||||
_GLADE_FILE = f"{settings.get_ui_widgets_path()}/file_exists_ui.glade"
|
||||
builder = settings.get_builder()
|
||||
self._builder = Gtk.Builder()
|
||||
|
||||
self._builder.add_from_file(_GLADE_FILE)
|
||||
|
||||
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_diff_from = self._builder.get_object("exists_file_diff_from")
|
||||
self._exists_file_diff_to = self._builder.get_object("exists_file_diff_to")
|
||||
self._exists_file_field = self._builder.get_object("exists_file_field")
|
||||
self._exists_file_from = self._builder.get_object("exists_file_from")
|
||||
self._exists_file_to = self._builder.get_object("exists_file_to")
|
||||
self._exists_file_rename_bttn = self._builder.get_object("exists_file_rename_bttn")
|
||||
|
||||
builder.expose_object(f"file_exists_dialog", self.file_exists_dialog)
|
||||
builder.expose_object(f"exists_file_diff_from", self._exists_file_diff_from)
|
||||
builder.expose_object(f"exists_file_diff_to", self._exists_file_diff_to)
|
||||
builder.expose_object(f"exists_file_field", self._exists_file_field)
|
||||
builder.expose_object(f"exists_file_rename_bttn", self._exists_file_rename_bttn)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("setup_exists_data", self.setup_exists_data)
|
||||
event_system.subscribe("show_exists_page", self.show_exists_page)
|
||||
|
||||
|
||||
classes = [self]
|
||||
handlers = {}
|
||||
for c in classes:
|
||||
methods = None
|
||||
try:
|
||||
methods = inspect.getmembers(c, predicate=inspect.ismethod)
|
||||
handlers.update(methods)
|
||||
except Exception as e:
|
||||
logger.debug(repr(e))
|
||||
|
||||
self._builder.connect_signals(handlers)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def show_exists_page(self, widget=None, eve=None):
|
||||
response = self.file_exists_dialog.run()
|
||||
self.file_exists_dialog.hide()
|
||||
|
||||
if response == Gtk.ResponseType.OK:
|
||||
return "rename"
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
return "rename_auto"
|
||||
if response == Gtk.ResponseType.CLOSE:
|
||||
return "rename_auto_all"
|
||||
if response == Gtk.ResponseType.YES:
|
||||
return "overwrite"
|
||||
if response == Gtk.ResponseType.APPLY:
|
||||
return "overwrite_all"
|
||||
if response == Gtk.ResponseType.NO:
|
||||
return "skip"
|
||||
if response == Gtk.ResponseType.REJECT:
|
||||
return "skip_all"
|
||||
|
||||
def hide_exists_page_rename(self, widget=None, eve=None):
|
||||
self.file_exists_dialog.response(Gtk.ResponseType.OK)
|
||||
|
||||
def hide_exists_page_auto_rename(self, widget=None, eve=None):
|
||||
self.file_exists_dialog.response(Gtk.ResponseType.ACCEPT)
|
||||
|
||||
def hide_exists_page_auto_rename_all(self, widget=None, eve=None):
|
||||
self.file_exists_dialog.response(Gtk.ResponseType.CLOSE)
|
||||
|
||||
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)
|
||||
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()
|
||||
|
||||
self._exists_file_from.set_label(from_file.get_parent().get_path())
|
||||
self._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')} {sizeof_fmt(from_size)} ( {from_size} bytes ) ( {age_text} & {size_text} )"
|
||||
to_label_text = f"{to_date.format('%F %R')} {sizeof_fmt(to_size)} ( {to_size} bytes )"
|
||||
|
||||
self._exists_file_diff_from.set_text(from_label_text)
|
||||
self._exists_file_diff_to.set_text(to_label_text)
|
||||
|
||||
|
||||
def exists_rename_field_changed(self, widget):
|
||||
nfile_name = widget.get_text().strip()
|
||||
ofile_name = self._exists_file_label.get_label()
|
||||
|
||||
if nfile_name:
|
||||
if nfile_name == ofile_name:
|
||||
self._exists_file_rename_bttn.set_sensitive(False)
|
||||
else:
|
||||
self._exists_file_rename_bttn.set_sensitive(True)
|
||||
else:
|
||||
self._exists_file_rename_bttn.set_sensitive(False)
|
@@ -0,0 +1,41 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class MessageWidget(Gtk.MessageDialog):
|
||||
"""docstring for MessageWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(MessageWidget, self).__init__()
|
||||
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.type = Gtk.MessageType.WARNING
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
|
||||
def _load_widgets(self):
|
||||
message_area = self.get_message_area()
|
||||
message_area.get_children()[0].set_label("Alert!")
|
||||
message_area.show_all()
|
||||
|
||||
self.set_image( Gtk.Image.new_from_icon_name("user-alert", 16) )
|
||||
self.add_buttons(Gtk.STOCK_NO, Gtk.ResponseType.NO)
|
||||
self.add_buttons(Gtk.STOCK_YES, Gtk.ResponseType.YES)
|
@@ -0,0 +1,78 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class NewFileWidget:
|
||||
"""docstring for NewFileWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(NewFileWidget, self).__init__()
|
||||
_GLADE_FILE = f"{settings.get_ui_widgets_path()}/new_file_ui.glade"
|
||||
builder = settings.get_builder()
|
||||
self._builder = Gtk.Builder()
|
||||
|
||||
self._builder.add_from_file(_GLADE_FILE)
|
||||
self._new_file_menu = self._builder.get_object("new_file_menu")
|
||||
self._new_fname_field = self._builder.get_object("new_fname_field")
|
||||
self._new_file_toggle_type = self._builder.get_object("new_file_toggle_type")
|
||||
|
||||
builder.expose_object(f"new_file_menu", self._new_file_menu)
|
||||
builder.expose_object(f"new_fname_field", self._new_fname_field)
|
||||
builder.expose_object(f"new_file_toggle_type", self._new_file_toggle_type)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_new_file_menu", self.show_new_file_menu)
|
||||
event_system.subscribe("hide_new_file_menu", self.hide_new_file_menu)
|
||||
|
||||
classes = [self]
|
||||
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))
|
||||
|
||||
self._builder.connect_signals(handlers)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def show_new_file_menu(self, widget=None, eve=None):
|
||||
if widget:
|
||||
widget.set_text("")
|
||||
widget.grab_focus()
|
||||
|
||||
response = self._new_file_menu.run()
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def hide_new_file_menu(self, widget=None, eve=None):
|
||||
self._builder.get_object("new_file_menu").hide()
|
||||
|
||||
def hide_new_file_menu_enter_key(self, widget=None, eve=None):
|
||||
keyname = Gdk.keyval_name(eve.keyval).lower()
|
||||
if keyname in ["return", "enter"]:
|
||||
self._new_file_menu.hide()
|
@@ -0,0 +1,92 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class RenameWidget:
|
||||
"""docstring for RenameWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(RenameWidget, self).__init__()
|
||||
_GLADE_FILE = f"{settings.get_ui_widgets_path()}/rename_ui.glade"
|
||||
builder = settings.get_builder()
|
||||
self._builder = Gtk.Builder()
|
||||
|
||||
self._builder.add_from_file(_GLADE_FILE)
|
||||
self._rename_file_menu = self._builder.get_object("rename_file_menu")
|
||||
self._rename_fname = self._builder.get_object("rename_fname")
|
||||
self._file_to_rename_label = self._builder.get_object("file_to_rename_label")
|
||||
|
||||
builder.expose_object(f"rename_file_menu", self._rename_file_menu)
|
||||
builder.expose_object(f"rename_fname", self._rename_fname)
|
||||
builder.expose_object(f"file_to_rename_label", self._file_to_rename_label)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_rename_file_menu", self.show_rename_file_menu)
|
||||
event_system.subscribe("hide_rename_file_menu", self.hide_rename_file_menu)
|
||||
|
||||
classes = [self]
|
||||
handlers = {}
|
||||
for c in classes:
|
||||
methods = None
|
||||
try:
|
||||
methods = inspect.getmembers(c, predicate=inspect.ismethod)
|
||||
handlers.update(methods)
|
||||
except Exception as e:
|
||||
logger.debug(repr(e))
|
||||
|
||||
self._builder.connect_signals(handlers)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def show_rename_file_menu(self, widget=None, eve=None):
|
||||
if widget:
|
||||
widget.grab_focus()
|
||||
|
||||
response = self._rename_file_menu.run()
|
||||
if response == Gtk.ResponseType.CLOSE:
|
||||
return "skip_edit"
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
return "cancel_edit"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def set_to_title_case(self, widget, eve=None):
|
||||
self._rename_fname.set_text( self._rename_fname.get_text().title() )
|
||||
|
||||
def set_to_upper_case(self, widget, eve=None):
|
||||
self._rename_fname.set_text( self._rename_fname.get_text().upper() )
|
||||
|
||||
def set_to_lower_case(self, widget, eve=None):
|
||||
self._rename_fname.set_text( self._rename_fname.get_text().lower() )
|
||||
|
||||
def set_to_invert_case(self, widget, eve=None):
|
||||
self._rename_fname.set_text( self._rename_fname.get_text().swapcase() )
|
||||
|
||||
def hide_rename_file_menu(self, widget=None, eve=None):
|
||||
self._rename_file_menu.hide()
|
||||
|
||||
def hide_rename_file_menu_enter_key(self, widget=None, eve=None):
|
||||
keyname = Gdk.keyval_name(eve.keyval).lower()
|
||||
if keyname in ["return", "enter"]:
|
||||
self._rename_file_menu.hide()
|
@@ -0,0 +1,83 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
import gc
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class SaveLoadWidget:
|
||||
"""docstring for SaveLoadWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(SaveLoadWidget, self).__init__()
|
||||
_GLADE_FILE = f"{settings.get_ui_widgets_path()}/save_load_ui.glade"
|
||||
builder = settings.get_builder()
|
||||
self._builder = Gtk.Builder()
|
||||
|
||||
self._builder.add_from_file(_GLADE_FILE)
|
||||
self.save_load_dialog = self._builder.get_object("save_load_dialog")
|
||||
|
||||
builder.expose_object(f"save_load_dialog", self.save_load_dialog)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("save_load_session", self.save_load_session)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
|
||||
def save_load_session(self, action="save_session"):
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
|
||||
if action == "save_session":
|
||||
if not settings.is_trace_debug():
|
||||
state.fm_controller.save_state()
|
||||
|
||||
return
|
||||
elif action == "save_session_as":
|
||||
self.save_load_dialog.set_action(Gtk.FileChooserAction.SAVE)
|
||||
elif action == "load_session":
|
||||
self.save_load_dialog.set_action(Gtk.FileChooserAction.OPEN)
|
||||
else:
|
||||
raise Exception(f"Unknown action given: {action}")
|
||||
|
||||
self.save_load_dialog.set_current_folder(state.tab.get_current_directory())
|
||||
self.save_load_dialog.set_current_name("session.json")
|
||||
response = self.save_load_dialog.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
if action == "save_session_as":
|
||||
path = f"{self.save_load_dialog.get_current_folder()}/{self.save_load_dialog.get_current_name()}"
|
||||
state.fm_controller.save_state(path)
|
||||
elif action == "load_session":
|
||||
path = f"{self.save_load_dialog.get_file().get_path()}"
|
||||
session_json = state.fm_controller.get_state_from_file(path)
|
||||
self.load_session(session_json)
|
||||
if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT):
|
||||
...
|
||||
|
||||
self.save_load_dialog.hide()
|
||||
|
||||
def load_session(self, session_json):
|
||||
if settings.is_debug():
|
||||
logger.debug(f"Session Data: {session_json}")
|
||||
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
event_system.emit("clear_notebooks")
|
||||
state.fm_controller.unload_tabs_and_windows()
|
||||
event_system.emit("generate_windows", (session_json,))
|
||||
gc.collect()
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
FileView module
|
||||
"""
|
@@ -0,0 +1,71 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from shellfm.windows.tabs.tab import Tab
|
||||
from .icon_view import IconView
|
||||
|
||||
|
||||
|
||||
|
||||
class FileView(Gtk.ScrolledWindow):
|
||||
"""docstring for FileView."""
|
||||
|
||||
def __init__(self):
|
||||
super(FileView, self).__init__()
|
||||
self.tab_state = Tab()
|
||||
self.icon_view = IconView(self.tab_state)
|
||||
self.tab_widget = self.create_tab_widget()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
|
||||
self.add(self.icon_view)
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_hexpand(True)
|
||||
self.set_vexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
# self.connect("update-tab-title", self._update_tab_title)
|
||||
...
|
||||
|
||||
def _update_tab_title(self, widget=None, eve=None):
|
||||
label = self.tab_widget.get_children()[0]
|
||||
label.set_label(f"{self.tab_state.get_end_of_path()}")
|
||||
label.set_width_chars( len(self.tab_state.get_end_of_path()) )
|
||||
|
||||
def set_path(self, path):
|
||||
if path:
|
||||
self.tab_state.set_path(path)
|
||||
self.icon_view.load_store()
|
||||
self._update_tab_title()
|
||||
|
||||
def create_tab_widget(self):
|
||||
button_box = Gtk.ButtonBox()
|
||||
label = Gtk.Label()
|
||||
close = Gtk.Button()
|
||||
icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
|
||||
|
||||
label.set_label(f"{self.tab_state.get_end_of_path()}")
|
||||
label.set_width_chars(len(self.tab_state.get_end_of_path()))
|
||||
label.set_xalign(0.0)
|
||||
|
||||
close.add(icon)
|
||||
button_box.add(label)
|
||||
button_box.add(close)
|
||||
|
||||
close.connect("released", self.close_tab)
|
||||
|
||||
button_box.show_all()
|
||||
return button_box
|
||||
|
||||
|
||||
def close_tab(self, widget, eve=None):
|
||||
self.get_parent().emit('close-view', (self,))
|
@@ -0,0 +1,149 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from .file_view import FileView
|
||||
|
||||
|
||||
|
||||
|
||||
class FilesWidget(Gtk.Notebook):
|
||||
"""docstring for FilesWidget."""
|
||||
|
||||
ccount = 0
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
obj = super(FilesWidget, cls).__new__(cls)
|
||||
cls.ccount += 1
|
||||
|
||||
return obj
|
||||
|
||||
def __init__(self):
|
||||
super(FilesWidget, self).__init__()
|
||||
|
||||
self.set_group_name("file_window")
|
||||
|
||||
self.NAME = f"window_{self.ccount}"
|
||||
builder = settings.get_builder()
|
||||
builder.expose_object(self.NAME, self)
|
||||
|
||||
self._add_action_widgets()
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_scrollable(True)
|
||||
self.set_show_tabs(True)
|
||||
self.set_show_border(False)
|
||||
self.set_hexpand(True)
|
||||
|
||||
self.set_margin_top(5)
|
||||
self.set_margin_bottom(5)
|
||||
self.set_margin_start(5)
|
||||
self.set_margin_end(5)
|
||||
|
||||
def _setup_signals(self):
|
||||
# self.connect("close-view", self.close_view)
|
||||
event_system.subscribe("load-window-state", self.load_window_state)
|
||||
|
||||
def _add_action_widgets(self):
|
||||
start_box = Gtk.Box()
|
||||
end_box = Gtk.Box()
|
||||
|
||||
search = Gtk.SearchEntry()
|
||||
search.set_placeholder_text("Search...")
|
||||
search.connect("changed", self._do_query)
|
||||
|
||||
home_btn = Gtk.Button()
|
||||
home_btn.set_image( Gtk.Image.new_from_icon_name("gtk-home", 4) )
|
||||
home_btn.set_always_show_image(True)
|
||||
home_btn.connect("released", self.do_action, ("go_home_dir"))
|
||||
|
||||
up_btn = Gtk.Button()
|
||||
up_btn.set_image( Gtk.Image.new_from_icon_name("up", 4) )
|
||||
up_btn.set_always_show_image(True)
|
||||
up_btn.connect("released", self.do_action, ("go_up_dir"))
|
||||
|
||||
refresh_btn = Gtk.Button()
|
||||
refresh_btn.set_image( Gtk.Image.new_from_icon_name("gtk-refresh", 4) )
|
||||
refresh_btn.set_always_show_image(True)
|
||||
refresh_btn.connect("released", self.do_action, ("refresh_dir"))
|
||||
|
||||
add_btn = Gtk.Button()
|
||||
add_btn.set_image( Gtk.Image.new_from_icon_name("add", 4) )
|
||||
add_btn.set_always_show_image(True)
|
||||
add_btn.connect("released", self.create_view)
|
||||
|
||||
start_box.add(home_btn)
|
||||
start_box.add(add_btn)
|
||||
end_box.add(search)
|
||||
end_box.add(up_btn)
|
||||
end_box.add(refresh_btn)
|
||||
|
||||
start_box.show_all()
|
||||
end_box.show_all()
|
||||
|
||||
# PACKTYPE: 0 Start, 1 = End
|
||||
self.set_action_widget(start_box, 0)
|
||||
self.set_action_widget(end_box, 1)
|
||||
|
||||
def _do_query(self, widget):
|
||||
text = widget.get_text()
|
||||
page = self.get_nth_page( self.get_current_page() )
|
||||
page.icon_view.search_filter(text)
|
||||
|
||||
|
||||
def load_window_state(self, win_name=None, tabs=None):
|
||||
if win_name == self.NAME:
|
||||
if len(tabs) > 0:
|
||||
for tab in tabs:
|
||||
self.create_view()
|
||||
self.load_tab(tab)
|
||||
else:
|
||||
self.create_view()
|
||||
|
||||
def create_view(self, widget = None):
|
||||
file_view = FileView()
|
||||
index = self.append_page(file_view, file_view.tab_widget)
|
||||
self.set_current_page(index)
|
||||
|
||||
return file_view
|
||||
|
||||
def load_tab(self, path = None):
|
||||
if path:
|
||||
file_view = self.get_nth_page( self.get_current_page() )
|
||||
file_view.set_path(path)
|
||||
|
||||
|
||||
def do_action(self, widget = None, action = None):
|
||||
file_view = self.get_nth_page( self.get_current_page() )
|
||||
|
||||
if action == "refresh_dir":
|
||||
file_view.icon_view.refresh_dir()
|
||||
|
||||
if action == "go_up_dir":
|
||||
file_view.icon_view.go_up_dir()
|
||||
|
||||
if action == "go_home_dir":
|
||||
file_view.icon_view.go_home_dir()
|
||||
|
||||
|
||||
def close_view(self, parent, data = None):
|
||||
widget = data[0]
|
||||
page = self.page_num(widget)
|
||||
|
||||
# watcher = widget.tab_state.get_dir_watcher()
|
||||
# watcher.cancel()
|
||||
if self.get_n_pages() > 1:
|
||||
self.remove_page(page)
|
||||
|
||||
# NOTE: Will prob try and call a window custom signal...
|
||||
# self.fm_controller.save_state()
|
||||
# self.set_window_title()
|
@@ -0,0 +1,146 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GdkPixbuf
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from .signals.icon_view_signals_mixin import IconViewSignalsMixin
|
||||
|
||||
|
||||
|
||||
|
||||
class IconView(IconViewSignalsMixin, Gtk.FlowBox):
|
||||
"""docstring for IconView."""
|
||||
|
||||
def __init__(self, _tab_state):
|
||||
super(IconView, self).__init__()
|
||||
self.tab_state = _tab_state
|
||||
self.selected_files = []
|
||||
self.icon_theme = settings.get_icon_theme()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
# self._setup_dnd()
|
||||
self.load_store()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _emmit_signal_to_file_view(self, signal_type):
|
||||
self.get_parent().get_parent().emit(signal_type, None)
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
|
||||
self.set_valign(Gtk.Align.START)
|
||||
self.set_column_spacing(15)
|
||||
self.set_row_spacing(15)
|
||||
self.set_homogeneous(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.set_activate_on_single_click(False)
|
||||
self.connect("child-activated", self.icon_double_click)
|
||||
|
||||
# self.connect("drag-data-get", self.on_drag_set)
|
||||
# self.connect("drag-data-received", self.on_drag_data_received)
|
||||
# self.connect("drag-motion", self.on_drag_motion)
|
||||
|
||||
|
||||
# NOTE: This gets called by a txt box which then shows/hides the flowbox child
|
||||
# https://stackoverflow.com/questions/55828169/how-to-filter-gtk-flowbox-children-with-gtk-entrysearch
|
||||
def search_filter(self, text):
|
||||
def filter_func(fb_child, text):
|
||||
if text.lower() in fb_child.get_name().lower():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
self.set_filter_func(filter_func, text)
|
||||
|
||||
|
||||
def _setup_dnd(self):
|
||||
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
|
||||
self.enable_model_drag_dest(targets, action)
|
||||
self.enable_model_drag_source(0, targets, action)
|
||||
|
||||
def _clear_children(self, widget: type) -> None:
|
||||
''' Clear children of a gtk widget. '''
|
||||
for child in widget.get_children():
|
||||
widget.remove(child)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# class IconView(IconViewSignalsMixin, Gtk.IconView):
|
||||
# """docstring for IconView."""
|
||||
#
|
||||
# def __init__(self, _tab_state):
|
||||
# super(IconView, self).__init__()
|
||||
# self.store = None
|
||||
# self.tab_state = _tab_state
|
||||
# self.selected_files = []
|
||||
# self.icon_theme = Gtk.IconTheme.get_default()
|
||||
#
|
||||
# self._setup_store()
|
||||
# self._setup_styling()
|
||||
# self._setup_signals()
|
||||
# self._setup_dnd()
|
||||
# self.load_store()
|
||||
#
|
||||
# self.show_all()
|
||||
#
|
||||
#
|
||||
# def _setup_store(self):
|
||||
# self.store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
|
||||
# self.set_model(self.store)
|
||||
# self.set_pixbuf_column(0)
|
||||
# self.set_text_column(1)
|
||||
#
|
||||
# def _setup_styling(self):
|
||||
# self.set_item_orientation(1)
|
||||
# self.set_selection_mode(3)
|
||||
# self.set_item_width(96)
|
||||
# self.set_item_padding(8)
|
||||
# self.set_margin(12)
|
||||
# self.set_row_spacing(18)
|
||||
# self.set_columns(-1)
|
||||
# self.set_spacing(12)
|
||||
# self.set_column_spacing(18)
|
||||
#
|
||||
# def _setup_signals(self):
|
||||
# # self.connect("button_release_event", self.icon_single_click)
|
||||
# self.connect("item-activated", self.icon_double_click)
|
||||
# self.connect("selection-changed", self.set_selected_items)
|
||||
# # self.connect("drag-data-get", self.on_drag_set)
|
||||
# # self.connect("drag-data-received", self.on_drag_data_received)
|
||||
# # self.connect("drag-motion", self.on_drag_motion)
|
||||
# pass
|
||||
#
|
||||
# def _setup_dnd(self):
|
||||
# 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
|
||||
# self.enable_model_drag_dest(targets, action)
|
||||
# self.enable_model_drag_source(0, targets, action)
|
||||
#
|
||||
# def set_selected_items(self, icons_grid):
|
||||
# self.selected_files = self.get_selected_items()
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Signals module
|
||||
"""
|
@@ -0,0 +1,183 @@
|
||||
# Python imports
|
||||
import os
|
||||
import traceback
|
||||
import threading
|
||||
import subprocess
|
||||
import time
|
||||
from os.path import isdir
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GdkPixbuf
|
||||
from gi.repository import Gio
|
||||
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class FileView(Gtk.Box):
|
||||
"""docstring for FileView."""
|
||||
|
||||
def __init__(self, _icon = None, _label = None):
|
||||
super(FileView, self).__init__()
|
||||
self.icon = None
|
||||
self.label = _label
|
||||
|
||||
self.label.set_line_wrap(True)
|
||||
self.label.set_selectable(True)
|
||||
self.label.set_max_width_chars(20)
|
||||
self.label.set_justify(2)
|
||||
self.label.set_line_wrap_mode(2)
|
||||
|
||||
self.add(self.label)
|
||||
self.set_orientation(1)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def set_img(self, img):
|
||||
self.icon = img
|
||||
self.add(img)
|
||||
self.reorder_child(self.icon, 0)
|
||||
|
||||
|
||||
|
||||
class IconViewSignalsMixin:
|
||||
"""docstring for WidgetMixin"""
|
||||
|
||||
def load_store(self):
|
||||
self._clear_children(self)
|
||||
self.search_filter("")
|
||||
|
||||
dir = self.tab_state.get_current_directory()
|
||||
files = self.tab_state.get_files()
|
||||
|
||||
for i, file in enumerate(files):
|
||||
label = Gtk.Label(label=file[0])
|
||||
child = Gtk.FlowBoxChild()
|
||||
child.set_name(file[0])
|
||||
|
||||
file_view = FileView(_label=label)
|
||||
child.add( file_view )
|
||||
self.add(child)
|
||||
self.create_icon(file_view, dir, file[0])
|
||||
|
||||
@threaded
|
||||
def create_icon(self, file_view, dir, file):
|
||||
icon = self.tab_state.create_icon(dir, file)
|
||||
GLib.idle_add(self.update_store, *(file_view, icon, dir, file,))
|
||||
|
||||
def update_store(self, file_view, icon, dir, file):
|
||||
if not icon:
|
||||
path = f"{dir}/{file}"
|
||||
icon = self.get_system_thumbnail(path, self.tab_state.sys_icon_wh[0])
|
||||
|
||||
if not icon:
|
||||
icon = GdkPixbuf.Pixbuf.new_from_file(self.tab_state.DEFAULT_ICON)
|
||||
|
||||
img = Gtk.Image.new_from_pixbuf(icon)
|
||||
img.show()
|
||||
file_view.set_img(img)
|
||||
self.show_all()
|
||||
|
||||
|
||||
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:
|
||||
...
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def icon_double_click(self, icons_grid, item, data=None):
|
||||
try:
|
||||
file_view = item.get_children()[0]
|
||||
file_name = file_view.label.get_label()
|
||||
dir = self.tab_state.get_current_directory()
|
||||
file = f"{dir}/{file_name}"
|
||||
|
||||
if isdir(file):
|
||||
self.tab_state.set_path(file)
|
||||
self.load_store()
|
||||
self._emmit_signal_to_file_view('update-tab-title')
|
||||
else:
|
||||
event_system.emit("open_files", (self, file))
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def go_up_dir(self):
|
||||
self.tab_state.pop_from_path()
|
||||
self.load_store()
|
||||
self._emmit_signal_to_file_view('update-tab-title')
|
||||
|
||||
def go_home_dir(self):
|
||||
self.tab_state.set_to_home()
|
||||
self.load_store()
|
||||
self._emmit_signal_to_file_view('update-tab-title')
|
||||
|
||||
def go_to_path(self, path):
|
||||
self.tab_state.set_path(path)
|
||||
self.load_store()
|
||||
self._emmit_signal_to_file_view('update-tab-title')
|
||||
|
||||
def refresh_dir(self):
|
||||
self.tab_state.load_directory()
|
||||
self.load_store()
|
||||
|
||||
# def on_drag_set(self, icons_grid, drag_context, data, info, time):
|
||||
# action = icons_grid.get_name()
|
||||
# wid, tid = action.split("|")
|
||||
# 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)
|
||||
# uris_text = '\n'.join(uris)
|
||||
#
|
||||
# data.set_uris(uris)
|
||||
# data.set_text(uris_text, -1)
|
||||
#
|
||||
# def 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 = 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.fm_controller.set_wid_and_tid(wid, tid)
|
||||
#
|
||||
#
|
||||
# def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
||||
# if info == 80:
|
||||
# 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)
|
||||
#
|
||||
# uris = data.get_uris()
|
||||
# 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")
|
||||
#
|
||||
# from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1])
|
||||
# if from_uri != dest:
|
||||
# self.move_files(uris, dest)
|
@@ -0,0 +1,76 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GdkPixbuf
|
||||
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IconGridWidget(Gtk.IconView):
|
||||
"""docstring for IconGridWidget"""
|
||||
|
||||
def __init__(self):
|
||||
super(IconGridWidget, self).__init__()
|
||||
|
||||
self._store = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._set_up_dnd()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
def get_store(self):
|
||||
return self._store
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_pixbuf_column(0)
|
||||
self.set_text_column(1)
|
||||
|
||||
self.set_item_orientation(1)
|
||||
self.set_selection_mode(3)
|
||||
self.set_item_width(96)
|
||||
self.set_item_padding(8)
|
||||
self.set_margin(12)
|
||||
self.set_row_spacing(18)
|
||||
self.set_columns(-1)
|
||||
self.set_spacing(12)
|
||||
self.set_column_spacing(18)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _setup_additional_signals(self, grid_icon_single_click,
|
||||
grid_icon_double_click,
|
||||
grid_set_selected_items,
|
||||
grid_on_drag_set,
|
||||
grid_on_drag_data_received,
|
||||
grid_on_drag_motion):
|
||||
|
||||
self.connect("button_release_event", grid_icon_single_click)
|
||||
self.connect("item-activated", grid_icon_double_click)
|
||||
self.connect("selection-changed", grid_set_selected_items)
|
||||
self.connect("drag-data-get", grid_on_drag_set)
|
||||
self.connect("drag-data-received", grid_on_drag_data_received)
|
||||
self.connect("drag-motion", grid_on_drag_motion)
|
||||
|
||||
def _load_widgets(self):
|
||||
self._store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
|
||||
self.set_model(self._store)
|
||||
|
||||
def _set_up_dnd(self):
|
||||
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
|
||||
self.enable_model_drag_dest(targets, action)
|
||||
self.enable_model_drag_source(0, targets, action)
|
@@ -0,0 +1,83 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GdkPixbuf
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IconTreeWidget(Gtk.TreeView):
|
||||
"""docstring for IconTreeWidget"""
|
||||
|
||||
def __init__(self):
|
||||
super(IconTreeWidget, self).__init__()
|
||||
|
||||
self._store = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._set_up_dnd()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
def get_store(self):
|
||||
return self._store
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_search_column(1)
|
||||
self.set_rubber_banding(True)
|
||||
self.set_headers_visible(False)
|
||||
self.set_enable_tree_lines(False)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _setup_additional_signals(self, grid_icon_single_click,
|
||||
grid_icon_double_click,
|
||||
grid_on_drag_set,
|
||||
grid_on_drag_data_received,
|
||||
grid_on_drag_motion):
|
||||
|
||||
self.connect("button_release_event", self.grid_icon_single_click)
|
||||
self.connect("row-activated", self.grid_icon_double_click)
|
||||
self.connect("drag-data-get", self.grid_on_drag_set)
|
||||
self.connect("drag-data-received", self.grid_on_drag_data_received)
|
||||
self.connect("drag-motion", self.grid_on_drag_motion)
|
||||
|
||||
def _load_widgets(self):
|
||||
self._store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
|
||||
column = Gtk.TreeViewColumn("Icons")
|
||||
icon = Gtk.CellRendererPixbuf()
|
||||
name = Gtk.CellRendererText()
|
||||
selec = self.get_selection()
|
||||
|
||||
self.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)
|
||||
|
||||
self.append_column(column)
|
||||
|
||||
|
||||
def _set_up_dnd(self):
|
||||
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
|
||||
self.enable_model_drag_dest(targets, action)
|
||||
self.enable_model_drag_source(0, targets, action)
|
81
src/versions/solarfm-0.0.1/solarfm/core/widgets/io_widget.py
Normal file
81
src/versions/solarfm-0.0.1/solarfm/core/widgets/io_widget.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IOWidget(Gtk.Box):
|
||||
"""docstring for IOWidget"""
|
||||
|
||||
def __init__(self, action, file):
|
||||
super(IOWidget, self).__init__()
|
||||
self._action = action
|
||||
self._file = file
|
||||
self._basename = self._file.get_basename()
|
||||
|
||||
self.cancle_eve = Gio.Cancellable.new()
|
||||
self.progress = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_orientation(1)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
stats = Gtk.Box()
|
||||
label = Gtk.Label()
|
||||
cncl_button = Gtk.Button(label="Cancel")
|
||||
del_button = Gtk.Button(label="Clear")
|
||||
self.progress = Gtk.ProgressBar()
|
||||
|
||||
label.set_label(self._basename)
|
||||
self.progress.set_show_text(True)
|
||||
self.progress.set_text(f"{self._action.upper()}ING")
|
||||
stats.set_orientation(0)
|
||||
|
||||
stats.pack_end(del_button, False, False, 5)
|
||||
del_button.connect("clicked", self.delete_self, ())
|
||||
|
||||
if not self._action in ("create", "rename"):
|
||||
stats.pack_end(cncl_button, False, False, 5)
|
||||
cncl_button.connect("clicked", self.do_cancel, *(self, self.cancle_eve))
|
||||
|
||||
stats.add(self.progress)
|
||||
self.add(label)
|
||||
self.add(stats)
|
||||
|
||||
def do_cancel(self, widget, container, eve):
|
||||
logger.info(f"Canceling: [{self._action}] of {self._basename} ...")
|
||||
eve.cancel()
|
||||
|
||||
def update_progress(self, current, total, eve=None):
|
||||
self.progress.set_fraction(current/total)
|
||||
|
||||
def finish_callback(self, file, task=None, eve=None):
|
||||
if self._action == "move" or self._action == "rename":
|
||||
status = self._file.move_finish(task)
|
||||
if self._action == "copy":
|
||||
status = self._file.copy_finish(task)
|
||||
|
||||
if status:
|
||||
self.delete_self()
|
||||
else:
|
||||
logger.info(f"{self._action} of {self._basename} failed...")
|
||||
|
||||
def delete_self(self, widget=None, eve=None):
|
||||
self.get_parent().remove(self)
|
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Popups module
|
||||
"""
|
@@ -0,0 +1,46 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class IOPopupWidget(Gtk.Popover):
|
||||
"""docstring for IOPopupWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(IOPopupWidget, self).__init__()
|
||||
self._builder = settings.get_builder()
|
||||
|
||||
self._builder.expose_object(f"io_popup", self)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
io_button = self._builder.get_object(f"io_button")
|
||||
self.set_relative_to(io_button)
|
||||
self.set_modal(True)
|
||||
self.set_position(Gtk.PositionType.BOTTOM)
|
||||
self.set_size_request(320, 280)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_io_popup", self.show_io_popup)
|
||||
|
||||
def _load_widgets(self):
|
||||
vbox = Gtk.Box()
|
||||
|
||||
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self._builder.expose_object(f"io_list", vbox)
|
||||
self.add(vbox)
|
||||
|
||||
def show_io_popup(self, widget=None, eve=None):
|
||||
self.popup()
|
@@ -0,0 +1,112 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
import traceback
|
||||
import time
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class MessagePopupWidget(Gtk.Popover):
|
||||
""" MessagePopupWidget custom exception hook viewer to reroute to log Gtk text area too. """
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super(MessagePopupWidget, self).__init__()
|
||||
self.builder = settings.get_builder()
|
||||
|
||||
self.builder.expose_object(f"message_popup_widget", self)
|
||||
self._message_buffer = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_relative_to( self.builder.get_object(f"main_menu_bar") )
|
||||
self.set_modal(True)
|
||||
self.set_position(Gtk.PositionType.BOTTOM)
|
||||
self.set_size_request(620, 580)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_messages_popup", self.show_messages_popup)
|
||||
event_system.subscribe("hide_messages_popup", self.hide_messages_popup)
|
||||
|
||||
|
||||
def _load_widgets(self):
|
||||
self._message_buffer = Gtk.TextBuffer()
|
||||
vbox = Gtk.Box()
|
||||
scroll_window = Gtk.ScrolledWindow()
|
||||
message_text_view = Gtk.TextView.new_with_buffer(self._message_buffer)
|
||||
|
||||
button = Gtk.Button(label="Save As")
|
||||
button.set_image( Gtk.Image(stock=Gtk.STOCK_SAVE_AS) )
|
||||
button.connect("released", self.save_debug_alerts)
|
||||
button.set_always_show_image(True)
|
||||
|
||||
vbox.set_vexpand(True)
|
||||
vbox.set_hexpand(True)
|
||||
scroll_window.set_vexpand(True)
|
||||
scroll_window.set_hexpand(True)
|
||||
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.builder.expose_object(f"message_popup_widget", self)
|
||||
self.builder.expose_object(f"message_text_view", message_text_view)
|
||||
|
||||
vbox.add(button)
|
||||
scroll_window.add(message_text_view)
|
||||
vbox.add(scroll_window)
|
||||
self.add(vbox)
|
||||
|
||||
def show_messages_popup(self):
|
||||
self.popup()
|
||||
|
||||
def hide_messages_popup(self):
|
||||
self.popup()
|
||||
|
||||
|
||||
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(settings.get_error_color(), data)
|
||||
|
||||
def display_message(self, type, text, seconds=None):
|
||||
self._message_buffer.insert_at_cursor(text)
|
||||
self.popup()
|
||||
if seconds:
|
||||
self.hide_message_timeout(seconds)
|
||||
|
||||
@threaded
|
||||
def hide_message_timeout(self, seconds=3):
|
||||
time.sleep(seconds)
|
||||
GLib.idle_add(event_system.emit, ("hide_messages_popup"))
|
||||
|
||||
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", settings.get_main_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()
|
@@ -0,0 +1,57 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class PathMenuPopupWidget(Gtk.Popover):
|
||||
"""docstring for PathMenuPopupWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(PathMenuPopupWidget, self).__init__()
|
||||
self.builder = settings.get_builder()
|
||||
|
||||
self.builder.expose_object(f"path_menu", self)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
path_entry = self.builder.get_object(f"path_entry")
|
||||
self.set_relative_to(path_entry)
|
||||
self.set_modal(False)
|
||||
self.set_position(Gtk.PositionType.BOTTOM)
|
||||
self.set_size_request(240, 420)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_path_menu", self.show_path_menu)
|
||||
event_system.subscribe("hide_path_menu", self.hide_path_menu)
|
||||
|
||||
def _load_widgets(self):
|
||||
path_menu_buttons = Gtk.ButtonBox()
|
||||
scroll_window = Gtk.ScrolledWindow()
|
||||
view_port = Gtk.Viewport()
|
||||
|
||||
scroll_window.set_vexpand(True)
|
||||
scroll_window.set_hexpand(True)
|
||||
path_menu_buttons.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.builder.expose_object(f"path_menu_buttons", path_menu_buttons)
|
||||
view_port.add(path_menu_buttons)
|
||||
scroll_window.add(view_port)
|
||||
scroll_window.show_all()
|
||||
self.add(scroll_window)
|
||||
|
||||
def show_path_menu(self, widget=None, eve=None):
|
||||
self.popup()
|
||||
|
||||
def hide_path_menu(self, widget=None, eve=None):
|
||||
self.popdown()
|
@@ -0,0 +1,50 @@
|
||||
# Python imports
|
||||
import inspect
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class PluginsPopupWidget(Gtk.Popover):
|
||||
"""docstring for PluginsPopupWidget."""
|
||||
|
||||
def __init__(self):
|
||||
super(PluginsPopupWidget, self).__init__()
|
||||
self.builder = settings.get_builder()
|
||||
|
||||
self.builder.expose_object(f"plugin_controls", self)
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
plugins_button = self.builder.get_object(f"plugins_button")
|
||||
self.set_relative_to(plugins_button)
|
||||
self.set_modal(True)
|
||||
self.set_position(Gtk.PositionType.BOTTOM)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_plugins_popup", self.show_plugins_popup)
|
||||
self.connect("button-release-event", self.hide_plugins_popup)
|
||||
self.connect("key-release-event", self.hide_plugins_popup)
|
||||
|
||||
def _load_widgets(self):
|
||||
vbox = Gtk.Box()
|
||||
|
||||
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.builder.expose_object(f"plugin_control_list", vbox)
|
||||
self.add(vbox)
|
||||
|
||||
def show_plugins_popup(self, widget=None, eve=None):
|
||||
self.popup()
|
||||
|
||||
def hide_plugins_popup(self, widget=None, eve=None):
|
||||
self.hide()
|
@@ -0,0 +1,50 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
|
||||
class TabHeaderWidget(Gtk.ButtonBox):
|
||||
"""docstring for TabHeaderWidget"""
|
||||
|
||||
def __init__(self, tab, close_tab):
|
||||
super(TabHeaderWidget, self).__init__()
|
||||
self._tab = tab
|
||||
self._close_tab = close_tab # NOTE: Close method in tab_mixin
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_orientation(0)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
label = Gtk.Label()
|
||||
tid = Gtk.Label()
|
||||
close = Gtk.Button()
|
||||
icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
|
||||
|
||||
label.set_label(f"{self._tab.get_end_of_path()}")
|
||||
label.set_width_chars(len(self._tab.get_end_of_path()))
|
||||
label.set_xalign(0.0)
|
||||
tid.set_label(f"{self._tab.get_id()}")
|
||||
|
||||
close.connect("released", self._close_tab)
|
||||
|
||||
close.add(icon)
|
||||
self.add(label)
|
||||
self.add(close)
|
||||
self.add(tid)
|
||||
|
||||
self.show_all()
|
||||
tid.hide()
|
95
src/versions/solarfm-0.0.1/solarfm/core/window.py
Normal file
95
src/versions/solarfm-0.0.1/solarfm/core/window.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# Python imports
|
||||
import time
|
||||
import signal
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
import cairo
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from core.controller import Controller
|
||||
|
||||
|
||||
|
||||
|
||||
class Window(Gtk.ApplicationWindow):
|
||||
"""docstring for Window."""
|
||||
|
||||
def __init__(self, args, unknownargs):
|
||||
super(Window, self).__init__()
|
||||
|
||||
self._controller = None
|
||||
settings.set_main_window(self)
|
||||
|
||||
self._set_window_data()
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
|
||||
self._load_widgets(args, unknownargs)
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_default_size(1670, 830)
|
||||
self.set_title(f"{app_name}")
|
||||
self.set_icon_from_file( settings.get_window_icon() )
|
||||
self.set_gravity(5) # 5 = CENTER
|
||||
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("delete-event", self._tear_down)
|
||||
|
||||
# self.connect("focus-out-event", self._controller.unset_keys_and_data)
|
||||
# self.connect("key-press-event", self._controller.on_global_key_press_controller)
|
||||
# self.connect("key-release-event", self._controller.on_global_key_release_controller)
|
||||
|
||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("tear_down", self._tear_down)
|
||||
|
||||
def _load_widgets(self, args, unknownargs):
|
||||
self._controller = Controller(args, unknownargs)
|
||||
|
||||
if not self._controller:
|
||||
raise ControllerStartException("Controller exited and doesn't exist...")
|
||||
|
||||
self.add( self._controller.get_core_widget() )
|
||||
|
||||
def _set_window_data(self) -> None:
|
||||
screen = self.get_screen()
|
||||
visual = screen.get_rgba_visual()
|
||||
|
||||
if visual != None and screen.is_composited():
|
||||
self.set_visual(visual)
|
||||
self.set_app_paintable(True)
|
||||
self.connect("draw", self._area_draw)
|
||||
|
||||
# bind css file
|
||||
cssProvider = Gtk.CssProvider()
|
||||
cssProvider.load_from_path( settings.get_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 _tear_down(self, widget=None, eve=None):
|
||||
if not settings.is_trace_debug():
|
||||
self._controller.fm_controller.save_state()
|
||||
|
||||
settings.clear_pid()
|
||||
time.sleep(event_sleep_time)
|
||||
Gtk.main_quit()
|
Reference in New Issue
Block a user