Compare commits

...

2 Commits

Author SHA1 Message Date
24bf1e471b refactor(command-system): standardize command execution with *args/**kwargs
- Refactor exec_with_args to use *args/**kwargs instead of tuple arguments
- Add *args/**kwargs to all command execute functions for consistency
- Support multiple key bindings per command in registration
- Add character-based key binding support via get_char() in KeyMapper
- Make execute_plugin async and use asyncio.run for plugin execution
- Use MIME type from Gio content_type instead of language for ftype
2026-02-24 22:30:07 -06:00
824dd93696 Add file deletion detection, WebKit improvements, and bug fixes
- Add FileExternallyDeletedEvent with UI indicator in tabs for deleted files
- Set minimum height (300px) for editors container
- Add Developer Tools to WebKit context menu
- Fix DnD URI handling when uris list is empty
- Fix buffer replacement using freeze_notify and proper bounds handling
- Move webkit_ui_settings to new path
- Remove <change_me> placeholders from APP_NAME and user config
2026-02-23 00:48:09 -06:00
59 changed files with 291 additions and 95 deletions

View File

@@ -43,7 +43,7 @@ def call_chain_wrapper(fn):
# NOTE: Just reminding myself we can add to builtins two different ways... # NOTE: Just reminding myself we can add to builtins two different ways...
# __builtins__.update({"event_system": Builtins()}) # __builtins__.update({"event_system": Builtins()})
builtins.APP_NAME = "<change_me>" builtins.APP_NAME = "<change_me>".replace("<","").replace(">","")
builtins.keybindings = Keybindings() builtins.keybindings = Keybindings()
builtins.event_system = EventSystem() builtins.event_system = EventSystem()

View File

@@ -26,6 +26,7 @@ class EditorsContainer(Gtk.Paned):
self.ctx = self.get_style_context() self.ctx = self.get_style_context()
self.ctx.add_class("paned-editors-container") self.ctx.add_class("paned-editors-container")
self.set_size_request(-1, 300)
self.set_hexpand(True) self.set_hexpand(True)
self.set_vexpand(True) self.set_vexpand(True)
self.set_wide_handle(True) self.set_wide_handle(True)

View File

@@ -9,7 +9,6 @@ from gi.repository import GtkSource
def set_language_and_style(view, file): def set_language_and_style(view, file):
language = view.language_manager.guess_language(file.fname, None) language = view.language_manager.guess_language(file.fname, None)
file.ftype = "buffer" if not language else language
file.buffer.set_language(language) file.buffer.set_language(language)
file.buffer.set_style_scheme(view.syntax_theme) file.buffer.set_style_scheme(view.syntax_theme)

View File

@@ -26,16 +26,13 @@ class CommandSystem:
method = getattr(commands, command) method = getattr(commands, command)
args, kwargs = self.data args, kwargs = self.data
if kwargs: return method.execute(*args, **kwargs)
return method.execute(*args, kwargs)
else:
return method.execute(*args)
def exec_with_args(self, command: str, args: list) -> any: def exec_with_args(self, command: str, *args, **kwargs) -> any:
if not hasattr(commands, command): return if not hasattr(commands, command): return
method = getattr(commands, command) method = getattr(commands, command)
return method.execute(*args) return method.execute(*args, **kwargs)
def add_command(self, command_name: str, command: callable): def add_command(self, command_name: str, command: callable):
setattr(commands, command_name, command) setattr(commands, command_name, command)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Buffer Redo") logger.debug("Command: Buffer Redo")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Buffer Undo") logger.debug("Command: Buffer Undo")

View File

@@ -13,7 +13,9 @@ from ..command_helpers import update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Close File") logger.debug("Command: Close File")
view.command.remove_file(view) view.command.remove_file(view)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Cut to Temp Buffer") logger.debug("Command: Cut to Temp Buffer")

View File

@@ -15,7 +15,9 @@ from ..command_helpers import update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
uri: str uri: str,
*args,
**kwargs
): ):
logger.debug("Command: DnD Load File To Buffer") logger.debug("Command: DnD Load File To Buffer")
file = view.command.new_file(view) file = view.command.new_file(view)
@@ -23,8 +25,9 @@ def execute(
gfile = Gio.File.new_for_uri(uri) gfile = Gio.File.new_for_uri(uri)
view.command.exec_with_args( view.command.exec_with_args(
"load_file", "load_file",
(view, gfile, file) view, gfile, file
) )
view.set_buffer(file.buffer) view.set_buffer(file.buffer)
update_info_bar_if_focused(view.command, view) update_info_bar_if_focused(view.command, view)

View File

@@ -14,7 +14,9 @@ from gi.repository import Gio
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
uris: list = [] uris: list = [],
*args,
**kwargs
): ):
logger.debug("Command: DnD Load Files") logger.debug("Command: DnD Load Files")
for uri in uris: for uri in uris:
@@ -23,4 +25,4 @@ def execute(
except Exception as e: except Exception as e:
gfile = Gio.File.new_for_path(uri) gfile = Gio.File.new_for_path(uri)
view.command.exec_with_args("load_file", (view, gfile)) view.command.exec_with_args("load_file", view, gfile)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Duplicate Line") logger.debug("Command: Duplicate Line")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Focus Left Sibling") logger.debug("Command: Focus Left Sibling")
if not view.sibling_left: return if not view.sibling_left: return

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Focus Right Sibling") logger.debug("Command: Focus Right Sibling")
if not view.sibling_right: return if not view.sibling_right: return

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Get Current File") logger.debug("Command: Get Current File")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Get File Type") logger.debug("Command: Get File Type")
file = view.command.get_file(view) file = view.command.get_file(view)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Get Text") logger.debug("Command: Get Text")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Go-To") logger.debug("Command: Go-To")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Has Focus") logger.debug("Command: Has Focus")
ctx = view.get_parent().get_style_context() ctx = view.get_parent().get_style_context()

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Line Down") logger.debug("Command: Line Down")
view.emit("move-lines", True) view.emit("move-lines", True)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Line Up") logger.debug("Command: Line Up")
view.emit("move-lines", False) view.emit("move-lines", False)

View File

@@ -18,6 +18,8 @@ def execute(
view: GtkSource.View, view: GtkSource.View,
gfile: Gio.File, gfile: Gio.File,
file: SourceFile = None, file: SourceFile = None,
*args,
**kwargs
): ):
logger.debug("Command: Load File") logger.debug("Command: Load File")
if not file: if not file:

View File

@@ -14,6 +14,8 @@ from gi.repository import Gio
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Load Start File(s)") logger.debug("Command: Load Start File(s)")
@@ -28,7 +30,7 @@ def execute(
view.command.exec_with_args( view.command.exec_with_args(
"load_file", "load_file",
(view, gfile, file) view, gfile, file
) )
if not starting_files: return if not starting_files: return
@@ -37,4 +39,4 @@ def execute(
file = file.replace("FILE|", "") file = file.replace("FILE|", "")
gfile = Gio.File.new_for_path(file) gfile = Gio.File.new_for_path(file)
view.command.exec_with_args("load_file", (view, gfile)) view.command.exec_with_args("load_file", view, gfile)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Move To Left Sibling") logger.debug("Command: Move To Left Sibling")
if not view.sibling_left: return if not view.sibling_left: return

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Move To Right Sibling") logger.debug("Command: Move To Right Sibling")
if not view.sibling_right: return if not view.sibling_right: return

View File

@@ -14,7 +14,9 @@ from ..command_helpers import set_language_and_style, update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: New File") logger.debug("Command: New File")

View File

@@ -13,7 +13,9 @@ from ..command_helpers import update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Open File(s)") logger.debug("Command: Open File(s)")
gfiles = event_system.emit_and_await("open-files") gfiles = event_system.emit_and_await("open-files")
@@ -22,9 +24,9 @@ def execute(
file = view.command.get_file(view) file = view.command.get_file(view)
if file.ftype == "buffer": if file.ftype == "buffer":
gfile = gfiles.pop() gfile = gfiles.pop()
view.command.exec_with_args("load_file", (view, gfile, file)) view.command.exec_with_args("load_file", view, gfile, file)
view.set_buffer(file.buffer) view.set_buffer(file.buffer)
update_info_bar_if_focused(view.command, view) update_info_bar_if_focused(view.command, view)
for i, gfile in enumerate(gfiles): for i, gfile in enumerate(gfiles):
view.command.exec_with_args("load_file", (view, gfile)) view.command.exec_with_args("load_file", view, gfile)

View File

@@ -13,7 +13,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Paste Temp Buffer") logger.debug("Command: Paste Temp Buffer")

View File

@@ -13,7 +13,9 @@ from ..command_helpers import set_language_and_style
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Save File") logger.debug("Command: Save File")
file = view.command.get_file(view) file = view.command.get_file(view)

View File

@@ -13,7 +13,9 @@ from ..command_helpers import set_language_and_style, update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.info("Command: Save File As") logger.info("Command: Save File As")
file = view.command.get_file(view) file = view.command.get_file(view)

View File

@@ -16,7 +16,9 @@ from ..command_helpers import update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
file: SourceFile file: SourceFile,
*args,
**kwargs
): ):
logger.debug("Command: Set Buffer") logger.debug("Command: Set Buffer")

View File

@@ -13,7 +13,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
language: str language: str,
*args,
**kwargs
): ):
logger.debug("Command: Set Buffer Language") logger.debug("Command: Set Buffer Language")

View File

@@ -13,7 +13,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
style: str style: str,
*args,
**kwargs
): ):
logger.debug("Command: Set Buffer Style") logger.debug("Command: Set Buffer Style")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Set Focus Border") logger.debug("Command: Set Focus Border")
ctx = view.get_parent().get_style_context() ctx = view.get_parent().get_style_context()

View File

@@ -13,6 +13,8 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Set MiniView") logger.debug("Command: Set MiniView")
event_system.emit("set-mini-view", (view,)) event_system.emit("set-mini-view", (view,))

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Show Completion") logger.debug("Command: Show Completion")
completer = view.get_completion() completer = view.get_completion()

View File

@@ -13,6 +13,8 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Update Info Bar") logger.debug("Command: Update Info Bar")
file = view.command.get_file(view) file = view.command.get_file(view)

View File

@@ -13,7 +13,9 @@ from gi.repository import Pango
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Zoom In") logger.debug("Command: Zoom In")

View File

@@ -13,7 +13,9 @@ from gi.repository import Pango
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Zoom Out") logger.debug("Command: Zoom Out")

View File

@@ -31,6 +31,8 @@ class TabsController(ControllerBase):
self.update_tab_label(event) self.update_tab_label(event)
elif isinstance(event, Code_Event_Types.ModifiedChangedEvent): elif isinstance(event, Code_Event_Types.ModifiedChangedEvent):
self.tabs_widget.modified_changed( event.buffer ) self.tabs_widget.modified_changed( event.buffer )
elif isinstance(event, Code_Event_Types.FileExternallyDeletedEvent):
self.tabs_widget.externally_deleted( event.buffer )
elif isinstance(event, Code_Event_Types.AddedNewFileEvent): elif isinstance(event, Code_Event_Types.AddedNewFileEvent):
self.add_tab(event) self.add_tab(event)
elif isinstance(event, Code_Event_Types.PoppedFileEvent): elif isinstance(event, Code_Event_Types.PoppedFileEvent):

View File

@@ -40,10 +40,14 @@ class SourceViewsController(ControllerBase, list):
self.signal_mapper.insert_text(event.file, event.text) self.signal_mapper.insert_text(event.file, event.text)
def _register_command(self, event: Code_Event_Types.RegisterCommandEvent): def _register_command(self, event: Code_Event_Types.RegisterCommandEvent):
if not isinstance(event.binding, list):
event.binding = [ event.binding ]
for binding in event.binding:
self.state_manager.key_mapper.map_command( self.state_manager.key_mapper.map_command(
event.command_name, event.command_name,
{ {
f"{event.binding_mode}": event.binding f"{event.binding_mode}": binding
} }
) )

View File

@@ -22,7 +22,8 @@ class SourceViewsInsertState:
event = Event_Factory.create_event("focused_view", view = source_view) event = Event_Factory.create_event("focused_view", view = source_view)
emit(event) emit(event)
def insert_text(self, file, text): def insert_text(self, file, text: str):
return True return True
def move_cursor(self, source_view, step, count, extend_selection, emit): def move_cursor(self, source_view, step, count, extend_selection, emit):
@@ -51,21 +52,23 @@ class SourceViewsInsertState:
def key_press_event(self, source_view, eve, key_mapper): def key_press_event(self, source_view, eve, key_mapper):
command = key_mapper._key_press_event(eve) command = key_mapper._key_press_event(eve)
is_future = key_mapper._key_release_event(eve) is_future = key_mapper._key_release_event(eve)
char_str = key_mapper.get_char(eve)
if is_future: return True if is_future: return True
if not command: return False if not command: return False
source_view.command.exec(command) source_view.command.exec_with_args(command, source_view, char_str)
return True return True
def key_release_event(self, source_view, eve, key_mapper): def key_release_event(self, source_view, eve, key_mapper):
command = key_mapper._key_release_event(eve) command = key_mapper._key_release_event(eve)
is_past = key_mapper._key_press_event(eve) is_past = key_mapper._key_press_event(eve)
char_str = key_mapper.get_char(eve)
if is_past: return True if is_past: return True
if not command: return False if not command: return False
source_view.command.exec(command) source_view.command.exec_with_args(command, source_view, char_str)
return True return True

View File

@@ -96,19 +96,28 @@ class KeyMapper:
getattr(self.states[state], press_state)[keyname] = command getattr(self.states[state], press_state)[keyname] = command
def _key_press_event(self, eve): def _key_press_event(self, eve):
keyname = Gdk.keyval_name(eve.keyval).lower() keyname = self.get_keyname(eve)
char_str = self.get_char(eve)
self._set_key_state(eve) self._set_key_state(eve)
if keyname in self.states[self.state].held: if keyname in self.states[self.state].held:
return self.states[self.state].held[keyname] return self.states[self.state].held[keyname]
if char_str in self.states[self.state].held:
return self.states[self.state].held[char_str]
def _key_release_event(self, eve): def _key_release_event(self, eve):
keyname = Gdk.keyval_name(eve.keyval).lower() keyname = self.get_keyname(eve)
char_str = self.get_char(eve)
self._set_key_state(eve) self._set_key_state(eve)
if keyname in self.states[self.state].released: if keyname in self.states[self.state].released:
return self.states[self.state].released[keyname] return self.states[self.state].released[keyname]
if char_str in self.states[self.state].released:
return self.states[self.state].released[char_str]
def _set_key_state(self, eve): def _set_key_state(self, eve):
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK) modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
is_control = modifiers & Gdk.ModifierType.CONTROL_MASK is_control = modifiers & Gdk.ModifierType.CONTROL_MASK
@@ -137,3 +146,9 @@ class KeyMapper:
def get_raw_keyname(self, eve): def get_raw_keyname(self, eve):
return Gdk.keyval_name(eve.keyval) return Gdk.keyval_name(eve.keyval)
def get_keyname(self, eve):
return Gdk.keyval_name(eve.keyval).lower()
def get_char(self, eve):
return chr( Gdk.keyval_to_unicode(eve.keyval) )

View File

@@ -26,14 +26,14 @@ class SourceViewDnDMixin:
if info == 80: if info == 80:
uris = data.get_uris() uris = data.get_uris()
if not uris: return if not uris:
uris = data.get_text().split("\n") uris = data.get_text().split("\n")
self._on_uri_data_received(uris) self._on_uri_data_received(uris)
def _on_uri_data_received(self, uris: []): def _on_uri_data_received(self, uris: []):
uri = uris.pop(0) uri = uris.pop(0)
self.command.exec_with_args("dnd_load_file_to_buffer", (self, uri)) self.command.exec_with_args("dnd_load_file_to_buffer", self, uri)
if not uris: return if not uris: return

View File

@@ -27,6 +27,7 @@ class SourceFile(GtkSource.File):
self.fname: str = "buffer" self.fname: str = "buffer"
self.fpath: str = "buffer" self.fpath: str = "buffer"
self.ftype: str = "buffer" self.ftype: str = "buffer"
self.was_deleted: bool = False
self.buffer: SourceBuffer = SourceBuffer() self.buffer: SourceBuffer = SourceBuffer()
self._set_signals() self._set_signals()
@@ -56,16 +57,21 @@ class SourceFile(GtkSource.File):
self.emit(event) self.emit(event)
if self.is_deleted(): if self.is_deleted():
print("is_deleted") self.was_deleted = True
# event = Event_Factory.create_event("file_deleted", buffer = buffer) event = Event_Factory.create_event(
# event.file = self "file_externally_deleted",
# self.emit(event) file = self,
buffer = buffer
)
self.emit(event)
return return
if self.is_externally_modified(): if self.is_externally_modified():
print("is_externally_modified") # event = Event_Factory.create_event(
# event = Event_Factory.create_event("file_externally_modified", buffer = buffer) # "file_externally_modified",
# event.file = self # file = self,
# buffer = buffer
# )
# self.emit(event) # self.emit(event)
return return
@@ -128,6 +134,11 @@ class SourceFile(GtkSource.File):
f.write(text) f.write(text)
if self.was_deleted:
self.was_deleted = False
self.set_location( None )
self.set_location( gfile )
return gfile return gfile
@@ -135,11 +146,27 @@ class SourceFile(GtkSource.File):
if not gfile: return if not gfile: return
self.set_path(gfile) self.set_path(gfile)
data = gfile.load_bytes()[0].get_data().decode("UTF-8") text = gfile.load_bytes()[0].get_data().decode("UTF-8")
info = gfile.query_info('standard::content-type', Gio.FileQueryInfoFlags.NONE, None)
content_type = info.get_content_type()
self.ftype = Gio.content_type_get_mime_type(content_type)
logger.debug(f"File content type: {self.ftype}")
undo_manager = self.buffer.get_undo_manager() undo_manager = self.buffer.get_undo_manager()
def move_insert_to_start():
start_itr = self.buffer.get_start_iter()
self.buffer.place_cursor(start_itr)
undo_manager.begin_not_undoable_action() undo_manager.begin_not_undoable_action()
self.buffer.insert_at_cursor(data)
with self.buffer.freeze_notify():
start_itr, end_itr = self.buffer.get_bounds()
self.buffer.delete(start_itr, end_itr)
self.buffer.insert(start_itr, text, -1)
GLib.idle_add(move_insert_to_start)
undo_manager.end_not_undoable_action() undo_manager.end_not_undoable_action()
self.buffer.set_modified(False) self.buffer.set_modified(False)

View File

@@ -31,6 +31,8 @@ class TabWidget(Gtk.Box):
self.set_orientation(0) self.set_orientation(0)
self.set_hexpand(False) self.set_hexpand(False)
self.set_vexpand(False)
self.set_size_request(-1, 12)
def _setup_signals(self): def _setup_signals(self):
... ...

View File

@@ -122,11 +122,19 @@ class TabsWidget(Gtk.Notebook):
if not buffer == tab.file.buffer: continue if not buffer == tab.file.buffer: continue
ctx = tab.label.get_style_context() ctx = tab.label.get_style_context()
ctx.remove_class("file-deleted")
if buffer.get_modified(): if buffer.get_modified():
ctx.add_class("file-changed") ctx.add_class("file-changed")
else: else:
ctx.remove_class("file-changed") ctx.remove_class("file-changed")
def externally_deleted(self, buffer):
for page_widget in self.get_children():
tab = self.get_tab_label(page_widget)
if not buffer == tab.file.buffer: continue
ctx = tab.label.get_style_context()
ctx.add_class("file-deleted")
def close_item(self, menu_item, page_widget): def close_item(self, menu_item, page_widget):
tab = self.get_tab_label(page_widget) tab = self.get_tab_label(page_widget)
tab.close_bttn.clicked() tab.close_bttn.clicked()

View File

@@ -1,4 +1,5 @@
# Python imports # Python imports
from pathlib import Path
import json import json
# Lib imports # Lib imports
@@ -6,10 +7,12 @@ import gi
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
gi.require_version('WebKit2', '4.0') gi.require_version('WebKit2', '4.0')
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import WebKit2 from gi.repository import WebKit2
# Application imports # Application imports
from libs.settings.other.webkit_ui_settings import WebkitUISettings from libs.settings.webkit.webkit_ui_settings import WebkitUISettings
from libs.dto.base_event import BaseEvent from libs.dto.base_event import BaseEvent
@@ -17,7 +20,9 @@ class WebkitUI(WebKit2.WebView):
def __init__(self): def __init__(self):
super(WebkitUI, self).__init__() super(WebkitUI, self).__init__()
self._load_settings()
self._setup_styling() self._setup_styling()
self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
self._setup_content_manager() self._setup_content_manager()
@@ -29,6 +34,9 @@ class WebkitUI(WebKit2.WebView):
self.set_hexpand(True) self.set_hexpand(True)
self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) ) self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) )
def _setup_signals(self):
self.connect("context-menu", self._on_context_menu)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe(f"ui-message", self.ui_message) event_system.subscribe(f"ui-message", self.ui_message)
@@ -50,6 +58,18 @@ class WebkitUI(WebKit2.WebView):
except Exception as e: except Exception as e:
logger.info(e) logger.info(e)
def _on_context_menu(self, web_view, context_menu, event, hit_test_result):
action = Gio.SimpleAction.new("Developer Tools", None)
item = WebKit2.ContextMenuItem.new_from_gaction(action, "Developer Tools")
def show_developer_tools(action, parameter):
inspector = self.get_inspector()
inspector.show()
action.connect("activate", show_developer_tools)
context_menu.append(item)
def load_url(self, url: str = ""): def load_url(self, url: str = ""):
if not url: if not url:
url = "https://duckduckgo.com/" url = "https://duckduckgo.com/"
@@ -60,11 +80,21 @@ class WebkitUI(WebKit2.WebView):
if not path: if not path:
path = settings_manager.path_manager.get_context_path() path = settings_manager.path_manager.get_context_path()
data = None base_path = Path(path)
with open(f"{path}/index.html", "r") as f: index_file = base_path / "index.html"
data = f.read()
self.load_html(content = data, base_uri = f"file://{path}") if not index_file.exists():
raise FileNotFoundError(f"index.html not found in {base_path}")
try:
data = index_file.read_text(encoding = "utf-8")
except Exception as e:
raise RuntimeError(f"Failed to read {index_file}: {e}")
self.load_html(
content = data,
base_uri = index_file.as_uri()
)
def ui_message(self, message, mtype): def ui_message(self, message, mtype):
command = f"displayMessage('{message}', '{mtype}', '3')" command = f"displayMessage('{message}', '{mtype}', '3')"

View File

@@ -33,7 +33,9 @@ class ControllerManager(Singleton, dict):
raise ControllerManagerException("Must pass in a 'name' and 'controller'...") raise ControllerManagerException("Must pass in a 'name' and 'controller'...")
if name in self.keys(): if name in self.keys():
raise ControllerManagerException(f"Can't bind controller to registered name of '{name}'...") raise ControllerManagerException(
f"Can't bind controller to existing registered name of '{name}'..."
)
controller.set_controller_context( self._crete_controller_context() ) controller.set_controller_context( self._crete_controller_context() )

View File

@@ -6,6 +6,8 @@
from .code_event import CodeEvent from .code_event import CodeEvent
from .register_provider_event import RegisterProviderEvent from .register_provider_event import RegisterProviderEvent
from .register_command_event import RegisterCommandEvent from .register_command_event import RegisterCommandEvent
from .file_externally_modified_event import FileExternallyModifiedEvent
from .file_externally_deleted_event import FileExternallyDeletedEvent
from .get_new_command_system_event import GetNewCommandSystemEvent from .get_new_command_system_event import GetNewCommandSystemEvent
from .request_completion_event import RequestCompletionEvent from .request_completion_event import RequestCompletionEvent

View File

@@ -0,0 +1,13 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from .code_event import CodeEvent
@dataclass
class FileExternallyDeletedEvent(CodeEvent):
...

View File

@@ -0,0 +1,13 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from .code_event import CodeEvent
@dataclass
class FileExternallyModifiedEvent(CodeEvent):
...

View File

@@ -17,4 +17,4 @@ class RegisterCommandEvent(BaseEvent):
command_name: str = "" command_name: str = ""
command: callable = None command: callable = None
binding_mode: str = "" binding_mode: str = ""
binding: str = "" binding: str or list = ""

View File

@@ -95,7 +95,7 @@ class WebkitUISettings(WebKit2.Settings):
self.set_property('javascript-can-open-windows-automatically', False) self.set_property('javascript-can-open-windows-automatically', False)
# Debugging # Debugging
self.set_property('enable-developer-extras', False) self.set_property('enable-developer-extras', True)
self.set_property('enable-write-console-messages-to-stdout', False) self.set_property('enable-write-console-messages-to-stdout', False)
self.set_property('draw-compositing-indicators', False) self.set_property('draw-compositing-indicators', False)
self.set_property('enable-mock-capture-devices', False) self.set_property('enable-mock-capture-devices', False)

View File

@@ -3,6 +3,7 @@ import os
import sys import sys
import importlib import importlib
import traceback import traceback
import asyncio
from os.path import join from os.path import join
from os.path import isdir from os.path import isdir
@@ -75,9 +76,14 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
module = self._load_plugin_module(path, folder, target) module = self._load_plugin_module(path, folder, target)
if is_pre_launch: if is_pre_launch:
asyncio.run(
self.execute_plugin(module, manifest_meta) self.execute_plugin(module, manifest_meta)
)
else: else:
GLib.idle_add(self.execute_plugin, module, manifest_meta) GLib.idle_add(
asyncio.run,
self.execute_plugin(module, manifest_meta)
)
except Exception as e: except Exception as e:
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !") logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
logger.debug(f"Trace: {traceback.print_exc()}") logger.debug(f"Trace: {traceback.print_exc()}")
@@ -119,7 +125,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
manifest_metas: list = self._manifest_manager.get_post_launch_plugins() manifest_metas: list = self._manifest_manager.get_post_launch_plugins()
self._load_plugins(manifest_metas) self._load_plugins(manifest_metas)
def execute_plugin(self, module: type, manifest_meta: ManifestMeta): async def execute_plugin(self, module: type, manifest_meta: ManifestMeta):
plugin = module.Plugin() plugin = module.Plugin()
plugin.plugin_context: PluginContext = self.create_plugin_context() plugin.plugin_context: PluginContext = self.create_plugin_context()

View File

@@ -3,6 +3,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Gtk + HTML + Python App</title> <title>Gtk + HTML + Python App</title>
<!-- <base href="/"> -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
<link rel="stylesheet" href="resources/css/libs/bootstrap5/bootstrap.min.css"> <link rel="stylesheet" href="resources/css/libs/bootstrap5/bootstrap.min.css">
<link rel="stylesheet" href="resources/css/libs/bootstrap-icons/bootstrap-icons.css"> <link rel="stylesheet" href="resources/css/libs/bootstrap-icons/bootstrap-icons.css">

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB