feat: Complete plugin lifecycle management with lazy loading and runtime reload
Major changes: - Add unload() method to all plugins for proper cleanup (unregister commands/providers/LSP clients, destroy widgets, clear state) - Implement lazy widget loading via "show" signal across all containers - Add autoload: false manifest option for manual/conditional plugin loading - Add Plugins UI with runtime load/unload toggle via Ctrl+Shift+p - Implement controller unregistration system with proper signal disconnection - Add new events: UnregisterCommandEvent, GetFilesEvent, GetSourceViewsEvent, TogglePluginsUiEvent - Fix signal leaks by tracking and disconnecting handlers in widgets (search/replace, LSP manager, tabs, telescope, markdown preview) - Add Save/Save As to tabs context menu - Improve search/replace behavior (selection handling, focus management) - Add telescope file initialization from existing loaded files - Refactor plugin reload watcher to dynamically add/remove plugins on filesystem changes - Add new plugins: file_history, extend_source_view_menu, godot_lsp_client - Fix bug in prettify_json (undefined variable reference
This commit is contained in:
@@ -19,7 +19,6 @@ class BaseContainer(Gtk.Box):
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -32,12 +31,16 @@ class BaseContainer(Gtk.Box):
|
||||
self._update_transparency()
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("update-transparency", self._update_transparency)
|
||||
event_system.subscribe("remove-transparency", self._remove_transparency)
|
||||
|
||||
def _handle_show(self, widget):
|
||||
self.disconnect_by_func( self._handle_show )
|
||||
self._load_widgets()
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("base-container", self)
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ class BodyContainer(Gtk.Box):
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -29,14 +28,17 @@ class BodyContainer(Gtk.Box):
|
||||
self.ctx.add_class("body-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
self.set_homogeneous(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _handle_show(self, widget):
|
||||
self.disconnect_by_func( self._handle_show )
|
||||
self._load_widgets()
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("body-container", self)
|
||||
|
||||
|
||||
@@ -14,11 +14,9 @@ class CenterContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(CenterContainer, self).__init__()
|
||||
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -32,11 +30,15 @@ class CenterContainer(Gtk.Box):
|
||||
self.set_vexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _handle_show(self, widget):
|
||||
self.disconnect_by_func( self._handle_show )
|
||||
self._load_widgets()
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("center-container", self)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
#from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
|
||||
@@ -31,7 +31,7 @@ class EditorsContainer(Gtk.Paned):
|
||||
self.set_wide_handle(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.map_id = self.connect("map", self._init_map)
|
||||
self.connect("map", self._init_map)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
@@ -59,13 +59,6 @@ class EditorsContainer(Gtk.Paned):
|
||||
return scrolled_win1, scrolled_win2
|
||||
|
||||
def _init_map(self, view):
|
||||
def _first_show_init():
|
||||
self.disconnect(self.map_id)
|
||||
del self.map_id
|
||||
|
||||
self.code_base.first_map_load()
|
||||
|
||||
del self.code_base
|
||||
return False
|
||||
|
||||
GLib.timeout_add(100, _first_show_init)
|
||||
self.disconnect_by_func( self._init_map )
|
||||
self.code_base.first_map_load()
|
||||
del self.code_base
|
||||
|
||||
@@ -17,7 +17,6 @@ class FooterContainer(Gtk.Box):
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -30,11 +29,15 @@ class FooterContainer(Gtk.Box):
|
||||
self.set_hexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _handle_show(self, widget):
|
||||
self.disconnect_by_func( self._handle_show )
|
||||
self._load_widgets()
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("footer-container", self)
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ class HeaderContainer(Gtk.Box):
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -32,11 +31,15 @@ class HeaderContainer(Gtk.Box):
|
||||
self.set_hexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("tggl-top-main-menubar", self.tggl_top_main_menubar)
|
||||
|
||||
def _handle_show(self, widget):
|
||||
self.disconnect_by_func( self._handle_show )
|
||||
self._load_widgets()
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("header-container", self)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ class LeftContainer(Gtk.Box):
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -31,11 +30,15 @@ class LeftContainer(Gtk.Box):
|
||||
self.set_vexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _handle_show(self, widget):
|
||||
self.disconnect_by_func( self._handle_show )
|
||||
self._load_widgets()
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("left-container", self)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ class RightContainer(Gtk.Box):
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -31,11 +30,15 @@ class RightContainer(Gtk.Box):
|
||||
self.set_vexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _handle_show(self, widget):
|
||||
self.disconnect_by_func( self._handle_show )
|
||||
self._load_widgets()
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("right-container", self)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
|
||||
def __init__(self):
|
||||
|
||||
self._setup_controller_data()
|
||||
self.plugins_controller.manual_launch_plugins()
|
||||
|
||||
self._load_plugins(is_pre = True)
|
||||
self._setup_styling()
|
||||
@@ -31,6 +32,7 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
|
||||
self._subscribe_to_events()
|
||||
self._load_controllers()
|
||||
self._load_plugins(is_pre = False)
|
||||
|
||||
self._load_files()
|
||||
|
||||
logger.info(f"Made it past {self.__class__} loading...")
|
||||
@@ -43,7 +45,6 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
|
||||
self.base_container = BaseContainer()
|
||||
self.plugins_controller = plugins_controller
|
||||
|
||||
widget_registery.expose_object("main_window", self.window)
|
||||
settings_manager.register_signals_to_builder([self, self.base_container])
|
||||
|
||||
self._collect_files_dirs()
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
from ..mixins.command_system_mixin import CommandSystemMixin
|
||||
from ..source_view import SourceView
|
||||
|
||||
from . import commands
|
||||
|
||||
|
||||
|
||||
class CommandSystem:
|
||||
class CommandSystem(CommandSystemMixin):
|
||||
def __init__(self):
|
||||
super(CommandSystem, self).__init__()
|
||||
|
||||
@@ -37,6 +38,10 @@ class CommandSystem:
|
||||
def add_command(self, command_name: str, command: callable):
|
||||
setattr(commands, command_name, command)
|
||||
|
||||
def remove_command(self, command_name: str, command: callable):
|
||||
if hasattr(commands, command_name):
|
||||
delattr(commands, command_name)
|
||||
|
||||
|
||||
def emit(self, event: Code_Event_Types.CodeEvent):
|
||||
""" Monkey patch 'emit' from command controller... """
|
||||
@@ -47,69 +52,69 @@ class CommandSystem:
|
||||
...
|
||||
|
||||
|
||||
def filter_out_loaded_files(self, uris: list[str]):
|
||||
event = Event_Factory.create_event(
|
||||
"filter_out_loaded_files",
|
||||
uris = uris
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def set_info_labels(self, data: tuple[str]):
|
||||
event = Event_Factory.create_event(
|
||||
"set_info_labels",
|
||||
info = data
|
||||
)
|
||||
|
||||
self.emit_to("plugins", event)
|
||||
|
||||
def get_file(self, view: SourceView):
|
||||
event = Event_Factory.create_event(
|
||||
"get_file",
|
||||
view = view,
|
||||
buffer = view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def get_swap_file(self, view: SourceView):
|
||||
event = Event_Factory.create_event(
|
||||
"get_swap_file",
|
||||
view = view,
|
||||
buffer = view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def new_file(self, view: SourceView):
|
||||
event = Event_Factory.create_event("add_new_file", view = view)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def remove_file(self, view: SourceView):
|
||||
event = Event_Factory.create_event(
|
||||
"remove_file",
|
||||
view = view,
|
||||
buffer = view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def request_completion(self, view: SourceView):
|
||||
event = Event_Factory.create_event(
|
||||
"request_completion",
|
||||
view = view,
|
||||
buffer = view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("completion", event)
|
||||
# def filter_out_loaded_files(self, uris: list[str]):
|
||||
# event = Event_Factory.create_event(
|
||||
# "filter_out_loaded_files",
|
||||
# uris = uris
|
||||
# )
|
||||
#
|
||||
# self.emit_to("files", event)
|
||||
#
|
||||
# return event.response
|
||||
#
|
||||
# def set_info_labels(self, data: tuple[str]):
|
||||
# event = Event_Factory.create_event(
|
||||
# "set_info_labels",
|
||||
# info = data
|
||||
# )
|
||||
#
|
||||
# self.emit_to("plugins", event)
|
||||
#
|
||||
# def get_file(self, view: SourceView):
|
||||
# event = Event_Factory.create_event(
|
||||
# "get_file",
|
||||
# view = view,
|
||||
# buffer = view.get_buffer()
|
||||
# )
|
||||
#
|
||||
# self.emit_to("files", event)
|
||||
#
|
||||
# return event.response
|
||||
#
|
||||
# def get_swap_file(self, view: SourceView):
|
||||
# event = Event_Factory.create_event(
|
||||
# "get_swap_file",
|
||||
# view = view,
|
||||
# buffer = view.get_buffer()
|
||||
# )
|
||||
#
|
||||
# self.emit_to("files", event)
|
||||
#
|
||||
# return event.response
|
||||
#
|
||||
# def new_file(self, view: SourceView):
|
||||
# event = Event_Factory.create_event("add_new_file", view = view)
|
||||
#
|
||||
# self.emit_to("files", event)
|
||||
#
|
||||
# return event.response
|
||||
#
|
||||
# def remove_file(self, view: SourceView):
|
||||
# event = Event_Factory.create_event(
|
||||
# "remove_file",
|
||||
# view = view,
|
||||
# buffer = view.get_buffer()
|
||||
# )
|
||||
#
|
||||
# self.emit_to("files", event)
|
||||
#
|
||||
# return event.response
|
||||
#
|
||||
# def request_completion(self, view: SourceView):
|
||||
# event = Event_Factory.create_event(
|
||||
# "request_completion",
|
||||
# view = view,
|
||||
# buffer = view.get_buffer()
|
||||
# )
|
||||
#
|
||||
# self.emit_to("completion", event)
|
||||
@@ -0,0 +1,21 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
logger.debug("Command: Toggle Plugins UI")
|
||||
view.command.toggle_plugins_ui()
|
||||
@@ -28,6 +28,8 @@ class FilesController(ControllerBase, list):
|
||||
self.remove_file(event)
|
||||
elif isinstance(event, Code_Event_Types.GetFileEvent):
|
||||
self.get_file(event)
|
||||
elif isinstance(event, Code_Event_Types.GetFilesEvent):
|
||||
event.response = self
|
||||
elif isinstance(event, Code_Event_Types.GetSwapFileEvent):
|
||||
self.get_swap_file(event)
|
||||
|
||||
|
||||
@@ -29,7 +29,11 @@ class SourceViewSignalMapper:
|
||||
def connect_signals(self, source_view: SourceView):
|
||||
signal_mappings = self._get_signal_mappings()
|
||||
for signal, handler in signal_mappings.items():
|
||||
source_view.connect(signal, handler)
|
||||
if not signal == "populate-popup":
|
||||
source_view.connect(signal, handler)
|
||||
continue
|
||||
|
||||
source_view.connect_after(signal, handler)
|
||||
|
||||
def disconnect_signals(self, source_view: SourceView):
|
||||
signal_mappings = self._get_signal_mappings()
|
||||
|
||||
@@ -33,11 +33,15 @@ class SourceViewsController(ControllerBase, list):
|
||||
self._remove_file(event)
|
||||
elif isinstance(event, Code_Event_Types.RegisterCommandEvent):
|
||||
self._register_command(event)
|
||||
elif isinstance(event, Code_Event_Types.UnregisterCommandEvent):
|
||||
self._unregister_command(event)
|
||||
|
||||
if not self.signal_mapper.active_view: return
|
||||
|
||||
if isinstance(event, Code_Event_Types.GetActiveViewEvent):
|
||||
event.response = self.signal_mapper.active_view
|
||||
elif isinstance(event, Code_Event_Types.GetSourceViewsEvent):
|
||||
event.response = self
|
||||
elif isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||
self.signal_mapper.active_view.command.exec("update_info_bar")
|
||||
elif isinstance(event, Code_Event_Types.SetActiveFileEvent):
|
||||
@@ -63,6 +67,24 @@ class SourceViewsController(ControllerBase, list):
|
||||
event.command
|
||||
)
|
||||
|
||||
def _unregister_command(self, event: Code_Event_Types.UnregisterCommandEvent):
|
||||
if not isinstance(event.binding, list):
|
||||
event.binding = [ event.binding ]
|
||||
|
||||
for binding in event.binding:
|
||||
self.state_manager.key_mapper.unmap_command(
|
||||
event.command_name,
|
||||
{
|
||||
f"{event.binding_mode}": binding
|
||||
}
|
||||
)
|
||||
|
||||
for view in self:
|
||||
view.command.remove_command(
|
||||
event.command_name,
|
||||
event.command
|
||||
)
|
||||
|
||||
def _get_command_system(self):
|
||||
event = Event_Factory.create_event("get_new_command_system")
|
||||
self.message_to("commands", event)
|
||||
|
||||
@@ -95,6 +95,28 @@ class KeyMapper:
|
||||
|
||||
getattr(self.states[state], press_state)[keyname] = command
|
||||
|
||||
def unmap_command(self, command, entry):
|
||||
press_state = "held" if "held" in entry else "released"
|
||||
keyname = entry[press_state]
|
||||
|
||||
state = NoKeyState
|
||||
if "<Control>" in keyname:
|
||||
state = state | CtrlKeyState
|
||||
if "<Shift>" in keyname:
|
||||
state = state | ShiftKeyState
|
||||
if "<Alt>" in keyname:
|
||||
state = state | AltKeyState
|
||||
|
||||
keyname = keyname.replace("<Control>", "") \
|
||||
.replace("<Shift>", "") \
|
||||
.replace("<Alt>", "") \
|
||||
.lower()
|
||||
|
||||
mapping = getattr(self.states[state], press_state)
|
||||
|
||||
if keyname in mapping and mapping[keyname] == command:
|
||||
del mapping[keyname]
|
||||
|
||||
def _key_press_event(self, eve):
|
||||
keyname = self.get_keyname(eve)
|
||||
char_str = self.get_char(eve)
|
||||
|
||||
83
src/core/widgets/code/mixins/command_system_mixin.py
Normal file
83
src/core/widgets/code/mixins/command_system_mixin.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
from ..source_view import SourceView
|
||||
|
||||
|
||||
|
||||
class CommandSystemMixin:
|
||||
def toggle_plugins_ui(self):
|
||||
event = Event_Factory.create_event( "toggle_plugins_ui" )
|
||||
|
||||
self.emit_to("plugins", event)
|
||||
|
||||
def filter_out_loaded_files(self, uris: list[str]):
|
||||
event = Event_Factory.create_event(
|
||||
"filter_out_loaded_files",
|
||||
uris = uris
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def set_info_labels(self, data: tuple[str]):
|
||||
event = Event_Factory.create_event(
|
||||
"set_info_labels",
|
||||
info = data
|
||||
)
|
||||
|
||||
self.emit_to("plugins", event)
|
||||
|
||||
def get_file(self, view: SourceView):
|
||||
event = Event_Factory.create_event(
|
||||
"get_file",
|
||||
view = view,
|
||||
buffer = view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def get_swap_file(self, view: SourceView):
|
||||
event = Event_Factory.create_event(
|
||||
"get_swap_file",
|
||||
view = view,
|
||||
buffer = view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def new_file(self, view: SourceView):
|
||||
event = Event_Factory.create_event("add_new_file", view = view)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def remove_file(self, view: SourceView):
|
||||
event = Event_Factory.create_event(
|
||||
"remove_file",
|
||||
view = view,
|
||||
buffer = view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
return event.response
|
||||
|
||||
def request_completion(self, view: SourceView):
|
||||
event = Event_Factory.create_event(
|
||||
"request_completion",
|
||||
view = view,
|
||||
buffer = view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("completion", event)
|
||||
@@ -42,7 +42,6 @@ class Window(Gtk.ApplicationWindow):
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self._set_window_data()
|
||||
self._set_size_constraints()
|
||||
@@ -67,6 +66,7 @@ class Window(Gtk.ApplicationWindow):
|
||||
def _setup_signals(self):
|
||||
self.connect("focus-in-event", self._on_focus_in_event)
|
||||
self.connect("focus-out-event", self._on_focus_out_event)
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
self.connect("delete-event", self.stop)
|
||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.stop)
|
||||
@@ -75,6 +75,10 @@ class Window(Gtk.ApplicationWindow):
|
||||
event_system.subscribe("tear-down", self.stop)
|
||||
event_system.subscribe("load-interactive-debug", self._load_interactive_debug)
|
||||
|
||||
def _handle_show(self, widget):
|
||||
self.disconnect_by_func( self._handle_show )
|
||||
self._load_widgets()
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("main-window", self)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from ..singleton_raised import SingletonRaised
|
||||
from ..singleton import Singleton
|
||||
|
||||
from ..dto.base_event import BaseEvent
|
||||
|
||||
@@ -17,7 +17,7 @@ class ControllerBaseException(Exception):
|
||||
|
||||
|
||||
|
||||
class ControllerBase(SingletonRaised, EmitDispatcher):
|
||||
class ControllerBase(Singleton, EmitDispatcher):
|
||||
def __init__(self):
|
||||
super(ControllerBase, self).__init__()
|
||||
|
||||
@@ -42,3 +42,6 @@ class ControllerBase(SingletonRaised, EmitDispatcher):
|
||||
|
||||
def register_controller(self, name: str, controller):
|
||||
self.controller_message_bus.register_controller(name, controller)
|
||||
|
||||
def unregister_controller(self, name: str):
|
||||
self.controller_message_bus.unregister_controller(name)
|
||||
|
||||
@@ -31,10 +31,11 @@ class ControllerManager(Singleton, dict):
|
||||
|
||||
|
||||
def _crete_controller_message_bus(self) -> ControllerMessageBus:
|
||||
controller_message_bus = ControllerMessageBus()
|
||||
controller_message_bus.message_to = self.message_to
|
||||
controller_message_bus.message = self.message
|
||||
controller_message_bus.register_controller = self.register_controller
|
||||
controller_message_bus = ControllerMessageBus()
|
||||
controller_message_bus.message_to = self.message_to
|
||||
controller_message_bus.message = self.message
|
||||
controller_message_bus.register_controller = self.register_controller
|
||||
controller_message_bus.unregister_controller = self.unregister_controller
|
||||
|
||||
return controller_message_bus
|
||||
|
||||
@@ -51,6 +52,17 @@ class ControllerManager(Singleton, dict):
|
||||
|
||||
self[name] = controller
|
||||
|
||||
def unregister_controller(self, name: str):
|
||||
if not name:
|
||||
raise ControllerManagerException("Must pass in a 'name'...")
|
||||
|
||||
if not name in self.keys():
|
||||
raise ControllerManagerException(
|
||||
f"Can't find controller registered with name of '{name}'..."
|
||||
)
|
||||
|
||||
self.pop(name, None)
|
||||
|
||||
def get_controllers_key_list(self) -> list[str]:
|
||||
return self.keys()
|
||||
|
||||
|
||||
@@ -28,3 +28,6 @@ class ControllerMessageBus:
|
||||
|
||||
def register_controller(self, name: str, controller):
|
||||
raise ControllerMessageBusException("Controller Message Bus 'register_controller' must be overriden by Controller Manager...")
|
||||
|
||||
def unregister_controller(self, name: str):
|
||||
raise ControllerMessageBusException("Controller Message Bus 'unregister_controller' must be overriden by Controller Manager...")
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
|
||||
|
||||
from .code_event import CodeEvent
|
||||
from .toggle_plugins_ui_event import TogglePluginsUiEvent
|
||||
from .create_source_view_event import CreateSourceViewEvent
|
||||
from .register_completer_event import RegisterCompleterEvent
|
||||
from .unregister_completer_event import UnregisterCompleterEvent
|
||||
from .register_provider_event import RegisterProviderEvent
|
||||
from .unregister_provider_event import UnregisterProviderEvent
|
||||
from .register_command_event import RegisterCommandEvent
|
||||
from .unregister_command_event import UnregisterCommandEvent
|
||||
from .file_externally_modified_event import FileExternallyModifiedEvent
|
||||
from .file_externally_deleted_event import FileExternallyDeletedEvent
|
||||
from .set_info_labels_event import SetInfoLabelsEvent
|
||||
from .populate_source_view_popup_event import PopulateSourceViewPopupEvent
|
||||
from .filter_out_loaded_files_event import FilterOutLoadedFilesEvent
|
||||
from .get_active_view_event import GetActiveViewEvent
|
||||
from .get_source_views_event import GetSourceViewsEvent
|
||||
|
||||
from .get_new_command_system_event import GetNewCommandSystemEvent
|
||||
from .request_completion_event import RequestCompletionEvent
|
||||
@@ -34,6 +37,7 @@ from .removed_file_event import RemovedFileEvent
|
||||
from .saved_file_event import SavedFileEvent
|
||||
|
||||
from .get_file_event import GetFileEvent
|
||||
from .get_files_event import GetFilesEvent
|
||||
from .get_swap_file_event import GetSwapFileEvent
|
||||
from .add_new_file_event import AddNewFileEvent
|
||||
from .pop_file_event import PopFileEvent
|
||||
|
||||
13
src/libs/dto/code/events/get_files_event.py
Normal file
13
src/libs/dto/code/events/get_files_event.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class GetFilesEvent(CodeEvent):
|
||||
...
|
||||
13
src/libs/dto/code/events/get_source_views_event.py
Normal file
13
src/libs/dto/code/events/get_source_views_event.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class GetSourceViewsEvent(CodeEvent):
|
||||
...
|
||||
17
src/libs/dto/code/events/toggle_plugins_ui_event.py
Normal file
17
src/libs/dto/code/events/toggle_plugins_ui_event.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class TogglePluginsUiEvent(CodeEvent):
|
||||
...
|
||||
20
src/libs/dto/code/events/unregister_command_event.py
Normal file
20
src/libs/dto/code/events/unregister_command_event.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class UnregisterCommandEvent(CodeEvent):
|
||||
command_name: str = ""
|
||||
command: callable = None
|
||||
binding_mode: str = ""
|
||||
binding: str or list = ""
|
||||
@@ -17,6 +17,7 @@ class Manifest:
|
||||
version: str = "0.0.1"
|
||||
support: str = "support@mail.com"
|
||||
pre_launch: bool = False
|
||||
autoload: bool = True
|
||||
requests: Requests = field(default_factory = lambda: Requests())
|
||||
|
||||
def __post_init__(self):
|
||||
|
||||
@@ -36,6 +36,19 @@ class EventFactory(Singleton):
|
||||
|
||||
logger.debug(f"Registered {i} event types:")
|
||||
|
||||
def unregister_events(self, events: dict):
|
||||
i = 0
|
||||
for name, obj in events:
|
||||
if not self._is_valid_event_class(obj): continue
|
||||
|
||||
event_type = self._class_name_to_event_type(name)
|
||||
|
||||
del self._event_classes[event_type]
|
||||
Code_Event_Types.remove_event_class(name)
|
||||
i += 1
|
||||
|
||||
logger.debug(f"Unregistered {i} event types:")
|
||||
|
||||
def create_event(self, event_type: str, **kwargs) -> BaseEvent:
|
||||
if event_type not in self._event_classes:
|
||||
raise ValueError(f"Unknown event type: {event_type}")
|
||||
@@ -80,6 +93,9 @@ class EventNamespace:
|
||||
def add_event_class(self, name: str, event_class: Type[BaseEvent]):
|
||||
setattr(self, name, event_class)
|
||||
|
||||
def remove_event_class(self, name: str):
|
||||
delattr(self, name)
|
||||
|
||||
|
||||
|
||||
Code_Event_Types = EventNamespace()
|
||||
|
||||
@@ -12,21 +12,21 @@ class SingletonError(Exception):
|
||||
|
||||
|
||||
|
||||
T = TypeVar('T', bound='Singleton')
|
||||
T = TypeVar('T', bound = 'Singleton')
|
||||
|
||||
|
||||
|
||||
class Singleton:
|
||||
__instance = None
|
||||
_instances = {}
|
||||
|
||||
def __new__(cls: Type[T], *args: Any, **kwargs: Any) -> T:
|
||||
if cls.__instance is not None:
|
||||
logger.debug(f"'{cls.__name__}' is a Singleton. Returning instance...")
|
||||
return cls.__instance
|
||||
if cls in cls._instances: return cls._instances[cls]
|
||||
|
||||
cls.__instance = super(Singleton, cls).__new__(cls)
|
||||
return cls.__instance
|
||||
instance = super().__new__(cls)
|
||||
cls._instances[cls] = instance
|
||||
return instance
|
||||
|
||||
def __init__(self) -> None:
|
||||
if self.__instance is not None:
|
||||
return
|
||||
|
||||
super(Singleton, self).__init__()
|
||||
@classmethod
|
||||
def destroy(cls):
|
||||
if cls in cls._instances:
|
||||
del cls._instances[cls]
|
||||
|
||||
@@ -4,25 +4,25 @@ import sys
|
||||
import importlib
|
||||
import traceback
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from os.path import join
|
||||
from os.path import isdir
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
from libs.controllers.controller_base import ControllerBase
|
||||
|
||||
from libs.dto.plugins.manifest_meta import ManifestMeta
|
||||
|
||||
from libs.dto.base_event import BaseEvent
|
||||
|
||||
from .manifest_manager import ManifestManager
|
||||
from .plugins_controller_mixin import PluginsControllerMixin
|
||||
from .plugin_reload_mixin import PluginReloadMixin
|
||||
from .plugin_context import PluginContext
|
||||
from .plugins_ui import PluginsUI
|
||||
|
||||
|
||||
|
||||
@@ -40,11 +40,12 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
|
||||
# path = os.path.dirname(os.path.realpath(__file__))
|
||||
# sys.path.insert(0, path) # NOTE: I think I'm not using this correctly...
|
||||
|
||||
self._plugin_collection: list = []
|
||||
|
||||
self._plugins_path: str = settings_manager.path_manager.get_plugins_path()
|
||||
self.plugins_ui: PluginsUI = PluginsUI()
|
||||
self._manifest_manager: ManifestManager = ManifestManager()
|
||||
|
||||
self._plugin_collection: list = []
|
||||
self._plugins_path: str = settings_manager.path_manager.get_plugins_path()
|
||||
|
||||
self._set_plugins_watcher()
|
||||
|
||||
|
||||
@@ -52,6 +53,14 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
|
||||
for manifest_meta in self._plugin_collection:
|
||||
manifest_meta.instance._controller_message(event)
|
||||
|
||||
if isinstance(event, Code_Event_Types.PopulateSourceViewPopupEvent):
|
||||
event.menu.append( Gtk.SeparatorMenuItem() )
|
||||
item = Gtk.MenuItem(label = "Plugins")
|
||||
item.connect("activate", self.toggle_plugins_ui)
|
||||
event.menu.append(item)
|
||||
elif isinstance(event, Code_Event_Types.TogglePluginsUiEvent):
|
||||
self.toggle_plugins_ui()
|
||||
|
||||
def _collect_search_locations(self, path: str, locations: list):
|
||||
locations.append(path)
|
||||
for file in os.listdir(path):
|
||||
@@ -105,33 +114,46 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
|
||||
):
|
||||
if not is_pre_launch:
|
||||
GLib.idle_add(
|
||||
self._run_with_pool, module, manifest_meta
|
||||
self.execute_plugin, module, manifest_meta
|
||||
)
|
||||
return
|
||||
|
||||
self._run_with_pool(module, manifest_meta)
|
||||
self.execute_plugin(module, manifest_meta)
|
||||
|
||||
def _run_with_pool(self, module: type, manifest_meta: ManifestMeta):
|
||||
with ThreadPoolExecutor(max_workers = 1) as executor:
|
||||
future = executor.submit(self.execute_plugin, module, manifest_meta)
|
||||
future.add_done_callback(self._handle_future_exception)
|
||||
|
||||
def _handle_future_exception(self, future):
|
||||
try:
|
||||
future.result()
|
||||
except Exception:
|
||||
logger.exception("Plugin crashed during execution...")
|
||||
|
||||
def pre_launch_plugins(self) -> None:
|
||||
def pre_launch_plugins(self):
|
||||
logger.info(f"Loading pre-launch plugins...")
|
||||
manifest_metas: list = self._manifest_manager.get_pre_launch_plugins()
|
||||
self._load_plugins(manifest_metas, is_pre_launch = True)
|
||||
|
||||
def post_launch_plugins(self) -> None:
|
||||
for manifest_meta in manifest_metas:
|
||||
self.plugins_ui.add_row(manifest_meta, self.toggle_plugin_load_state)
|
||||
|
||||
def post_launch_plugins(self):
|
||||
logger.info(f"Loading post-launch plugins...")
|
||||
manifest_metas: list = self._manifest_manager.get_post_launch_plugins()
|
||||
self._load_plugins(manifest_metas)
|
||||
|
||||
for manifest_meta in manifest_metas:
|
||||
self.plugins_ui.add_row(manifest_meta, self.toggle_plugin_load_state)
|
||||
|
||||
def manual_launch_plugins(self):
|
||||
logger.info(f"Collecting manual-launch plugins...")
|
||||
manifest_metas: list = self._manifest_manager.get_manual_launch_plugins()
|
||||
|
||||
for manifest_meta in manifest_metas:
|
||||
self.plugins_ui.add_row(manifest_meta, self.toggle_plugin_load_state)
|
||||
|
||||
def toggle_plugin_load_state(self, widget, manifest_meta):
|
||||
if manifest_meta.instance:
|
||||
self._plugin_collection.remove(manifest_meta)
|
||||
manifest_meta.instance.unload()
|
||||
manifest_meta.instance = None
|
||||
widget.set_label("Load")
|
||||
return
|
||||
|
||||
self._load_plugins( [manifest_meta] )
|
||||
widget.set_label("Unload")
|
||||
|
||||
def execute_plugin(self, module: type, manifest_meta: ManifestMeta):
|
||||
plugin = module.Plugin()
|
||||
plugin.plugin_context: PluginContext = self.create_plugin_context()
|
||||
@@ -148,15 +170,18 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
|
||||
self._plugin_collection.append(manifest_meta)
|
||||
|
||||
def create_plugin_context(self):
|
||||
plugin_context: PluginContext = PluginContext()
|
||||
plugin_context: PluginContext = PluginContext()
|
||||
|
||||
plugin_context.request_ui_element: callable = self.request_ui_element
|
||||
plugin_context.emit: callable = self.emit
|
||||
plugin_context.emit_to: callable = self.emit_to
|
||||
plugin_context.emit_to_selected: callable = self.emit_to_selected
|
||||
plugin_context.register_controller: callable = self.register_controller
|
||||
plugin_context.request_ui_element: callable = self.request_ui_element
|
||||
plugin_context.emit: callable = self.emit
|
||||
plugin_context.emit_to: callable = self.emit_to
|
||||
plugin_context.emit_to_selected: callable = self.emit_to_selected
|
||||
plugin_context.register_controller: callable = self.register_controller
|
||||
plugin_context.unregister_controller: callable = self.unregister_controller
|
||||
|
||||
return plugin_context
|
||||
|
||||
def toggle_plugins_ui(self, widget = None):
|
||||
self.plugins_ui.hide() if self.plugins_ui.is_visible() else self.plugins_ui.show()
|
||||
|
||||
plugins_controller = PluginsController()
|
||||
|
||||
@@ -19,10 +19,12 @@ class ManifestMapperException(Exception):
|
||||
class ManifestManager:
|
||||
def __init__(self):
|
||||
|
||||
self._plugins_path = settings_manager.path_manager.get_plugins_path()
|
||||
self._plugins_path: str = \
|
||||
settings_manager.path_manager.get_plugins_path()
|
||||
|
||||
self.pre_launch_manifests: list = []
|
||||
self.post_launch_manifests: list = []
|
||||
self.pre_launch_manifests: list = []
|
||||
self.post_launch_manifests: list = []
|
||||
self.manual_launch_manifests: list = []
|
||||
|
||||
self.load_manifests()
|
||||
|
||||
@@ -37,7 +39,7 @@ class ManifestManager:
|
||||
]:
|
||||
self.load(folder, path)
|
||||
|
||||
def load(self, folder, path):
|
||||
def load(self, folder, path) -> ManifestMeta:
|
||||
manifest_pth = join(path, "manifest.json")
|
||||
|
||||
if not os.path.exists(manifest_pth):
|
||||
@@ -52,14 +54,22 @@ class ManifestManager:
|
||||
manifest_meta.path = path
|
||||
manifest_meta.manifest = manifest
|
||||
|
||||
if not manifest.autoload:
|
||||
self.manual_launch_manifests.append(manifest_meta)
|
||||
return
|
||||
|
||||
if manifest.pre_launch:
|
||||
self.pre_launch_manifests.append(manifest_meta)
|
||||
else:
|
||||
self.post_launch_manifests.append(manifest_meta)
|
||||
|
||||
def get_pre_launch_plugins(self) -> dict:
|
||||
return manifest_meta
|
||||
|
||||
def get_pre_launch_plugins(self) -> list:
|
||||
return self.pre_launch_manifests
|
||||
|
||||
def get_post_launch_plugins(self) -> None:
|
||||
def get_post_launch_plugins(self) -> list:
|
||||
return self.post_launch_manifests
|
||||
|
||||
def get_manual_launch_plugins(self) -> list:
|
||||
return self.manual_launch_manifests
|
||||
|
||||
@@ -37,3 +37,6 @@ class PluginContext:
|
||||
def register_controller(self, name: str, controller):
|
||||
raise PluginContextException("Plugin Context 'register_controller' must be overridden...")
|
||||
|
||||
def unregister_controller(self, name: str):
|
||||
raise PluginContextException("Plugin Context 'unregister_controller' must be overridden...")
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ class PluginReloadMixin:
|
||||
_plugins_dir_watcher = None
|
||||
|
||||
def _set_plugins_watcher(self) -> None:
|
||||
self._plugins_dir_watcher = Gio.File.new_for_path(
|
||||
self._plugins_path
|
||||
).monitor_directory(
|
||||
Gio.FileMonitorFlags.WATCH_MOVES,
|
||||
Gio.Cancellable()
|
||||
)
|
||||
self._plugins_dir_watcher = \
|
||||
Gio.File.new_for_path( self._plugins_path ) \
|
||||
.monitor_directory(
|
||||
Gio.FileMonitorFlags.WATCH_MOVES,
|
||||
Gio.Cancellable()
|
||||
)
|
||||
|
||||
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
|
||||
|
||||
@@ -27,10 +27,40 @@ class PluginReloadMixin:
|
||||
eve_type = None,
|
||||
data = None
|
||||
):
|
||||
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
||||
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||
self.reload_plugins(file)
|
||||
if eve_type is Gio.FileMonitorEvent.RENAMED:
|
||||
...
|
||||
|
||||
def reload_plugins(self, file: str = None) -> None:
|
||||
logger.info(f"Reloading plugins... stub.")
|
||||
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.MOVED_IN]:
|
||||
self.add_plugin(file)
|
||||
|
||||
if eve_type in [Gio.FileMonitorEvent.DELETED, Gio.FileMonitorEvent.MOVED_OUT]:
|
||||
self.remove_plugin(file)
|
||||
|
||||
def add_plugin(self, file: str) -> None:
|
||||
logger.info(f"Adding plugin: {file.get_uri()}")
|
||||
uri = file.get_uri()
|
||||
path = uri.replace("file://", "")
|
||||
folder = path.split("/")[-1]
|
||||
manifest_meta = self._manifest_manager.load(folder, path)
|
||||
|
||||
self._load_plugins( [manifest_meta] )
|
||||
self.plugins_ui.add_row(manifest_meta, self.toggle_plugin_load_state)
|
||||
|
||||
def remove_plugin(self, file: str) -> None:
|
||||
logger.info(f"Removing plugin: {file.get_uri()}")
|
||||
for manifest_meta in self._plugin_collection[:]:
|
||||
if not manifest_meta.folder in file.get_uri(): continue
|
||||
|
||||
manifest_meta.instance.unload()
|
||||
manifest_meta.instance = None
|
||||
self._plugin_collection.remove(manifest_meta)
|
||||
self.plugins_ui.remove_row(manifest_meta)
|
||||
|
||||
if manifest_meta in self._manifest_manager.pre_launch_manifests:
|
||||
self._manifest_manager.pre_launch_manifests.remove(manifest_meta)
|
||||
elif manifest_meta in self._manifest_manager.post_launch_manifests:
|
||||
self._manifest_manager.post_launch_manifests.remove(manifest_meta)
|
||||
elif manifest_meta in self._manifest_manager.manual_launch_manifests:
|
||||
self._manifest_manager.manual_launch_manifests.remove(manifest_meta)
|
||||
|
||||
break
|
||||
|
||||
@@ -27,6 +27,9 @@ class PluginBase:
|
||||
def load(self):
|
||||
raise PluginBaseException("Plugin Base 'load' must be overriden by Plugin")
|
||||
|
||||
def unload(self):
|
||||
raise PluginBaseException("Plugin Base 'unload' must be overriden by Plugin")
|
||||
|
||||
def run(self):
|
||||
raise PluginBaseException("Plugin Base 'run' must be overriden by Plugin")
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ class PluginCode(PluginBase):
|
||||
def register_controller(self, name: str, controller):
|
||||
return self.plugin_context.register_controller(name, controller)
|
||||
|
||||
def unregister_controller(self, name: str):
|
||||
return self.plugin_context.unregister_controller(name)
|
||||
|
||||
def request_ui_element(self, element_id: str):
|
||||
return self.plugin_context.request_ui_element(element_id)
|
||||
|
||||
|
||||
100
src/plugins/plugins_ui.py
Normal file
100
src/plugins/plugins_ui.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class PluginsUI(Gtk.Dialog):
|
||||
def __init__(self):
|
||||
super(PluginsUI, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
header = Gtk.HeaderBar()
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("plugin-ui")
|
||||
|
||||
self.set_title("Plugins")
|
||||
self.set_size_request(450, 530)
|
||||
self.set_deletable(False)
|
||||
self.set_skip_pager_hint(True)
|
||||
self.set_skip_taskbar_hint(True)
|
||||
|
||||
header.set_title("Plugins")
|
||||
self.set_titlebar(header)
|
||||
header.show()
|
||||
|
||||
window = widget_registery.get_object("main-window")
|
||||
self.set_transient_for(window)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("plugin-ui", self)
|
||||
|
||||
content_area = self.get_content_area()
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
viewport = Gtk.Viewport()
|
||||
self.list_box = Gtk.ListBox()
|
||||
|
||||
self.list_box.set_selection_mode( Gtk.SelectionMode.NONE )
|
||||
scrolled_win.set_vexpand(True)
|
||||
|
||||
viewport.add(self.list_box)
|
||||
scrolled_win.add(viewport)
|
||||
content_area.add(scrolled_win)
|
||||
|
||||
scrolled_win.show_all()
|
||||
|
||||
def add_row(self, manifest_meta, callback: callable):
|
||||
box = Gtk.Box()
|
||||
plugin_lbl = Gtk.Label(label = manifest_meta.manifest.name)
|
||||
author_lbl = Gtk.Label(label = manifest_meta.manifest.author)
|
||||
version_lbl = Gtk.Label(label = manifest_meta.manifest.version)
|
||||
is_autoload = manifest_meta.manifest.autoload
|
||||
toggle_bttn = Gtk.ToggleButton(label = "Unload" if is_autoload else "Load")
|
||||
|
||||
toggle_bttn.set_active(is_autoload)
|
||||
plugin_lbl.set_hexpand(True)
|
||||
box.set_hexpand(True)
|
||||
version_lbl.set_margin_left(15)
|
||||
version_lbl.set_margin_right(15)
|
||||
toggle_bttn.set_size_request(120, -1)
|
||||
|
||||
toggle_bttn.toggle_id = \
|
||||
toggle_bttn.connect("toggled", callback, manifest_meta)
|
||||
|
||||
box.add(plugin_lbl)
|
||||
box.add(author_lbl)
|
||||
box.add(version_lbl)
|
||||
box.add(toggle_bttn)
|
||||
box.manifest_meta = manifest_meta
|
||||
|
||||
box.show_all()
|
||||
self.list_box.add(box)
|
||||
|
||||
def remove_row(self, manifest_meta):
|
||||
for row in self.list_box.get_children():
|
||||
child = row.get_children()[0]
|
||||
if not child.manifest_meta == manifest_meta: continue
|
||||
|
||||
child.manifest_meta = None
|
||||
toggle_bttn = getattr(child, "toggle_bttn", None)
|
||||
toggle_bttn.disconnect(toggle_bttn.toggle_id)
|
||||
|
||||
self.list_box.remove(row)
|
||||
box.destroy()
|
||||
break
|
||||
Reference in New Issue
Block a user