diff --git a/src/core/widgets/code/command_system.py b/src/core/widgets/code/command_system.py index 0c93923..3f234c9 100644 --- a/src/core/widgets/code/command_system.py +++ b/src/core/widgets/code/command_system.py @@ -17,18 +17,18 @@ class CommandSystem: def set_data(self, *args, **kwargs): self.data = (args, kwargs) - def exec(self, command: str): + def exec(self, command: str) -> any: if not hasattr(commands, command): return method = getattr(commands, command) args, kwargs = self.data if kwargs: - method.execute(*args, kwargs) + return method.execute(*args, kwargs) else: - method.execute(*args) + return method.execute(*args) - def exec_with_args(self, command: str, args: list): + def exec_with_args(self, command: str, args: list) -> any: if not hasattr(commands, command): return method = getattr(commands, command) - method.execute(*args) + return method.execute(*args) diff --git a/src/core/widgets/code/commands/close_file.py b/src/core/widgets/code/commands/close_file.py index 67f609e..346d5d2 100644 --- a/src/core/widgets/code/commands/close_file.py +++ b/src/core/widgets/code/commands/close_file.py @@ -15,9 +15,9 @@ def execute( editor: GtkSource.View = None ): logger.debug("Close File Command") - file = editor.files.new() buffer = editor.get_buffer() - editor.set_buffer(file.buffer) + + editor.command.exec("new_file") editor.files.remove_file(buffer) editor.command.exec("update_info_bar") diff --git a/src/core/widgets/code/commands/dnd_load_file_to_buffer.py b/src/core/widgets/code/commands/dnd_load_file_to_buffer.py new file mode 100644 index 0000000..28c2ffc --- /dev/null +++ b/src/core/widgets/code/commands/dnd_load_file_to_buffer.py @@ -0,0 +1,38 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource +from gi.repository import Gio + +# Application imports + + + +def execute( + editor: GtkSource.View, + uri: str +): + logger.debug("DnD Load File To Buffer Command") + if not uri: return + + buffer = editor.get_buffer() + file = editor.files.get_file(buffer) + + if not file.ftype == "buffer": return + + gfile = Gio.File.new_for_uri(uri) + editor.command.exec_with_args( + "load_file", + (editor, gfile, file) + ) + + ctx = editor.get_parent().get_style_context() + is_focused = ctx.has_class("source-view-focused") + if is_focused: + editor.command.exec("update_info_bar") + + return uri diff --git a/src/core/widgets/code/commands/dnd_load_files.py b/src/core/widgets/code/commands/dnd_load_files.py new file mode 100644 index 0000000..8970b04 --- /dev/null +++ b/src/core/widgets/code/commands/dnd_load_files.py @@ -0,0 +1,27 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource +from gi.repository import Gio + +# Application imports +from ..source_file import SourceFile + + + +def execute( + editor: GtkSource.View, + uris: list = [] +): + logger.debug("DnD Load Files Command") + for uri in uris: + try: + gfile = Gio.File.new_for_uri(uri) + except Exception as e: + gfile = Gio.File.new_for_path(uri) + + editor.command.exec_with_args("load_file", (editor, gfile)) \ No newline at end of file diff --git a/src/core/widgets/code/commands/has_focus.py b/src/core/widgets/code/commands/has_focus.py new file mode 100644 index 0000000..643008a --- /dev/null +++ b/src/core/widgets/code/commands/has_focus.py @@ -0,0 +1,19 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports + + + +def execute( + editor: GtkSource.View = None +): + logger.debug("Has Focus Command") + ctx = editor.get_parent().get_style_context() + return ctx.has_class("source-view-focused") diff --git a/src/core/widgets/code/commands/move_to_left_sibling.py b/src/core/widgets/code/commands/move_to_left_sibling.py index 4e6ca05..aa8d6f1 100644 --- a/src/core/widgets/code/commands/move_to_left_sibling.py +++ b/src/core/widgets/code/commands/move_to_left_sibling.py @@ -18,9 +18,17 @@ def execute( if not editor.sibling_left: return buffer = editor.get_buffer() - sibling_file, popped_file = editor.files.pop_file(buffer) + popped_file, sibling_file = editor.files.pop_file(buffer) + + if sibling_file: + sibling_file.subscribe(editor) + editor.set_buffer(sibling_file.buffer) + else: + sibling_file = editor.command.exec("new_file") + + popped_file.unsubscribe(editor) + popped_file.subscribe(editor.sibling_left) - editor.set_buffer(sibling_file.buffer) editor.sibling_left.set_buffer(buffer) editor.sibling_left.files.append(popped_file) editor.sibling_left.grab_focus() diff --git a/src/core/widgets/code/commands/move_to_right_sibling.py b/src/core/widgets/code/commands/move_to_right_sibling.py index edb75dc..cb8b84d 100644 --- a/src/core/widgets/code/commands/move_to_right_sibling.py +++ b/src/core/widgets/code/commands/move_to_right_sibling.py @@ -18,9 +18,17 @@ def execute( if not editor.sibling_right: return buffer = editor.get_buffer() - sibling_file, popped_file = editor.files.pop_file(buffer) + popped_file, sibling_file = editor.files.pop_file(buffer) + + if sibling_file: + sibling_file.subscribe(editor) + editor.set_buffer(sibling_file.buffer) + else: + sibling_file = editor.command.exec("new_file") + + popped_file.unsubscribe(editor) + popped_file.subscribe(editor.sibling_right) - editor.set_buffer(sibling_file.buffer) editor.sibling_right.set_buffer(buffer) editor.sibling_right.files.append(popped_file) editor.sibling_right.grab_focus() diff --git a/src/core/widgets/code/commands/new_file.py b/src/core/widgets/code/commands/new_file.py index 6893472..ab34c2f 100644 --- a/src/core/widgets/code/commands/new_file.py +++ b/src/core/widgets/code/commands/new_file.py @@ -23,4 +23,10 @@ def execute( file.buffer.set_style_scheme(editor.syntax_theme) editor.set_buffer(file.buffer) - editor.exec_command("update_info_bar") + file.subscribe(editor) + + has_focus = editor.command.exec("has_focus") + if not has_focus: return file + + editor.command.exec("update_info_bar") + return file diff --git a/src/core/widgets/code/commands/open_files.py b/src/core/widgets/code/commands/open_files.py index 361ff88..eec6c63 100644 --- a/src/core/widgets/code/commands/open_files.py +++ b/src/core/widgets/code/commands/open_files.py @@ -24,5 +24,10 @@ def execute( editor.command.exec_with_args("load_file", (editor, gfile, file)) if i == (size - 1): + buffer = editor.get_buffer() + _file = editor.files.get_file(buffer) + _file.unsubscribe(editor) + editor.set_buffer(file.buffer) + file.subscribe(editor) editor.command.exec("update_info_bar") diff --git a/src/core/widgets/code/commands/save_file_as.py b/src/core/widgets/code/commands/save_file_as.py index 9cf44b8..edfbd32 100644 --- a/src/core/widgets/code/commands/save_file_as.py +++ b/src/core/widgets/code/commands/save_file_as.py @@ -24,5 +24,5 @@ def execute( .guess_language(file.fname, None) file.ftype = language file.buffer.set_language(language) - + file.subscribe(editor) editor.exec_command("update_info_bar") diff --git a/src/core/widgets/code/commands/set_focus_border.py b/src/core/widgets/code/commands/set_focus_border.py index 2a2f1a3..a9feaa1 100644 --- a/src/core/widgets/code/commands/set_focus_border.py +++ b/src/core/widgets/code/commands/set_focus_border.py @@ -15,12 +15,12 @@ def execute( editor: GtkSource.View = None ): logger.debug("Set Focus Border Command") - ctx = editor.get_style_context() + ctx = editor.get_parent().get_style_context() ctx.add_class("source-view-focused") if editor.sibling_right: - ctx = editor.sibling_right.get_style_context() + ctx = editor.sibling_right.get_parent().get_style_context() elif editor.sibling_left: - ctx = editor.sibling_left.get_style_context() + ctx = editor.sibling_left.get_parent().get_style_context() ctx.remove_class("source-view-focused") diff --git a/src/core/widgets/code/mixins/source_view_dnd_mixin.py b/src/core/widgets/code/mixins/source_view_dnd_mixin.py index cdf9b88..5e29146 100644 --- a/src/core/widgets/code/mixins/source_view_dnd_mixin.py +++ b/src/core/widgets/code/mixins/source_view_dnd_mixin.py @@ -4,7 +4,6 @@ import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from gi.repository import Gio # Application imports @@ -15,36 +14,23 @@ class SourceViewDnDMixin: def _set_up_dnd(self): PLAIN_TEXT_TARGET_TYPE = 70 URI_TARGET_TYPE = 80 - text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE) - uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) - targets = [ text_target, uri_target ] + text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE) + uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) + targets = [ text_target, uri_target ] + self.drag_dest_set_target_list(targets) def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time): if info == 70: return if info == 80: - uris = data.get_uris() - buffer = self.get_buffer() - file = self.files.get_file(buffer) + uris = data.get_uris() if len(uris) == 0: uris = data.get_text().split("\n") - if file.ftype == "buffer": - gfile = Gio.File.new_for_uri(uris[0]) - self.command.exec_with_args( - "load_file", - (self, gfile, file) - ) - - self.command.exec("update_info_bar") + pop_file = self.command.exec_with_args("dnd_load_file_to_buffer", (self, uris[0])) + if pop_file: uris.pop(0) - for uri in uris: - try: - gfile = Gio.File.new_for_uri(uri) - except Exception as e: - gfile = Gio.File.new_for_path(uri) - - self.command.exec_with_args("load_file", (self, gfile)) + self.command.exec_with_args("dnd_load_files", (self, uris)) diff --git a/src/core/widgets/code/mixins/source_view_events_mixin.py b/src/core/widgets/code/mixins/source_view_events_mixin.py new file mode 100644 index 0000000..63342a2 --- /dev/null +++ b/src/core/widgets/code/mixins/source_view_events_mixin.py @@ -0,0 +1,55 @@ +# Python imports + +# Lib imports + +# Application imports + + + +class SourceViewEventsMixin: + def _focus_in_event(self, view, eve): + self.command.exec("set_miniview") + self.command.exec("set_focus_border") + self.command.exec("update_info_bar") + + def _move_cursor(self, view, step, count, extend_selection): + self.command.exec("update_info_bar") + + def _button_press_event(self, view, eve): + self.command.exec("update_info_bar") + + def _button_release_event(self, view, eve): + self.command.exec("update_info_bar") + + def _key_press_event(self, view, eve): + command = self.key_mapper._key_press_event(eve) + if not command: return False + + self.command.exec(command) + return True + + def _key_release_event(self, view, eve): + command = self.key_mapper._key_release_event(eve) + if not command: return False + + self.command.exec(command) + return True + + def notify(self, file, buffer, etype: str): + has_focus = self.command.exec("has_focus") + if not has_focus: return + + self.command.exec("update_info_bar") + match etype: + case "changed": + logger.debug("SourceFile._changed") + case "modified_changed": + logger.debug("SourceFile._modified_changed") + case "insert_text": + logger.debug("SourceFile._insert_text") + case "mark_set": + # logger.debug("SourceFile._mark_set") + ... + case _: + ... + diff --git a/src/core/widgets/code/source_file.py b/src/core/widgets/code/source_file.py index 8a62015..9596f80 100644 --- a/src/core/widgets/code/source_file.py +++ b/src/core/widgets/code/source_file.py @@ -20,6 +20,8 @@ class SourceFile(GtkSource.File): def __init__(self): super(SourceFile, self).__init__() + self.observers = [] + self.encoding: str = "UTF-8" self.fname: str = "buffer" self.fpath: str = "buffer" @@ -30,6 +32,46 @@ class SourceFile(GtkSource.File): self._set_signals() + def _set_signals(self): + self.buffer.set_signals( + self._changed, + self._mark_set, + self._insert_text, + self._modified_changed + ) + + + def _insert_text(self, buffer: SourceBuffer, location: Gtk.TextIter, + text: str, length: int + ): + self.notify((self, buffer, "insert_text")) + + def _changed(self, buffer: SourceBuffer): + self.notify((self, buffer, "changed")) + + def _mark_set(self, buffer: SourceBuffer, location: Gtk.TextIter, + mark: Gtk.TextMark + ): + # self.notify((self, buffer, "mark_set")) + ... + + def _modified_changed(self, buffer: SourceBuffer): + self.notify((self, buffer, "modified_changed")) + + + def _write_file(self, gfile: Gio.File): + if not gfile: return + + with open(gfile.get_path(), 'w') as f: + start_itr = self.buffer.get_start_iter() + end_itr = self.buffer.get_end_iter() + text = self.buffer.get_text(start_itr, end_itr, True) + + f.write(text) + + return gfile + + def load_path(self, gfile: Gio.File): if not gfile: return @@ -45,49 +87,16 @@ class SourceFile(GtkSource.File): self.fpath = gfile.get_path() self.fname = gfile.get_basename() - def _set_signals(self): - self.buffer.set_signals( - self._changed, - self._mark_set, - self._insert_text, - self._modified_changed - ) - def _insert_text( - self, - buffer: SourceBuffer, - location: Gtk.TextIter, - text: str, - length: int - ): - logger.info("SourceFile._insert_text") + def subscribe(self, editor): + self.observers.append(editor) - def _changed(self, buffer: SourceBuffer): - logger.info("SourceFile._changed") + def unsubscribe(self, editor): + self.observers.remove(editor) - def _mark_set( - self, - buffer: SourceBuffer, - location: Gtk.TextIter, - mark: Gtk.TextMark - ): - # logger.info("SourceFile._mark_set") - ... - - def _modified_changed(self, buffer: SourceBuffer): - logger.info("SourceFile._modified_changed") - - def _write_file(self, gfile: Gio.File): - if not gfile: return - - with open(gfile.get_path(), 'w') as f: - start_itr = self.buffer.get_start_iter() - end_itr = self.buffer.get_end_iter() - text = self.buffer.get_text(start_itr, end_itr, True) - - f.write(text) - - return gfile + def notify(self, data): + for editor in self.observers: + editor.notify(*data) def save(self): self._write_file( self.get_location() ) @@ -102,4 +111,7 @@ class SourceFile(GtkSource.File): return file def close(self): + self.observers.clear() + + del observers del self.buffer \ No newline at end of file diff --git a/src/core/widgets/code/source_files_manager.py b/src/core/widgets/code/source_files_manager.py index bec0890..9c5b4eb 100644 --- a/src/core/widgets/code/source_files_manager.py +++ b/src/core/widgets/code/source_files_manager.py @@ -15,13 +15,11 @@ class SourceFilesManager(list): def new(self): file = SourceFile() - super().append(file) - + self.append(file) return file def append(self, file: SourceFile): if not file: return - super().append(file) def get_file(self, buffer: SourceBuffer): @@ -42,13 +40,12 @@ class SourceFilesManager(list): size = len(self) if size == 0: - sibling_file = self.new() - else: - new_i = 0 if size == 1 else i - 1 if i > 1 else i + 1 + return popped_file, sibling_file - sibling_file = self[new_i] + j = 0 if size == 1 else i - 1 if i > 1 else i + 1 + sibling_file = self[j] - return sibling_file, popped_file + return popped_file, sibling_file def remove_file(self, buffer: SourceBuffer): if not buffer: return @@ -56,7 +53,7 @@ class SourceFilesManager(list): for file in self: if not buffer == file.buffer: continue self.remove(file) - file.close() + del file break diff --git a/src/core/widgets/code/view.py b/src/core/widgets/code/view.py index d939a9e..6bf66cc 100644 --- a/src/core/widgets/code/view.py +++ b/src/core/widgets/code/view.py @@ -10,6 +10,7 @@ from gi.repository import GLib from gi.repository import GtkSource # Application imports +from .mixins.source_view_events_mixin import SourceViewEventsMixin from .mixins.source_view_dnd_mixin import SourceViewDnDMixin from .source_files_manager import SourceFilesManager @@ -19,7 +20,7 @@ from .key_mapper import KeyMapper -class SourceView(SourceViewDnDMixin, GtkSource.View): +class SourceView(SourceViewEventsMixin, SourceViewDnDMixin, GtkSource.View): def __init__(self): super(SourceView, self).__init__() @@ -57,7 +58,7 @@ class SourceView(SourceViewDnDMixin, GtkSource.View): self.map_id = self.connect("map", self._init_map) self.connect("drag-data-received", self._on_drag_data_received) - self.connect("focus-in-event", self._focus_in_event) + self.connect("move-cursor", self._move_cursor) self.connect("key-press-event", self._key_press_event) self.connect("key-release-event", self._key_release_event) self.connect("button-press-event", self._button_press_event) @@ -70,18 +71,17 @@ class SourceView(SourceViewDnDMixin, GtkSource.View): self._set_up_dnd() def _init_map(self, view): - def _first_show_init(): + def _init_first_show(): self.disconnect(self.map_id) del self.map_id - self._handle_first_show() + self._init_show() return False - # GLib.timeout_add(1000, _first_show_init) - GLib.idle_add(_first_show_init) + GLib.idle_add(_init_first_show) - def _handle_first_show(self): + def _init_show(self): self.language_manager = GtkSource.LanguageManager() self.style_scheme_manager = GtkSource.StyleSchemeManager() self.command = CommandSystem() @@ -98,42 +98,12 @@ class SourceView(SourceViewDnDMixin, GtkSource.View): f"{settings_manager.settings.theming.syntax_theme}" ) - self.exec_command("new_file") - if self.sibling_right: - self.grab_focus() + def _inner_init(): + self.connect("focus-in-event", self._focus_in_event) + self.command.exec("new_file") + if self.sibling_right: + self.grab_focus() + self._focus_in_event(None, None) - def _focus_in_event(self, view, eve): - self.command.exec("set_miniview") - self.command.exec("set_focus_border") - self.command.exec("update_info_bar") - - def _move_cursor(self, view, step, count, extend_selection): - self.command.exec("update_info_bar") - - def _button_press_event(self, view, eve): - self.command.exec("update_info_bar") - - def _button_release_event(self, view, eve): - self.command.exec("update_info_bar") - - def _key_press_event(self, view, eve): - self.command.exec("update_info_bar") - - command = self.key_mapper._key_press_event(eve) - if not command: return False - - self.exec_command(command) - return True - - def _key_release_event(self, view, eve): - self.command.exec("update_info_bar") - - command = self.key_mapper._key_release_event(eve) - if not command: return False - - self.exec_command(command) - return True - - def exec_command(self, command: str): - self.command.exec(command) + GLib.idle_add(_inner_init) diff --git a/src/core/widgets/general_info_widget.py b/src/core/widgets/general_info_widget.py new file mode 100644 index 0000000..10e407e --- /dev/null +++ b/src/core/widgets/general_info_widget.py @@ -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 Pango +from gi.repository import Gio + +# Application imports + + + +class GeneralInfoWidget(Gtk.Box): + """ docstring for StatusInfoWidget. """ + + def __init__(self): + super(GeneralInfoWidget, self).__init__() + + self._setup_styling() + self._setup_signals() + self._subscribe_to_events() + self._load_widgets() + + self.show_all() + + + def _setup_styling(self): + self.set_margin_top(20) + self.set_margin_bottom(20) + self.set_margin_left(25) + self.set_margin_right(25) + + def _setup_signals(self): + ... + + def _subscribe_to_events(self): + event_system.subscribe("set-info-labels", self._set_info_labels) + event_system.subscribe("set-path-label", self._set_path_label) + event_system.subscribe("set-encoding-label", self._set_encoding_label) + event_system.subscribe("set-line-char-label", self._set_line_char_label) + event_system.subscribe("set-file-type-label", self._set_file_type_label) + + + def _load_widgets(self): + self.path_label = Gtk.Label(label = "...") + self.line_char_label = Gtk.Label(label = "1:0") + self.encoding_label = Gtk.Label(label = "utf-8") + self.file_type_label = Gtk.Label(label = "buffer") + + self.add(self.path_label) + self.add(self.line_char_label) + self.add(self.encoding_label) + self.add(self.file_type_label) + + self.path_label.set_hexpand(True) + self.path_label.set_ellipsize(Pango.EllipsizeMode.START) + self.path_label.set_single_line_mode(True) + self.path_label.set_max_width_chars(48) + + self.line_char_label.set_hexpand(True) + self.encoding_label.set_hexpand(True) + self.file_type_label.set_hexpand(True) + + def _set_info_labels( + self, + path: Gio.File or str = None, + line_char: str = None, + file_type: str = None, + encoding_type: str = None + ): + self._set_path_label(path) + self._set_line_char_label(line_char) + self._set_file_type_label(file_type) + self._set_encoding_label(encoding_type) + + def _set_path_label(self, gfile: Gio.File or str = "..."): + gfile = "" if not gfile else gfile + + if isinstance(gfile, str): + # path = gfile + # path = "..." + path[-120: -1] if len(path) >= 123 else path + # self.path_label.set_text( path ) + self.path_label.set_text( gfile ) + self.path_label.set_tooltip_text( gfile ) + else: + self.path_label.set_text( gfile.get_path() ) + self.path_label.set_tooltip_text( gfile.get_path() ) + + def _set_line_char_label(self, line_char = "1:1"): + line_char = "1:1" if not line_char else line_char + + self.line_char_label.set_text(line_char) + + def _set_file_type_label(self, file_type = "buffer"): + file_type = "buffer" if not file_type else file_type + + self.file_type_label.set_text(file_type) + + def _set_encoding_label(self, encoding_type = "utf-8"): + encoding_type = "utf-8" if not encoding_type else encoding_type + + self.encoding_label.set_text(encoding_type) + +