From 3f54e04e79d9f8576628b50c36901e8407c6d838 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Fri, 4 Oct 2024 08:56:32 +0800 Subject: [PATCH 01/25] centralize editor/plugin info --- controlyourtabs/__init__.py | 211 +++++++++++++++++------------------- controlyourtabs/editor.py | 75 +++++++++++++ controlyourtabs/keyinfo.py | 16 +-- controlyourtabs/log.py | 12 +- controlyourtabs/plugin.py | 40 +++++++ controlyourtabs/tabinfo.py | 52 ++++----- 6 files changed, 255 insertions(+), 151 deletions(-) create mode 100644 controlyourtabs/editor.py create mode 100644 controlyourtabs/plugin.py diff --git a/controlyourtabs/__init__.py b/controlyourtabs/__init__.py index 155ace1..10a1e19 100644 --- a/controlyourtabs/__init__.py +++ b/controlyourtabs/__init__.py @@ -21,31 +21,21 @@ import gi gi.require_version('Gtk', '3.0') -gi.require_version('Gedit', '3.0') import math import os.path from functools import wraps -from gi.repository import GObject, GLib, Gtk, Gdk, GdkPixbuf, Gio, Gedit, PeasGtk +from gi.repository import GObject, GLib, Gtk, Gdk, GdkPixbuf, Gio, PeasGtk +from .plugin import _, data_dir as plugin_data_dir from .utils import connect_handlers, disconnect_handlers -from . import keyinfo, log, tabinfo +from . import editor, keyinfo, log, tabinfo -BASE_PATH = os.path.dirname(os.path.realpath(__file__)) -LOCALE_PATH = os.path.join(BASE_PATH, 'locale') -try: - import gettext - gettext.bindtextdomain('gedit-control-your-tabs', LOCALE_PATH) - _ = lambda s: gettext.dgettext('gedit-control-your-tabs', s) -except: - _ = lambda s: s - - -class ControlYourTabsWindowActivatable(GObject.Object, Gedit.WindowActivatable): +class ControlYourTabsWindowActivatable(GObject.Object, editor.Editor.WindowActivatable): __gtype_name__ = 'ControlYourTabsWindowActivatable' - window = GObject.property(type=Gedit.Window) # before pygobject 3.2, lowercase 'p' + window = GObject.property(type=editor.Editor.Window) # before pygobject 3.2, lowercase 'p' MAX_TAB_WINDOW_ROWS = 9 @@ -57,7 +47,7 @@ def __init__(self): def do_activate(self): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", self.window)) + editor.debug_plugin_message(log.format("%s", self.window)) window = self.window tab_models = {} @@ -128,7 +118,7 @@ def do_activate(self): if tab: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("found active tab %s, setting up now", tab)) + editor.debug_plugin_message(log.format("found active tab %s, setting up now", tab)) self.setup(window, tab, tab_models) @@ -137,13 +127,13 @@ def do_activate(self): else: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("waiting for new tab")) + editor.debug_plugin_message(log.format("waiting for new tab")) connect_handlers(self, window, ['tab-added'], 'setup', tab_models) def do_deactivate(self): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", self.window)) + editor.debug_plugin_message(log.format("%s", self.window)) multi = self._multi tab_models = self._tab_models @@ -184,7 +174,7 @@ def do_update_state(self): def on_setup_tab_added(self, window, tab, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", window, tab)) + editor.debug_plugin_message(log.format("%s, %s", window, tab)) disconnect_handlers(self, window) @@ -192,14 +182,14 @@ def on_setup_tab_added(self, window, tab, tab_models): def setup(self, window, tab, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", window, tab)) + editor.debug_plugin_message(log.format("%s, %s", window, tab)) icon_size = self._tabinfo.get_tab_icon_size(tab) self._icon_cell.set_fixed_size(icon_size, icon_size) self._space_cell.set_fixed_size(icon_size, icon_size) - multi = window.get_template_child(Gedit.Window, 'multi_notebook') + multi = window.get_template_child(editor.Editor.Window, 'multi_notebook') connect_handlers( self, multi, @@ -228,7 +218,7 @@ def setup(self, window, tab, tab_models): self._multi = multi for document in window.get_documents(): - notebook = Gedit.Tab.get_from_document(document).get_parent() + notebook = editor.Editor.Tab.get_from_document(document).get_parent() self.track_notebook(notebook, tab_models) @@ -236,11 +226,11 @@ def setup(self, window, tab, tab_models): def track_notebook(self, notebook, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, notebook)) + editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) if notebook in tab_models: if log.query(log.WARNING): - Gedit.debug_plugin_message(log.format("already tracking notebook")) + editor.debug_plugin_message(log.format("already tracking notebook")) return @@ -270,11 +260,11 @@ def track_notebook(self, notebook, tab_models): def untrack_notebook(self, notebook, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, notebook)) + editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) if notebook not in tab_models: if log.query(log.WARNING): - Gedit.debug_plugin_message(log.format("not tracking notebook")) + editor.debug_plugin_message(log.format("not tracking notebook")) return @@ -292,11 +282,11 @@ def untrack_notebook(self, notebook, tab_models): def track_tab(self, tab, tab_model): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, tab)) + editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) if tab in tab_model: if log.query(log.WARNING): - Gedit.debug_plugin_message(log.format("already tracking tab")) + editor.debug_plugin_message(log.format("already tracking tab")) return @@ -314,17 +304,17 @@ def track_tab(self, tab, tab_model): def untrack_tab(self, tab, tab_model): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, tab)) + editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) if tab == self._initial_tab: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("tab is initial tab, clearing")) + editor.debug_plugin_message(log.format("tab is initial tab, clearing")) self._initial_tab = None if tab not in tab_model: if log.query(log.WARNING): - Gedit.debug_plugin_message(log.format("not tracking tab")) + editor.debug_plugin_message(log.format("not tracking tab")) return @@ -334,7 +324,7 @@ def untrack_tab(self, tab, tab_model): def active_tab_changed(self, tab, tab_model): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, tab)) + editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) if not self._is_switching: tab_model.move_after(tab) @@ -350,25 +340,25 @@ def active_tab_changed(self, tab, tab_model): def on_multi_notebook_notebook_added(self, multi, notebook, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, notebook)) + editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) self.track_notebook(notebook, tab_models) def on_multi_notebook_notebook_removed(self, multi, notebook, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, notebook)) + editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) self.untrack_notebook(notebook, tab_models) def on_multi_notebook_tab_added(self, multi, notebook, tab, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) + editor.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) self.track_tab(tab, tab_models[notebook]) def on_multi_notebook_tab_removed(self, multi, notebook, tab, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) + editor.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) self.untrack_tab(tab, tab_models[notebook]) @@ -379,14 +369,14 @@ def on_window_active_tab_changed(self, window, tab, tab_models=None): tab = window.get_active_tab() if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", window, tab)) + editor.debug_plugin_message(log.format("%s, %s", window, tab)) if tab: self.active_tab_changed(tab, tab_models[tab.get_parent()]) def on_window_key_press_event(self, window, event, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, key=%s", window, Gdk.keyval_name(event.keyval))) + editor.debug_plugin_message(log.format("%s, key=%s", window, Gdk.keyval_name(event.keyval))) self._is_control_held = keyinfo.update_control_held(event, self._is_control_held, True) @@ -394,45 +384,45 @@ def on_window_key_press_event(self, window, event, tab_models): def on_window_key_release_event(self, window, event, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) + editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) self._is_control_held = keyinfo.update_control_held(event, self._is_control_held, False) if not any(self._is_control_held): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("no control keys held down")) + editor.debug_plugin_message(log.format("no control keys held down")) self.end_switching() else: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("one or more control keys held down")) + editor.debug_plugin_message(log.format("one or more control keys held down")) def on_window_focus_out_event(self, window, event, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", window)) + editor.debug_plugin_message(log.format("%s", window)) self.end_switching() def on_window_configure_event(self, window, event, tab_models): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", window)) + editor.debug_plugin_message(log.format("%s", window)) self.schedule_tabwin_resize() def on_tab_notify_name_state(self, tab, pspec, tab_model): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, tab)) + editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) tab_model.update(tab) def on_tab_model_row_changed(self, tab_model, path): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, path=%s", self.window, path)) + editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) if not self.is_active_view_model(tab_model): if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("tab model not active")) + editor.debug_plugin_message(log.format("tab model not active")) return @@ -440,11 +430,11 @@ def on_tab_model_row_changed(self, tab_model, path): def on_tab_model_selected_path_changed(self, tab_model, path): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, path=%s", self.window, path)) + editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) if not self.is_active_view_model(tab_model): if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("tab model not active")) + editor.debug_plugin_message(log.format("tab model not active")) return @@ -459,7 +449,7 @@ def is_active_view_model(self, tab_model): def set_active_view_model(self, tab_model): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self.window, tab_model)) + editor.debug_plugin_message(log.format("%s, %s", self.window, tab_model)) model = None selected_path = None @@ -473,7 +463,7 @@ def set_active_view_model(self, tab_model): def set_view_selection(self, path): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, path=%s", self.window, path)) + editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) view = self._view selection = view.get_selection() @@ -490,7 +480,7 @@ def set_view_selection(self, path): def key_press_event(self, event): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) + editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) settings = self._settings is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) @@ -498,33 +488,33 @@ def key_press_event(self, event): if is_control_tab and settings and settings['use-tabbar-order']: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("coercing ctrl-tab into ctrl-page because of settings")) + editor.debug_plugin_message(log.format("coercing ctrl-tab into ctrl-page because of settings")) is_control_tab = False is_control_page = True if self._is_switching and is_control_escape: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("ctrl-esc while switching")) + editor.debug_plugin_message(log.format("ctrl-esc while switching")) self.end_switching(True) elif is_control_tab or is_control_page: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("ctrl-tab or ctrl-page")) + editor.debug_plugin_message(log.format("ctrl-tab or ctrl-page")) self.switch_tab(is_control_tab, keyinfo.is_next_key(event), event.time) elif self._is_switching and not self._is_tabwin_visible: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("normal key while switching and tabwin not visible")) + editor.debug_plugin_message(log.format("normal key while switching and tabwin not visible")) self.end_switching() block_event = False else: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("normal key while %s", "switching" if self._is_switching else "not switching")) + editor.debug_plugin_message(log.format("normal key while %s", "switching" if self._is_switching else "not switching")) block_event = self._is_switching @@ -532,14 +522,14 @@ def key_press_event(self, event): def switch_tab(self, use_mru_order, to_next_tab, time): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, use_mru_order=%s, to_next_tab=%s, time=%s", self.window, use_mru_order, to_next_tab, time)) + editor.debug_plugin_message(log.format("%s, use_mru_order=%s, to_next_tab=%s, time=%s", self.window, use_mru_order, to_next_tab, time)) window = self.window current_tab = window.get_active_tab() if not current_tab: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("no tabs")) + editor.debug_plugin_message(log.format("no tabs")) return @@ -550,7 +540,7 @@ def switch_tab(self, use_mru_order, to_next_tab, time): if num_tabs < 2: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("only 1 tab")) + editor.debug_plugin_message(log.format("only 1 tab")) return @@ -561,11 +551,11 @@ def switch_tab(self, use_mru_order, to_next_tab, time): next_tab = tabs[next_index] if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("switching from %s to %s", current_tab, next_tab)) + editor.debug_plugin_message(log.format("switching from %s to %s", current_tab, next_tab)) if not self._is_switching: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("saving %s as initial tab", current_tab)) + editor.debug_plugin_message(log.format("saving %s as initial tab", current_tab)) self._initial_tab = current_tab @@ -578,13 +568,13 @@ def switch_tab(self, use_mru_order, to_next_tab, time): if not self._is_tabwin_visible: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("showing tabwin")) + editor.debug_plugin_message(log.format("showing tabwin")) tabwin.show_all() else: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("presenting tabwin")) + editor.debug_plugin_message(log.format("presenting tabwin")) tabwin.present_with_time(time) @@ -592,11 +582,11 @@ def switch_tab(self, use_mru_order, to_next_tab, time): def end_switching(self, do_revert=False): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, do_revert=%s", self.window, do_revert)) + editor.debug_plugin_message(log.format("%s, do_revert=%s", self.window, do_revert)) if not self._is_switching: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("not switching")) + editor.debug_plugin_message(log.format("not switching")) return @@ -611,7 +601,7 @@ def end_switching(self, do_revert=False): if do_revert and initial_tab: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("switching to initial tab %s", initial_tab)) + editor.debug_plugin_message(log.format("switching to initial tab %s", initial_tab)) window.set_active_tab(initial_tab) @@ -626,11 +616,11 @@ def end_switching(self, do_revert=False): def schedule_tabwin_resize(self): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", self.window)) + editor.debug_plugin_message(log.format("%s", self.window)) if self._tabwin_resize_id: if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("already scheduled")) + editor.debug_plugin_message(log.format("already scheduled")) return @@ -646,11 +636,11 @@ def schedule_tabwin_resize(self): def cancel_tabwin_resize(self): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", self.window)) + editor.debug_plugin_message(log.format("%s", self.window)) if not self._tabwin_resize_id: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("not scheduled")) + editor.debug_plugin_message(log.format("not scheduled")) return @@ -660,7 +650,7 @@ def cancel_tabwin_resize(self): def do_tabwin_resize(self): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", self.window)) + editor.debug_plugin_message(log.format("%s", self.window)) view = self._view sw = self._sw @@ -698,11 +688,11 @@ def do_tabwin_resize(self): tabwin_height = min(view_height, max_height) if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("view height = %s", view_height)) - Gedit.debug_plugin_message(log.format("max rows height = %s", max_rows_height)) - Gedit.debug_plugin_message(log.format("max win height = %s", max_win_height)) - Gedit.debug_plugin_message(log.format("tabwin height = %s", tabwin_height)) - Gedit.debug_plugin_message(log.format("tabwin width = %s", tabwin_width)) + editor.debug_plugin_message(log.format("view height = %s", view_height)) + editor.debug_plugin_message(log.format("max rows height = %s", max_rows_height)) + editor.debug_plugin_message(log.format("max win height = %s", max_win_height)) + editor.debug_plugin_message(log.format("tabwin height = %s", tabwin_height)) + editor.debug_plugin_message(log.format("tabwin width = %s", tabwin_width)) self._tabwin.set_size_request(tabwin_width, tabwin_height) @@ -745,9 +735,9 @@ def __init__(self, tabinfo): GObject.Object.__init__(self) if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", self)) + editor.debug_plugin_message(log.format("%s", self)) - self._model = Gtk.ListStore.new((GdkPixbuf.Pixbuf, str, Gedit.Tab)) + self._model = Gtk.ListStore.new((GdkPixbuf.Pixbuf, str, editor.Editor.Tab)) self._references = {} self._selected = None self._tabinfo = tabinfo @@ -772,7 +762,7 @@ def __getitem__(self, key): @_model_modifier def __delitem__(self, key): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, key=%s", self, key)) + editor.debug_plugin_message(log.format("%s, key=%s", self, key)) tab = self._model[key][2] @@ -796,19 +786,19 @@ def model(self): def on_model_row_inserted(self, model, path, iter_): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) + editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) self.emit('row-inserted', path) def on_model_row_deleted(self, model, path): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) + editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) self.emit('row-deleted', path) def on_model_row_changed(self, model, path, iter_): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) + editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) self.emit('row-changed', path) @@ -817,34 +807,34 @@ def on_model_rows_reordered(self, model, path, iter_, new_order): # path is suppose to point to the parent node of the reordered rows # if top level rows are reordered, path is invalid (null?) # so don't print it out here, because will throw an error - Gedit.debug_plugin_message(log.format("%s, %s", self, model)) + editor.debug_plugin_message(log.format("%s, %s", self, model)) self.emit('rows-reordered') def do_row_inserted(self, path): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, path=%s", self, path)) + editor.debug_plugin_message(log.format("%s, path=%s", self, path)) def do_row_deleted(self, path): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, path=%s", self, path)) + editor.debug_plugin_message(log.format("%s, path=%s", self, path)) def do_row_changed(self, path): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, path=%s", self, path)) + editor.debug_plugin_message(log.format("%s, path=%s", self, path)) def do_rows_reordered(self): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", self)) + editor.debug_plugin_message(log.format("%s", self)) def do_selected_path_changed(self, path): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, path=%s", self, path)) + editor.debug_plugin_message(log.format("%s, path=%s", self, path)) @_model_modifier def insert(self, position, tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, position=%s, %s", self, position, tab)) + editor.debug_plugin_message(log.format("%s, position=%s, %s", self, position, tab)) tab_iter = self._model.insert( position, @@ -859,26 +849,26 @@ def insert(self, position, tab): def append(self, tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self, tab)) + editor.debug_plugin_message(log.format("%s, %s", self, tab)) self.insert(len(self._model), tab) # before pygobject 3.2, -1 position does not work def prepend(self, tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self, tab)) + editor.debug_plugin_message(log.format("%s, %s", self, tab)) self.insert(0, tab) def remove(self, tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self, tab)) + editor.debug_plugin_message(log.format("%s, %s", self, tab)) del self[self.get_path(tab)] @_model_modifier def move(self, tab, sibling, move_before): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s, %s, move_before=%s", self, tab, sibling, move_before)) + editor.debug_plugin_message(log.format("%s, %s, %s, move_before=%s", self, tab, sibling, move_before)) tab_iter = self._get_iter(tab) sibling_iter = self._get_iter(sibling) if sibling else None @@ -890,13 +880,13 @@ def move(self, tab, sibling, move_before): def move_before(self, tab, sibling=None): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) + editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) self.move(tab, sibling, True) def move_after(self, tab, sibling=None): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) + editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) self.move(tab, sibling, False) @@ -912,13 +902,13 @@ def _get_iter(self, tab): @_model_modifier def select(self, tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self, tab)) + editor.debug_plugin_message(log.format("%s, %s", self, tab)) self._selected = tab def unselect(self): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", self)) + editor.debug_plugin_message(log.format("%s", self)) self.select(None) @@ -930,7 +920,7 @@ def get_selected_path(self): def update(self, tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s, %s", self, tab)) + editor.debug_plugin_message(log.format("%s, %s", self, tab)) path = self.get_path(tab) @@ -945,13 +935,13 @@ class ControlYourTabsConfigurable(GObject.Object, PeasGtk.Configurable): def do_create_configure_widget(self): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("")) + editor.debug_plugin_message(log.format("")) settings = get_settings() if settings: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("have settings")) + editor.debug_plugin_message(log.format("have settings")) widget = Gtk.CheckButton.new_with_label( _("Use tab row order for Ctrl+Tab / Ctrl+Shift+Tab") @@ -967,10 +957,11 @@ def do_create_configure_widget(self): else: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("no settings")) + editor.debug_plugin_message(log.format("no settings")) widget = Gtk.Label.new( - _("Sorry, no preferences are available for this version of gedit.") + # translators: %s: application name + _("Sorry, no preferences are available for this version of %s.") % editor.name ) box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) @@ -982,9 +973,9 @@ def do_create_configure_widget(self): def get_settings(): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("")) + editor.debug_plugin_message(log.format("")) - schemas_path = os.path.join(BASE_PATH, 'schemas') + schemas_path = os.path.join(plugin_data_dir, 'schemas') try: schema_source = Gio.SettingsSchemaSource.new_from_directory( @@ -995,30 +986,30 @@ def get_settings(): except: if log.query(log.WARNING): - Gedit.debug_plugin_message(log.format("could not load settings schema source from %s", schemas_path)) + editor.debug_plugin_message(log.format("could not load settings schema source from %s", schemas_path)) schema_source = None if not schema_source: if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("no schema source")) + editor.debug_plugin_message(log.format("no schema source")) return None schema = schema_source.lookup( - 'com.thingsthemselves.gedit.plugins.controlyourtabs', + 'com.thingsthemselves.%s.plugins.controlyourtabs' % editor.name.lower(), False ) if not schema: if log.query(log.WARNING): - Gedit.debug_plugin_message(log.format("could not lookup schema")) + editor.debug_plugin_message(log.format("could not lookup schema")) return None return Gio.Settings.new_full( schema, None, - '/com/thingsthemselves/gedit/plugins/controlyourtabs/' + '/com/thingsthemselves/%s/plugins/controlyourtabs/' % editor.name.lower() ) diff --git a/controlyourtabs/editor.py b/controlyourtabs/editor.py new file mode 100644 index 0000000..bf17a39 --- /dev/null +++ b/controlyourtabs/editor.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# editor.py +# This file is part of Control Your Tabs, a plugin for gedit +# +# Copyright (C) 2010-2013, 2017-2018, 2020, 2023-2024 Jeffery To +# https://github.com/jefferyto/gedit-control-your-tabs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +import inspect +import os + + +# based on get_trace_info() in Gedit.py +def get_trace_info(num_back_frames=0): + frame = inspect.currentframe().f_back + try: + for i in range(num_back_frames): + back_frame = frame.f_back + if back_frame is None: + break + frame = back_frame + + filename = frame.f_code.co_filename + + # http://code.activestate.com/recipes/145297-grabbing-the-current-line-number-easily/ + lineno = frame.f_lineno + + func_name = frame.f_code.co_name + try: + # http://stackoverflow.com/questions/2203424/python-how-to-retrieve-class-information-from-a-frame-object + cls_name = frame.f_locals["self"].__class__.__name__ + except: + pass + else: + func_name = "%s.%s" % (cls_name, func_name) + + return (filename, lineno, func_name) + finally: + frame = None + +# based on debug_plugin_message() in Gedit.py and gedit-debug.c +def _debug_plugin_message(format, *format_args): + filename, lineno, func_name = get_trace_info(1) + message = format % format_args + print("%s:%d (%s) %s" % (filename, lineno, func_name, message), flush=True) + +gi.require_version('Gedit', '3.0') +from gi.repository import Gedit as Editor +name = 'gedit' + +try: + debug_plugin_message = Editor.debug_plugin_message +except AttributeError: + debug_plugin_message = lambda *args: None + + is_debug = os.getenv('%s_DEBUG' % name.upper()) + is_debug_plugins = os.getenv('%s_DEBUG_PLUGINS' % name.upper()) + + if is_debug or is_debug_plugins: + debug_plugin_message = _debug_plugin_message + diff --git a/controlyourtabs/keyinfo.py b/controlyourtabs/keyinfo.py index 23dcadc..dcbd63d 100644 --- a/controlyourtabs/keyinfo.py +++ b/controlyourtabs/keyinfo.py @@ -19,8 +19,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from gi.repository import Gtk, Gdk, Gedit -from . import log +from gi.repository import Gtk, Gdk +from . import editor, log CONTROL_MASK = Gdk.ModifierType.CONTROL_MASK @@ -45,7 +45,7 @@ def update_control_held(event, prev_statuses, new_status): keyval = event.keyval if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("key=%s, %s, new_status=%s", Gdk.keyval_name(keyval), prev_statuses, new_status)) + editor.debug_plugin_message(log.format("key=%s, %s, new_status=%s", Gdk.keyval_name(keyval), prev_statuses, new_status)) new_statuses = [ new_status if keyval == control_key else prev_status @@ -53,7 +53,7 @@ def update_control_held(event, prev_statuses, new_status): ] if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("new_statuses=%s", new_statuses)) + editor.debug_plugin_message(log.format("new_statuses=%s", new_statuses)) return new_statuses @@ -62,7 +62,7 @@ def is_control_keys(event): state = event.state & Gtk.accelerator_get_default_mod_mask() if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("key=%s, state=%s", Gdk.keyval_name(keyval), state)) + editor.debug_plugin_message(log.format("key=%s, state=%s", Gdk.keyval_name(keyval), state)) is_control = state == CONTROL_MASK is_control_shift = state == CONTROL_SHIFT_MASK @@ -76,18 +76,18 @@ def is_control_keys(event): is_control_escape = (is_control or is_control_shift) and is_escape if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("is_control_tab=%s, is_control_page=%s, is_control_escape=%s", is_control_tab, is_control_page, is_control_escape)) + editor.debug_plugin_message(log.format("is_control_tab=%s, is_control_page=%s, is_control_escape=%s", is_control_tab, is_control_page, is_control_escape)) return (is_control_tab, is_control_page, is_control_escape) def is_next_key(event): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("key=%s", Gdk.keyval_name(event.keyval))) + editor.debug_plugin_message(log.format("key=%s", Gdk.keyval_name(event.keyval))) result = event.keyval in NEXT_KEY_SET if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("result=%s", result)) + editor.debug_plugin_message(log.format("result=%s", result)) return result diff --git a/controlyourtabs/log.py b/controlyourtabs/log.py index 3717ef7..8da28a6 100644 --- a/controlyourtabs/log.py +++ b/controlyourtabs/log.py @@ -22,6 +22,7 @@ import os from gi.repository import GLib from .utils import debug_str +from . import editor # for convenience, in decreasing order of severity @@ -49,9 +50,14 @@ # messages equal or higher in severity will be printed output_level = MESSAGE -name = os.getenv('GEDIT_CONTROL_YOUR_TABS_DEBUG_LEVEL', '').lower() -if name in NAMES_TO_LEVELS: - output_level = NAMES_TO_LEVELS[name] +gedit_env_name = os.getenv('GEDIT_CONTROL_YOUR_TABS_DEBUG_LEVEL', '') +editor_env_name = os.getenv( + '%s_CONTROL_YOUR_TABS_DEBUG_LEVEL' % editor.name.upper(), + gedit_env_name +) +env_name = editor_env_name.lower() +if env_name in NAMES_TO_LEVELS: + output_level = NAMES_TO_LEVELS[env_name] # set by query(), used by name() last_queried_level = None diff --git a/controlyourtabs/plugin.py b/controlyourtabs/plugin.py new file mode 100644 index 0000000..75de9d7 --- /dev/null +++ b/controlyourtabs/plugin.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# +# plugin.py +# This file is part of Control Your Tabs, a plugin for gedit +# +# Copyright (C) 2010-2013, 2017-2018, 2020, 2023-2024 Jeffery To +# https://github.com/jefferyto/gedit-control-your-tabs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('Peas', '1.0') + +import os.path +from gi.repository import Peas + + +data_dir = Peas.Engine.get_default().get_plugin_info('controlyourtabs').get_data_dir() + +try: + import gettext + gettext.bindtextdomain( + 'gedit-control-your-tabs', + os.path.join(data_dir, 'locale') + ) + _ = lambda s: gettext.dgettext('gedit-control-your-tabs', s) +except: + _ = lambda s: s + diff --git a/controlyourtabs/tabinfo.py b/controlyourtabs/tabinfo.py index 2165ee8..6e8c611 100644 --- a/controlyourtabs/tabinfo.py +++ b/controlyourtabs/tabinfo.py @@ -20,50 +20,42 @@ # along with this program. If not, see . import os.path -from gi.repository import Gtk, GtkSource, Gedit +from gi.repository import Gtk, GtkSource from xml.sax.saxutils import escape -from . import log -BASE_PATH = os.path.dirname(os.path.realpath(__file__)) -LOCALE_PATH = os.path.join(BASE_PATH, 'locale') - -try: - import gettext - gettext.bindtextdomain('gedit-control-your-tabs', LOCALE_PATH) - _ = lambda s: gettext.dgettext('gedit-control-your-tabs', s) -except: - _ = lambda s: s +from .plugin import _ +from . import editor, log # based on switch statement in _gedit_tab_get_icon() in gedit-tab.c TAB_STATE_TO_NAMED_ICON = {} try: - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.PRINTING] = 'printer-printing-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.SHOWING_PRINT_PREVIEW] = 'printer-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.LOADING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.REVERTING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.SAVING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.GENERIC_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.EXTERNALLY_MODIFIED_NOTIFICATION] = 'dialog-warning-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.PRINTING] = 'printer-printing-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.SHOWING_PRINT_PREVIEW] = 'printer-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.LOADING_ERROR] = 'dialog-error-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.REVERTING_ERROR] = 'dialog-error-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.SAVING_ERROR] = 'dialog-error-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.GENERIC_ERROR] = 'dialog-error-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.EXTERNALLY_MODIFIED_NOTIFICATION] = 'dialog-warning-symbolic' except AttributeError: # constant names before gedit 47 - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.STATE_PRINTING] = 'printer-printing-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.STATE_SHOWING_PRINT_PREVIEW] = 'printer-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.STATE_LOADING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.STATE_REVERTING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.STATE_SAVING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.STATE_GENERIC_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.STATE_EXTERNALLY_MODIFIED_NOTIFICATION] = 'dialog-warning-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_PRINTING] = 'printer-printing-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_SHOWING_PRINT_PREVIEW] = 'printer-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_LOADING_ERROR] = 'dialog-error-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_REVERTING_ERROR] = 'dialog-error-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_SAVING_ERROR] = 'dialog-error-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_GENERIC_ERROR] = 'dialog-error-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_EXTERNALLY_MODIFIED_NOTIFICATION] = 'dialog-warning-symbolic' try: # Gedit.TabState.STATE_PRINT_PREVIEWING removed in gedit 3.36 - TAB_STATE_TO_NAMED_ICON[Gedit.TabState.STATE_PRINT_PREVIEWING] = 'printer-symbolic' + TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_PRINT_PREVIEWING] = 'printer-symbolic' except AttributeError: pass # based on doc_get_name() and document_row_sync_tab_name_and_icon() in gedit-documents-panel.c def get_tab_name(tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", tab)) + editor.debug_plugin_message(log.format("%s", tab)) doc = tab.get_document() name = tab.get_property('name') @@ -83,14 +75,14 @@ def get_tab_name(tab): tab_name += " [%s]" % escape(_("Read-Only")) if log.query(log.DEBUG): - Gedit.debug_plugin_message(log.format("tab_name=%s", tab_name)) + editor.debug_plugin_message(log.format("tab_name=%s", tab_name)) return tab_name # based on _gedit_tab_get_icon() in gedit-tab.c def get_tab_icon(tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", tab)) + editor.debug_plugin_message(log.format("%s", tab)) state = tab.get_state() @@ -105,7 +97,7 @@ def get_tab_icon(tab): def get_tab_icon_size(tab): if log.query(log.INFO): - Gedit.debug_plugin_message(log.format("%s", tab)) + editor.debug_plugin_message(log.format("%s", tab)) is_valid_size, icon_size_width, icon_size_height = Gtk.icon_size_lookup(Gtk.IconSize.MENU) From 4a9410d8b1380adccc4b35b1c61d70e62cbb8cc9 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sat, 5 Oct 2024 05:44:21 +0800 Subject: [PATCH 02/25] no need to save references to tabinfo anymore --- controlyourtabs/__init__.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/controlyourtabs/__init__.py b/controlyourtabs/__init__.py index 10a1e19..1b543ff 100644 --- a/controlyourtabs/__init__.py +++ b/controlyourtabs/__init__.py @@ -112,7 +112,6 @@ def do_activate(self): self._space_cell = space_cell self._tabwin_resize_id = None self._settings = get_settings() - self._tabinfo = tabinfo tab = window.get_active_tab() @@ -164,7 +163,6 @@ def do_deactivate(self): self._space_cell = None self._tabwin_resize_id = None self._settings = None - self._tabinfo = None def do_update_state(self): pass @@ -184,7 +182,7 @@ def setup(self, window, tab, tab_models): if log.query(log.INFO): editor.debug_plugin_message(log.format("%s, %s", window, tab)) - icon_size = self._tabinfo.get_tab_icon_size(tab) + icon_size = tabinfo.get_tab_icon_size(tab) self._icon_cell.set_fixed_size(icon_size, icon_size) self._space_cell.set_fixed_size(icon_size, icon_size) @@ -234,7 +232,7 @@ def track_notebook(self, notebook, tab_models): return - tab_model = ControlYourTabsTabModel(self._tabinfo) + tab_model = ControlYourTabsTabModel() connect_handlers( self, tab_model, @@ -731,7 +729,7 @@ def wrapper(self, *args, **kwargs): return wrapper - def __init__(self, tabinfo): + def __init__(self): GObject.Object.__init__(self) if log.query(log.INFO): @@ -740,7 +738,6 @@ def __init__(self, tabinfo): self._model = Gtk.ListStore.new((GdkPixbuf.Pixbuf, str, editor.Editor.Tab)) self._references = {} self._selected = None - self._tabinfo = tabinfo connect_handlers( self, self._model, @@ -839,8 +836,8 @@ def insert(self, position, tab): tab_iter = self._model.insert( position, ( - self._tabinfo.get_tab_icon(tab), - self._tabinfo.get_tab_name(tab), + tabinfo.get_tab_icon(tab), + tabinfo.get_tab_name(tab), tab ) ) @@ -924,8 +921,8 @@ def update(self, tab): path = self.get_path(tab) - self._model[path][0] = self._tabinfo.get_tab_icon(tab) - self._model[path][1] = self._tabinfo.get_tab_name(tab) + self._model[path][0] = tabinfo.get_tab_icon(tab) + self._model[path][1] = tabinfo.get_tab_name(tab) class ControlYourTabsConfigurable(GObject.Object, PeasGtk.Configurable): From 9028d289f7f04b6af365a79f7784a240785ae355 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sat, 5 Oct 2024 06:38:45 +0800 Subject: [PATCH 03/25] split into separate files, require versions --- controlyourtabs/__init__.py | 992 +-------------------------- controlyourtabs/configurable.py | 75 ++ controlyourtabs/keyinfo.py | 6 +- controlyourtabs/log.py | 3 + controlyourtabs/settings.py | 72 ++ controlyourtabs/tabinfo.py | 4 + controlyourtabs/tabmodel.py | 257 +++++++ controlyourtabs/windowactivatable.py | 703 +++++++++++++++++++ 8 files changed, 1121 insertions(+), 991 deletions(-) create mode 100644 controlyourtabs/configurable.py create mode 100644 controlyourtabs/settings.py create mode 100644 controlyourtabs/tabmodel.py create mode 100644 controlyourtabs/windowactivatable.py diff --git a/controlyourtabs/__init__.py b/controlyourtabs/__init__.py index 1b543ff..f1f59f5 100644 --- a/controlyourtabs/__init__.py +++ b/controlyourtabs/__init__.py @@ -19,994 +19,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import gi -gi.require_version('Gtk', '3.0') - -import math -import os.path -from functools import wraps -from gi.repository import GObject, GLib, Gtk, Gdk, GdkPixbuf, Gio, PeasGtk -from .plugin import _, data_dir as plugin_data_dir -from .utils import connect_handlers, disconnect_handlers -from . import editor, keyinfo, log, tabinfo - - -class ControlYourTabsWindowActivatable(GObject.Object, editor.Editor.WindowActivatable): - - __gtype_name__ = 'ControlYourTabsWindowActivatable' - - window = GObject.property(type=editor.Editor.Window) # before pygobject 3.2, lowercase 'p' - - MAX_TAB_WINDOW_ROWS = 9 - - MAX_TAB_WINDOW_HEIGHT_PERCENTAGE = 0.5 - - - def __init__(self): - GObject.Object.__init__(self) - - def do_activate(self): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", self.window)) - - window = self.window - tab_models = {} - - tabwin = Gtk.Window.new(Gtk.WindowType.POPUP) - tabwin.set_transient_for(window) - tabwin.set_destroy_with_parent(True) - tabwin.set_accept_focus(False) - tabwin.set_decorated(False) - tabwin.set_resizable(False) - tabwin.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) - tabwin.set_type_hint(Gdk.WindowTypeHint.UTILITY) - tabwin.set_skip_taskbar_hint(False) - tabwin.set_skip_pager_hint(False) - - sw = Gtk.ScrolledWindow.new(None, None) - sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - sw.show() - - tabwin.add(sw) - - view = Gtk.TreeView.new() - view.set_enable_search(False) - view.set_headers_visible(False) - view.show() - - sw.add(view) - - col = Gtk.TreeViewColumn.new() - col.set_title(_("Documents")) - col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) - - icon_cell = Gtk.CellRendererPixbuf.new() - name_cell = Gtk.CellRendererText.new() - space_cell = Gtk.CellRendererPixbuf.new() - - col.pack_start(icon_cell, False) - col.pack_start(name_cell, True) - col.pack_start(space_cell, False) - - col.add_attribute(icon_cell, 'pixbuf', 0) - col.add_attribute(name_cell, 'markup', 1) - - view.append_column(col) - - sel = view.get_selection() - sel.set_mode(Gtk.SelectionMode.SINGLE) - - # hack to ensure tabwin is correctly positioned/sized on first show - view.realize() - - self._is_switching = False - self._is_tabwin_visible = False - self._is_control_held = keyinfo.default_control_held() - self._initial_tab = None - self._multi = None - self._tab_models = tab_models - self._tabwin = tabwin - self._view = view - self._sw = sw - self._icon_cell = icon_cell - self._space_cell = space_cell - self._tabwin_resize_id = None - self._settings = get_settings() - - tab = window.get_active_tab() - - if tab: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("found active tab %s, setting up now", tab)) - - self.setup(window, tab, tab_models) - - if self._multi: - self.active_tab_changed(tab, tab_models[tab.get_parent()]) - - else: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("waiting for new tab")) - - connect_handlers(self, window, ['tab-added'], 'setup', tab_models) - - def do_deactivate(self): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", self.window)) - - multi = self._multi - tab_models = self._tab_models - - for notebook in list(tab_models.keys()): - self.untrack_notebook(notebook, tab_models) - - if multi: - disconnect_handlers(self, multi) - - disconnect_handlers(self, self.window) - - self.cancel_tabwin_resize() - self.end_switching() - - self._tabwin.destroy() - - self._is_switching = None - self._is_tabwin_visible = None - self._is_control_held = None - self._initial_tab = None - self._multi = None - self._tab_models = None - self._tabwin = None - self._view = None - self._sw = None - self._icon_cell = None - self._space_cell = None - self._tabwin_resize_id = None - self._settings = None - - def do_update_state(self): - pass - - - # plugin setup - - def on_setup_tab_added(self, window, tab, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", window, tab)) - - disconnect_handlers(self, window) - - self.setup(window, tab, tab_models) - - def setup(self, window, tab, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", window, tab)) - - icon_size = tabinfo.get_tab_icon_size(tab) - - self._icon_cell.set_fixed_size(icon_size, icon_size) - self._space_cell.set_fixed_size(icon_size, icon_size) - - multi = window.get_template_child(editor.Editor.Window, 'multi_notebook') - - connect_handlers( - self, multi, - [ - 'notebook-added', - 'notebook-removed', - 'tab-added', - 'tab-removed' - ], - 'multi_notebook', - tab_models - ) - connect_handlers( - self, window, - [ - 'active-tab-changed', - 'key-press-event', - 'key-release-event', - 'focus-out-event', - 'configure-event' - ], - 'window', - tab_models - ) - - self._multi = multi - - for document in window.get_documents(): - notebook = editor.Editor.Tab.get_from_document(document).get_parent() - self.track_notebook(notebook, tab_models) - - - # tracking notebooks / tabs - - def track_notebook(self, notebook, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) - - if notebook in tab_models: - if log.query(log.WARNING): - editor.debug_plugin_message(log.format("already tracking notebook")) - - return - - tab_model = ControlYourTabsTabModel() - - connect_handlers( - self, tab_model, - [ - 'row-inserted', - 'row-deleted', - 'row-changed' - ], - self.on_tab_model_row_changed - ) - connect_handlers( - self, tab_model, - [ - 'selected-path-changed' - ], - 'tab_model' - ) - - tab_models[notebook] = tab_model - - for tab in notebook.get_children(): - self.track_tab(tab, tab_model) - - def untrack_notebook(self, notebook, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) - - if notebook not in tab_models: - if log.query(log.WARNING): - editor.debug_plugin_message(log.format("not tracking notebook")) - - return - - tab_model = tab_models[notebook] - - for tab in notebook.get_children(): - self.untrack_tab(tab, tab_model) - - if self.is_active_view_model(tab_model): - self.set_active_view_model(None) - - disconnect_handlers(self, tab_model) - - del tab_models[notebook] - - def track_tab(self, tab, tab_model): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) - - if tab in tab_model: - if log.query(log.WARNING): - editor.debug_plugin_message(log.format("already tracking tab")) - - return - - tab_model.append(tab) - - connect_handlers( - self, tab, - [ - 'notify::name', - 'notify::state' - ], - self.on_tab_notify_name_state, - tab_model - ) - - def untrack_tab(self, tab, tab_model): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) - - if tab == self._initial_tab: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("tab is initial tab, clearing")) - - self._initial_tab = None - - if tab not in tab_model: - if log.query(log.WARNING): - editor.debug_plugin_message(log.format("not tracking tab")) - - return - - disconnect_handlers(self, tab) - - tab_model.remove(tab) - - def active_tab_changed(self, tab, tab_model): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) - - if not self._is_switching: - tab_model.move_after(tab) - - tab_model.select(tab) - - if not self.is_active_view_model(tab_model): - self.set_active_view_model(tab_model) - self.schedule_tabwin_resize() - - - # signal handlers - - def on_multi_notebook_notebook_added(self, multi, notebook, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) - - self.track_notebook(notebook, tab_models) - - def on_multi_notebook_notebook_removed(self, multi, notebook, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) - - self.untrack_notebook(notebook, tab_models) - - def on_multi_notebook_tab_added(self, multi, notebook, tab, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) - - self.track_tab(tab, tab_models[notebook]) - - def on_multi_notebook_tab_removed(self, multi, notebook, tab, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) - - self.untrack_tab(tab, tab_models[notebook]) - - def on_window_active_tab_changed(self, window, tab, tab_models=None): - # tab parameter removed in gedit 47 - if not tab_models: - tab_models = tab - tab = window.get_active_tab() - - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", window, tab)) - - if tab: - self.active_tab_changed(tab, tab_models[tab.get_parent()]) - - def on_window_key_press_event(self, window, event, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, key=%s", window, Gdk.keyval_name(event.keyval))) - - self._is_control_held = keyinfo.update_control_held(event, self._is_control_held, True) - - return self.key_press_event(event) - - def on_window_key_release_event(self, window, event, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) - - self._is_control_held = keyinfo.update_control_held(event, self._is_control_held, False) - - if not any(self._is_control_held): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("no control keys held down")) - - self.end_switching() - - else: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("one or more control keys held down")) - - def on_window_focus_out_event(self, window, event, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", window)) - - self.end_switching() - - def on_window_configure_event(self, window, event, tab_models): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", window)) - - self.schedule_tabwin_resize() - - def on_tab_notify_name_state(self, tab, pspec, tab_model): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) - - tab_model.update(tab) - - def on_tab_model_row_changed(self, tab_model, path): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) - - if not self.is_active_view_model(tab_model): - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("tab model not active")) - - return - - self.schedule_tabwin_resize() - - def on_tab_model_selected_path_changed(self, tab_model, path): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) - - if not self.is_active_view_model(tab_model): - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("tab model not active")) - - return - - self.set_view_selection(path) - - - # tree view - - def is_active_view_model(self, tab_model): - model = tab_model.model if tab_model else None - return self._view.get_model() is model - - def set_active_view_model(self, tab_model): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self.window, tab_model)) - - model = None - selected_path = None - - if tab_model: - model = tab_model.model - selected_path = tab_model.get_selected_path() - - self._view.set_model(model) - self.set_view_selection(selected_path) - - def set_view_selection(self, path): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) - - view = self._view - selection = view.get_selection() - - if path: - selection.select_path(path) - view.scroll_to_cell(path, None, True, 0.5, 0) - - else: - selection.unselect_all() - - - # tab switching - - def key_press_event(self, event): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) - - settings = self._settings - is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) - block_event = True - - if is_control_tab and settings and settings['use-tabbar-order']: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("coercing ctrl-tab into ctrl-page because of settings")) - - is_control_tab = False - is_control_page = True - - if self._is_switching and is_control_escape: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("ctrl-esc while switching")) - - self.end_switching(True) - - elif is_control_tab or is_control_page: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("ctrl-tab or ctrl-page")) - - self.switch_tab(is_control_tab, keyinfo.is_next_key(event), event.time) - - elif self._is_switching and not self._is_tabwin_visible: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("normal key while switching and tabwin not visible")) - - self.end_switching() - block_event = False - - else: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("normal key while %s", "switching" if self._is_switching else "not switching")) - - block_event = self._is_switching - - return block_event - - def switch_tab(self, use_mru_order, to_next_tab, time): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, use_mru_order=%s, to_next_tab=%s, time=%s", self.window, use_mru_order, to_next_tab, time)) - - window = self.window - current_tab = window.get_active_tab() - - if not current_tab: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("no tabs")) - - return - - notebook = current_tab.get_parent() - - tabs = self._tab_models[notebook] if use_mru_order else notebook.get_children() - num_tabs = len(tabs) - - if num_tabs < 2: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("only 1 tab")) - - return - - current_index = tabs.index(current_tab) - step = 1 if to_next_tab else -1 - next_index = (current_index + step) % num_tabs - - next_tab = tabs[next_index] - - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("switching from %s to %s", current_tab, next_tab)) - - if not self._is_switching: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("saving %s as initial tab", current_tab)) - - self._initial_tab = current_tab - - self._is_switching = True - - window.set_active_tab(next_tab) - - if use_mru_order: - tabwin = self._tabwin - - if not self._is_tabwin_visible: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("showing tabwin")) - - tabwin.show_all() - - else: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("presenting tabwin")) - - tabwin.present_with_time(time) - - self._is_tabwin_visible = True - - def end_switching(self, do_revert=False): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, do_revert=%s", self.window, do_revert)) - - if not self._is_switching: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("not switching")) - - return - - window = self.window - initial_tab = self._initial_tab - - self._tabwin.hide() - - self._is_switching = False - self._is_tabwin_visible = False - self._initial_tab = None - - if do_revert and initial_tab: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("switching to initial tab %s", initial_tab)) - - window.set_active_tab(initial_tab) - - else: - tab = window.get_active_tab() - - if tab: - self.active_tab_changed(tab, self._tab_models[tab.get_parent()]) - - - # tab window resizing - - def schedule_tabwin_resize(self): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", self.window)) - - if self._tabwin_resize_id: - if log.query(log.INFO): - editor.debug_plugin_message(log.format("already scheduled")) - - return - - # need to wait a little before asking the treeview for its preferred size - # maybe because treeview rendering is async? - # this feels like a giant hack - try: - resize_id = GLib.idle_add(self.do_tabwin_resize) - except TypeError: # before pygobject 3.0 - resize_id = GObject.idle_add(self.do_tabwin_resize) - - self._tabwin_resize_id = resize_id - - def cancel_tabwin_resize(self): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", self.window)) - - if not self._tabwin_resize_id: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("not scheduled")) - - return - - GLib.source_remove(self._tabwin_resize_id) - - self._tabwin_resize_id = None - - def do_tabwin_resize(self): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", self.window)) - - view = self._view - sw = self._sw - - view_min_size, view_nat_size = view.get_preferred_size() - view_height = max(view_min_size.height, view_nat_size.height) - - num_rows = len(view.get_model()) - if num_rows: - row_height = math.ceil(view_height / num_rows) - max_rows_height = self.MAX_TAB_WINDOW_ROWS * row_height - else: - max_rows_height = float('inf') - - win_width, win_height = self.window.get_size() - max_win_height = round(self.MAX_TAB_WINDOW_HEIGHT_PERCENTAGE * win_height) - - max_height = min(max_rows_height, max_win_height) - - # we can't reliably tell if overlay scrolling is being used - # since gtk_scrolled_window_get_overlay_scrolling() can still return True if GTK_OVERLAY_SCROLLING=0 is set - # and even if we can tell if overlay scrolling is disabled, - # we cannot tell if the scrolled window has reserved enough space for the scrollbar - # fedora < 25: reserved - # fedora >= 25: not reserved - # ubuntu 17.04: reserved - # so let's ignore overlay scrolling for now :-( - - vscrollbar_policy = Gtk.PolicyType.AUTOMATIC if view_height > max_height else Gtk.PolicyType.NEVER - sw.set_policy(Gtk.PolicyType.NEVER, vscrollbar_policy) - - sw_min_size, sw_nat_size = sw.get_preferred_size() - - tabwin_width = max(sw_min_size.width, sw_nat_size.width) - tabwin_height = min(view_height, max_height) - - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("view height = %s", view_height)) - editor.debug_plugin_message(log.format("max rows height = %s", max_rows_height)) - editor.debug_plugin_message(log.format("max win height = %s", max_win_height)) - editor.debug_plugin_message(log.format("tabwin height = %s", tabwin_height)) - editor.debug_plugin_message(log.format("tabwin width = %s", tabwin_width)) - - self._tabwin.set_size_request(tabwin_width, tabwin_height) - - self._tabwin_resize_id = None - - return False - - -class ControlYourTabsTabModel(GObject.Object): - - __gtype_name__ = 'ControlYourTabsTabModel' - - __gsignals__ = { # before pygobject 3.4 - 'row-inserted': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.TreePath,)), - 'row-deleted': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.TreePath,)), - 'row-changed': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.TreePath,)), - 'rows-reordered': (GObject.SignalFlags.RUN_FIRST, None, ()), - 'selected-path-changed': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.TreePath,)) - } - - - def _model_modifier(fn): - @wraps(fn) - def wrapper(self, *args, **kwargs): - prev_path = self.get_selected_path() - - result = fn(self, *args, **kwargs) - - cur_path = self.get_selected_path() - - if cur_path != prev_path: - self.emit('selected-path-changed', cur_path) - - return result - - return wrapper - - - def __init__(self): - GObject.Object.__init__(self) - - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", self)) - - self._model = Gtk.ListStore.new((GdkPixbuf.Pixbuf, str, editor.Editor.Tab)) - self._references = {} - self._selected = None - - connect_handlers( - self, self._model, - [ - 'row-inserted', - 'row-deleted', - 'row-changed', - 'rows-reordered' - ], - 'model' - ) - - def __len__(self): - return len(self._model) - - def __getitem__(self, key): - return self._model[key][2] - - @_model_modifier - def __delitem__(self, key): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, key=%s", self, key)) - - tab = self._model[key][2] - - if self._selected == tab: - self._selected = None - - del self._references[tab] - - # before pygobject 3.2, cannot del model[path] - self._model.remove(self._model.get_iter(key)) - - def __iter__(self): - return [row[2] for row in self._model] - - def __contains__(self, item): - return item in self._references - - @property - def model(self): - return self._model - - def on_model_row_inserted(self, model, path, iter_): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) - - self.emit('row-inserted', path) - - def on_model_row_deleted(self, model, path): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) - - self.emit('row-deleted', path) - - def on_model_row_changed(self, model, path, iter_): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) - - self.emit('row-changed', path) - - def on_model_rows_reordered(self, model, path, iter_, new_order): - if log.query(log.INFO): - # path is suppose to point to the parent node of the reordered rows - # if top level rows are reordered, path is invalid (null?) - # so don't print it out here, because will throw an error - editor.debug_plugin_message(log.format("%s, %s", self, model)) - - self.emit('rows-reordered') - - def do_row_inserted(self, path): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, path=%s", self, path)) - - def do_row_deleted(self, path): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, path=%s", self, path)) - - def do_row_changed(self, path): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, path=%s", self, path)) - - def do_rows_reordered(self): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", self)) - - def do_selected_path_changed(self, path): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, path=%s", self, path)) - - @_model_modifier - def insert(self, position, tab): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, position=%s, %s", self, position, tab)) - - tab_iter = self._model.insert( - position, - ( - tabinfo.get_tab_icon(tab), - tabinfo.get_tab_name(tab), - tab - ) - ) - - self._references[tab] = Gtk.TreeRowReference.new(self._model, self._model.get_path(tab_iter)) - - def append(self, tab): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self, tab)) - - self.insert(len(self._model), tab) # before pygobject 3.2, -1 position does not work - - def prepend(self, tab): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self, tab)) - - self.insert(0, tab) - - def remove(self, tab): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self, tab)) - - del self[self.get_path(tab)] - - @_model_modifier - def move(self, tab, sibling, move_before): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s, %s, move_before=%s", self, tab, sibling, move_before)) - - tab_iter = self._get_iter(tab) - sibling_iter = self._get_iter(sibling) if sibling else None - - if move_before: - self._model.move_before(tab_iter, sibling_iter) - else: - self._model.move_after(tab_iter, sibling_iter) - - def move_before(self, tab, sibling=None): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) - - self.move(tab, sibling, True) - - def move_after(self, tab, sibling=None): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) - - self.move(tab, sibling, False) - - def get_path(self, tab): - return self._references[tab].get_path() - - def index(self, tab): - return int(str(self.get_path(tab))) - - def _get_iter(self, tab): - return self._model.get_iter(self.get_path(tab)) - - @_model_modifier - def select(self, tab): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self, tab)) - - self._selected = tab - - def unselect(self): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", self)) - - self.select(None) - - def get_selected(self): - return self._selected - - def get_selected_path(self): - return self.get_path(self._selected) if self._selected else None - - def update(self, tab): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s, %s", self, tab)) - - path = self.get_path(tab) - - self._model[path][0] = tabinfo.get_tab_icon(tab) - self._model[path][1] = tabinfo.get_tab_name(tab) - - -class ControlYourTabsConfigurable(GObject.Object, PeasGtk.Configurable): - - __gtype_name__ = 'ControlYourTabsConfigurable' - - - def do_create_configure_widget(self): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("")) - - settings = get_settings() - - if settings: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("have settings")) - - widget = Gtk.CheckButton.new_with_label( - _("Use tab row order for Ctrl+Tab / Ctrl+Shift+Tab") - ) - - settings.bind( - 'use-tabbar-order', - widget, 'active', - Gio.SettingsBindFlags.DEFAULT - ) - - widget._settings = settings - - else: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("no settings")) - - widget = Gtk.Label.new( - # translators: %s: application name - _("Sorry, no preferences are available for this version of %s.") % editor.name - ) - - box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) - box.set_border_width(5) - box.add(widget) - - return box - - -def get_settings(): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("")) - - schemas_path = os.path.join(plugin_data_dir, 'schemas') - - try: - schema_source = Gio.SettingsSchemaSource.new_from_directory( - schemas_path, - Gio.SettingsSchemaSource.get_default(), - False - ) - - except: - if log.query(log.WARNING): - editor.debug_plugin_message(log.format("could not load settings schema source from %s", schemas_path)) - - schema_source = None - - if not schema_source: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("no schema source")) - - return None - - schema = schema_source.lookup( - 'com.thingsthemselves.%s.plugins.controlyourtabs' % editor.name.lower(), - False - ) - - if not schema: - if log.query(log.WARNING): - editor.debug_plugin_message(log.format("could not lookup schema")) - - return None - - return Gio.Settings.new_full( - schema, - None, - '/com/thingsthemselves/%s/plugins/controlyourtabs/' % editor.name.lower() - ) +from .configurable import ControlYourTabsConfigurable +from .windowactivatable import ControlYourTabsWindowActivatable diff --git a/controlyourtabs/configurable.py b/controlyourtabs/configurable.py new file mode 100644 index 0000000..3fc3436 --- /dev/null +++ b/controlyourtabs/configurable.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# configurable.py +# This file is part of Control Your Tabs, a plugin for gedit +# +# Copyright (C) 2010-2013, 2017-2018, 2020, 2023-2024 Jeffery To +# https://github.com/jefferyto/gedit-control-your-tabs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('GObject', '2.0') +gi.require_version('Gio', '2.0') +gi.require_version('Gtk', '3.0') +gi.require_version('PeasGtk', '1.0') + +from gi.repository import GObject, Gio, Gtk, PeasGtk +from .plugin import _ +from .settings import get_settings +from . import editor, log + + +class ControlYourTabsConfigurable(GObject.Object, PeasGtk.Configurable): + + __gtype_name__ = 'ControlYourTabsConfigurable' + + + def do_create_configure_widget(self): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("")) + + settings = get_settings() + + if settings: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("have settings")) + + widget = Gtk.CheckButton.new_with_label( + _("Use tab row order for Ctrl+Tab / Ctrl+Shift+Tab") + ) + + settings.bind( + 'use-tabbar-order', + widget, 'active', + Gio.SettingsBindFlags.DEFAULT + ) + + widget._settings = settings + + else: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("no settings")) + + widget = Gtk.Label.new( + # translators: %s: application name + _("Sorry, no preferences are available for this version of %s.") % editor.name + ) + + box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + box.set_border_width(5) + box.add(widget) + + return box + diff --git a/controlyourtabs/keyinfo.py b/controlyourtabs/keyinfo.py index dcbd63d..bfd09b5 100644 --- a/controlyourtabs/keyinfo.py +++ b/controlyourtabs/keyinfo.py @@ -19,7 +19,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from gi.repository import Gtk, Gdk +import gi +gi.require_version('Gdk', '3.0') +gi.require_version('Gtk', '3.0') + +from gi.repository import Gdk, Gtk from . import editor, log diff --git a/controlyourtabs/log.py b/controlyourtabs/log.py index 8da28a6..e1297ca 100644 --- a/controlyourtabs/log.py +++ b/controlyourtabs/log.py @@ -19,6 +19,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import gi +gi.require_version('GLib', '2.0') + import os from gi.repository import GLib from .utils import debug_str diff --git a/controlyourtabs/settings.py b/controlyourtabs/settings.py new file mode 100644 index 0000000..9a09e0a --- /dev/null +++ b/controlyourtabs/settings.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# +# settings.py +# This file is part of Control Your Tabs, a plugin for gedit +# +# Copyright (C) 2010-2013, 2017-2018, 2020, 2023-2024 Jeffery To +# https://github.com/jefferyto/gedit-control-your-tabs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('Gio', '2.0') + +import os.path +from gi.repository import Gio +from .plugin import data_dir as plugin_data_dir +from . import editor, log + + +def get_settings(): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("")) + + schemas_path = os.path.join(plugin_data_dir, 'schemas') + + try: + schema_source = Gio.SettingsSchemaSource.new_from_directory( + schemas_path, + Gio.SettingsSchemaSource.get_default(), + False + ) + + except: + if log.query(log.WARNING): + editor.debug_plugin_message(log.format("could not load settings schema source from %s", schemas_path)) + + schema_source = None + + if not schema_source: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("no schema source")) + + return None + + schema = schema_source.lookup( + 'com.thingsthemselves.%s.plugins.controlyourtabs' % editor.name.lower(), + False + ) + + if not schema: + if log.query(log.WARNING): + editor.debug_plugin_message(log.format("could not lookup schema")) + + return None + + return Gio.Settings.new_full( + schema, + None, + '/com/thingsthemselves/%s/plugins/controlyourtabs/' % editor.name.lower() + ) + diff --git a/controlyourtabs/tabinfo.py b/controlyourtabs/tabinfo.py index 6e8c611..ad7b5c2 100644 --- a/controlyourtabs/tabinfo.py +++ b/controlyourtabs/tabinfo.py @@ -19,6 +19,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import gi +gi.require_version('Gtk', '3.0') +# GtkSource can be version 3 or 4 or 300 + import os.path from gi.repository import Gtk, GtkSource from xml.sax.saxutils import escape diff --git a/controlyourtabs/tabmodel.py b/controlyourtabs/tabmodel.py new file mode 100644 index 0000000..46881dd --- /dev/null +++ b/controlyourtabs/tabmodel.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +# +# tabmodel.py +# This file is part of Control Your Tabs, a plugin for gedit +# +# Copyright (C) 2010-2013, 2017-2018, 2020, 2023-2024 Jeffery To +# https://github.com/jefferyto/gedit-control-your-tabs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('GObject', '2.0') +gi.require_version('GdkPixbuf', '2.0') +gi.require_version('Gtk', '3.0') + +from functools import wraps +from gi.repository import GObject, GdkPixbuf, Gtk +from .utils import connect_handlers +from . import editor, log, tabinfo + + +class ControlYourTabsTabModel(GObject.Object): + + __gtype_name__ = 'ControlYourTabsTabModel' + + __gsignals__ = { # before pygobject 3.4 + 'row-inserted': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.TreePath,)), + 'row-deleted': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.TreePath,)), + 'row-changed': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.TreePath,)), + 'rows-reordered': (GObject.SignalFlags.RUN_FIRST, None, ()), + 'selected-path-changed': (GObject.SignalFlags.RUN_FIRST, None, (Gtk.TreePath,)) + } + + + def _model_modifier(fn): + @wraps(fn) + def wrapper(self, *args, **kwargs): + prev_path = self.get_selected_path() + + result = fn(self, *args, **kwargs) + + cur_path = self.get_selected_path() + + if cur_path != prev_path: + self.emit('selected-path-changed', cur_path) + + return result + + return wrapper + + + def __init__(self): + GObject.Object.__init__(self) + + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", self)) + + self._model = Gtk.ListStore.new((GdkPixbuf.Pixbuf, str, editor.Editor.Tab)) + self._references = {} + self._selected = None + + connect_handlers( + self, self._model, + [ + 'row-inserted', + 'row-deleted', + 'row-changed', + 'rows-reordered' + ], + 'model' + ) + + def __len__(self): + return len(self._model) + + def __getitem__(self, key): + return self._model[key][2] + + @_model_modifier + def __delitem__(self, key): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, key=%s", self, key)) + + tab = self._model[key][2] + + if self._selected == tab: + self._selected = None + + del self._references[tab] + + # before pygobject 3.2, cannot del model[path] + self._model.remove(self._model.get_iter(key)) + + def __iter__(self): + return [row[2] for row in self._model] + + def __contains__(self, item): + return item in self._references + + @property + def model(self): + return self._model + + def on_model_row_inserted(self, model, path, iter_): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) + + self.emit('row-inserted', path) + + def on_model_row_deleted(self, model, path): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) + + self.emit('row-deleted', path) + + def on_model_row_changed(self, model, path, iter_): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) + + self.emit('row-changed', path) + + def on_model_rows_reordered(self, model, path, iter_, new_order): + if log.query(log.INFO): + # path is suppose to point to the parent node of the reordered rows + # if top level rows are reordered, path is invalid (null?) + # so don't print it out here, because will throw an error + editor.debug_plugin_message(log.format("%s, %s", self, model)) + + self.emit('rows-reordered') + + def do_row_inserted(self, path): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, path=%s", self, path)) + + def do_row_deleted(self, path): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, path=%s", self, path)) + + def do_row_changed(self, path): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, path=%s", self, path)) + + def do_rows_reordered(self): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", self)) + + def do_selected_path_changed(self, path): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, path=%s", self, path)) + + @_model_modifier + def insert(self, position, tab): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, position=%s, %s", self, position, tab)) + + tab_iter = self._model.insert( + position, + ( + tabinfo.get_tab_icon(tab), + tabinfo.get_tab_name(tab), + tab + ) + ) + + self._references[tab] = Gtk.TreeRowReference.new(self._model, self._model.get_path(tab_iter)) + + def append(self, tab): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self, tab)) + + self.insert(len(self._model), tab) # before pygobject 3.2, -1 position does not work + + def prepend(self, tab): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self, tab)) + + self.insert(0, tab) + + def remove(self, tab): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self, tab)) + + del self[self.get_path(tab)] + + @_model_modifier + def move(self, tab, sibling, move_before): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, %s, move_before=%s", self, tab, sibling, move_before)) + + tab_iter = self._get_iter(tab) + sibling_iter = self._get_iter(sibling) if sibling else None + + if move_before: + self._model.move_before(tab_iter, sibling_iter) + else: + self._model.move_after(tab_iter, sibling_iter) + + def move_before(self, tab, sibling=None): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) + + self.move(tab, sibling, True) + + def move_after(self, tab, sibling=None): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) + + self.move(tab, sibling, False) + + def get_path(self, tab): + return self._references[tab].get_path() + + def index(self, tab): + return int(str(self.get_path(tab))) + + def _get_iter(self, tab): + return self._model.get_iter(self.get_path(tab)) + + @_model_modifier + def select(self, tab): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self, tab)) + + self._selected = tab + + def unselect(self): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", self)) + + self.select(None) + + def get_selected(self): + return self._selected + + def get_selected_path(self): + return self.get_path(self._selected) if self._selected else None + + def update(self, tab): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self, tab)) + + path = self.get_path(tab) + + self._model[path][0] = tabinfo.get_tab_icon(tab) + self._model[path][1] = tabinfo.get_tab_name(tab) + diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py new file mode 100644 index 0000000..b80df10 --- /dev/null +++ b/controlyourtabs/windowactivatable.py @@ -0,0 +1,703 @@ +# -*- coding: utf-8 -*- +# +# windowactivatable.py +# This file is part of Control Your Tabs, a plugin for gedit +# +# Copyright (C) 2010-2013, 2017-2018, 2020, 2023-2024 Jeffery To +# https://github.com/jefferyto/gedit-control-your-tabs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gi +gi.require_version('GLib', '2.0') +gi.require_version('GObject', '2.0') +gi.require_version('Gdk', '3.0') +gi.require_version('Gtk', '3.0') + +import math +from gi.repository import GLib, GObject, Gdk, Gtk +from .plugin import _ +from .settings import get_settings +from .tabmodel import ControlYourTabsTabModel +from .utils import connect_handlers, disconnect_handlers +from . import editor, keyinfo, log, tabinfo + + +class ControlYourTabsWindowActivatable(GObject.Object, editor.Editor.WindowActivatable): + + __gtype_name__ = 'ControlYourTabsWindowActivatable' + + window = GObject.property(type=editor.Editor.Window) # before pygobject 3.2, lowercase 'p' + + MAX_TAB_WINDOW_ROWS = 9 + + MAX_TAB_WINDOW_HEIGHT_PERCENTAGE = 0.5 + + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", self.window)) + + window = self.window + tab_models = {} + + tabwin = Gtk.Window.new(Gtk.WindowType.POPUP) + tabwin.set_transient_for(window) + tabwin.set_destroy_with_parent(True) + tabwin.set_accept_focus(False) + tabwin.set_decorated(False) + tabwin.set_resizable(False) + tabwin.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) + tabwin.set_type_hint(Gdk.WindowTypeHint.UTILITY) + tabwin.set_skip_taskbar_hint(False) + tabwin.set_skip_pager_hint(False) + + sw = Gtk.ScrolledWindow.new(None, None) + sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + sw.show() + + tabwin.add(sw) + + view = Gtk.TreeView.new() + view.set_enable_search(False) + view.set_headers_visible(False) + view.show() + + sw.add(view) + + col = Gtk.TreeViewColumn.new() + col.set_title(_("Documents")) + col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + + icon_cell = Gtk.CellRendererPixbuf.new() + name_cell = Gtk.CellRendererText.new() + space_cell = Gtk.CellRendererPixbuf.new() + + col.pack_start(icon_cell, False) + col.pack_start(name_cell, True) + col.pack_start(space_cell, False) + + col.add_attribute(icon_cell, 'pixbuf', 0) + col.add_attribute(name_cell, 'markup', 1) + + view.append_column(col) + + sel = view.get_selection() + sel.set_mode(Gtk.SelectionMode.SINGLE) + + # hack to ensure tabwin is correctly positioned/sized on first show + view.realize() + + self._is_switching = False + self._is_tabwin_visible = False + self._is_control_held = keyinfo.default_control_held() + self._initial_tab = None + self._multi = None + self._tab_models = tab_models + self._tabwin = tabwin + self._view = view + self._sw = sw + self._icon_cell = icon_cell + self._space_cell = space_cell + self._tabwin_resize_id = None + self._settings = get_settings() + + tab = window.get_active_tab() + + if tab: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("found active tab %s, setting up now", tab)) + + self.setup(window, tab, tab_models) + + if self._multi: + self.active_tab_changed(tab, tab_models[tab.get_parent()]) + + else: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("waiting for new tab")) + + connect_handlers(self, window, ['tab-added'], 'setup', tab_models) + + def do_deactivate(self): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", self.window)) + + multi = self._multi + tab_models = self._tab_models + + for notebook in list(tab_models.keys()): + self.untrack_notebook(notebook, tab_models) + + if multi: + disconnect_handlers(self, multi) + + disconnect_handlers(self, self.window) + + self.cancel_tabwin_resize() + self.end_switching() + + self._tabwin.destroy() + + self._is_switching = None + self._is_tabwin_visible = None + self._is_control_held = None + self._initial_tab = None + self._multi = None + self._tab_models = None + self._tabwin = None + self._view = None + self._sw = None + self._icon_cell = None + self._space_cell = None + self._tabwin_resize_id = None + self._settings = None + + def do_update_state(self): + pass + + + # plugin setup + + def on_setup_tab_added(self, window, tab, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", window, tab)) + + disconnect_handlers(self, window) + + self.setup(window, tab, tab_models) + + def setup(self, window, tab, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", window, tab)) + + icon_size = tabinfo.get_tab_icon_size(tab) + + self._icon_cell.set_fixed_size(icon_size, icon_size) + self._space_cell.set_fixed_size(icon_size, icon_size) + + multi = window.get_template_child(editor.Editor.Window, 'multi_notebook') + + connect_handlers( + self, multi, + [ + 'notebook-added', + 'notebook-removed', + 'tab-added', + 'tab-removed' + ], + 'multi_notebook', + tab_models + ) + connect_handlers( + self, window, + [ + 'active-tab-changed', + 'key-press-event', + 'key-release-event', + 'focus-out-event', + 'configure-event' + ], + 'window', + tab_models + ) + + self._multi = multi + + for document in window.get_documents(): + notebook = editor.Editor.Tab.get_from_document(document).get_parent() + self.track_notebook(notebook, tab_models) + + + # tracking notebooks / tabs + + def track_notebook(self, notebook, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) + + if notebook in tab_models: + if log.query(log.WARNING): + editor.debug_plugin_message(log.format("already tracking notebook")) + + return + + tab_model = ControlYourTabsTabModel() + + connect_handlers( + self, tab_model, + [ + 'row-inserted', + 'row-deleted', + 'row-changed' + ], + self.on_tab_model_row_changed + ) + connect_handlers( + self, tab_model, + [ + 'selected-path-changed' + ], + 'tab_model' + ) + + tab_models[notebook] = tab_model + + for tab in notebook.get_children(): + self.track_tab(tab, tab_model) + + def untrack_notebook(self, notebook, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) + + if notebook not in tab_models: + if log.query(log.WARNING): + editor.debug_plugin_message(log.format("not tracking notebook")) + + return + + tab_model = tab_models[notebook] + + for tab in notebook.get_children(): + self.untrack_tab(tab, tab_model) + + if self.is_active_view_model(tab_model): + self.set_active_view_model(None) + + disconnect_handlers(self, tab_model) + + del tab_models[notebook] + + def track_tab(self, tab, tab_model): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) + + if tab in tab_model: + if log.query(log.WARNING): + editor.debug_plugin_message(log.format("already tracking tab")) + + return + + tab_model.append(tab) + + connect_handlers( + self, tab, + [ + 'notify::name', + 'notify::state' + ], + self.on_tab_notify_name_state, + tab_model + ) + + def untrack_tab(self, tab, tab_model): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) + + if tab == self._initial_tab: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("tab is initial tab, clearing")) + + self._initial_tab = None + + if tab not in tab_model: + if log.query(log.WARNING): + editor.debug_plugin_message(log.format("not tracking tab")) + + return + + disconnect_handlers(self, tab) + + tab_model.remove(tab) + + def active_tab_changed(self, tab, tab_model): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) + + if not self._is_switching: + tab_model.move_after(tab) + + tab_model.select(tab) + + if not self.is_active_view_model(tab_model): + self.set_active_view_model(tab_model) + self.schedule_tabwin_resize() + + + # signal handlers + + def on_multi_notebook_notebook_added(self, multi, notebook, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) + + self.track_notebook(notebook, tab_models) + + def on_multi_notebook_notebook_removed(self, multi, notebook, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) + + self.untrack_notebook(notebook, tab_models) + + def on_multi_notebook_tab_added(self, multi, notebook, tab, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) + + self.track_tab(tab, tab_models[notebook]) + + def on_multi_notebook_tab_removed(self, multi, notebook, tab, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) + + self.untrack_tab(tab, tab_models[notebook]) + + def on_window_active_tab_changed(self, window, tab, tab_models=None): + # tab parameter removed in gedit 47 + if not tab_models: + tab_models = tab + tab = window.get_active_tab() + + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", window, tab)) + + if tab: + self.active_tab_changed(tab, tab_models[tab.get_parent()]) + + def on_window_key_press_event(self, window, event, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, key=%s", window, Gdk.keyval_name(event.keyval))) + + self._is_control_held = keyinfo.update_control_held(event, self._is_control_held, True) + + return self.key_press_event(event) + + def on_window_key_release_event(self, window, event, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) + + self._is_control_held = keyinfo.update_control_held(event, self._is_control_held, False) + + if not any(self._is_control_held): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("no control keys held down")) + + self.end_switching() + + else: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("one or more control keys held down")) + + def on_window_focus_out_event(self, window, event, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", window)) + + self.end_switching() + + def on_window_configure_event(self, window, event, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", window)) + + self.schedule_tabwin_resize() + + def on_tab_notify_name_state(self, tab, pspec, tab_model): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) + + tab_model.update(tab) + + def on_tab_model_row_changed(self, tab_model, path): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) + + if not self.is_active_view_model(tab_model): + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("tab model not active")) + + return + + self.schedule_tabwin_resize() + + def on_tab_model_selected_path_changed(self, tab_model, path): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) + + if not self.is_active_view_model(tab_model): + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("tab model not active")) + + return + + self.set_view_selection(path) + + + # tree view + + def is_active_view_model(self, tab_model): + model = tab_model.model if tab_model else None + return self._view.get_model() is model + + def set_active_view_model(self, tab_model): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s", self.window, tab_model)) + + model = None + selected_path = None + + if tab_model: + model = tab_model.model + selected_path = tab_model.get_selected_path() + + self._view.set_model(model) + self.set_view_selection(selected_path) + + def set_view_selection(self, path): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) + + view = self._view + selection = view.get_selection() + + if path: + selection.select_path(path) + view.scroll_to_cell(path, None, True, 0.5, 0) + + else: + selection.unselect_all() + + + # tab switching + + def key_press_event(self, event): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) + + settings = self._settings + is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) + block_event = True + + if is_control_tab and settings and settings['use-tabbar-order']: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("coercing ctrl-tab into ctrl-page because of settings")) + + is_control_tab = False + is_control_page = True + + if self._is_switching and is_control_escape: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("ctrl-esc while switching")) + + self.end_switching(True) + + elif is_control_tab or is_control_page: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("ctrl-tab or ctrl-page")) + + self.switch_tab(is_control_tab, keyinfo.is_next_key(event), event.time) + + elif self._is_switching and not self._is_tabwin_visible: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("normal key while switching and tabwin not visible")) + + self.end_switching() + block_event = False + + else: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("normal key while %s", "switching" if self._is_switching else "not switching")) + + block_event = self._is_switching + + return block_event + + def switch_tab(self, use_mru_order, to_next_tab, time): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, use_mru_order=%s, to_next_tab=%s, time=%s", self.window, use_mru_order, to_next_tab, time)) + + window = self.window + current_tab = window.get_active_tab() + + if not current_tab: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("no tabs")) + + return + + notebook = current_tab.get_parent() + + tabs = self._tab_models[notebook] if use_mru_order else notebook.get_children() + num_tabs = len(tabs) + + if num_tabs < 2: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("only 1 tab")) + + return + + current_index = tabs.index(current_tab) + step = 1 if to_next_tab else -1 + next_index = (current_index + step) % num_tabs + + next_tab = tabs[next_index] + + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("switching from %s to %s", current_tab, next_tab)) + + if not self._is_switching: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("saving %s as initial tab", current_tab)) + + self._initial_tab = current_tab + + self._is_switching = True + + window.set_active_tab(next_tab) + + if use_mru_order: + tabwin = self._tabwin + + if not self._is_tabwin_visible: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("showing tabwin")) + + tabwin.show_all() + + else: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("presenting tabwin")) + + tabwin.present_with_time(time) + + self._is_tabwin_visible = True + + def end_switching(self, do_revert=False): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, do_revert=%s", self.window, do_revert)) + + if not self._is_switching: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("not switching")) + + return + + window = self.window + initial_tab = self._initial_tab + + self._tabwin.hide() + + self._is_switching = False + self._is_tabwin_visible = False + self._initial_tab = None + + if do_revert and initial_tab: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("switching to initial tab %s", initial_tab)) + + window.set_active_tab(initial_tab) + + else: + tab = window.get_active_tab() + + if tab: + self.active_tab_changed(tab, self._tab_models[tab.get_parent()]) + + + # tab window resizing + + def schedule_tabwin_resize(self): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", self.window)) + + if self._tabwin_resize_id: + if log.query(log.INFO): + editor.debug_plugin_message(log.format("already scheduled")) + + return + + # need to wait a little before asking the treeview for its preferred size + # maybe because treeview rendering is async? + # this feels like a giant hack + try: + resize_id = GLib.idle_add(self.do_tabwin_resize) + except TypeError: # before pygobject 3.0 + resize_id = GObject.idle_add(self.do_tabwin_resize) + + self._tabwin_resize_id = resize_id + + def cancel_tabwin_resize(self): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", self.window)) + + if not self._tabwin_resize_id: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("not scheduled")) + + return + + GLib.source_remove(self._tabwin_resize_id) + + self._tabwin_resize_id = None + + def do_tabwin_resize(self): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s", self.window)) + + view = self._view + sw = self._sw + + view_min_size, view_nat_size = view.get_preferred_size() + view_height = max(view_min_size.height, view_nat_size.height) + + num_rows = len(view.get_model()) + if num_rows: + row_height = math.ceil(view_height / num_rows) + max_rows_height = self.MAX_TAB_WINDOW_ROWS * row_height + else: + max_rows_height = float('inf') + + win_width, win_height = self.window.get_size() + max_win_height = round(self.MAX_TAB_WINDOW_HEIGHT_PERCENTAGE * win_height) + + max_height = min(max_rows_height, max_win_height) + + # we can't reliably tell if overlay scrolling is being used + # since gtk_scrolled_window_get_overlay_scrolling() can still return True if GTK_OVERLAY_SCROLLING=0 is set + # and even if we can tell if overlay scrolling is disabled, + # we cannot tell if the scrolled window has reserved enough space for the scrollbar + # fedora < 25: reserved + # fedora >= 25: not reserved + # ubuntu 17.04: reserved + # so let's ignore overlay scrolling for now :-( + + vscrollbar_policy = Gtk.PolicyType.AUTOMATIC if view_height > max_height else Gtk.PolicyType.NEVER + sw.set_policy(Gtk.PolicyType.NEVER, vscrollbar_policy) + + sw_min_size, sw_nat_size = sw.get_preferred_size() + + tabwin_width = max(sw_min_size.width, sw_nat_size.width) + tabwin_height = min(view_height, max_height) + + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("view height = %s", view_height)) + editor.debug_plugin_message(log.format("max rows height = %s", max_rows_height)) + editor.debug_plugin_message(log.format("max win height = %s", max_win_height)) + editor.debug_plugin_message(log.format("tabwin height = %s", tabwin_height)) + editor.debug_plugin_message(log.format("tabwin width = %s", tabwin_width)) + + self._tabwin.set_size_request(tabwin_width, tabwin_height) + + self._tabwin_resize_id = None + + return False + From 9ee93c14c702f6756707d1c7d63f6fae4ec8b383 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sat, 5 Oct 2024 08:23:46 +0800 Subject: [PATCH 04/25] non-symbolic icons --- controlyourtabs/editor.py | 1 + controlyourtabs/tabinfo.py | 52 +++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/controlyourtabs/editor.py b/controlyourtabs/editor.py index bf17a39..33881d0 100644 --- a/controlyourtabs/editor.py +++ b/controlyourtabs/editor.py @@ -61,6 +61,7 @@ def _debug_plugin_message(format, *format_args): gi.require_version('Gedit', '3.0') from gi.repository import Gedit as Editor name = 'gedit' +use_symbolic_icons = True try: debug_plugin_message = Editor.debug_plugin_message diff --git a/controlyourtabs/tabinfo.py b/controlyourtabs/tabinfo.py index ad7b5c2..c783065 100644 --- a/controlyourtabs/tabinfo.py +++ b/controlyourtabs/tabinfo.py @@ -31,30 +31,30 @@ # based on switch statement in _gedit_tab_get_icon() in gedit-tab.c -TAB_STATE_TO_NAMED_ICON = {} -try: - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.PRINTING] = 'printer-printing-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.SHOWING_PRINT_PREVIEW] = 'printer-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.LOADING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.REVERTING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.SAVING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.GENERIC_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.EXTERNALLY_MODIFIED_NOTIFICATION] = 'dialog-warning-symbolic' -except AttributeError: - # constant names before gedit 47 - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_PRINTING] = 'printer-printing-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_SHOWING_PRINT_PREVIEW] = 'printer-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_LOADING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_REVERTING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_SAVING_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_GENERIC_ERROR] = 'dialog-error-symbolic' - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_EXTERNALLY_MODIFIED_NOTIFICATION] = 'dialog-warning-symbolic' - -try: - # Gedit.TabState.STATE_PRINT_PREVIEWING removed in gedit 3.36 - TAB_STATE_TO_NAMED_ICON[editor.Editor.TabState.STATE_PRINT_PREVIEWING] = 'printer-symbolic' -except AttributeError: - pass +STATE_ICONS = { + 'PRINTING': 'document-print', + 'PRINT_PREVIEWING': 'document-print-preview', # removed in gedit 3.36 + 'SHOWING_PRINT_PREVIEW': 'document-print-preview', + 'LOADING_ERROR': 'dialog-error', + 'REVERTING_ERROR': 'dialog-error', + 'SAVING_ERROR': 'dialog-error', + 'GENERIC_ERROR': 'dialog-error', + 'EXTERNALLY_MODIFIED_NOTIFICATION': 'dialog-warning' +} + +TAB_STATE_ICONS = {} +for state_name, icon_name in STATE_ICONS.items(): + state = None + if hasattr(editor.Editor.TabState, state_name): + state = getattr(editor.Editor.TabState, state_name) + elif hasattr(editor.Editor.TabState, 'STATE_' + state_name): # before gedit 47 + state = getattr(editor.Editor.TabState, 'STATE_' + state_name) + + if editor.use_symbolic_icons: + icon_name += '-symbolic' + + if state: + TAB_STATE_ICONS[state] = icon_name # based on doc_get_name() and document_row_sync_tab_name_and_icon() in gedit-documents-panel.c def get_tab_name(tab): @@ -90,11 +90,11 @@ def get_tab_icon(tab): state = tab.get_state() - if state not in TAB_STATE_TO_NAMED_ICON: + if state not in TAB_STATE_ICONS: return None theme = Gtk.IconTheme.get_for_screen(tab.get_screen()) - icon_name = TAB_STATE_TO_NAMED_ICON[state] + icon_name = TAB_STATE_ICONS[state] icon_size = get_tab_icon_size(tab) return Gtk.IconTheme.load_icon(theme, icon_name, icon_size, 0) From f73a210c306c65fab8b0ca2f5d1fb46557f0e09e Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sun, 6 Oct 2024 21:55:54 +0800 Subject: [PATCH 05/25] tab name style --- controlyourtabs/editor.py | 1 + controlyourtabs/tabinfo.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/controlyourtabs/editor.py b/controlyourtabs/editor.py index 33881d0..964eb14 100644 --- a/controlyourtabs/editor.py +++ b/controlyourtabs/editor.py @@ -61,6 +61,7 @@ def _debug_plugin_message(format, *format_args): gi.require_version('Gedit', '3.0') from gi.repository import Gedit as Editor name = 'gedit' +use_new_tab_name_style = True use_symbolic_icons = True try: diff --git a/controlyourtabs/tabinfo.py b/controlyourtabs/tabinfo.py index c783065..ed72506 100644 --- a/controlyourtabs/tabinfo.py +++ b/controlyourtabs/tabinfo.py @@ -62,12 +62,16 @@ def get_tab_name(tab): editor.debug_plugin_message(log.format("%s", tab)) doc = tab.get_document() + is_modified = doc.get_modified() name = tab.get_property('name') - if doc.get_modified() and name[0] == "*": + if is_modified and name[0] == "*": name = name[1:] - tab_format = "%s" if doc.get_modified() else "%s" - tab_name = tab_format % escape(name) + if is_modified: + name_format = '%s' if editor.use_new_tab_name_style else '%s' + else: + name_format = '%s' + tab_name = name_format % escape(name) try: file = doc.get_file() @@ -76,7 +80,9 @@ def get_tab_name(tab): is_readonly = doc.get_readonly() # deprecated since gedit 3.18 if is_readonly: - tab_name += " [%s]" % escape(_("Read-Only")) + readonly_format = '%s' if editor.use_new_tab_name_style else '%s' + readonly_text = readonly_format % escape(_("Read-Only")) + tab_name += ' [%s]' % readonly_text if log.query(log.DEBUG): editor.debug_plugin_message(log.format("tab_name=%s", tab_name)) From a0b91e080ef3cc832005d56520337919864a8c73 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sun, 6 Oct 2024 23:03:23 +0800 Subject: [PATCH 06/25] document icons --- controlyourtabs/editor.py | 1 + controlyourtabs/tabinfo.py | 73 ++++++++++++++++++++++++---- controlyourtabs/windowactivatable.py | 2 +- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/controlyourtabs/editor.py b/controlyourtabs/editor.py index 964eb14..c47c82b 100644 --- a/controlyourtabs/editor.py +++ b/controlyourtabs/editor.py @@ -63,6 +63,7 @@ def _debug_plugin_message(format, *format_args): name = 'gedit' use_new_tab_name_style = True use_symbolic_icons = True +use_document_icons = False try: debug_plugin_message = Editor.debug_plugin_message diff --git a/controlyourtabs/tabinfo.py b/controlyourtabs/tabinfo.py index ed72506..7aa953a 100644 --- a/controlyourtabs/tabinfo.py +++ b/controlyourtabs/tabinfo.py @@ -20,11 +20,13 @@ # along with this program. If not, see . import gi +gi.require_version('GObject', '2.0') +gi.require_version('Gio', '2.0') gi.require_version('Gtk', '3.0') # GtkSource can be version 3 or 4 or 300 import os.path -from gi.repository import Gtk, GtkSource +from gi.repository import GObject, Gio, Gtk, GtkSource from xml.sax.saxutils import escape from .plugin import _ from . import editor, log @@ -95,21 +97,74 @@ def get_tab_icon(tab): editor.debug_plugin_message(log.format("%s", tab)) state = tab.get_state() + theme = Gtk.IconTheme.get_for_screen(tab.get_screen()) + icon_size = get_tab_icon_size() + pixbuf = None - if state not in TAB_STATE_ICONS: - return None + if state in TAB_STATE_ICONS: + icon_name = TAB_STATE_ICONS[state] - theme = Gtk.IconTheme.get_for_screen(tab.get_screen()) - icon_name = TAB_STATE_ICONS[state] - icon_size = get_tab_icon_size(tab) + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("getting icon for state %s (%s)", state, icon_name)) + + pixbuf = Gtk.IconTheme.load_icon(theme, icon_name, icon_size, 0) + + elif editor.use_document_icons: + doc = tab.get_document() + try: + file = doc.get_file() + location = file.get_location() + except AttributeError: + location = doc.get_location() + + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("getting icon for location %s", location)) - return Gtk.IconTheme.load_icon(theme, icon_name, icon_size, 0) + pixbuf = get_icon(theme, location, icon_size) -def get_tab_icon_size(tab): + return pixbuf + +def get_tab_icon_size(): if log.query(log.INFO): - editor.debug_plugin_message(log.format("%s", tab)) + editor.debug_plugin_message(log.format("")) is_valid_size, icon_size_width, icon_size_height = Gtk.icon_size_lookup(Gtk.IconSize.MENU) return icon_size_height +# based on get_icon() in gedit-tab.c +def get_icon(theme, location, size): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, size=%s", theme, location, size)) + + pixbuf = None + + if location: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("querying info for location %s", location)) + + # FIXME: Doing a sync stat is bad, this should be fixed + try: + info = location.query_info( + Gio.FILE_ATTRIBUTE_STANDARD_ICON, + Gio.FileQueryInfoFlags.NONE, + None + ) + except GObject.GError: + if log.query(log.WARNING): + editor.debug_plugin_message(log.format("could not query info for location %s", location)) + + info = None + + icon = info.get_icon() if info else None + icon_info = theme.lookup_by_gicon(icon, size, 0) if icon else None + pixbuf = icon_info.load_icon() if icon_info else None + + if not pixbuf: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("no pixbuf, getting generic text document icon")) + + pixbuf = Gtk.IconTheme.load_icon(theme, 'text-x-generic', size, 0) + + return pixbuf + diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index b80df10..025ff4f 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -185,7 +185,7 @@ def setup(self, window, tab, tab_models): if log.query(log.INFO): editor.debug_plugin_message(log.format("%s, %s", window, tab)) - icon_size = tabinfo.get_tab_icon_size(tab) + icon_size = tabinfo.get_tab_icon_size() self._icon_cell.set_fixed_size(icon_size, icon_size) self._space_cell.set_fixed_size(icon_size, icon_size) From 35223ea1b66fdc3bd8e96d8db002acec58e62906 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Sun, 6 Oct 2024 23:34:52 +0800 Subject: [PATCH 07/25] listen to tab signals on window when multi_notebook is not present --- controlyourtabs/windowactivatable.py | 51 ++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 025ff4f..0bc3f01 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -124,9 +124,6 @@ def do_activate(self): self.setup(window, tab, tab_models) - if self._multi: - self.active_tab_changed(tab, tab_models[tab.get_parent()]) - else: if log.query(log.DEBUG): editor.debug_plugin_message(log.format("waiting for new tab")) @@ -192,17 +189,29 @@ def setup(self, window, tab, tab_models): multi = window.get_template_child(editor.Editor.Window, 'multi_notebook') - connect_handlers( - self, multi, - [ - 'notebook-added', - 'notebook-removed', - 'tab-added', - 'tab-removed' - ], - 'multi_notebook', - tab_models - ) + if multi: + connect_handlers( + self, multi, + [ + 'notebook-added', + 'notebook-removed', + 'tab-added', + 'tab-removed' + ], + 'multi_notebook', + tab_models + ) + else: + connect_handlers( + self, window, + [ + 'tab-added', + 'tab-removed' + ], + 'window', + tab.get_parent(), tab_models + ) + connect_handlers( self, window, [ @@ -222,6 +231,8 @@ def setup(self, window, tab, tab_models): notebook = editor.Editor.Tab.get_from_document(document).get_parent() self.track_notebook(notebook, tab_models) + self.active_tab_changed(tab, tab_models[tab.get_parent()]) + # tracking notebooks / tabs @@ -363,6 +374,18 @@ def on_multi_notebook_tab_removed(self, multi, notebook, tab, tab_models): self.untrack_tab(tab, tab_models[notebook]) + def on_window_tab_added(self, window, tab, notebook, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, %s", window, notebook, tab)) + + self.track_tab(tab, tab_models[notebook]) + + def on_window_tab_removed(self, window, tab, notebook, tab_models): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("%s, %s, %s", window, notebook, tab)) + + self.untrack_tab(tab, tab_models[notebook]) + def on_window_active_tab_changed(self, window, tab, tab_models=None): # tab parameter removed in gedit 47 if not tab_models: From f34eced063eafeac630f71021f859271f77166d5 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Mon, 7 Oct 2024 09:17:54 +0800 Subject: [PATCH 08/25] warnings don't make sense during setup --- controlyourtabs/windowactivatable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 0bc3f01..9a863f1 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -229,19 +229,19 @@ def setup(self, window, tab, tab_models): for document in window.get_documents(): notebook = editor.Editor.Tab.get_from_document(document).get_parent() - self.track_notebook(notebook, tab_models) + self.track_notebook(notebook, tab_models, is_setup=True) self.active_tab_changed(tab, tab_models[tab.get_parent()]) # tracking notebooks / tabs - def track_notebook(self, notebook, tab_models): + def track_notebook(self, notebook, tab_models, is_setup=False): if log.query(log.INFO): editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) if notebook in tab_models: - if log.query(log.WARNING): + if log.query(log.DEBUG if is_setup else log.WARNING): editor.debug_plugin_message(log.format("already tracking notebook")) return From 5ce45f192aef204f835353f7222a209d6eb918b5 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Mon, 7 Oct 2024 06:33:16 +0800 Subject: [PATCH 09/25] let schema be installed to a shared location --- Changelog.md | 2 + ....gedit.plugins.controlyourtabs.gschema.xml | 4 +- controlyourtabs/schemas/gschemas.compiled | Bin 240 -> 328 bytes controlyourtabs/settings.py | 35 +++++------------- 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/Changelog.md b/Changelog.md index ec9b22a..f5691c1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,8 @@ # Changelog ## [v0.4.2-dev][Unreleased] - Unreleased +* Settings schema can be stored in a shared schemas location instead of + the plugin "schemas" directory ## [v0.4.1] - 2024-06-07 * Fixed error when loaded in gedit 47 diff --git a/controlyourtabs/schemas/com.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml b/controlyourtabs/schemas/com.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml index 1e2eb1d..406a7e0 100644 --- a/controlyourtabs/schemas/com.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml +++ b/controlyourtabs/schemas/com.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml @@ -1,6 +1,6 @@ - - + + false Use tab row order diff --git a/controlyourtabs/schemas/gschemas.compiled b/controlyourtabs/schemas/gschemas.compiled index 097915c01db8eba69e4d689ec4273c46ac2940bf..e22b26694ef0c6629a1cf34ba6c3e987c8a5b499 100644 GIT binary patch delta 222 zcmeysc!FtyjnoSU1_oA928RDY;KB&xF!(TlXb;ATVOI6bAOR>~0@4R+>$pKG!MYgA z7(M{S)Pbt6aO@WY$%EJeKyeEoZ41OOlR<2d$qqmoWV&8KVo3&2L_aw{SHC19GcUck zBqKGqI5np%wOBtrH6^n|zaXd7GCeb|7^o(%q$oe9GQYH_Br&O2pMjycv^Z54$WBTu V(# Date: Mon, 7 Oct 2024 06:36:50 +0800 Subject: [PATCH 10/25] update message --- controlyourtabs/configurable.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/controlyourtabs/configurable.py b/controlyourtabs/configurable.py index 3fc3436..dca3f96 100644 --- a/controlyourtabs/configurable.py +++ b/controlyourtabs/configurable.py @@ -63,8 +63,7 @@ def do_create_configure_widget(self): editor.debug_plugin_message(log.format("no settings")) widget = Gtk.Label.new( - # translators: %s: application name - _("Sorry, no preferences are available for this version of %s.") % editor.name + _("Unable to load preferences") ) box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) From 3740805860e2687bb5c1ad4c8efaba2d2f567023 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Mon, 7 Oct 2024 07:17:42 +0800 Subject: [PATCH 11/25] pluma and xed --- Changelog.md | 1 + controlyourtabs/editor.py | 41 +++++++++++++++--- ....pluma.plugins.controlyourtabs.gschema.xml | 10 +++++ ...es.xed.plugins.controlyourtabs.gschema.xml | 10 +++++ controlyourtabs/schemas/gschemas.compiled | Bin 328 -> 868 bytes 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 controlyourtabs/schemas/com.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml create mode 100644 controlyourtabs/schemas/com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml diff --git a/Changelog.md b/Changelog.md index f5691c1..19a8eea 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ # Changelog ## [v0.4.2-dev][Unreleased] - Unreleased +* Added support for Pluma and xed * Settings schema can be stored in a shared schemas location instead of the plugin "schemas" directory diff --git a/controlyourtabs/editor.py b/controlyourtabs/editor.py index c47c82b..3d7f0f1 100644 --- a/controlyourtabs/editor.py +++ b/controlyourtabs/editor.py @@ -58,12 +58,41 @@ def _debug_plugin_message(format, *format_args): message = format % format_args print("%s:%d (%s) %s" % (filename, lineno, func_name, message), flush=True) -gi.require_version('Gedit', '3.0') -from gi.repository import Gedit as Editor -name = 'gedit' -use_new_tab_name_style = True -use_symbolic_icons = True -use_document_icons = False +try: + gi.require_version('Gedit', '3.0') +except ValueError: + Editor = None +else: + from gi.repository import Gedit as Editor + name = 'gedit' + use_new_tab_name_style = True + use_symbolic_icons = True + use_document_icons = False + +if not Editor: + try: + gi.require_version('Xed', '1.0') + except ValueError: + Editor = None + else: + from gi.repository import Xed as Editor + name = 'xed' + use_new_tab_name_style = False + use_symbolic_icons = True + use_document_icons = True + +if not Editor: + try: + # needs to be last because Pluma is a non-private namespace + gi.require_version('Pluma', '1.0') + except ValueError: + Editor = None + else: + from gi.repository import Pluma as Editor + name = 'Pluma' + use_new_tab_name_style = False + use_symbolic_icons = False + use_document_icons = True try: debug_plugin_message = Editor.debug_plugin_message diff --git a/controlyourtabs/schemas/com.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml b/controlyourtabs/schemas/com.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml new file mode 100644 index 0000000..8b2a107 --- /dev/null +++ b/controlyourtabs/schemas/com.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml @@ -0,0 +1,10 @@ + + + + + false + Use tab row order + Use tab row order when switching tabs with Ctrl+Tab / Ctrl+Shift+Tab. + + + diff --git a/controlyourtabs/schemas/com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml b/controlyourtabs/schemas/com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml new file mode 100644 index 0000000..44103b6 --- /dev/null +++ b/controlyourtabs/schemas/com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml @@ -0,0 +1,10 @@ + + + + + false + Use tab row order + Use tab row order when switching tabs with Ctrl+Tab / Ctrl+Shift+Tab. + + + diff --git a/controlyourtabs/schemas/gschemas.compiled b/controlyourtabs/schemas/gschemas.compiled index e22b26694ef0c6629a1cf34ba6c3e987c8a5b499..98017425eda0cb62867c83a0252ef9a703bb39e8 100644 GIT binary patch literal 868 zcmb7?J5Iwu7=(Wyk&tLo5QIdN(iE`~mk0$C6^Mq16R%=R@k8Eq6eK{RqoCjz904jQ zastE|xB&Dp6SBNyjIz?dGyc8VoiFRoo}Wj4Vq`j2q1PYYsZD!x(0LS}$1iI;eCC=> z*^)7|g+{$8@`iEBm$EI?OVsn#;T>D_8(4n}UB+gQ7tL4p4LtX`fz}p8dTH#MFiQH` zgeul*FjU&j21V?1?njAsdud|wba0Xux$(Q2=ZS-#ehy}RI6Gfro=G+tii5+BP*02Z zD|}DiKtF>PD4U<2`A1MPxS4N4bUXy}eh2d=!8kJ*mi!3jiRePBHbQp&yq)HO%CVEv zUB}F0Y-Z*uB+kfJwRDrdh#Aa1JwV#lW;QK+G!2!0;akTtJcxJ`5n* zgK=V?_~cF|Yg@1ihBAf^Ksj}w%oUFPVjxpNYyqIS1(3D{Vwfo)HpmnQAnh_)mQiuC K8nYb>NDKh67aR%z From 8d9ec1b3d90b643d3386f79081b82ceb48afef10 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Wed, 9 Oct 2024 23:53:56 +0800 Subject: [PATCH 12/25] formatting --- controlyourtabs/log.py | 5 ++++- controlyourtabs/windowactivatable.py | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/controlyourtabs/log.py b/controlyourtabs/log.py index e1297ca..0450268 100644 --- a/controlyourtabs/log.py +++ b/controlyourtabs/log.py @@ -110,7 +110,10 @@ def name(log_level=None): if log_level is None: log_level = last_queried_level - return LEVELS_TO_NAMES[highest(log_level)] if log_level is not None else 'unknown' + if log_level is None: + return "unknown" + + return LEVELS_TO_NAMES[highest(log_level)] def format(message, *args): msg = message % tuple(debug_str(arg) for arg in args) diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 9a863f1..8294a5e 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -128,7 +128,12 @@ def do_activate(self): if log.query(log.DEBUG): editor.debug_plugin_message(log.format("waiting for new tab")) - connect_handlers(self, window, ['tab-added'], 'setup', tab_models) + connect_handlers( + self, window, + ['tab-added'], + 'setup', + tab_models + ) def do_deactivate(self): if log.query(log.INFO): @@ -259,9 +264,7 @@ def track_notebook(self, notebook, tab_models, is_setup=False): ) connect_handlers( self, tab_model, - [ - 'selected-path-changed' - ], + ['selected-path-changed'], 'tab_model' ) From f18fdca4bfe7bc3194b4c22338f63519ac371a56 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Mon, 14 Oct 2024 22:33:52 +0800 Subject: [PATCH 13/25] compare tab objects by identity --- controlyourtabs/tabmodel.py | 2 +- controlyourtabs/windowactivatable.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controlyourtabs/tabmodel.py b/controlyourtabs/tabmodel.py index 46881dd..360bccb 100644 --- a/controlyourtabs/tabmodel.py +++ b/controlyourtabs/tabmodel.py @@ -94,7 +94,7 @@ def __delitem__(self, key): tab = self._model[key][2] - if self._selected == tab: + if self._selected is tab: self._selected = None del self._references[tab] diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 8294a5e..ec2c7be 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -321,7 +321,7 @@ def untrack_tab(self, tab, tab_model): if log.query(log.INFO): editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) - if tab == self._initial_tab: + if tab is self._initial_tab: if log.query(log.DEBUG): editor.debug_plugin_message(log.format("tab is initial tab, clearing")) From 6fe4efc97a78c4e1e1dc71bfbc86fba69103bc31 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Tue, 15 Oct 2024 03:11:03 +0800 Subject: [PATCH 14/25] fix log levels, update log messages --- controlyourtabs/configurable.py | 10 +- controlyourtabs/keyinfo.py | 6 +- controlyourtabs/settings.py | 6 +- controlyourtabs/tabinfo.py | 18 +-- controlyourtabs/tabmodel.py | 42 +++---- controlyourtabs/windowactivatable.py | 157 ++++++++++++++------------- 6 files changed, 122 insertions(+), 117 deletions(-) diff --git a/controlyourtabs/configurable.py b/controlyourtabs/configurable.py index dca3f96..8293d2b 100644 --- a/controlyourtabs/configurable.py +++ b/controlyourtabs/configurable.py @@ -37,14 +37,14 @@ class ControlYourTabsConfigurable(GObject.Object, PeasGtk.Configurable): def do_create_configure_widget(self): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("")) settings = get_settings() if settings: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("have settings")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Loaded settings")) widget = Gtk.CheckButton.new_with_label( _("Use tab row order for Ctrl+Tab / Ctrl+Shift+Tab") @@ -59,8 +59,8 @@ def do_create_configure_widget(self): widget._settings = settings else: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("no settings")) + if log.query(log.WARNING): + editor.debug_plugin_message(log.format("Could not load settings")) widget = Gtk.Label.new( _("Unable to load preferences") diff --git a/controlyourtabs/keyinfo.py b/controlyourtabs/keyinfo.py index bfd09b5..7e3c2a1 100644 --- a/controlyourtabs/keyinfo.py +++ b/controlyourtabs/keyinfo.py @@ -48,7 +48,7 @@ def default_control_held(): def update_control_held(event, prev_statuses, new_status): keyval = event.keyval - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("key=%s, %s, new_status=%s", Gdk.keyval_name(keyval), prev_statuses, new_status)) new_statuses = [ @@ -65,7 +65,7 @@ def is_control_keys(event): keyval = event.keyval state = event.state & Gtk.accelerator_get_default_mod_mask() - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("key=%s, state=%s", Gdk.keyval_name(keyval), state)) is_control = state == CONTROL_MASK @@ -85,7 +85,7 @@ def is_control_keys(event): return (is_control_tab, is_control_page, is_control_escape) def is_next_key(event): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("key=%s", Gdk.keyval_name(event.keyval))) result = event.keyval in NEXT_KEY_SET diff --git a/controlyourtabs/settings.py b/controlyourtabs/settings.py index b733b0a..533c7a7 100644 --- a/controlyourtabs/settings.py +++ b/controlyourtabs/settings.py @@ -29,7 +29,7 @@ def get_settings(): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("")) schemas_directory = os.path.join(plugin_data_dir, 'schemas') @@ -44,8 +44,8 @@ def get_settings(): ) except: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("could not load schema source from %s", schemas_directory)) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Could not load schema source from %s", schemas_directory)) schema_source = None diff --git a/controlyourtabs/tabinfo.py b/controlyourtabs/tabinfo.py index 7aa953a..d46e9e9 100644 --- a/controlyourtabs/tabinfo.py +++ b/controlyourtabs/tabinfo.py @@ -60,7 +60,7 @@ # based on doc_get_name() and document_row_sync_tab_name_and_icon() in gedit-documents-panel.c def get_tab_name(tab): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", tab)) doc = tab.get_document() @@ -93,7 +93,7 @@ def get_tab_name(tab): # based on _gedit_tab_get_icon() in gedit-tab.c def get_tab_icon(tab): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", tab)) state = tab.get_state() @@ -105,7 +105,7 @@ def get_tab_icon(tab): icon_name = TAB_STATE_ICONS[state] if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("getting icon for state %s (%s)", state, icon_name)) + editor.debug_plugin_message(log.format("Getting icon for state %s (%s)", state, icon_name)) pixbuf = Gtk.IconTheme.load_icon(theme, icon_name, icon_size, 0) @@ -118,14 +118,14 @@ def get_tab_icon(tab): location = doc.get_location() if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("getting icon for location %s", location)) + editor.debug_plugin_message(log.format("Getting icon for location %s", location)) pixbuf = get_icon(theme, location, icon_size) return pixbuf def get_tab_icon_size(): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("")) is_valid_size, icon_size_width, icon_size_height = Gtk.icon_size_lookup(Gtk.IconSize.MENU) @@ -134,14 +134,14 @@ def get_tab_icon_size(): # based on get_icon() in gedit-tab.c def get_icon(theme, location, size): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, size=%s", theme, location, size)) pixbuf = None if location: if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("querying info for location %s", location)) + editor.debug_plugin_message(log.format("Querying info for location %s", location)) # FIXME: Doing a sync stat is bad, this should be fixed try: @@ -152,7 +152,7 @@ def get_icon(theme, location, size): ) except GObject.GError: if log.query(log.WARNING): - editor.debug_plugin_message(log.format("could not query info for location %s", location)) + editor.debug_plugin_message(log.format("Could not query info for location %s", location)) info = None @@ -162,7 +162,7 @@ def get_icon(theme, location, size): if not pixbuf: if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("no pixbuf, getting generic text document icon")) + editor.debug_plugin_message(log.format("No pixbuf, getting generic text document icon")) pixbuf = Gtk.IconTheme.load_icon(theme, 'text-x-generic', size, 0) diff --git a/controlyourtabs/tabmodel.py b/controlyourtabs/tabmodel.py index 360bccb..ecea7bb 100644 --- a/controlyourtabs/tabmodel.py +++ b/controlyourtabs/tabmodel.py @@ -63,7 +63,7 @@ def wrapper(self, *args, **kwargs): def __init__(self): GObject.Object.__init__(self) - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", self)) self._model = Gtk.ListStore.new((GdkPixbuf.Pixbuf, str, editor.Editor.Tab)) @@ -89,7 +89,7 @@ def __getitem__(self, key): @_model_modifier def __delitem__(self, key): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, key=%s", self, key)) tab = self._model[key][2] @@ -113,25 +113,25 @@ def model(self): return self._model def on_model_row_inserted(self, model, path, iter_): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) self.emit('row-inserted', path) def on_model_row_deleted(self, model, path): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) self.emit('row-deleted', path) def on_model_row_changed(self, model, path, iter_): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, path=%s", self, model, path)) self.emit('row-changed', path) def on_model_rows_reordered(self, model, path, iter_, new_order): - if log.query(log.INFO): + if log.query(log.DEBUG): # path is suppose to point to the parent node of the reordered rows # if top level rows are reordered, path is invalid (null?) # so don't print it out here, because will throw an error @@ -140,28 +140,28 @@ def on_model_rows_reordered(self, model, path, iter_, new_order): self.emit('rows-reordered') def do_row_inserted(self, path): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, path=%s", self, path)) def do_row_deleted(self, path): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, path=%s", self, path)) def do_row_changed(self, path): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, path=%s", self, path)) def do_rows_reordered(self): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", self)) def do_selected_path_changed(self, path): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, path=%s", self, path)) @_model_modifier def insert(self, position, tab): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, position=%s, %s", self, position, tab)) tab_iter = self._model.insert( @@ -176,26 +176,26 @@ def insert(self, position, tab): self._references[tab] = Gtk.TreeRowReference.new(self._model, self._model.get_path(tab_iter)) def append(self, tab): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self, tab)) self.insert(len(self._model), tab) # before pygobject 3.2, -1 position does not work def prepend(self, tab): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self, tab)) self.insert(0, tab) def remove(self, tab): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self, tab)) del self[self.get_path(tab)] @_model_modifier def move(self, tab, sibling, move_before): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s, move_before=%s", self, tab, sibling, move_before)) tab_iter = self._get_iter(tab) @@ -207,13 +207,13 @@ def move(self, tab, sibling, move_before): self._model.move_after(tab_iter, sibling_iter) def move_before(self, tab, sibling=None): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) self.move(tab, sibling, True) def move_after(self, tab, sibling=None): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) self.move(tab, sibling, False) @@ -229,13 +229,13 @@ def _get_iter(self, tab): @_model_modifier def select(self, tab): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self, tab)) self._selected = tab def unselect(self): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", self)) self.select(None) @@ -247,7 +247,7 @@ def get_selected_path(self): return self.get_path(self._selected) if self._selected else None def update(self, tab): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self, tab)) path = self.get_path(tab) diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index ec2c7be..0779423 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -49,7 +49,7 @@ def __init__(self): GObject.Object.__init__(self) def do_activate(self): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", self.window)) window = self.window @@ -119,14 +119,14 @@ def do_activate(self): tab = window.get_active_tab() if tab: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("found active tab %s, setting up now", tab)) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Found active tab %s, setting up now", tab)) self.setup(window, tab, tab_models) else: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("waiting for new tab")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Waiting for new tab")) connect_handlers( self, window, @@ -136,7 +136,7 @@ def do_activate(self): ) def do_deactivate(self): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", self.window)) multi = self._multi @@ -176,7 +176,7 @@ def do_update_state(self): # plugin setup def on_setup_tab_added(self, window, tab, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", window, tab)) disconnect_handlers(self, window) @@ -184,7 +184,7 @@ def on_setup_tab_added(self, window, tab, tab_models): self.setup(window, tab, tab_models) def setup(self, window, tab, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", window, tab)) icon_size = tabinfo.get_tab_icon_size() @@ -242,12 +242,12 @@ def setup(self, window, tab, tab_models): # tracking notebooks / tabs def track_notebook(self, notebook, tab_models, is_setup=False): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) if notebook in tab_models: if log.query(log.DEBUG if is_setup else log.WARNING): - editor.debug_plugin_message(log.format("already tracking notebook")) + editor.debug_plugin_message(log.format("Already tracking %s", notebook)) return @@ -274,12 +274,12 @@ def track_notebook(self, notebook, tab_models, is_setup=False): self.track_tab(tab, tab_model) def untrack_notebook(self, notebook, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) if notebook not in tab_models: if log.query(log.WARNING): - editor.debug_plugin_message(log.format("not tracking notebook")) + editor.debug_plugin_message(log.format("Not tracking %s", notebook)) return @@ -296,12 +296,12 @@ def untrack_notebook(self, notebook, tab_models): del tab_models[notebook] def track_tab(self, tab, tab_model): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) if tab in tab_model: if log.query(log.WARNING): - editor.debug_plugin_message(log.format("already tracking tab")) + editor.debug_plugin_message(log.format("Already tracking %s", tab)) return @@ -318,18 +318,18 @@ def track_tab(self, tab, tab_model): ) def untrack_tab(self, tab, tab_model): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) if tab is self._initial_tab: if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("tab is initial tab, clearing")) + editor.debug_plugin_message(log.format("Tab is initial tab, clearing")) self._initial_tab = None if tab not in tab_model: if log.query(log.WARNING): - editor.debug_plugin_message(log.format("not tracking tab")) + editor.debug_plugin_message(log.format("Not tracking %s", tab)) return @@ -338,7 +338,7 @@ def untrack_tab(self, tab, tab_model): tab_model.remove(tab) def active_tab_changed(self, tab, tab_model): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) if not self._is_switching: @@ -354,37 +354,37 @@ def active_tab_changed(self, tab, tab_model): # signal handlers def on_multi_notebook_notebook_added(self, multi, notebook, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) self.track_notebook(notebook, tab_models) def on_multi_notebook_notebook_removed(self, multi, notebook, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, notebook)) self.untrack_notebook(notebook, tab_models) def on_multi_notebook_tab_added(self, multi, notebook, tab, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) self.track_tab(tab, tab_models[notebook]) def on_multi_notebook_tab_removed(self, multi, notebook, tab, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s", self.window, notebook, tab)) self.untrack_tab(tab, tab_models[notebook]) def on_window_tab_added(self, window, tab, notebook, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s", window, notebook, tab)) self.track_tab(tab, tab_models[notebook]) def on_window_tab_removed(self, window, tab, notebook, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s", window, notebook, tab)) self.untrack_tab(tab, tab_models[notebook]) @@ -395,14 +395,14 @@ def on_window_active_tab_changed(self, window, tab, tab_models=None): tab_models = tab tab = window.get_active_tab() - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", window, tab)) if tab: self.active_tab_changed(tab, tab_models[tab.get_parent()]) def on_window_key_press_event(self, window, event, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, key=%s", window, Gdk.keyval_name(event.keyval))) self._is_control_held = keyinfo.update_control_held(event, self._is_control_held, True) @@ -410,58 +410,58 @@ def on_window_key_press_event(self, window, event, tab_models): return self.key_press_event(event) def on_window_key_release_event(self, window, event, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) self._is_control_held = keyinfo.update_control_held(event, self._is_control_held, False) if not any(self._is_control_held): - if log.query(log.INFO): - editor.debug_plugin_message(log.format("no control keys held down")) + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("No control keys held down")) self.end_switching() else: if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("one or more control keys held down")) + editor.debug_plugin_message(log.format("One or more control keys held down")) def on_window_focus_out_event(self, window, event, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", window)) self.end_switching() def on_window_configure_event(self, window, event, tab_models): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", window)) self.schedule_tabwin_resize() def on_tab_notify_name_state(self, tab, pspec, tab_model): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) tab_model.update(tab) def on_tab_model_row_changed(self, tab_model, path): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) if not self.is_active_view_model(tab_model): if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("tab model not active")) + editor.debug_plugin_message(log.format("Tab model not active")) return self.schedule_tabwin_resize() def on_tab_model_selected_path_changed(self, tab_model, path): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) if not self.is_active_view_model(tab_model): if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("tab model not active")) + editor.debug_plugin_message(log.format("Tab model not active")) return @@ -475,7 +475,7 @@ def is_active_view_model(self, tab_model): return self._view.get_model() is model def set_active_view_model(self, tab_model): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, tab_model)) model = None @@ -489,7 +489,7 @@ def set_active_view_model(self, tab_model): self.set_view_selection(selected_path) def set_view_selection(self, path): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, path=%s", self.window, path)) view = self._view @@ -506,57 +506,62 @@ def set_view_selection(self, path): # tab switching def key_press_event(self, event): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) settings = self._settings is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) - block_event = True + block_event = False if is_control_tab and settings and settings['use-tabbar-order']: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("coercing ctrl-tab into ctrl-page because of settings")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Coercing Ctrl-Tab into Ctrl-PgUp/PgDn because of settings")) is_control_tab = False is_control_page = True if self._is_switching and is_control_escape: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("ctrl-esc while switching")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Ctrl-Esc while switching, cancel tab switching")) self.end_switching(True) + block_event = True elif is_control_tab or is_control_page: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("ctrl-tab or ctrl-page")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Ctrl-Tab or Ctrl-PgUp/PgDn, switch tab")) self.switch_tab(is_control_tab, keyinfo.is_next_key(event), event.time) + block_event = True elif self._is_switching and not self._is_tabwin_visible: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("normal key while switching and tabwin not visible")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Normal key while switching and tabwin not visible, end tab switching")) self.end_switching() - block_event = False + + elif self._is_switching: + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Normal key while switching, block key press")) + + block_event = True else: if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("normal key while %s", "switching" if self._is_switching else "not switching")) - - block_event = self._is_switching + editor.debug_plugin_message(log.format("Normal key, no action")) return block_event def switch_tab(self, use_mru_order, to_next_tab, time): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, use_mru_order=%s, to_next_tab=%s, time=%s", self.window, use_mru_order, to_next_tab, time)) window = self.window current_tab = window.get_active_tab() if not current_tab: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("no tabs")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("No tabs")) return @@ -566,8 +571,8 @@ def switch_tab(self, use_mru_order, to_next_tab, time): num_tabs = len(tabs) if num_tabs < 2: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("only 1 tab")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Only 1 tab")) return @@ -577,12 +582,12 @@ def switch_tab(self, use_mru_order, to_next_tab, time): next_tab = tabs[next_index] - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("switching from %s to %s", current_tab, next_tab)) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Switching from %s to %s", current_tab, next_tab)) if not self._is_switching: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("saving %s as initial tab", current_tab)) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Saving %s as initial tab", current_tab)) self._initial_tab = current_tab @@ -594,26 +599,26 @@ def switch_tab(self, use_mru_order, to_next_tab, time): tabwin = self._tabwin if not self._is_tabwin_visible: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("showing tabwin")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Showing tabwin")) tabwin.show_all() else: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("presenting tabwin")) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Presenting tabwin")) tabwin.present_with_time(time) self._is_tabwin_visible = True def end_switching(self, do_revert=False): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, do_revert=%s", self.window, do_revert)) if not self._is_switching: if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("not switching")) + editor.debug_plugin_message(log.format("Not switching")) return @@ -627,8 +632,8 @@ def end_switching(self, do_revert=False): self._initial_tab = None if do_revert and initial_tab: - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("switching to initial tab %s", initial_tab)) + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Switching to initial tab %s", initial_tab)) window.set_active_tab(initial_tab) @@ -642,12 +647,12 @@ def end_switching(self, do_revert=False): # tab window resizing def schedule_tabwin_resize(self): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", self.window)) if self._tabwin_resize_id: - if log.query(log.INFO): - editor.debug_plugin_message(log.format("already scheduled")) + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("Already scheduled")) return @@ -662,12 +667,12 @@ def schedule_tabwin_resize(self): self._tabwin_resize_id = resize_id def cancel_tabwin_resize(self): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", self.window)) if not self._tabwin_resize_id: if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("not scheduled")) + editor.debug_plugin_message(log.format("Not scheduled")) return @@ -676,7 +681,7 @@ def cancel_tabwin_resize(self): self._tabwin_resize_id = None def do_tabwin_resize(self): - if log.query(log.INFO): + if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s", self.window)) view = self._view From ab8d7ad8b2325d1307206e57febda7c293108ff8 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Tue, 15 Oct 2024 03:48:58 +0800 Subject: [PATCH 15/25] use keyword arguments --- controlyourtabs/tabmodel.py | 4 ++-- controlyourtabs/windowactivatable.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/controlyourtabs/tabmodel.py b/controlyourtabs/tabmodel.py index ecea7bb..f91c925 100644 --- a/controlyourtabs/tabmodel.py +++ b/controlyourtabs/tabmodel.py @@ -210,13 +210,13 @@ def move_before(self, tab, sibling=None): if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) - self.move(tab, sibling, True) + self.move(tab, sibling, move_before=True) def move_after(self, tab, sibling=None): if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s, %s", self, tab, sibling)) - self.move(tab, sibling, False) + self.move(tab, sibling, move_before=False) def get_path(self, tab): return self._references[tab].get_path() diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 0779423..6217056 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -524,14 +524,18 @@ def key_press_event(self, event): if log.query(log.INFO): editor.debug_plugin_message(log.format("Ctrl-Esc while switching, cancel tab switching")) - self.end_switching(True) + self.end_switching(do_revert=True) block_event = True elif is_control_tab or is_control_page: if log.query(log.INFO): editor.debug_plugin_message(log.format("Ctrl-Tab or Ctrl-PgUp/PgDn, switch tab")) - self.switch_tab(is_control_tab, keyinfo.is_next_key(event), event.time) + self.switch_tab( + use_mru_order=is_control_tab, + to_next_tab=keyinfo.is_next_key(event), + time=event.time + ) block_event = True elif self._is_switching and not self._is_tabwin_visible: From d36ea6ca6db3ca2a6b1f776ef488ad33ba97838c Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Wed, 16 Oct 2024 04:46:37 +0800 Subject: [PATCH 16/25] restructure key press event logic --- controlyourtabs/windowactivatable.py | 33 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 6217056..4ddacb1 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -520,14 +520,7 @@ def key_press_event(self, event): is_control_tab = False is_control_page = True - if self._is_switching and is_control_escape: - if log.query(log.INFO): - editor.debug_plugin_message(log.format("Ctrl-Esc while switching, cancel tab switching")) - - self.end_switching(do_revert=True) - block_event = True - - elif is_control_tab or is_control_page: + if is_control_tab or is_control_page: if log.query(log.INFO): editor.debug_plugin_message(log.format("Ctrl-Tab or Ctrl-PgUp/PgDn, switch tab")) @@ -538,17 +531,25 @@ def key_press_event(self, event): ) block_event = True - elif self._is_switching and not self._is_tabwin_visible: - if log.query(log.INFO): - editor.debug_plugin_message(log.format("Normal key while switching and tabwin not visible, end tab switching")) + elif self._is_switching: + if is_control_escape: + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Ctrl-Esc while switching, cancel tab switching")) - self.end_switching() + self.end_switching(do_revert=True) + block_event = True - elif self._is_switching: - if log.query(log.INFO): - editor.debug_plugin_message(log.format("Normal key while switching, block key press")) + elif not self._is_tabwin_visible: + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Normal key while switching and tabwin not visible, end tab switching")) - block_event = True + self.end_switching() + + else: + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Normal key while switching, block key press")) + + block_event = True else: if log.query(log.DEBUG): From f86230ca2df2e4d2fdc21d801709b83a3aa956e4 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Tue, 15 Oct 2024 05:37:59 +0800 Subject: [PATCH 17/25] ensure modifier key presses do not end switching --- controlyourtabs/keyinfo.py | 27 +++++++++++++++++++++++++++ controlyourtabs/windowactivatable.py | 4 ++++ 2 files changed, 31 insertions(+) diff --git a/controlyourtabs/keyinfo.py b/controlyourtabs/keyinfo.py index 7e3c2a1..5bfda86 100644 --- a/controlyourtabs/keyinfo.py +++ b/controlyourtabs/keyinfo.py @@ -41,6 +41,22 @@ ESCAPE_KEY = Gdk.KEY_Escape +MODIFIER_KEY_SET = set( + [ + Gdk.KEY_Shift_L, Gdk.KEY_Shift_R, + Gdk.KEY_Control_L, Gdk.KEY_Control_R, + Gdk.KEY_Meta_L, Gdk.KEY_Meta_R, + Gdk.KEY_Alt_L, Gdk.KEY_Alt_R, + Gdk.KEY_Super_L, Gdk.KEY_Super_R, + Gdk.KEY_Hyper_L, Gdk.KEY_Hyper_R, + Gdk.KEY_Caps_Lock, Gdk.KEY_Shift_Lock, Gdk.KEY_Num_Lock, Gdk.KEY_Scroll_Lock, + Gdk.KEY_ISO_Lock, Gdk.KEY_ISO_Level2_Latch, + Gdk.KEY_ISO_Level3_Shift, Gdk.KEY_ISO_Level3_Latch, Gdk.KEY_ISO_Level3_Lock, + Gdk.KEY_ISO_Level5_Shift, Gdk.KEY_ISO_Level5_Latch, Gdk.KEY_ISO_Level5_Lock, + Gdk.KEY_Mode_switch + ] +) + def default_control_held(): return [False for control_key in CONTROL_KEY_LIST] @@ -95,3 +111,14 @@ def is_next_key(event): return result +def is_modifier_key(event): + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("key=%s", Gdk.keyval_name(event.keyval))) + + result = event.keyval in MODIFIER_KEY_SET + + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("result=%s", result)) + + return result + diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 4ddacb1..b4c75ba 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -539,6 +539,10 @@ def key_press_event(self, event): self.end_switching(do_revert=True) block_event = True + elif keyinfo.is_modifier_key(event): + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Modifier key while switching, no action")) + elif not self._is_tabwin_visible: if log.query(log.INFO): editor.debug_plugin_message(log.format("Normal key while switching and tabwin not visible, end tab switching")) From f1203de4a5f5d33ac0af81d85561187b605b60ec Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Tue, 15 Oct 2024 05:46:21 +0800 Subject: [PATCH 18/25] support numpad keys --- Changelog.md | 2 ++ controlyourtabs/keyinfo.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index 19a8eea..c0cde09 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ ## [v0.4.2-dev][Unreleased] - Unreleased * Added support for Pluma and xed +* Added support for switching tabs with Tab/Page Up/Page Down keys in + numeric keypad * Settings schema can be stored in a shared schemas location instead of the plugin "schemas" directory diff --git a/controlyourtabs/keyinfo.py b/controlyourtabs/keyinfo.py index 5bfda86..b962f67 100644 --- a/controlyourtabs/keyinfo.py +++ b/controlyourtabs/keyinfo.py @@ -33,11 +33,11 @@ CONTROL_KEY_LIST = [Gdk.KEY_Control_L, Gdk.KEY_Control_R] # will need to iterate through this list -TAB_KEY_SET = set([Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab]) +TAB_KEY_SET = set([Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab, Gdk.KEY_KP_Tab]) # what is shift numpad tab? -PAGE_KEY_SET = set([Gdk.KEY_Page_Up, Gdk.KEY_Page_Down]) +PAGE_KEY_SET = set([Gdk.KEY_Page_Up, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Up, Gdk.KEY_KP_Page_Down]) -NEXT_KEY_SET = set([Gdk.KEY_Tab, Gdk.KEY_Page_Down]) +NEXT_KEY_SET = set([Gdk.KEY_Tab, Gdk.KEY_KP_Tab, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down]) ESCAPE_KEY = Gdk.KEY_Escape From e7dbbb392f305d5902005a5e35e96328ad2a4fb6 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Tue, 15 Oct 2024 20:33:25 +0800 Subject: [PATCH 19/25] add workaround to bypass xed's built-in tab switching --- controlyourtabs/editor.py | 3 ++ controlyourtabs/windowactivatable.py | 64 +++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/controlyourtabs/editor.py b/controlyourtabs/editor.py index 3d7f0f1..1d0636f 100644 --- a/controlyourtabs/editor.py +++ b/controlyourtabs/editor.py @@ -68,6 +68,7 @@ def _debug_plugin_message(format, *format_args): use_new_tab_name_style = True use_symbolic_icons = True use_document_icons = False + use_editor_workaround = False if not Editor: try: @@ -80,6 +81,7 @@ def _debug_plugin_message(format, *format_args): use_new_tab_name_style = False use_symbolic_icons = True use_document_icons = True + use_editor_workaround = True if not Editor: try: @@ -93,6 +95,7 @@ def _debug_plugin_message(format, *format_args): use_new_tab_name_style = False use_symbolic_icons = False use_document_icons = True + use_editor_workaround = False try: debug_plugin_message = Editor.debug_plugin_message diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index b4c75ba..46878d6 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -105,6 +105,7 @@ def do_activate(self): self._is_switching = False self._is_tabwin_visible = False self._is_control_held = keyinfo.default_control_held() + self._pre_key_press_control_keys = None self._initial_tab = None self._multi = None self._tab_models = tab_models @@ -158,6 +159,7 @@ def do_deactivate(self): self._is_switching = None self._is_tabwin_visible = None self._is_control_held = None + self._pre_key_press_control_keys = None self._initial_tab = None self._multi = None self._tab_models = None @@ -230,6 +232,16 @@ def setup(self, window, tab, tab_models): tab_models ) + if editor.use_editor_workaround: + connect_handlers( + self, window, + [ + 'event', + 'event-after' + ], + 'window' + ) + self._multi = multi for document in window.get_documents(): @@ -437,6 +449,20 @@ def on_window_configure_event(self, window, event, tab_models): self.schedule_tabwin_resize() + def on_window_event(self, window, event): + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("%s", window)) + + if event.type is Gdk.EventType.KEY_PRESS: + self.pre_key_press_event(event) + + def on_window_event_after(self, window, event): + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("%s", window)) + + if event.type is Gdk.EventType.KEY_PRESS: + self._pre_key_press_control_keys = None + def on_tab_notify_name_state(self, tab, pspec, tab_model): if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, %s", self.window, tab)) @@ -505,14 +531,50 @@ def set_view_selection(self, path): # tab switching + def pre_key_press_event(self, event): + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) + + is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) + + if is_control_tab: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("Applying editor workaround for Ctrl-Tab")) + + event.state &= ~keyinfo.CONTROL_MASK + self._pre_key_press_control_keys = (is_control_tab, is_control_page, is_control_escape) + + elif self._is_switching and is_control_escape: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("Applying editor workaround for Ctrl-Esc")) + + event.keyval = Gdk.KEY_VoidSymbol + self._pre_key_press_control_keys = (is_control_tab, is_control_page, is_control_escape) + def key_press_event(self, event): if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) settings = self._settings - is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) block_event = False + if self._pre_key_press_control_keys: + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("Completing editor workaround")) + + is_control_tab, is_control_page, is_control_escape = self._pre_key_press_control_keys + + if is_control_tab: + event.state |= keyinfo.CONTROL_MASK + + elif self._is_switching and is_control_escape: + event.keyval = Gdk.KEY_Escape + + self._pre_key_press_control_keys = None + + else: + is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) + if is_control_tab and settings and settings['use-tabbar-order']: if log.query(log.INFO): editor.debug_plugin_message(log.format("Coercing Ctrl-Tab into Ctrl-PgUp/PgDn because of settings")) From 9b9ab3dd7a0553ac21c25ee210740a8e2f44d986 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Tue, 15 Oct 2024 22:47:18 +0800 Subject: [PATCH 20/25] use namedtuple, return more detailed information --- controlyourtabs/keyinfo.py | 47 ++++++++++++++-------------- controlyourtabs/windowactivatable.py | 37 ++++++++++++---------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/controlyourtabs/keyinfo.py b/controlyourtabs/keyinfo.py index b962f67..65f08a9 100644 --- a/controlyourtabs/keyinfo.py +++ b/controlyourtabs/keyinfo.py @@ -23,6 +23,7 @@ gi.require_version('Gdk', '3.0') gi.require_version('Gtk', '3.0') +from collections import namedtuple from gi.repository import Gdk, Gtk from . import editor, log @@ -33,13 +34,12 @@ CONTROL_KEY_LIST = [Gdk.KEY_Control_L, Gdk.KEY_Control_R] # will need to iterate through this list -TAB_KEY_SET = set([Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab, Gdk.KEY_KP_Tab]) # what is shift numpad tab? - -PAGE_KEY_SET = set([Gdk.KEY_Page_Up, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Up, Gdk.KEY_KP_Page_Down]) - -NEXT_KEY_SET = set([Gdk.KEY_Tab, Gdk.KEY_KP_Tab, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down]) - -ESCAPE_KEY = Gdk.KEY_Escape +KEY_SETS = { + 'tab': set([Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab, Gdk.KEY_KP_Tab]), # what is shift numpad tab? + 'page_up': set([Gdk.KEY_Page_Up, Gdk.KEY_KP_Page_Up]), + 'page_down': set([Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down]), + 'escape': set([Gdk.KEY_Escape]) +} MODIFIER_KEY_SET = set( [ @@ -57,6 +57,15 @@ ] ) +ControlKeys = namedtuple( + 'ControlKeys', + [ + *[key for key in KEY_SETS.keys()], + *['shift_' + key for key in KEY_SETS.keys()], + *[key + '_key' for key in KEY_SETS.keys()] + ] +) + def default_control_held(): return [False for control_key in CONTROL_KEY_LIST] @@ -86,25 +95,15 @@ def is_control_keys(event): is_control = state == CONTROL_MASK is_control_shift = state == CONTROL_SHIFT_MASK + is_control_key = is_control or is_control_shift - is_tab = keyval in TAB_KEY_SET - is_page = keyval in PAGE_KEY_SET - is_escape = keyval == ESCAPE_KEY - - is_control_tab = (is_control or is_control_shift) and is_tab - is_control_page = is_control and is_page - is_control_escape = (is_control or is_control_shift) and is_escape - - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("is_control_tab=%s, is_control_page=%s, is_control_escape=%s", is_control_tab, is_control_page, is_control_escape)) - - return (is_control_tab, is_control_page, is_control_escape) - -def is_next_key(event): - if log.query(log.DEBUG): - editor.debug_plugin_message(log.format("key=%s", Gdk.keyval_name(event.keyval))) + is_key = {key: keyval in set for (key, set) in KEY_SETS.items()} - result = event.keyval in NEXT_KEY_SET + result = ControlKeys(**{ + **{key: value and is_control for (key, value) in is_key.items()}, + **{'shift_' + key: value and is_control_shift for (key, value) in is_key.items()}, + **{key + '_key': value and is_control_key for (key, value) in is_key.items()} + }) if log.query(log.DEBUG): editor.debug_plugin_message(log.format("result=%s", result)) diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 46878d6..6ec995d 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -535,21 +535,21 @@ def pre_key_press_event(self, event): if log.query(log.DEBUG): editor.debug_plugin_message(log.format("%s, key=%s", self.window, Gdk.keyval_name(event.keyval))) - is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) + is_control = keyinfo.is_control_keys(event) - if is_control_tab: + if is_control.tab_key: if log.query(log.DEBUG): editor.debug_plugin_message(log.format("Applying editor workaround for Ctrl-Tab")) event.state &= ~keyinfo.CONTROL_MASK - self._pre_key_press_control_keys = (is_control_tab, is_control_page, is_control_escape) + self._pre_key_press_control_keys = is_control - elif self._is_switching and is_control_escape: + elif self._is_switching and is_control.escape_key: if log.query(log.DEBUG): editor.debug_plugin_message(log.format("Applying editor workaround for Ctrl-Esc")) event.keyval = Gdk.KEY_VoidSymbol - self._pre_key_press_control_keys = (is_control_tab, is_control_page, is_control_escape) + self._pre_key_press_control_keys = is_control def key_press_event(self, event): if log.query(log.DEBUG): @@ -562,39 +562,44 @@ def key_press_event(self, event): if log.query(log.DEBUG): editor.debug_plugin_message(log.format("Completing editor workaround")) - is_control_tab, is_control_page, is_control_escape = self._pre_key_press_control_keys + is_control = self._pre_key_press_control_keys - if is_control_tab: + if is_control.tab_key: event.state |= keyinfo.CONTROL_MASK - elif self._is_switching and is_control_escape: + elif self._is_switching and is_control.escape_key: event.keyval = Gdk.KEY_Escape self._pre_key_press_control_keys = None else: - is_control_tab, is_control_page, is_control_escape = keyinfo.is_control_keys(event) + is_control = keyinfo.is_control_keys(event) - if is_control_tab and settings and settings['use-tabbar-order']: + if is_control.tab_key and settings and settings['use-tabbar-order']: if log.query(log.INFO): editor.debug_plugin_message(log.format("Coercing Ctrl-Tab into Ctrl-PgUp/PgDn because of settings")) - is_control_tab = False - is_control_page = True + is_control = is_control._replace( + tab=False, shift_tab=False, tab_key=False, + page_up=is_control.shift_tab, + page_up_key=is_control.shift_tab, + page_down=is_control.tab, + page_down_key=is_control.tab + ) - if is_control_tab or is_control_page: + if is_control.tab_key or is_control.page_up or is_control.page_down: if log.query(log.INFO): editor.debug_plugin_message(log.format("Ctrl-Tab or Ctrl-PgUp/PgDn, switch tab")) self.switch_tab( - use_mru_order=is_control_tab, - to_next_tab=keyinfo.is_next_key(event), + use_mru_order=is_control.tab_key, + to_next_tab=is_control.tab or is_control.page_down, time=event.time ) block_event = True elif self._is_switching: - if is_control_escape: + if is_control.escape_key: if log.query(log.INFO): editor.debug_plugin_message(log.format("Ctrl-Esc while switching, cancel tab switching")) From 10387431ac4161757f93c6afe3b12933acb25da7 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Wed, 16 Oct 2024 04:08:17 +0800 Subject: [PATCH 21/25] move tabs with ctrl-shift-page up/page down --- Changelog.md | 3 ++ controlyourtabs/windowactivatable.py | 42 +++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index c0cde09..83884b4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ ## [v0.4.2-dev][Unreleased] - Unreleased * Added support for Pluma and xed +* Added support for moving tabs with Ctrl-Shift-Page Up and + Ctrl-Shift-Page Down ([#13]) * Added support for switching tabs with Tab/Page Up/Page Down keys in numeric keypad * Settings schema can be stored in a shared schemas location instead of @@ -97,5 +99,6 @@ [#4]: https://github.com/jefferyto/gedit-control-your-tabs/pull/4 [#8]: https://github.com/jefferyto/gedit-control-your-tabs/pull/8 +[#13]: https://github.com/jefferyto/gedit-control-your-tabs/issues/13 [#15]: https://github.com/jefferyto/gedit-control-your-tabs/pull/15 [#17]: https://github.com/jefferyto/gedit-control-your-tabs/issues/17 diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index 6ec995d..a160b2e 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -529,7 +529,7 @@ def set_view_selection(self, path): selection.unselect_all() - # tab switching + # tab switching/moving def pre_key_press_event(self, event): if log.query(log.DEBUG): @@ -598,6 +598,14 @@ def key_press_event(self, event): ) block_event = True + elif is_control.shift_page_up or is_control.shift_page_down: + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Ctrl-Shift-PgUp/PgDn, move tab")) + + self.end_switching() + self.move_tab(to_right=is_control.shift_page_down) + block_event = True + elif self._is_switching: if is_control.escape_key: if log.query(log.INFO): @@ -719,6 +727,38 @@ def end_switching(self, do_revert=False): if tab: self.active_tab_changed(tab, self._tab_models[tab.get_parent()]) + def move_tab(self, to_right): + if log.query(log.DEBUG): + editor.debug_plugin_message(log.format("%s, to_right=%s", self.window, to_right)) + + window = self.window + current_tab = window.get_active_tab() + + if not current_tab: + if log.query(log.INFO): + editor.debug_plugin_message(log.format("No tabs")) + + return + + notebook = current_tab.get_parent() + tabs = notebook.get_children() + num_tabs = len(tabs) + + if num_tabs < 2: + if log.query(log.INFO): + editor.debug_plugin_message(log.format("Only 1 tab")) + + return + + current_index = tabs.index(current_tab) + step = 1 if to_right else -1 + next_index = (current_index + step) % num_tabs + + try: + notebook.reorder_tab(current_tab, next_index) + except AttributeError: + notebook.reorder_child(current_tab, next_index) + # tab window resizing From 5ae0c1b97884f2af68620bc9cc889f32de109a06 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Wed, 16 Oct 2024 20:26:40 +0800 Subject: [PATCH 22/25] fix untracked tab error on pluma/xed --- controlyourtabs/windowactivatable.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/controlyourtabs/windowactivatable.py b/controlyourtabs/windowactivatable.py index a160b2e..31ed63f 100644 --- a/controlyourtabs/windowactivatable.py +++ b/controlyourtabs/windowactivatable.py @@ -411,7 +411,14 @@ def on_window_active_tab_changed(self, window, tab, tab_models=None): editor.debug_plugin_message(log.format("%s, %s", window, tab)) if tab: - self.active_tab_changed(tab, tab_models[tab.get_parent()]) + tab_model = tab_models[tab.get_parent()] + + # in pluma/xed, when a tab is added to an empty notebook, + # active-tab-changed is emitted before tab-added + if tab not in tab_model: + self.track_tab(tab, tab_model) + + self.active_tab_changed(tab, tab_model) def on_window_key_press_event(self, window, event, tab_models): if log.query(log.DEBUG): From 525728a465b9e7e1ccacf0a2239894b3b29ba295 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Thu, 24 Oct 2024 08:12:57 +0800 Subject: [PATCH 23/25] better wording, hopefully --- controlyourtabs/configurable.py | 2 +- ...m.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml | 2 +- ...m.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml | 2 +- ...com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/controlyourtabs/configurable.py b/controlyourtabs/configurable.py index 8293d2b..a545ffb 100644 --- a/controlyourtabs/configurable.py +++ b/controlyourtabs/configurable.py @@ -47,7 +47,7 @@ def do_create_configure_widget(self): editor.debug_plugin_message(log.format("Loaded settings")) widget = Gtk.CheckButton.new_with_label( - _("Use tab row order for Ctrl+Tab / Ctrl+Shift+Tab") + _("Ctrl+Tab and Ctrl+Shift+Tab switch to tabs on the left and right") ) settings.bind( diff --git a/controlyourtabs/schemas/com.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml b/controlyourtabs/schemas/com.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml index 406a7e0..bfca954 100644 --- a/controlyourtabs/schemas/com.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml +++ b/controlyourtabs/schemas/com.thingsthemselves.gedit.plugins.controlyourtabs.gschema.xml @@ -4,7 +4,7 @@ false Use tab row order - Use tab row order when switching tabs with Ctrl+Tab / Ctrl+Shift+Tab. + Ctrl+Tab and Ctrl+Shift+Tab switch to tabs on the left and right. diff --git a/controlyourtabs/schemas/com.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml b/controlyourtabs/schemas/com.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml index 8b2a107..63a7a46 100644 --- a/controlyourtabs/schemas/com.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml +++ b/controlyourtabs/schemas/com.thingsthemselves.pluma.plugins.controlyourtabs.gschema.xml @@ -4,7 +4,7 @@ false Use tab row order - Use tab row order when switching tabs with Ctrl+Tab / Ctrl+Shift+Tab. + Ctrl+Tab and Ctrl+Shift+Tab switch to tabs on the left and right. diff --git a/controlyourtabs/schemas/com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml b/controlyourtabs/schemas/com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml index 44103b6..39562ec 100644 --- a/controlyourtabs/schemas/com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml +++ b/controlyourtabs/schemas/com.thingsthemselves.xed.plugins.controlyourtabs.gschema.xml @@ -4,7 +4,7 @@ false Use tab row order - Use tab row order when switching tabs with Ctrl+Tab / Ctrl+Shift+Tab. + Ctrl+Tab and Ctrl+Shift+Tab switch to tabs on the left and right. From f39ca6dd8499eb1eb6f1642281ac20ea72fbfcba Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Thu, 24 Oct 2024 08:16:54 +0800 Subject: [PATCH 24/25] update docs --- README.md | 53 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 4e6cc29..b6a0ad0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Control Your Tabs, a plugin for gedit +# Control Your Tabs, a plugin for gedit, Pluma, and xed -Switch between document tabs using Ctrl+Tab / Ctrl+Shift+Tab and -Ctrl+PageUp / Ctrl+PageDown +Switch between document tabs using Ctrl+Tab and other common keyboard +shortcuts v0.4.2-dev @@ -15,9 +15,14 @@ releases. ## Requirements -This plugin requires gedit 3.12 or newer. The last version compatible -with gedit 2 is [v0.1.2], and the last version compatible with gedit -3.0-3.10 is [v0.3.5]. +This plugin requires one of these text editors: + +* gedit 3.12 or newer +* Pluma 1.26.0 or newer +* xed 1.4.0 or newer + +The last version compatible with gedit 2 is [v0.1.2], and the last +version compatible with gedit 3.0–3.10 is [v0.3.5]. [v0.1.2]: https://github.com/jefferyto/gedit-control-your-tabs/releases/tag/v0.1.2 [v0.3.5]: https://github.com/jefferyto/gedit-control-your-tabs/releases/tag/v0.3.5 @@ -26,10 +31,12 @@ with gedit 2 is [v0.1.2], and the last version compatible with gedit 1. Download the [latest release] and extract. 2. Copy the `controlyourtabs` folder and the `controlyourtabs.plugin` - file into `~/.local/share/gedit/plugins` (create if it does not - exist). -3. Restart gedit, then activate the plugin in the **Plugins** tab in - gedit's **Preferences** window. + file into one of these paths (create if it does not exist): + * gedit: `~/.local/share/gedit/plugins` + * Pluma: `~/.local/share/pluma/plugins` + * xed: `~/.local/share/xed/plugins` +3. Restart the text editor, then activate the plugin in the **Plugins** + tab of the text editor's **Preferences** window. [latest release]: https://github.com/jefferyto/gedit-control-your-tabs/releases/latest @@ -42,21 +49,27 @@ with gedit 2 is [v0.1.2], and the last version compatible with gedit ## Usage -* Ctrl+Tab / - Ctrl+Shift+Tab - Switch tabs in - most recently used order. -* Ctrl+Page Up / - Ctrl+Page Down - Switch tabs in tab row order. +This plugin adds the following keyboard shortcuts: + +| Action | Shortcut | +| :------------------------------------ | :-------------------------------------------------------- | +| Switch to next most recently used tab | Ctrl + Tab | +| Switch to tab on the left | Ctrl + Page Up | +| Switch to tab on the right | Ctrl + Page Down | +| Move current tab left | Ctrl + Shift + Page Up | +| Move current tab right | Ctrl + Shift + Page Down | Hold down Ctrl to continue tab switching. Press -Esc while switching to cancel and return to the initial tab. +Esc while holding Ctrl to cancel and return to the +initial tab. ## Preferences -* `Use tab row order for Ctrl+Tab / Ctrl+Shift+Tab` - Change - Ctrl+Tab / - Ctrl+Shift+Tab to switch tabs in - tab row order instead of most recently used order. +* `Ctrl+Tab and Ctrl+Shift+Tab switch to tabs on the left and right` + + Change Ctrl + Tab and Ctrl + + Shift + Tab to switch to tabs on the left and + right instead of in most recently used order. ## Contributing From ed6ca0bd16c5602f01a28b03ceed4c48dea5e20d Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Thu, 24 Oct 2024 08:18:28 +0800 Subject: [PATCH 25/25] update pot --- .../locale/gedit-control-your-tabs.pot | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/controlyourtabs/locale/gedit-control-your-tabs.pot b/controlyourtabs/locale/gedit-control-your-tabs.pot index 74d29fb..b452013 100644 --- a/controlyourtabs/locale/gedit-control-your-tabs.pot +++ b/controlyourtabs/locale/gedit-control-your-tabs.pot @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: gedit-control-your-tabs 0.4.2-dev\n" -"POT-Creation-Date: 2024-06-08 03:17+0800\n" +"POT-Creation-Date: 2024-10-24 08:17+0800\n" "PO-Revision-Date: 2014-12-03 21:27+0800\n" "Last-Translator: Jeffery To \n" "Language-Team: \n" @@ -16,18 +16,18 @@ msgstr "" "X-Poedit-SearchPathExcluded-0: schemas\n" "X-Poedit-SearchPathExcluded-1: utils\n" -#: __init__.py:90 -msgid "Documents" +#: configurable.py:50 +msgid "Ctrl+Tab and Ctrl+Shift+Tab switch to tabs on the left and right" msgstr "" -#: __init__.py:957 -msgid "Use tab row order for Ctrl+Tab / Ctrl+Shift+Tab" +#: configurable.py:66 +msgid "Unable to load preferences" msgstr "" -#: __init__.py:973 -msgid "Sorry, no preferences are available for this version of gedit." +#: tabinfo.py:86 +msgid "Read-Only" msgstr "" -#: tabinfo.py:83 -msgid "Read-Only" +#: windowactivatable.py:83 +msgid "Documents" msgstr ""