Improved multi insert feature; separated events to files

This commit is contained in:
itdominator 2023-10-11 23:32:02 -05:00
parent 198f483863
commit 92f1eab4d8
5 changed files with 232 additions and 160 deletions

View File

@ -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
return self.core_widget

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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