generated from itdominator/Python-With-Gtk-Template
Improved loading files and added saving functionality
This commit is contained in:
parent
404aba0c2e
commit
5cf1861929
|
@ -30,3 +30,7 @@ class EditorControllerMixin:
|
|||
self.keyboard_prev_tab(page_num)
|
||||
if action == "keyboard_next_tab":
|
||||
self.keyboard_next_tab(page_num)
|
||||
if action == "save_file":
|
||||
source_view.save_file()
|
||||
if action == "save_file_as":
|
||||
source_view.save_file_as()
|
||||
|
|
|
@ -13,7 +13,10 @@ class EditorEventsMixin:
|
|||
def _keyboard_close_tab(self):
|
||||
self.action_controller("close_tab")
|
||||
|
||||
def _keyboard_create_tab(self, _gfile):
|
||||
def _keyboard_open_file(self, gfile):
|
||||
self.open_file(gfile)
|
||||
|
||||
def _keyboard_create_tab(self, _gfile=None):
|
||||
self.create_view(gfile=_gfile)
|
||||
|
||||
def _keyboard_next_tab(self):
|
||||
|
@ -29,10 +32,10 @@ class EditorEventsMixin:
|
|||
self.action_controller("scale_down_text")
|
||||
|
||||
def _keyboard_save_file(self):
|
||||
...
|
||||
self.action_controller("save_file")
|
||||
|
||||
def _keyboard_save_file_as(self):
|
||||
...
|
||||
self.action_controller("save_file_as")
|
||||
|
||||
def _text_search(self, widget = None, eve = None):
|
||||
self.action_controller("do_text_search", widget.get_text())
|
||||
|
|
|
@ -47,6 +47,7 @@ class EditorNotebook(EditorEventsMixin, EditorControllerMixin, Gtk.Notebook):
|
|||
event_system.subscribe("set_buffer_style", self.action_controller)
|
||||
event_system.subscribe("toggle_highlight_line", self._toggle_highlight_line)
|
||||
event_system.subscribe("keyboard_create_tab", self._keyboard_create_tab)
|
||||
event_system.subscribe("keyboard_open_file", self._keyboard_open_file)
|
||||
event_system.subscribe("keyboard_close_tab", self._keyboard_close_tab)
|
||||
event_system.subscribe("keyboard_prev_tab", self._keyboard_prev_tab)
|
||||
event_system.subscribe("keyboard_next_tab", self._keyboard_next_tab)
|
||||
|
@ -81,6 +82,10 @@ class EditorNotebook(EditorEventsMixin, EditorControllerMixin, Gtk.Notebook):
|
|||
def _load_widgets(self):
|
||||
self.create_view()
|
||||
|
||||
def _dbl_click_create_view(self, notebook, eve):
|
||||
if eve.type == Gdk.EventType.DOUBLE_BUTTON_PRESS and eve.button == 1: # l-click
|
||||
...
|
||||
|
||||
def create_view(self, widget = None, eve = None, gfile = None):
|
||||
container = SourceViewContainer(self.close_tab)
|
||||
|
||||
|
@ -98,17 +103,20 @@ class EditorNotebook(EditorEventsMixin, EditorControllerMixin, Gtk.Notebook):
|
|||
self.show_all()
|
||||
self.set_current_page(index)
|
||||
|
||||
def open_file(self, gfile):
|
||||
page_num = self.get_current_page()
|
||||
container = self.get_nth_page( page_num )
|
||||
source_view = container.get_source_view()
|
||||
|
||||
if source_view._current_filename == "":
|
||||
source_view.open_file(gfile)
|
||||
else:
|
||||
self.create_view(None, None, gfile)
|
||||
|
||||
def close_tab(self, button, container, source_view, eve = None):
|
||||
if self.get_n_pages() == 1:
|
||||
return
|
||||
|
||||
page_num = self.page_num(container)
|
||||
watcher = source_view.get_file_watcher()
|
||||
if watcher:
|
||||
watcher.cancel()
|
||||
|
||||
source_view._cancel_current_file_watchers()
|
||||
self.remove_page(page_num)
|
||||
|
||||
def _dbl_click_create_view(self, notebook, eve):
|
||||
if eve.type == Gdk.EventType.DOUBLE_BUTTON_PRESS and eve.button == 1: # l-click
|
||||
...
|
||||
|
|
|
@ -23,12 +23,30 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
|
|||
self._style_scheme_manager = GtkSource.StyleSchemeManager()
|
||||
|
||||
self._general_style_tag = None
|
||||
self._file_watcher = None
|
||||
self._is_changed = False
|
||||
self._file_change_watcher = None
|
||||
self._file_cdr_watcher = None
|
||||
|
||||
self._is_changed = False
|
||||
self._ignore_internal_change = False
|
||||
self._last_eve_in_queue = None
|
||||
self._current_file: Gio.File = None
|
||||
self._file_loader = None
|
||||
self._buffer = self.get_buffer()
|
||||
self._current_filename: str = ""
|
||||
self._file_loader = None
|
||||
self._buffer = self.get_buffer()
|
||||
|
||||
|
||||
|
||||
self._file_filter_text = Gtk.FileFilter()
|
||||
self._file_filter_text.set_name("Text Files")
|
||||
# TODO: Need to externalize to settings file...
|
||||
pattern = ["*.txt", "*.py", "*.c", "*.h", "*.cpp", "*.csv", "*.m3*", "*.lua", "*.js", "*.toml", "*.xml", "*.pom", "*.htm", "*.md" "*.vala", "*.tsv", "*.css", "*.html", ".json", "*.java", "*.go", "*.php", "*.ts", "*.rs"]
|
||||
for p in pattern:
|
||||
self._file_filter_text.add_pattern(p)
|
||||
|
||||
self._file_filter_all = Gtk.FileFilter()
|
||||
self._file_filter_all.set_name("All Files")
|
||||
self._file_filter_all.add_pattern("*.*")
|
||||
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
|
@ -69,40 +87,10 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
|
|||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def _is_modified(self, *args):
|
||||
self._is_changed = True
|
||||
self.update_cursor_position()
|
||||
|
||||
def get_file_watcher(self):
|
||||
return self._file_watcher
|
||||
|
||||
def create_file_watcher(self, file_path = None):
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
if self._file_watcher:
|
||||
self._file_watcher.cancel()
|
||||
self._file_watcher = None
|
||||
|
||||
self._file_watcher = Gio.File.new_for_path(file_path) \
|
||||
.monitor_file([
|
||||
Gio.FileMonitorFlags.WATCH_MOVES,
|
||||
Gio.FileMonitorFlags.WATCH_HARD_LINKS
|
||||
], Gio.Cancellable())
|
||||
|
||||
self._file_watcher.connect("changed", self.file_watch_updates)
|
||||
|
||||
def file_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None):
|
||||
if settings.is_debug():
|
||||
logger.debug(eve_type)
|
||||
|
||||
if eve_type in [Gio.FileMonitorEvent.CREATED,
|
||||
Gio.FileMonitorEvent.DELETED,
|
||||
Gio.FileMonitorEvent.RENAMED]:
|
||||
...
|
||||
|
||||
if eve_type in [ Gio.FileMonitorEvent.CHANGED ]:
|
||||
...
|
||||
def _create_default_tag(self):
|
||||
self._general_style_tag = self._buffer.create_tag('general_style')
|
||||
self._general_style_tag.set_property('size', 100)
|
||||
self._general_style_tag.set_property('scale', 100)
|
||||
|
||||
def _set_up_dnd(self):
|
||||
URI_TARGET_TYPE = 80
|
||||
|
@ -137,42 +125,96 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
|
|||
|
||||
event_system.emit('create_view', (None, None, gfile,))
|
||||
|
||||
def _is_modified(self, *args):
|
||||
self._is_changed = True
|
||||
self.update_cursor_position()
|
||||
|
||||
def open_file(self, gfile, *args):
|
||||
self._current_file = gfile
|
||||
def _on_cursor_move(self, buf, cursor_iter, mark, user_data = None):
|
||||
if mark != buf.get_insert(): return
|
||||
|
||||
self.load_file_info(gfile)
|
||||
self.load_file_async(gfile)
|
||||
self.grab_focus()
|
||||
self.update_cursor_position()
|
||||
|
||||
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()
|
||||
tab_widget = self.get_parent().get_tab_widget()
|
||||
def _create_file_watcher(self, gfile = None):
|
||||
if not gfile: return
|
||||
|
||||
try:
|
||||
lm = self._language_manager.guess_language(None, content_type)
|
||||
self.set_buffer_language( lm.get_id() )
|
||||
except Exception as e:
|
||||
...
|
||||
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)
|
||||
|
||||
logger.debug(f"Detected Content Type: {content_type}")
|
||||
tab_widget.set_tab_label(display_name)
|
||||
event_system.emit("set_bottom_labels", (gfile, info))
|
||||
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
|
||||
|
||||
def load_file_async(self, gfile):
|
||||
file = GtkSource.File()
|
||||
file.set_location(gfile)
|
||||
self._file_loader = GtkSource.FileLoader.new(self._buffer, file)
|
||||
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
|
||||
|
||||
def finish_load_callback(obj, res, user_data=None):
|
||||
self._file_loader.load_finish(res)
|
||||
self._is_changed = False
|
||||
if eve_type in [ Gio.FileMonitorEvent.CHANGES_DONE_HINT ]:
|
||||
if self._ignore_internal_change:
|
||||
self._ignore_internal_change = False
|
||||
return
|
||||
|
||||
self._file_loader.load_async(io_priority=98,
|
||||
cancellable=None,
|
||||
progress_callback=None,
|
||||
progress_callback_data=None,
|
||||
callback=finish_load_callback,
|
||||
user_data=(None))
|
||||
# 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 save_file(self):
|
||||
if not self._current_file:
|
||||
self.save_file_as()
|
||||
return
|
||||
|
||||
self._write_file(self._current_file)
|
||||
|
||||
|
||||
def save_file_as(self):
|
||||
# TODO: Move Chooser logic to own widget
|
||||
dlg = Gtk.FileChooserDialog(title="Please choose a file...", parent = None, action = 1)
|
||||
|
||||
dlg.add_buttons("Cancel", Gtk.ResponseType.CANCEL, "Save", Gtk.ResponseType.OK)
|
||||
dlg.set_do_overwrite_confirmation(True)
|
||||
dlg.add_filter(self._file_filter_text)
|
||||
dlg.add_filter(self._file_filter_all)
|
||||
|
||||
if self._current_filename == "":
|
||||
dlg.set_current_name("new.txt")
|
||||
else:
|
||||
dlg.set_current_folder(self._current_file.get_parent())
|
||||
dlg.set_current_name(self._current_filename)
|
||||
|
||||
response = dlg.run()
|
||||
file = dlg.get_filename() if response == Gtk.ResponseType.OK else ""
|
||||
dlg.destroy()
|
||||
|
||||
if not file == "":
|
||||
gfile = Gio.File.new_for_path(file)
|
||||
self._write_file(gfile, True)
|
||||
|
||||
def _write_file(self, gfile, save_as = False):
|
||||
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()
|
||||
|
||||
if self._current_filename == "" and save_as:
|
||||
self.open_file(gfile)
|
||||
else:
|
||||
event_system.emit("create_view", (None, None, gfile,))
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('GtkSource', '4')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class SourceViewEventsMixin:
|
||||
def _create_default_tag(self):
|
||||
self._general_style_tag = self._buffer.create_tag('general_style')
|
||||
self._general_style_tag.set_property('size', 100)
|
||||
self._general_style_tag.set_property('scale', 100)
|
||||
|
||||
def set_buffer_language(self, language = "python3"):
|
||||
self._buffer.set_language( self._language_manager.get_language(language) )
|
||||
|
||||
|
@ -38,12 +38,6 @@ class SourceViewEventsMixin:
|
|||
tag.set_property('scale', tag.get_property('scale') - scale_step)
|
||||
self._buffer.apply_tag(tag, start_itr, end_itr)
|
||||
|
||||
def _on_cursor_move(self, buf, cursor_iter, mark, user_data = None):
|
||||
if mark != buf.get_insert():
|
||||
return
|
||||
|
||||
self.update_cursor_position()
|
||||
|
||||
def update_cursor_position(self):
|
||||
iter = self._buffer.get_iter_at_mark(self._buffer.get_insert())
|
||||
chars = iter.get_offset()
|
||||
|
@ -71,3 +65,44 @@ class SourceViewEventsMixin:
|
|||
|
||||
def action_comment_out_selection(self):
|
||||
pass
|
||||
|
||||
def open_file(self, gfile, *args):
|
||||
self._current_file = gfile
|
||||
|
||||
self.load_file_info(gfile)
|
||||
self.load_file_async(gfile)
|
||||
self._create_file_watcher(gfile)
|
||||
self.grab_focus()
|
||||
|
||||
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()
|
||||
tab_widget = self.get_parent().get_tab_widget()
|
||||
self._current_filename = display_name
|
||||
|
||||
try:
|
||||
lm = self._language_manager.guess_language(None, content_type)
|
||||
self.set_buffer_language( lm.get_id() )
|
||||
except Exception as e:
|
||||
...
|
||||
|
||||
logger.debug(f"Detected Content Type: {content_type}")
|
||||
tab_widget.set_tab_label(display_name)
|
||||
event_system.emit("set_bottom_labels", (gfile, info))
|
||||
|
||||
def load_file_async(self, gfile):
|
||||
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._file_loader.load_async(io_priority = 98,
|
||||
cancellable = None,
|
||||
progress_callback = None,
|
||||
progress_callback_data = None,
|
||||
callback = finish_load_callback,
|
||||
user_data = (None))
|
||||
|
|
|
@ -55,6 +55,6 @@ class OpenFileButton(Gtk.Button):
|
|||
if filename:
|
||||
path = filename if os.path.isabs(filename) else os.path.abspath(filename)
|
||||
_gfile = Gio.File.new_for_path(path)
|
||||
event_system.emit("keyboard_create_tab", (_gfile,))
|
||||
event_system.emit("keyboard_open_file", (_gfile,))
|
||||
|
||||
chooser.destroy()
|
||||
|
|
Loading…
Reference in New Issue