From 92f1eab4d895251a527c497fa70749b2a5e5177f Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 11 Oct 2023 23:32:02 -0500 Subject: [PATCH] Improved multi insert feature; separated events to files --- src/core/controller.py | 2 +- .../sourceview/source_file_events_mixin.py | 134 ++++++++++++++++++ .../sourceview/source_marks_events_mixin.py | 80 +++++++++++ .../widgets/base/sourceview/source_view.py | 85 ++--------- .../base/sourceview/source_view_events.py | 91 +----------- 5 files changed, 232 insertions(+), 160 deletions(-) create mode 100644 src/core/widgets/base/sourceview/source_file_events_mixin.py create mode 100644 src/core/widgets/base/sourceview/source_marks_events_mixin.py diff --git a/src/core/controller.py b/src/core/controller.py index e818f8b..b2de605 100644 --- a/src/core/controller.py +++ b/src/core/controller.py @@ -64,4 +64,4 @@ class Controller(SignalsMixins, ControllerData): settings_manager.register_signals_to_builder([self, self.core_widget]) def get_core_widget(self): - return self.core_widget \ No newline at end of file + return self.core_widget diff --git a/src/core/widgets/base/sourceview/source_file_events_mixin.py b/src/core/widgets/base/sourceview/source_file_events_mixin.py new file mode 100644 index 0000000..86b83fe --- /dev/null +++ b/src/core/widgets/base/sourceview/source_file_events_mixin.py @@ -0,0 +1,134 @@ +# Python imports +import random + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('GtkSource', '4') +from gi.repository import Gtk +from gi.repository import Gio +from gi.repository import GtkSource + +# Application imports + + + +class FileEventsMixin: + + def get_current_file(self): + return self._current_file + + def open_file(self, gfile, line: int = 0, *args): + self._current_file = gfile + + self.load_file_info(gfile) + self.load_file_async(gfile, line) + self._create_file_watcher(gfile) + + def save_file(self): + self._skip_file_load = True + gfile = event_system.emit_and_await("save_file_dialog", (self._current_filename, self._current_file)) if not self._current_file else self._current_file + + if not gfile: + self._skip_file_load = False + return + + self.open_file( self._write_file(gfile) ) + self._skip_file_load = False + + def save_file_as(self): + gfile = event_system.emit_and_await("save_file_dialog", (self._current_filename, self._current_file)) + self._write_file(gfile, True) + if gfile: event_system.emit("create_view", (gfile,)) + + def load_file_info(self, gfile): + info = gfile.query_info("standard::*", 0, cancellable=None) + content_type = info.get_content_type() + display_name = info.get_display_name() + self._current_filename = display_name + + try: + lm = self._language_manager.guess_language(None, content_type) + self._current_filetype = lm.get_id() + self.set_buffer_language(self._current_filetype) + except Exception as e: + ... + + logger.debug(f"Detected Content Type: {content_type}") + if self._current_filetype == "buffer": + self._current_filetype = info.get_content_type() + + def load_file_async(self, gfile, line: int = 0): + if self._skip_file_load: + self.update_labels(gfile) + return + + file = GtkSource.File() + file.set_location(gfile) + self._file_loader = GtkSource.FileLoader.new(self._buffer, file) + + def finish_load_callback(obj, res, user_data=None): + self._file_loader.load_finish(res) + self._is_changed = False + self._document_loaded() + self.got_to_line(line) + self.update_labels(gfile) + + self._file_loader.load_async(io_priority = 70, + cancellable = None, + progress_callback = None, + progress_callback_data = None, + callback = finish_load_callback, + user_data = (None)) + + def _create_file_watcher(self, gfile = None): + if not gfile: return + + self._cancel_current_file_watchers() + self._file_change_watcher = gfile.monitor(Gio.FileMonitorFlags.NONE, Gio.Cancellable()) + self._file_change_watcher.connect("changed", self._file_monitor) + + def _file_monitor(self, file_monitor, file, other_file = None, eve_type = None, data = None): + if not file.get_path() == self._current_file.get_path(): return + + if eve_type in [Gio.FileMonitorEvent.CREATED, + Gio.FileMonitorEvent.DELETED, + Gio.FileMonitorEvent.RENAMED, + Gio.FileMonitorEvent.MOVED_IN, + Gio.FileMonitorEvent.MOVED_OUT]: + self._is_changed = True + + if eve_type in [ Gio.FileMonitorEvent.CHANGES_DONE_HINT ]: + if self._ignore_internal_change: + self._ignore_internal_change = False + return + + # TODO: Any better way to load the difference?? + if self._current_file.query_exists(): + self.load_file_async(self._current_file) + + def _cancel_current_file_watchers(self): + if self._file_change_watcher: + self._file_change_watcher.cancel() + self._file_change_watcher = None + + if self._file_cdr_watcher: + self._file_cdr_watcher.cancel() + self._file_cdr_watcher = None + + def _write_file(self, gfile, save_as = False): + if not gfile: return + + with open(gfile.get_path(), 'w') as f: + if not save_as: + self._ignore_internal_change = True + self._is_changed = False + + 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) + f.close() + + return gfile diff --git a/src/core/widgets/base/sourceview/source_marks_events_mixin.py b/src/core/widgets/base/sourceview/source_marks_events_mixin.py new file mode 100644 index 0000000..6c5fd86 --- /dev/null +++ b/src/core/widgets/base/sourceview/source_marks_events_mixin.py @@ -0,0 +1,80 @@ +# Python imports +import random + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + + +class MarkEventsMixin: + + def keyboard_insert_mark(self, target_iter = None, is_keyboard_insert = True): + if not target_iter: + target_iter = self._buffer.get_iter_at_mark( self._buffer.get_insert() ) + + found_mark = self.check_for_insert_marks(target_iter, is_keyboard_insert) + if not found_mark: + random_bits = random.getrandbits(128) + hash = "%032x" % random_bits + mark = Gtk.TextMark.new(name = f"multi_insert_{hash}", left_gravity = False) + + self._buffer.add_mark(mark, target_iter) + self._multi_insert_marks.append(mark) + mark.set_visible(True) + + def button_press_insert_mark(self, eve): + data = self.window_to_buffer_coords(Gtk.TextWindowType.TEXT , eve.x, eve.y) + is_over_text, target_iter, is_trailing = self.get_iter_at_position(data.buffer_x, data.buffer_y) + + if not is_over_text: + # NOTE: Trying to put at very end of line if not over text (aka, clicking right of text) + target_iter.forward_visible_line() + target_iter.backward_char() + + self.keyboard_insert_mark(target_iter, is_keyboard_insert = False) + + def check_for_insert_marks(self, target_iter, is_keyboard_insert): + marks = target_iter.get_marks() + found_mark = False + for mark in marks: + for _mark in self._multi_insert_marks: + if _mark == mark: + mark.set_visible(False) + self._buffer.delete_mark(mark) + found_mark = True + break + + if found_mark: + self._multi_insert_marks.remove(mark) + break + + if not is_keyboard_insert: + for mark in marks: + if "insert" in mark.get_name(): + found_mark = True + + return found_mark + + def keyboard_clear_marks(self): + self._buffer.begin_user_action() + + for mark in self._multi_insert_marks: + mark.set_visible(False) + self._buffer.delete_mark(mark) + + self._multi_insert_marks.clear() + self._buffer.end_user_action() + + + def _update_multi_line_markers(self, text_str): + for mark in self._multi_insert_marks: + iter = self._buffer.get_iter_at_mark(mark) + self._buffer.insert(iter, text_str, -1) + + if len(self._multi_insert_marks) > 0: + self._buffer.end_user_action() + self.freeze_multi_line_insert = False diff --git a/src/core/widgets/base/sourceview/source_view.py b/src/core/widgets/base/sourceview/source_view.py index 689e942..3b20c56 100644 --- a/src/core/widgets/base/sourceview/source_view.py +++ b/src/core/widgets/base/sourceview/source_view.py @@ -81,7 +81,7 @@ class SourceView(SourceViewEventsMixin, GtkSource.View): self.connect("focus-in-event", self._focus_in_event) self.connect("drag-data-received", self._on_drag_data_received) - self.connect("button-release-event", self._button_release_event) + self.connect("button-press-event", self._button_press_event) self._buffer.connect('changed', self._is_modified) self._buffer.connect("mark-set", self._on_cursor_move) @@ -124,17 +124,22 @@ class SourceView(SourceViewEventsMixin, GtkSource.View): def _insert_text(self, text_buffer, location_itr, text_str, len_int): if self.freeze_multi_line_insert: return + if len(self._multi_insert_marks) > 0: + self._buffer.begin_user_action() + self.freeze_multi_line_insert = True + with self._buffer.freeze_notify(): GLib.idle_add(self._update_multi_line_markers, *(text_str,)) - def _update_multi_line_markers(self, text_str): - self.freeze_multi_line_insert = True - - for mark in self._multi_insert_marks: - itr = self._buffer.get_iter_at_mark(mark) - self._buffer.insert(itr, text_str, -1) - - self.freeze_multi_line_insert = False + def _button_press_event(self, widget = None, eve = None, user_data = None): + if eve.type == Gdk.EventType.BUTTON_PRESS and eve.button == 1 : # l-click + if eve.state & Gdk.ModifierType.CONTROL_MASK: + self.button_press_insert_mark(eve) + return True + else: + self.keyboard_clear_marks() + elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click + ... def _focus_in_event(self, widget, eve = None): event_system.emit("set_active_src_view", (self,)) @@ -159,15 +164,6 @@ class SourceView(SourceViewEventsMixin, GtkSource.View): # NOTE: Not sure but this might not be efficient if the map reloads the same view... event_system.emit(f"set_source_view", (self,)) - def _button_release_event(self, widget = None, eve = None, user_data = None): - if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1 : # l-click - if eve.state & Gdk.ModifierType.CONTROL_MASK: - self.keyboard_insert_mark() - else: - self.keyboard_clear_marks() - elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click - ... - def _set_up_dnd(self): PLAIN_TEXT_TARGET_TYPE = 70 URI_TARGET_TYPE = 80 @@ -204,56 +200,3 @@ class SourceView(SourceViewEventsMixin, GtkSource.View): gfile = Gio.File.new_for_path(uri) event_system.emit('create_view', (gfile,)) - - - def _create_file_watcher(self, gfile = None): - if not gfile: return - - self._cancel_current_file_watchers() - self._file_change_watcher = gfile.monitor(Gio.FileMonitorFlags.NONE, Gio.Cancellable()) - self._file_change_watcher.connect("changed", self._file_monitor) - - def _file_monitor(self, file_monitor, file, other_file = None, eve_type = None, data = None): - if not file.get_path() == self._current_file.get_path(): return - - if eve_type in [Gio.FileMonitorEvent.CREATED, - Gio.FileMonitorEvent.DELETED, - Gio.FileMonitorEvent.RENAMED, - Gio.FileMonitorEvent.MOVED_IN, - Gio.FileMonitorEvent.MOVED_OUT]: - self._is_changed = True - - if eve_type in [ Gio.FileMonitorEvent.CHANGES_DONE_HINT ]: - if self._ignore_internal_change: - self._ignore_internal_change = False - return - - # TODO: Any better way to load the difference?? - if self._current_file.query_exists(): - self.load_file_async(self._current_file) - - def _cancel_current_file_watchers(self): - if self._file_change_watcher: - self._file_change_watcher.cancel() - self._file_change_watcher = None - - if self._file_cdr_watcher: - self._file_cdr_watcher.cancel() - self._file_cdr_watcher = None - - def _write_file(self, gfile, save_as = False): - if not gfile: return - - with open(gfile.get_path(), 'w') as f: - if not save_as: - self._ignore_internal_change = True - self._is_changed = False - - 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) - f.close() - - return gfile diff --git a/src/core/widgets/base/sourceview/source_view_events.py b/src/core/widgets/base/sourceview/source_view_events.py index 5bac8e0..f8e6272 100644 --- a/src/core/widgets/base/sourceview/source_view_events.py +++ b/src/core/widgets/base/sourceview/source_view_events.py @@ -3,18 +3,15 @@ # Lib imports import gi gi.require_version('Gtk', '3.0') -gi.require_version('GtkSource', '4') from gi.repository import Gtk -from gi.repository import Gio -from gi.repository import GtkSource # Application imports +from .source_file_events_mixin import FileEventsMixin +from .source_marks_events_mixin import MarkEventsMixin -class SourceViewEventsMixin: - def get_current_file(self): - return self._current_file +class SourceViewEventsMixin(MarkEventsMixin, FileEventsMixin): def set_buffer_language(self, language = "python3"): self._buffer.set_language( self._language_manager.get_language(language) ) @@ -69,24 +66,6 @@ class SourceViewEventsMixin: def keyboard_tggl_comment(self): logger.info("SourceViewEventsMixin > keyboard_tggl_comment > stub...") - def keyboard_insert_mark(self): - iter = self._buffer.get_iter_at_mark( self._buffer.get_insert() ) - mark = Gtk.TextMark.new(name = f"multi_insert_{len(self._multi_insert_marks)}", left_gravity = False) - - self._buffer.add_mark(mark, iter) - self._multi_insert_marks.append(mark) - mark.set_visible(True) - - def keyboard_clear_marks(self): - self._buffer.begin_user_action() - - for mark in self._multi_insert_marks: - mark.set_visible(False) - self._buffer.delete_mark(mark) - - self._multi_insert_marks.clear() - self._buffer.end_user_action() - def got_to_line(self, line: int = 0): index = line - 1 buffer = self.get_buffer() @@ -111,70 +90,6 @@ class SourceViewEventsMixin: def move_lines_down(self): self.emit("move-lines", *(True,)) - def open_file(self, gfile, line: int = 0, *args): - self._current_file = gfile - - self.load_file_info(gfile) - self.load_file_async(gfile, line) - self._create_file_watcher(gfile) - - def save_file(self): - self._skip_file_load = True - gfile = event_system.emit_and_await("save_file_dialog", (self._current_filename, self._current_file)) if not self._current_file else self._current_file - - if not gfile: - self._skip_file_load = False - return - - self.open_file( self._write_file(gfile) ) - self._skip_file_load = False - - def save_file_as(self): - gfile = event_system.emit_and_await("save_file_dialog", (self._current_filename, self._current_file)) - self._write_file(gfile, True) - if gfile: event_system.emit("create_view", (gfile,)) - - def load_file_info(self, gfile): - info = gfile.query_info("standard::*", 0, cancellable=None) - content_type = info.get_content_type() - display_name = info.get_display_name() - self._current_filename = display_name - - try: - lm = self._language_manager.guess_language(None, content_type) - self._current_filetype = lm.get_id() - self.set_buffer_language(self._current_filetype) - except Exception as e: - ... - - logger.debug(f"Detected Content Type: {content_type}") - if self._current_filetype == "buffer": - self._current_filetype = info.get_content_type() - - def load_file_async(self, gfile, line: int = 0): - if self._skip_file_load: - self.update_labels(gfile) - return - - file = GtkSource.File() - file.set_location(gfile) - self._file_loader = GtkSource.FileLoader.new(self._buffer, file) - - def finish_load_callback(obj, res, user_data=None): - self._file_loader.load_finish(res) - self._is_changed = False - self._document_loaded() - self.got_to_line(line) - self.update_labels(gfile) - - self._file_loader.load_async(io_priority = 98, - cancellable = None, - progress_callback = None, - progress_callback_data = None, - callback = finish_load_callback, - user_data = (None)) - - def update_labels(self, gfile = None): if not gfile: return