generated from itdominator/Python-With-Gtk-Template
Improved multi insert feature; separated events to files
This commit is contained in:
parent
198f483863
commit
92f1eab4d8
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue