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("focus-in-event", self._focus_in_event)
|
||||||
|
|
||||||
self.connect("drag-data-received", self._on_drag_data_received)
|
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('changed', self._is_modified)
|
||||||
self._buffer.connect("mark-set", self._on_cursor_move)
|
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):
|
def _insert_text(self, text_buffer, location_itr, text_str, len_int):
|
||||||
if self.freeze_multi_line_insert: return
|
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():
|
with self._buffer.freeze_notify():
|
||||||
GLib.idle_add(self._update_multi_line_markers, *(text_str,))
|
GLib.idle_add(self._update_multi_line_markers, *(text_str,))
|
||||||
|
|
||||||
def _update_multi_line_markers(self, text_str):
|
def _button_press_event(self, widget = None, eve = None, user_data = None):
|
||||||
self.freeze_multi_line_insert = True
|
if eve.type == Gdk.EventType.BUTTON_PRESS and eve.button == 1 : # l-click
|
||||||
|
if eve.state & Gdk.ModifierType.CONTROL_MASK:
|
||||||
for mark in self._multi_insert_marks:
|
self.button_press_insert_mark(eve)
|
||||||
itr = self._buffer.get_iter_at_mark(mark)
|
return True
|
||||||
self._buffer.insert(itr, text_str, -1)
|
else:
|
||||||
|
self.keyboard_clear_marks()
|
||||||
self.freeze_multi_line_insert = False
|
elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
|
||||||
|
...
|
||||||
|
|
||||||
def _focus_in_event(self, widget, eve = None):
|
def _focus_in_event(self, widget, eve = None):
|
||||||
event_system.emit("set_active_src_view", (self,))
|
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...
|
# NOTE: Not sure but this might not be efficient if the map reloads the same view...
|
||||||
event_system.emit(f"set_source_view", (self,))
|
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):
|
def _set_up_dnd(self):
|
||||||
PLAIN_TEXT_TARGET_TYPE = 70
|
PLAIN_TEXT_TARGET_TYPE = 70
|
||||||
URI_TARGET_TYPE = 80
|
URI_TARGET_TYPE = 80
|
||||||
|
@ -204,56 +200,3 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
|
||||||
gfile = Gio.File.new_for_path(uri)
|
gfile = Gio.File.new_for_path(uri)
|
||||||
|
|
||||||
event_system.emit('create_view', (gfile,))
|
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
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gio
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
from .source_file_events_mixin import FileEventsMixin
|
||||||
|
from .source_marks_events_mixin import MarkEventsMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SourceViewEventsMixin:
|
class SourceViewEventsMixin(MarkEventsMixin, FileEventsMixin):
|
||||||
def get_current_file(self):
|
|
||||||
return self._current_file
|
|
||||||
|
|
||||||
def set_buffer_language(self, language = "python3"):
|
def set_buffer_language(self, language = "python3"):
|
||||||
self._buffer.set_language( self._language_manager.get_language(language) )
|
self._buffer.set_language( self._language_manager.get_language(language) )
|
||||||
|
@ -69,24 +66,6 @@ class SourceViewEventsMixin:
|
||||||
def keyboard_tggl_comment(self):
|
def keyboard_tggl_comment(self):
|
||||||
logger.info("SourceViewEventsMixin > keyboard_tggl_comment > stub...")
|
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):
|
def got_to_line(self, line: int = 0):
|
||||||
index = line - 1
|
index = line - 1
|
||||||
buffer = self.get_buffer()
|
buffer = self.get_buffer()
|
||||||
|
@ -111,70 +90,6 @@ class SourceViewEventsMixin:
|
||||||
def move_lines_down(self):
|
def move_lines_down(self):
|
||||||
self.emit("move-lines", *(True,))
|
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):
|
def update_labels(self, gfile = None):
|
||||||
if not gfile: return
|
if not gfile: return
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue