diff --git a/src/core/containers/code/code_container.py b/src/core/containers/code/code_container.py new file mode 100644 index 0000000..16771c8 --- /dev/null +++ b/src/core/containers/code/code_container.py @@ -0,0 +1,38 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from ...widgets.code.tabs_widget import TabsWidget + +from .editors_container import EditorsContainer + + + +class CodeContainer(Gtk.Box): + def __init__(self): + super(CodeContainer, self).__init__() + + self._setup_styling() + self._setup_signals() + self._subscribe_to_events() + self._load_widgets() + + self.show_all() + + + def _setup_styling(self): + self.set_orientation(Gtk.Orientation.VERTICAL) + + def _setup_signals(self): + ... + + def _subscribe_to_events(self): + ... + + def _load_widgets(self): + self.add( TabsWidget() ) + self.add( EditorsContainer() ) diff --git a/src/core/containers/code/editors_container.py b/src/core/containers/code/editors_container.py index ba9bafa..55fe8f3 100644 --- a/src/core/containers/code/editors_container.py +++ b/src/core/containers/code/editors_container.py @@ -21,8 +21,6 @@ class EditorsContainer(Gtk.Box): self._subscribe_to_events() self._load_widgets() - self.show_all() - def _setup_styling(self): ... diff --git a/src/core/containers/footer_container.py b/src/core/containers/footer_container.py index 050280a..e2dff80 100644 --- a/src/core/containers/footer_container.py +++ b/src/core/containers/footer_container.py @@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from .code.editors_container import EditorsContainer +from .code.code_container import CodeContainer @@ -36,4 +36,4 @@ class FooterContainer(Gtk.Box): ... def _load_widgets(self): - self.add( EditorsContainer() ) + self.add( CodeContainer() ) diff --git a/src/core/widgets/code/commands/close_file.py b/src/core/widgets/code/commands/close_file.py index 346d5d2..ec985f7 100644 --- a/src/core/widgets/code/commands/close_file.py +++ b/src/core/widgets/code/commands/close_file.py @@ -12,12 +12,15 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Close File Command") - buffer = editor.get_buffer() + buffer = view.get_buffer() - editor.command.exec("new_file") + sibling_file = view.files_manager.remove_file(buffer) + if not sibling_file: + view.command.exec("new_file") + else: + view.set_buffer(sibling_file.buffer) - editor.files.remove_file(buffer) - editor.command.exec("update_info_bar") + view.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 index 28c2ffc..cc7e537 100644 --- a/src/core/widgets/code/commands/dnd_load_file_to_buffer.py +++ b/src/core/widgets/code/commands/dnd_load_file_to_buffer.py @@ -13,26 +13,23 @@ from gi.repository import Gio def execute( - editor: GtkSource.View, + view: 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) + buffer = view.get_buffer() + file = view.files_manager.get_file(buffer) - if not file.ftype == "buffer": return + if not file.ftype == "buffer": + file = view.command.exec("new_file") gfile = Gio.File.new_for_uri(uri) - editor.command.exec_with_args( + view.command.exec_with_args( "load_file", - (editor, gfile, file) + (view, 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 + has_focus = view.command.exec("has_focus") + if has_focus: + view.command.exec("update_info_bar") diff --git a/src/core/widgets/code/commands/dnd_load_files.py b/src/core/widgets/code/commands/dnd_load_files.py index 8970b04..0b063a7 100644 --- a/src/core/widgets/code/commands/dnd_load_files.py +++ b/src/core/widgets/code/commands/dnd_load_files.py @@ -14,7 +14,7 @@ from ..source_file import SourceFile def execute( - editor: GtkSource.View, + view: GtkSource.View, uris: list = [] ): logger.debug("DnD Load Files Command") @@ -24,4 +24,4 @@ def execute( 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 + view.command.exec_with_args("load_file", (view, gfile)) \ No newline at end of file diff --git a/src/core/widgets/code/commands/focus_left_sibling.py b/src/core/widgets/code/commands/focus_left_sibling.py index 2254cbe..1e9df2d 100644 --- a/src/core/widgets/code/commands/focus_left_sibling.py +++ b/src/core/widgets/code/commands/focus_left_sibling.py @@ -12,9 +12,9 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Focus Left Sibling Command") - if not editor.sibling_left: return - editor.sibling_left.grab_focus() - editor.sibling_left.command.exec("set_miniview") + if not view.sibling_left: return + view.sibling_left.grab_focus() + view.sibling_left.command.exec("set_miniview") diff --git a/src/core/widgets/code/commands/focus_right_sibling.py b/src/core/widgets/code/commands/focus_right_sibling.py index 644bcf5..cb723ec 100644 --- a/src/core/widgets/code/commands/focus_right_sibling.py +++ b/src/core/widgets/code/commands/focus_right_sibling.py @@ -12,9 +12,9 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Focus Right Sibling Command") - if not editor.sibling_right: return - editor.sibling_right.grab_focus() - editor.sibling_right.command.exec("set_miniview") \ No newline at end of file + if not view.sibling_right: return + view.sibling_right.grab_focus() + view.sibling_right.command.exec("set_miniview") \ 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 index 643008a..53fb64e 100644 --- a/src/core/widgets/code/commands/has_focus.py +++ b/src/core/widgets/code/commands/has_focus.py @@ -12,8 +12,8 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Has Focus Command") - ctx = editor.get_parent().get_style_context() + ctx = view.get_parent().get_style_context() return ctx.has_class("source-view-focused") diff --git a/src/core/widgets/code/commands/line_down.py b/src/core/widgets/code/commands/line_down.py index 65df512..01d5c37 100644 --- a/src/core/widgets/code/commands/line_down.py +++ b/src/core/widgets/code/commands/line_down.py @@ -12,7 +12,7 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Line Up Command") - editor.emit("move-lines", True) + view.emit("move-lines", True) diff --git a/src/core/widgets/code/commands/line_up.py b/src/core/widgets/code/commands/line_up.py index ee7d529..0472f01 100644 --- a/src/core/widgets/code/commands/line_up.py +++ b/src/core/widgets/code/commands/line_up.py @@ -12,7 +12,7 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Line Up Command") - editor.emit("move-lines", False) + view.emit("move-lines", False) diff --git a/src/core/widgets/code/commands/load_file.py b/src/core/widgets/code/commands/load_file.py index 3d3c72c..58677ea 100644 --- a/src/core/widgets/code/commands/load_file.py +++ b/src/core/widgets/code/commands/load_file.py @@ -14,19 +14,19 @@ from ..source_file import SourceFile def execute( - editor: GtkSource.View, + view: GtkSource.View, gfile: Gio.File, file: SourceFile = None, ): logger.debug("Load File Command") if not file: - file = editor.files.new() + file = view.files_manager.new() file.load_path(gfile) - language = editor.language_manager \ + language = view.language_manager \ .guess_language(file.fname, None) file.ftype = language file.buffer.set_language(language) - file.buffer.set_style_scheme(editor.syntax_theme) + file.buffer.set_style_scheme(view.syntax_theme) diff --git a/src/core/widgets/code/commands/load_start_files.py b/src/core/widgets/code/commands/load_start_files.py index 8f7dbe5..6802699 100644 --- a/src/core/widgets/code/commands/load_start_files.py +++ b/src/core/widgets/code/commands/load_start_files.py @@ -14,7 +14,7 @@ from ..source_file import SourceFile def execute( - editor: GtkSource.View, + view: GtkSource.View, ): logger.debug("Load Start File(s) Command") @@ -25,12 +25,12 @@ def execute( file = starting_files.pop() file = file.replace("FILE|", "") gfile = Gio.File.new_for_path(file) - buffer = editor.get_buffer() - file = editor.files.get_file(buffer) + buffer = view.get_buffer() + file = view.files_manager.get_file(buffer) - editor.command.exec_with_args( + view.command.exec_with_args( "load_file", - (editor, gfile, file) + (view, gfile, file) ) if len(starting_files) == 0: return @@ -39,4 +39,4 @@ def execute( file = file.replace("FILE|", "") gfile = Gio.File.new_for_path(file) - editor.command.exec_with_args("load_file", (editor, gfile)) + view.command.exec_with_args("load_file", (view, gfile)) 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 aa8d6f1..aa49b10 100644 --- a/src/core/widgets/code/commands/move_to_left_sibling.py +++ b/src/core/widgets/code/commands/move_to_left_sibling.py @@ -12,23 +12,22 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Move To Left Sibling Command") - if not editor.sibling_left: return + if not view.sibling_left: return - buffer = editor.get_buffer() - popped_file, sibling_file = editor.files.pop_file(buffer) + buffer = view.get_buffer() + popped_file, sibling_file = view.files_manager.swap_file(buffer) if sibling_file: - sibling_file.subscribe(editor) - editor.set_buffer(sibling_file.buffer) + sibling_file.add_observer(view) + view.set_buffer(sibling_file.buffer) else: - sibling_file = editor.command.exec("new_file") + sibling_file = view.command.exec("new_file") - popped_file.unsubscribe(editor) - popped_file.subscribe(editor.sibling_left) + popped_file.remove_observer(view) + popped_file.add_observer(view.sibling_left) - editor.sibling_left.set_buffer(buffer) - editor.sibling_left.files.append(popped_file) - editor.sibling_left.grab_focus() + view.sibling_left.set_buffer(buffer) + view.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 cb8b84d..bb66efb 100644 --- a/src/core/widgets/code/commands/move_to_right_sibling.py +++ b/src/core/widgets/code/commands/move_to_right_sibling.py @@ -12,24 +12,23 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Move To Right Sibling Command") - if not editor.sibling_right: return + if not view.sibling_right: return - buffer = editor.get_buffer() - popped_file, sibling_file = editor.files.pop_file(buffer) + buffer = view.get_buffer() + popped_file, sibling_file = view.files_manager.swap_file(buffer) if sibling_file: - sibling_file.subscribe(editor) - editor.set_buffer(sibling_file.buffer) + sibling_file.add_observer(view) + view.set_buffer(sibling_file.buffer) else: - sibling_file = editor.command.exec("new_file") + sibling_file = view.command.exec("new_file") - popped_file.unsubscribe(editor) - popped_file.subscribe(editor.sibling_right) + popped_file.remove_observer(view) + popped_file.add_observer(view.sibling_right) - editor.sibling_right.set_buffer(buffer) - editor.sibling_right.files.append(popped_file) - editor.sibling_right.grab_focus() + view.sibling_right.set_buffer(buffer) + view.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 ab34c2f..85eb07a 100644 --- a/src/core/widgets/code/commands/new_file.py +++ b/src/core/widgets/code/commands/new_file.py @@ -1,5 +1,6 @@ # Python imports + # Lib imports import gi @@ -12,21 +13,21 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("New File Command") - file = editor.files.new() - language = editor.language_manager \ + file = view.files_manager.new() + language = view.language_manager \ .guess_language("file.txt", None) file.buffer.set_language(language) - file.buffer.set_style_scheme(editor.syntax_theme) + file.buffer.set_style_scheme(view.syntax_theme) - editor.set_buffer(file.buffer) - file.subscribe(editor) + view.set_buffer(file.buffer) + file.add_observer(view) - has_focus = editor.command.exec("has_focus") + has_focus = view.command.exec("has_focus") if not has_focus: return file - editor.command.exec("update_info_bar") + view.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 eec6c63..82dd055 100644 --- a/src/core/widgets/code/commands/open_files.py +++ b/src/core/widgets/code/commands/open_files.py @@ -12,7 +12,7 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Open File(s) Command") gfiles = event_system.emit_and_await("open-files") @@ -20,14 +20,14 @@ def execute( size = len(gfiles) for i, gfile in enumerate(gfiles): - file = editor.files.new() - editor.command.exec_with_args("load_file", (editor, gfile, file)) + file = view.files_manager.new() + view.command.exec_with_args("load_file", (view, gfile, file)) if i == (size - 1): - buffer = editor.get_buffer() - _file = editor.files.get_file(buffer) - _file.unsubscribe(editor) + buffer = view.get_buffer() + _file = view.files_manager.get_file(buffer) + _file.remove_observer(view) - editor.set_buffer(file.buffer) - file.subscribe(editor) - editor.command.exec("update_info_bar") + view.set_buffer(file.buffer) + file.add_observer(view) + view.command.exec("update_info_bar") diff --git a/src/core/widgets/code/commands/save_file.py b/src/core/widgets/code/commands/save_file.py index bd6897d..f63dd5a 100644 --- a/src/core/widgets/code/commands/save_file.py +++ b/src/core/widgets/code/commands/save_file.py @@ -12,16 +12,16 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Save File Command") - buffer = editor.get_buffer() - file = editor.files.get_file(buffer) + buffer = view.get_buffer() + file = view.files_manager.get_file(buffer) if file.ftype == "buffer": file.save_as() - language = editor.language_manager \ - .guess_language(file.fname, None) + language = view.language_manager \ + .guess_language(file.fname, None) file.ftype = language file.buffer.set_language(language) return diff --git a/src/core/widgets/code/commands/save_file_as.py b/src/core/widgets/code/commands/save_file_as.py index edfbd32..6e1568d 100644 --- a/src/core/widgets/code/commands/save_file_as.py +++ b/src/core/widgets/code/commands/save_file_as.py @@ -12,17 +12,17 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.info("Save File As Command") - buffer = editor.get_buffer() - file = editor.files.get_file(buffer) + buffer = view.get_buffer() + file = view.files_manager.get_file(buffer) file.save_as() - language = editor.language_manager \ + language = view.language_manager \ .guess_language(file.fname, None) file.ftype = language file.buffer.set_language(language) - file.subscribe(editor) - editor.exec_command("update_info_bar") + file.add_observer(view) + view.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 a9feaa1..4f6bf39 100644 --- a/src/core/widgets/code/commands/set_focus_border.py +++ b/src/core/widgets/code/commands/set_focus_border.py @@ -12,15 +12,15 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Set Focus Border Command") - ctx = editor.get_parent().get_style_context() + ctx = view.get_parent().get_style_context() ctx.add_class("source-view-focused") - if editor.sibling_right: - ctx = editor.sibling_right.get_parent().get_style_context() - elif editor.sibling_left: - ctx = editor.sibling_left.get_parent().get_style_context() + if view.sibling_right: + ctx = view.sibling_right.get_parent().get_style_context() + elif view.sibling_left: + ctx = view.sibling_left.get_parent().get_style_context() ctx.remove_class("source-view-focused") diff --git a/src/core/widgets/code/commands/set_miniview.py b/src/core/widgets/code/commands/set_miniview.py index 8155e54..22b7161 100644 --- a/src/core/widgets/code/commands/set_miniview.py +++ b/src/core/widgets/code/commands/set_miniview.py @@ -9,13 +9,19 @@ from gi.repository import GtkSource from gi.repository import Gio # Application imports +from libs.dto.code_event import CodeEvent + from ..source_file import SourceFile def execute( - editor: GtkSource.View, + view: GtkSource.View, ): logger.debug("Set MiniView Command") - event_system.emit("set-mini-view", (editor,)) + event_system.emit("set-mini-view", (view,)) + event = CodeEvent() + event.etype = "focused_view_change" + event.view = view + view.notify_observers(event) diff --git a/src/core/widgets/code/commands/show_completion.py b/src/core/widgets/code/commands/show_completion.py index 89d06a7..ba12082 100644 --- a/src/core/widgets/code/commands/show_completion.py +++ b/src/core/widgets/code/commands/show_completion.py @@ -12,7 +12,7 @@ from gi.repository import GtkSource def execute( - editor: GtkSource.View = None + view: GtkSource.View = None ): logger.debug("Show Completion Command") - editor.completion.request_completion() + view.completion.request_completion() diff --git a/src/core/widgets/code/commands/update_info_bar.py b/src/core/widgets/code/commands/update_info_bar.py index 33a5408..73418a1 100644 --- a/src/core/widgets/code/commands/update_info_bar.py +++ b/src/core/widgets/code/commands/update_info_bar.py @@ -14,11 +14,11 @@ from ..source_file import SourceFile def execute( - editor: GtkSource.View, + view: GtkSource.View, ): logger.debug("Update Info Bar Command") - buffer = editor.get_buffer() - file = editor.files.get_file(buffer) + buffer = view.get_buffer() + file = view.files_manager.get_file(buffer) if not file: return diff --git a/src/core/widgets/general_info_widget.py b/src/core/widgets/code/general_info_widget.py similarity index 95% rename from src/core/widgets/general_info_widget.py rename to src/core/widgets/code/general_info_widget.py index 10e407e..a9c47b6 100644 --- a/src/core/widgets/general_info_widget.py +++ b/src/core/widgets/code/general_info_widget.py @@ -26,10 +26,8 @@ class GeneralInfoWidget(Gtk.Box): def _setup_styling(self): - self.set_margin_top(20) - self.set_margin_bottom(20) - self.set_margin_left(25) - self.set_margin_right(25) + self.set_margin_start(25) + self.set_margin_end(25) def _setup_signals(self): ... 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 5e29146..99b3fc2 100644 --- a/src/core/widgets/code/mixins/source_view_dnd_mixin.py +++ b/src/core/widgets/code/mixins/source_view_dnd_mixin.py @@ -29,8 +29,12 @@ class SourceViewDnDMixin: if len(uris) == 0: uris = data.get_text().split("\n") - pop_file = self.command.exec_with_args("dnd_load_file_to_buffer", (self, uris[0])) - if pop_file: - uris.pop(0) + self._on_uri_data_received(uris) + + def _on_uri_data_received(self, uris: []): + uri = uris.pop(0) + self.command.exec_with_args("dnd_load_file_to_buffer", (self, uri)) + + if len(uris) == 0: return 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 index 63342a2..1dd4b1e 100644 --- a/src/core/widgets/code/mixins/source_view_events_mixin.py +++ b/src/core/widgets/code/mixins/source_view_events_mixin.py @@ -3,6 +3,7 @@ # Lib imports # Application imports +from libs.dto.code_event import CodeEvent @@ -35,12 +36,12 @@ class SourceViewEventsMixin: self.command.exec(command) return True - def notify(self, file, buffer, etype: str): + def notification(self, event: CodeEvent): has_focus = self.command.exec("has_focus") if not has_focus: return self.command.exec("update_info_bar") - match etype: + match event.etype: case "changed": logger.debug("SourceFile._changed") case "modified_changed": diff --git a/src/core/widgets/code/source_file.py b/src/core/widgets/code/source_file.py index 9596f80..bf9f5e5 100644 --- a/src/core/widgets/code/source_file.py +++ b/src/core/widgets/code/source_file.py @@ -12,11 +12,14 @@ from gi.repository import GtkSource from gi.repository import Gio # Application imports +from libs.mixins.observable_mixin import ObservableMixin +from libs.dto.code_event import CodeEvent + from .source_buffer import SourceBuffer -class SourceFile(GtkSource.File): +class SourceFile(GtkSource.File, ObservableMixin): def __init__(self): super(SourceFile, self).__init__() @@ -44,19 +47,39 @@ class SourceFile(GtkSource.File): def _insert_text(self, buffer: SourceBuffer, location: Gtk.TextIter, text: str, length: int ): - self.notify((self, buffer, "insert_text")) + event = CodeEvent() + event.etype = "insert_text" + event.file = self + event.buffer = buffer + + self.notify_observers(event) def _changed(self, buffer: SourceBuffer): - self.notify((self, buffer, "changed")) + event = CodeEvent() + event.etype = "changed" + event.file = self + event.buffer = buffer + + self.notify_observers(event) def _mark_set(self, buffer: SourceBuffer, location: Gtk.TextIter, mark: Gtk.TextMark ): - # self.notify((self, buffer, "mark_set")) + # event = CodeEvent() + # event.etype = "mark_set" + # event.file = self + # event.buffer = buffer + + # self.notify_observers(event) ... def _modified_changed(self, buffer: SourceBuffer): - self.notify((self, buffer, "modified_changed")) + event = CodeEvent() + event.etype = "modified_changed" + event.file = self + event.buffer = buffer + + self.notify_observers(event) def _write_file(self, gfile: Gio.File): @@ -87,16 +110,11 @@ class SourceFile(GtkSource.File): self.fpath = gfile.get_path() self.fname = gfile.get_basename() + event = CodeEvent() + event.etype = "set_path" + event.file = self - def subscribe(self, editor): - self.observers.append(editor) - - def unsubscribe(self, editor): - self.observers.remove(editor) - - def notify(self, data): - for editor in self.observers: - editor.notify(*data) + self.notify_observers(event) def save(self): self._write_file( self.get_location() ) @@ -113,5 +131,5 @@ class SourceFile(GtkSource.File): def close(self): self.observers.clear() - del observers + del self.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 9c5b4eb..245099e 100644 --- a/src/core/widgets/code/source_files_manager.py +++ b/src/core/widgets/code/source_files_manager.py @@ -3,15 +3,21 @@ # Lib imports # Application imports +from libs.mixins.observable_mixin import ObservableMixin +from libs.singleton import Singleton +from libs.dto.code_event import CodeEvent + from .source_file import SourceFile from .source_buffer import SourceBuffer -class SourceFilesManager(list): +class SourceFilesManager(Singleton, list, ObservableMixin): def __init__(self): super(SourceFilesManager, self).__init__() + self.observers = [] + def new(self): file = SourceFile() @@ -22,10 +28,16 @@ class SourceFilesManager(list): if not file: return super().append(file) + event = CodeEvent() + event.file = file + event.etype = "appended_file" + + self.notify_observers(event) + def get_file(self, buffer: SourceBuffer): if not buffer: return - for i, file in enumerate(self): + for file in self: if not buffer == file.buffer: continue return file @@ -45,15 +57,52 @@ class SourceFilesManager(list): j = 0 if size == 1 else i - 1 if i > 1 else i + 1 sibling_file = self[j] + event = CodeEvent() + event.file = popped_file + event.etype = "popped_file" + + self.notify_observers(event) + return popped_file, sibling_file + def swap_file(self, buffer: SourceBuffer): + if not buffer: return + + for i, file in enumerate(self): + if not buffer == file.buffer: continue + + swapped_file = self[i] + sibling_file = None + size = len(self) + + if size == 0: + return swapped_file, sibling_file + + j = 0 if size == 1 else i - 1 if i > 1 else i + 1 + sibling_file = self[j] + + return swapped_file, sibling_file + def remove_file(self, buffer: SourceBuffer): if not buffer: return - for file in self: + for i, file in enumerate(self): if not buffer == file.buffer: continue self.remove(file) + + event = CodeEvent() + event.file = file + event.etype = "removed_file" + + self.notify_observers(event) + + size = len(self) + if size == 0: + return None + + j = 0 if size == 1 else i - 1 if i > 1 else i + 1 + sibling_file = self[j] + file.close() - del file - break + return sibling_file diff --git a/src/core/widgets/code/tab_widget.py b/src/core/widgets/code/tab_widget.py new file mode 100644 index 0000000..f98abfb --- /dev/null +++ b/src/core/widgets/code/tab_widget.py @@ -0,0 +1,53 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + + +class TabWidget(Gtk.Box): + def __init__(self): + super(TabWidget, self).__init__() + + self.file = None + + self._setup_styling() + self._setup_signals() + self._subscribe_to_events() + self._load_widgets() + + self.show_all() + + + def _setup_styling(self): + ctx = self.get_style_context() + ctx.add_class("tab-widget") + + def _setup_signals(self): + ... + + def _subscribe_to_events(self): + ... + + def _load_widgets(self): + self.label = Gtk.Label(label="") + self.close_btn = Gtk.Button(label="X") + + ctx = self.label.get_style_context() + ctx.add_class("tab-label") + ctx = self.close_btn.get_style_context() + ctx.add_class("tab-close-bttn") + + self.label.set_hexpand(True) + + self.add(self.label) + self.add(self.close_btn) + + def __del__(self): + # for handle_id in self._handler_ids: + # self.disconnect(handle_id) + ... \ No newline at end of file diff --git a/src/core/widgets/code/tabs_widget.py b/src/core/widgets/code/tabs_widget.py new file mode 100644 index 0000000..19fb810 --- /dev/null +++ b/src/core/widgets/code/tabs_widget.py @@ -0,0 +1,100 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from libs.dto.code_event import CodeEvent + +from .view import SourceView +from .source_files_manager import SourceFilesManager +from .source_file import SourceFile + +from .tab_widget import TabWidget + + + +class TabsWidget(Gtk.ScrolledWindow): + def __init__(self): + super(TabsWidget, self).__init__() + + self.active_view: SourceView = None + + self._setup_styling() + self._setup_signals() + self._subscribe_to_events() + self._load_widgets() + + + def _setup_styling(self): + self.set_overlay_scrolling(False) + + def _setup_signals(self): + event_system.subscribe("register-view-to-tabs-widget", self._register_view_to_tabs_widget) + + def _subscribe_to_events(self): + self.files_manager: SourceFilesManager = SourceFilesManager() + self.files_manager.add_observer(self) + + def _load_widgets(self): + self.viewport = Gtk.Viewport() + self.tabs = Gtk.ButtonBox() + + self.tabs.set_layout(Gtk.ButtonBoxStyle.CENTER) + + self.viewport.add(self.tabs) + self.add(self.viewport) + + def _register_view_to_tabs_widget(self, view: SourceView): + view.add_observer(self) + view.set_files_manager(self.files_manager) + + def notification(self, event: CodeEvent): + match event.etype: + case "focused_view_change": + logger.debug("SourceView.focused_view_change") + self.active_view = event.view + case "appended_file": + logger.debug("SourceFilesManager.appended") + self.add_tab(event) + case "popped_file": + logger.debug("SourceFilesManager.pop_file") + case "removed_file": + logger.debug("SourceFilesManager.remove_file") + self.remove_tab(event) + case "set_path": + logger.debug("SourceFile.set_path") + self.update_tab_label(event) + case _: + ... + + def add_tab(self, event: CodeEvent): + tab = TabWidget() + tab.file = event.file + + tab.label.set_label(event.file.fname) + event.file.add_observer(self) + + self.tabs.add(tab) + tab.show() + + def remove_tab(self, event: CodeEvent): + for child in self.tabs.get_children(): + if not child.file == event.file: continue + + child.file.remove_observer(self) + self.tabs.remove(child) + + del child.file + del child + + return + + def update_tab_label(self, event: CodeEvent): + for tab in self.tabs.get_children(): + if not tab.file == event.file: continue + tab.label.set_label(event.file.fname) + + return diff --git a/src/core/widgets/code/view.py b/src/core/widgets/code/view.py index ce6b075..fefb7b8 100644 --- a/src/core/widgets/code/view.py +++ b/src/core/widgets/code/view.py @@ -10,6 +10,8 @@ from gi.repository import GLib from gi.repository import GtkSource # Application imports +from libs.mixins.observable_mixin import ObservableMixin + from .mixins.source_view_events_mixin import SourceViewEventsMixin from .mixins.source_view_dnd_mixin import SourceViewDnDMixin @@ -20,13 +22,14 @@ from .key_mapper import KeyMapper -class SourceView(SourceViewEventsMixin, SourceViewDnDMixin, GtkSource.View): +class SourceView(GtkSource.View, ObservableMixin, SourceViewEventsMixin, SourceViewDnDMixin): def __init__(self): super(SourceView, self).__init__() + self.observers = [] + self.sibling_right = None self.sibling_left = None - self.key_mapper = KeyMapper() self._setup_styles() self._setup_signals() @@ -57,6 +60,7 @@ class SourceView(SourceViewEventsMixin, SourceViewDnDMixin, GtkSource.View): def _setup_signals(self): self.map_id = self.connect("map", self._init_map) + self.connect("focus-in-event", self._focus_in_event) self.connect("drag-data-received", self._on_drag_data_received) self.connect("move-cursor", self._move_cursor) self.connect("key-press-event", self._key_press_event) @@ -69,6 +73,7 @@ class SourceView(SourceViewEventsMixin, SourceViewDnDMixin, GtkSource.View): def _load_widgets(self): self._set_up_dnd() + event_system.emit("register-view-to-tabs-widget", (self,)) def _init_map(self, view): self.disconnect(self.map_id) @@ -79,8 +84,9 @@ class SourceView(SourceViewEventsMixin, SourceViewDnDMixin, GtkSource.View): def _init_show(self): self.language_manager = GtkSource.LanguageManager() self.style_scheme_manager = GtkSource.StyleSchemeManager() + + self.key_mapper = KeyMapper() self.command = CommandSystem() - self.files = SourceFilesManager() self.completion = CompletionManager() self.command.set_data(self) @@ -93,13 +99,15 @@ class SourceView(SourceViewEventsMixin, SourceViewDnDMixin, GtkSource.View): f"{settings_manager.settings.theming.syntax_theme}" ) - self.connect("focus-in-event", self._focus_in_event) - self.command.exec("new_file") + if not self.sibling_right: return self.grab_focus() - self._focus_in_event(None, None) self.command.exec("load_start_files") return False + + def set_files_manager(self, files_manager: SourceFilesManager): + self.files_manager = files_manager + # self.files_manager.add_observer(self) diff --git a/src/libs/dto/code_event.py b/src/libs/dto/code_event.py new file mode 100644 index 0000000..d3b0d90 --- /dev/null +++ b/src/libs/dto/code_event.py @@ -0,0 +1,16 @@ +# Python imports +from dataclasses import dataclass, field + +# Lib imports + +# Application imports +from .observable_event import ObservableEvent + + + +@dataclass +class CodeEvent(ObservableEvent): + etype: str = "" + view: any = None + file: any = None + buffer: any = None \ No newline at end of file diff --git a/src/libs/dto/observable_event.py b/src/libs/dto/observable_event.py new file mode 100644 index 0000000..1a51caa --- /dev/null +++ b/src/libs/dto/observable_event.py @@ -0,0 +1,10 @@ +# Python imports + +# Lib imports + +# Application imports + + + +class ObservableEvent: + ... \ No newline at end of file diff --git a/src/libs/mixins/observable_mixin.py b/src/libs/mixins/observable_mixin.py new file mode 100644 index 0000000..64207e5 --- /dev/null +++ b/src/libs/mixins/observable_mixin.py @@ -0,0 +1,26 @@ +# Python imports + +# Lib imports + +# Application imports +from ..dto.observable_event import ObservableEvent + + + +class ObservableMixin: + observers = [] + + def add_observer(self, observer: any): + if not hasattr(observer, 'notification') or not callable(getattr(observer, 'notification')): + raise ValueError(f"Observer '{observer}' must implement a `notification` method.") + + self.observers.append(observer) + + def remove_observer(self, observer: any): + if not observer in self.observers: return + + self.observers.remove(observer) + + def notify_observers(self, event: ObservableEvent): + for observer in self.observers: + observer.notification(event) \ No newline at end of file diff --git a/src/libs/singleton.py b/src/libs/singleton.py index b484b28..5f791c0 100644 --- a/src/libs/singleton.py +++ b/src/libs/singleton.py @@ -15,8 +15,15 @@ class Singleton: _instance = None def __new__(cls, *args, **kwargs): - if cls._instance: - raise SingletonError(f"'{cls.__name__}' is a Singleton. Cannot create a new instance...") + if cls._instance is not None: + logger.debug(f"'{cls.__name__}' is a Singleton. Returning instance...") + return cls._instance cls._instance = super(Singleton, cls).__new__(cls) return cls._instance + + def __init__(self): + if self._instance is not None: + return + + super(Singleton, self).__init__() diff --git a/src/libs/singleton_raised.py b/src/libs/singleton_raised.py new file mode 100644 index 0000000..e720553 --- /dev/null +++ b/src/libs/singleton_raised.py @@ -0,0 +1,26 @@ +# Python imports + +# Lib imports + +# Application imports + + + +class SingletonError(Exception): + pass + + + +class SingletonRaised: + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is not None: + raise SingletonError(f"'{cls.__name__}' is a Singleton. Cannot create a new instance...") + + cls._instance = super(Singleton, cls).__new__(cls) + return cls._instance + + def __init__(self): + if cls._instance is not None: + return diff --git a/user_config/usr/share/app_name/stylesheet.css b/user_config/usr/share/app_name/stylesheet.css index a8ed7b6..d8d272c 100644 --- a/user_config/usr/share/app_name/stylesheet.css +++ b/user_config/usr/share/app_name/stylesheet.css @@ -1,3 +1,5 @@ +/* ---- Make most desired things base transparent ---- */ + /* ---- Make most desired things base transparent ---- */ popover, popover > box, @@ -10,6 +12,14 @@ viewport { color: rgba(255, 255, 255, 1); } +scrolledwindow * { + background: rgba(0, 0, 0, 0.0); +} + +border { + background: rgba(39, 43, 52, 0.84); +} + tab { color: rgba(255, 255, 255, 1); } @@ -31,35 +41,349 @@ tab:checked { color: rgba(255, 255, 255, 1); } - .base-container { margin: 10px; } - - -/* * selection { - background-color: rgba(116, 0, 0, 0.65); - color: rgba(255, 255, 255, 0.5); -} */ - -/* Rubberband coloring */ -/* .rubberband, -rubberband, -flowbox rubberband, -treeview.view rubberband, -.content-view rubberband, -.content-view .rubberband, -XfdesktopIconView.view .rubberband { - border: 1px solid #6c6c6c; - background-color: rgba(21, 158, 167, 0.57); +.source-view-focused { + border-style: solid; + border-color: white; + border-width: 0.05em; } -XfdesktopIconView.view:active { - background-color: rgba(172, 102, 21, 1); -} */ +.tab-widget { + padding-left: 0.2em; + padding-right: 0.2em; + margin-right: 0.8em; + border-top-style: solid; + border-top-color: rgba(255, 255, 255, 0.64); + border-top-width: 2px; + + border-left-style: solid; + border-left-color: rgba(255, 255, 255, 0.64); + border-left-width: 2px; + + border-right-style: solid; + border-right-color: rgba(255, 255, 255, 0.64); + border-right-width: 2px; +} + +.tab-label { + margin-left: 2em; + margin-right: 2em; +} + +.tab-close-bttn { + background: rgba(116, 0, 0, 0.64); + border-color: rgba(0, 0, 0, 0.64); + color: rgba(255, 255, 255, 0.64); + border-style: solid; + border-width: 1px; +} + +.tab-close-bttn:hover { + background: rgba(256, 0, 0, 0.64); +} + + +.file-changed { + color: rgba(255, 168, 0, 0.64); +} + +.file-deleted { + color: rgba(255, 0, 0, 0.64); +} + + + + + + + + + + +/*popover,*/ +/*popover > box*/ +/*.main-window > box,*/ +/*.main-window > box > box,*/ +/*.main-window > box > box > paned,*/ +/*.main-window > box > box > paned > notebook,*/ +/*.main-window > box > box > paned > notebook > stack,*/ +/*.main-window > box > box > paned > notebook > stack > scrolledwindow > textview > * {*/ +/* background: rgba(0, 0, 0, 0.0);*/ +/* color: rgba(255, 255, 255, 1);*/ +/*}*/ + + +/* ---- top controls ---- */ +/*.main-window > box > box > button,*/ +/*.main-window > box > box > buttonbox > button {*/ +/* background: rgba(39, 43, 52, 0.64);*/ +/* margin-left: 12px;*/ +/* margin-right: 12px;*/ +/*}*/ + + +/* status bar */ +/*.main-window > box > statusbar {*/ +/* background: rgba(39, 43, 52, 0.64);*/ +/* padding-left: 96px;*/ +/* padding-right: 96px;*/ +/*}*/ + + +/* ---- notebook headers ---- */ +/*notebook > header {*/ +/* background: rgba(39, 43, 52, 0.72);*/ +/*}*/ + +/*notebook > header > tabs > tab {*/ +/* color: rgba(255, 255, 255, 1);*/ +/*}*/ + +/*notebook > header > tabs > tab:active {*/ +/* background: rgba(0, 0, 0, 0.0);*/ +/*}*/ + + +/* ---- notebook tab buttons ---- */ +/*tab > box > button {*/ +/* background: rgba(116, 0, 0, 0.64);*/ +/*}*/ + +/*tab > box > button:hover {*/ +/* background: rgba(256, 0, 0, 0.64);*/ +/*}*/ + +/*notebook > header > tabs > tab:checked {*/ + /* Neon Blue 00e8ff */ + /* background-color: rgba(0, 232, 255, 0.2); */ + +/* background-color: rgba(255, 255, 255, 0.46);*/ + + /* Dark Bergundy */ + /* background-color: rgba(116, 0, 0, 0.25); */ + +/* color: rgba(255, 255, 255, 0.8);*/ +/*}*/ + +/*.notebook-selected-focus {*/ + /* Neon Blue 00e8ff border */ + /* border: 2px solid rgba(0, 232, 255, 0.34); */ + +/* border: 2px solid rgba(255, 255, 255, 0.46);*/ + + /* Dark Bergundy */ + /* border: 2px solid rgba(116, 0, 0, 0.64); */ +/*}*/ + + +/* ---- source code notebooks ---- */ +/*notebook > stack > scrolledwindow > textview {*/ +/* background: rgba(39, 43, 52, 0.64);*/ +/* color: rgba(255, 255, 255, 1);*/ +/*}*/ + +/*textview {*/ +/* padding-bottom: 50em;*/ +/*}*/ + + +/* any popover */ +/*popover {*/ +/* background: rgba(39, 43, 52, 0.86);*/ +/* color: rgba(255, 255, 255, 1);*/ +/*}*/ + +/* ---- make text selection slightly transparent ---- */ +/** selection {*/ +/* background-color: rgba(0, 115, 115, 0.34);*/ + /* Bergundy */ + /* background-color: rgba(116, 0, 0, 0.64); */ +/* color: rgba(255, 255, 255, 0.5);*/ +/*}*/ + + +/* ---- miniview properties ---- */ +/*.source-view,*/ +/*.mini-view {*/ +/* font-family: Monospace;*/ +/*}*/ + +/* the mini view container of text */ +/*.mini-view > text {*/ +/* background: rgba(39, 43, 52, 0.64);*/ +/*}*/ + +/* draggable overlay of the miniview */ +/*.mini-view > * {*/ +/* background: rgba(64, 64, 64, 0.32);*/ +/* color: rgba(255, 255, 255, 1);*/ +/* font-size: 1px;*/ +/*}*/ + +/*.mini-view > text {*/ +/* background: rgba(39, 43, 52, 0.64);*/ +/*}*/ + + +/* other properties */ +/*.buffer_changed {*/ +/* color: rgba(255, 168, 0, 1.0);*/ + /* color: rgba(0, 232, 255, 0.64); */ +/*}*/ + + +/*.searching,*/ +/*.search_success,*/ +/*.search_fail {*/ +/* border-style: solid;*/ +/*}*/ + +/*.searching {*/ +/* border-color: rgba(0, 225, 225, 0.64);*/ +/*}*/ +/*.search_success {*/ +/* background: rgba(136, 204, 39, 0.12);*/ +/* border-color: rgba(136, 204, 39, 1);*/ +/*}*/ +/*.search_fail {*/ +/* background: rgba(170, 18, 18, 0.12);*/ +/* border-color: rgba(200, 18, 18, 1);*/ +/*}*/ + + + + + + + + + + +.error_txt { color: rgb(170, 18, 18); } +.warning_txt { color: rgb(255, 168, 0); } +.success_txt { color: rgb(136, 204, 39); } + + + + +/* + Need these because updating buffer with get_tag_table and tags updates minimap to the same size due to its mapping structure... + I've tried initial looks at: + https://github.com/johnfactotum/gedit-restore-minimap + which is re-adding minimap to gedit and they just used the below code snippit which still didn't work for me. + + desc = Pango.FontDescription(default_font) + desc.set_size(Pango.SCALE) # set size to 1pt + desc.set_family('BuilderBlocks,' + desc.get_family()) + self.source_map.set_property('font-desc', desc) + + So now we do this monstrocity until I can figure out what is needed to make something better work. + +*/ +.px1 { font-size: 1px; } +.px2 { font-size: 2px; } +.px3 { font-size: 3px; } +.px4 { font-size: 4px; } +.px5 { font-size: 5px; } +.px6 { font-size: 6px; } +.px7 { font-size: 7px; } +.px8 { font-size: 8px; } +.px9 { font-size: 9px; } +.px10 { font-size: 10px; } +.px11 { font-size: 11px; } +.px12 { font-size: 12px; } +.px13 { font-size: 13px; } +.px14 { font-size: 14px; } +.px15 { font-size: 15px; } +.px16 { font-size: 16px; } +.px17 { font-size: 17px; } +.px18 { font-size: 18px; } +.px19 { font-size: 19px; } +.px20 { font-size: 20px; } +.px21 { font-size: 21px; } +.px22 { font-size: 22px; } +.px23 { font-size: 23px; } +.px24 { font-size: 24px; } +.px25 { font-size: 25px; } +.px26 { font-size: 26px; } +.px27 { font-size: 27px; } +.px28 { font-size: 28px; } +.px29 { font-size: 29px; } +.px30 { font-size: 30px; } +.px31 { font-size: 31px; } +.px32 { font-size: 32px; } +.px33 { font-size: 33px; } +.px34 { font-size: 34px; } +.px35 { font-size: 35px; } +.px36 { font-size: 36px; } +.px37 { font-size: 37px; } +.px38 { font-size: 38px; } +.px39 { font-size: 39px; } +.px40 { font-size: 40px; } +.px41 { font-size: 41px; } +.px42 { font-size: 42px; } +.px43 { font-size: 43px; } +.px44 { font-size: 44px; } +.px45 { font-size: 45px; } +.px46 { font-size: 46px; } +.px47 { font-size: 47px; } +.px48 { font-size: 48px; } +.px49 { font-size: 49px; } +.px50 { font-size: 50px; } +.px51 { font-size: 51px; } +.px52 { font-size: 52px; } +.px53 { font-size: 53px; } +.px54 { font-size: 54px; } +.px55 { font-size: 55px; } +.px56 { font-size: 56px; } +.px57 { font-size: 57px; } +.px58 { font-size: 58px; } +.px59 { font-size: 59px; } +.px60 { font-size: 60px; } +.px61 { font-size: 61px; } +.px62 { font-size: 62px; } +.px63 { font-size: 63px; } +.px64 { font-size: 64px; } +.px65 { font-size: 65px; } +.px66 { font-size: 66px; } +.px67 { font-size: 67px; } +.px68 { font-size: 68px; } +.px69 { font-size: 69px; } +.px70 { font-size: 70px; } +.px71 { font-size: 71px; } +.px72 { font-size: 72px; } +.px73 { font-size: 73px; } +.px74 { font-size: 74px; } +.px75 { font-size: 75px; } +.px76 { font-size: 76px; } +.px77 { font-size: 77px; } +.px78 { font-size: 78px; } +.px79 { font-size: 79px; } +.px80 { font-size: 80px; } +.px81 { font-size: 81px; } +.px82 { font-size: 82px; } +.px83 { font-size: 83px; } +.px84 { font-size: 84px; } +.px85 { font-size: 85px; } +.px86 { font-size: 86px; } +.px87 { font-size: 87px; } +.px88 { font-size: 88px; } +.px89 { font-size: 89px; } +.px90 { font-size: 90px; } +.px91 { font-size: 91px; } +.px92 { font-size: 92px; } +.px93 { font-size: 93px; } +.px94 { font-size: 94px; } +.px95 { font-size: 95px; } +.px96 { font-size: 96px; } +.px97 { font-size: 97px; } +.px98 { font-size: 98px; } +.px99 { font-size: 99px; } @@ -161,5 +485,4 @@ XfdesktopIconView.view:active { .mw_transparency_96 { background: rgba(39, 43, 52, 0.96); } .mw_transparency_97 { background: rgba(39, 43, 52, 0.97); } .mw_transparency_98 { background: rgba(39, 43, 52, 0.98); } -.mw_transparency_99 { background: rgba(39, 43, 52, 0.99); } -.mw_transparency_100 { background: rgba(39, 43, 52, 1.00); } \ No newline at end of file +.mw_transparency_99 { background: rgba(39, 43, 52, 0.99); } \ No newline at end of file