From 91043c4a6a7fae4f7d0c827d163a2852e8dcb2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Wilmet?= Date: Thu, 27 Jun 2024 05:10:23 +0200 Subject: [PATCH] plugins: add textsize, coming from the gedit-plugins repo The textsize/ directory has been copied as-is from the gedit-plugins repo (after some adaptations in gedit-plugins). The user manual page will be moved in a follow-up commit. Fixes https://gitlab.gnome.org/GNOME/gedit/-/issues/482 --- plugins/meson.build | 1 + plugins/textsize/meson.build | 13 ++ plugins/textsize/textsize.plugin.desktop.in | 9 + plugins/textsize/textsize/__init__.py | 118 +++++++++++ plugins/textsize/textsize/signals.py | 92 +++++++++ plugins/textsize/textsize/viewactivatable.py | 198 +++++++++++++++++++ 6 files changed, 431 insertions(+) create mode 100644 plugins/textsize/meson.build create mode 100644 plugins/textsize/textsize.plugin.desktop.in create mode 100644 plugins/textsize/textsize/__init__.py create mode 100644 plugins/textsize/textsize/signals.py create mode 100644 plugins/textsize/textsize/viewactivatable.py diff --git a/plugins/meson.build b/plugins/meson.build index 21c5359c9..51a3d97b6 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -25,6 +25,7 @@ subdir('quickopen') subdir('snippets') subdir('sort') subdir('spell') +subdir('textsize') subdir('time') if get_option('buggy_plugins') diff --git a/plugins/textsize/meson.build b/plugins/textsize/meson.build new file mode 100644 index 000000000..48a61f80d --- /dev/null +++ b/plugins/textsize/meson.build @@ -0,0 +1,13 @@ +install_subdir( + 'textsize', + install_dir: get_option('libdir') / 'gedit/plugins', +) + +custom_target( + 'textsize.plugin', + input: 'textsize.plugin.desktop.in', + output: 'textsize.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: get_option('libdir') / 'gedit/plugins', +) diff --git a/plugins/textsize/textsize.plugin.desktop.in b/plugins/textsize/textsize.plugin.desktop.in new file mode 100644 index 000000000..428e94c97 --- /dev/null +++ b/plugins/textsize/textsize.plugin.desktop.in @@ -0,0 +1,9 @@ +[Plugin] +Loader=python3 +Module=textsize +IAge=3 +Name=Text Size +Description=Easily increase and decrease the text size. +Authors=Konstantin Mikhaylov ;Wouter Bolsterlee ;Jesse van den Kieboom +Copyright=Copyright © 2008 by the authors +Website=https://gedit-technology.github.io/apps/gedit/ diff --git a/plugins/textsize/textsize/__init__.py b/plugins/textsize/textsize/__init__.py new file mode 100644 index 000000000..270078391 --- /dev/null +++ b/plugins/textsize/textsize/__init__.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# +# __init__.py - Text size plugin +# +# Copyright (C) 2008 - Konstantin Mikhaylov +# Copyright (C) 2009 - Wouter Bolsterlee +# Copyright (C) 2010 - Ignacio Casal Quinteiro +# Copyright (C) 2010 - Jesse van den Kieboom +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gedit', '3.0') +from gi.repository import GObject, Gio, Gtk, Gdk, Gedit +from .viewactivatable import TextSizeViewActivatable + +try: + import gettext + gettext.bindtextdomain('gedit-plugins') + gettext.textdomain('gedit-plugins') + _ = gettext.gettext +except: + _ = lambda s: s + + +class TextSizeAppActivatable(GObject.Object, Gedit.AppActivatable): + + app = GObject.Property(type=Gedit.App) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + self.app.set_accels_for_action("win.text-larger", ["plus", "KP_Add"]) + self.app.set_accels_for_action("win.text-smaller", ["minus", "KP_Subtract"]) + self.app.set_accels_for_action("win.text-normal", ["0", "KP_0"]) + + self.menu_ext = self.extend_menu("view-section-2") + item = Gio.MenuItem.new(_("_Normal size"), "win.text-normal") + self.menu_ext.prepend_menu_item(item) + item = Gio.MenuItem.new(_("S_maller Text"), "win.text-smaller") + self.menu_ext.prepend_menu_item(item) + item = Gio.MenuItem.new(_("_Larger Text"), "win.text-larger") + self.menu_ext.prepend_menu_item(item) + + def do_deactivate(self): + self.app.set_accels_for_action("win.text-larger", []) + self.app.set_accels_for_action("win.text-smaller", []) + self.app.set_accels_for_action("win.text-normal", []) + self.menu_ext = None + + +class TextSizeWindowActivatable(GObject.Object, Gedit.WindowActivatable): + + window = GObject.Property(type=Gedit.Window) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + action = Gio.SimpleAction(name="text-larger") + action.connect('activate', self.on_larger_text_activate) + self.window.add_action(action) + + action = Gio.SimpleAction(name="text-smaller") + action.connect('activate', self.on_smaller_text_activate) + self.window.add_action(action) + + action = Gio.SimpleAction(name="text-normal") + action.connect('activate', self.on_normal_size_activate) + self.window.add_action(action) + + def do_deactivate(self): + self.window.remove_action("text-larger") + self.window.remove_action("text-smaller") + self.window.remove_action("text-normal") + + def do_update_state(self): + self.window.lookup_action("text-larger").set_enabled(self.window.get_active_document() != None) + self.window.lookup_action("text-smaller").set_enabled(self.window.get_active_document() != None) + self.window.lookup_action("text-normal").set_enabled(self.window.get_active_document() != None) + + def get_view_activatable(self, view): + if not hasattr(view, "textsize_view_activatable"): + return None + return view.textsize_view_activatable + + def call_view_activatable(self, cb): + view = self.window.get_active_view() + + if view: + cb(self.get_view_activatable(view)) + + # Menu activate handlers + def on_larger_text_activate(self, action, parameter, user_data=None): + self.call_view_activatable(lambda va: va.larger_text()) + + def on_smaller_text_activate(self, action, parameter, user_data=None): + self.call_view_activatable(lambda va: va.smaller_text()) + + def on_normal_size_activate(self, action, parameter, user_data=None): + self.call_view_activatable(lambda va: va.normal_size()) + +# ex:ts=4:et: diff --git a/plugins/textsize/textsize/signals.py b/plugins/textsize/textsize/signals.py new file mode 100644 index 000000000..5b46740a3 --- /dev/null +++ b/plugins/textsize/textsize/signals.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# +# signals.py +# +# Copyright (C) 2009 - Jesse van den Kieboom +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +class Signals(object): + def __init__(self): + self._signals = {} + + def _connect(self, obj, name, handler, connector): + ret = self._signals.setdefault(obj, {}) + + hid = connector(name, handler) + ret.setdefault(name, []).append(hid) + + return hid + + def connect_signal(self, obj, name, handler): + return self._connect(obj, name, handler, obj.connect) + + def connect_signal_after(self, obj, name, handler): + return self._connect(obj, name, handler, obj.connect_after) + + def disconnect_signals(self, obj): + if obj not in self._signals: + return False + + for name in self._signals[obj]: + for hid in self._signals[obj][name]: + obj.disconnect(hid) + + del self._signals[obj] + return True + + def block_signal(self, obj, name): + if obj not in self._signals: + return False + + if name not in self._signals[obj]: + return False + + for hid in self._signals[obj][name]: + obj.handler_block(hid) + + return True + + def unblock_signal(self, obj, name): + if obj not in self._signals: + return False + + if name not in self._signals[obj]: + return False + + for hid in self._signals[obj][name]: + obj.handler_unblock(hid) + + return True + + def disconnect_signal(self, obj, name): + if obj not in self._signals: + return False + + if name not in self._signals[obj]: + return False + + for hid in self._signals[obj][name]: + obj.disconnect(hid) + + del self._signals[obj][name] + + if len(self._signals[obj]) == 0: + del self._signals[obj] + + return True + +# ex:ts=4:et: diff --git a/plugins/textsize/textsize/viewactivatable.py b/plugins/textsize/textsize/viewactivatable.py new file mode 100644 index 000000000..db44c801a --- /dev/null +++ b/plugins/textsize/textsize/viewactivatable.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +# +# viewactivatable.py - Gedit View Activatable Implementation +# +# Copyright (C) 2010 - Jesse van den Kieboom +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +from .signals import Signals +from gi.repository import GObject, Gtk, Gdk, Pango, Gedit + +class TextSizeViewActivatable(GObject.Object, Gedit.ViewActivatable, Signals): + + view = GObject.Property(type=Gedit.View) + + def __init__(self): + GObject.Object.__init__(self) + Signals.__init__(self) + + def do_activate(self): + self.connect_signal(self.view, 'scroll-event', self.on_scroll_event) + self.connect_signal(self.view, 'button-press-event', self.on_button_press_event) + + self.view.textsize_view_activatable = self + + self._default_font = None + self._last_font = None + self._font_tags = {} + + def do_deactivate(self): + if self._default_font: + self.view.override_font(self._default_font) + + self.remove_font_tags() + self.disconnect_signals(self.view) + + delattr(self.view, "textsize_view_activatable") + + def remove_font_tags(self): + buf = self.view.get_buffer() + table = buf.get_tag_table() + + # Remove all the font tags + for size in self._font_tags: + tag = self._font_tags[size] + table.remove(tag) + + self._font_tags = {} + + def update_default_font(self): + context = self.view.get_style_context() + description = context.get_font(context.get_state()).copy() + + if not self._last_font or description.hash() != self._last_font.hash(): + self._default_font = description + + def get_font_tags(self, start, end): + tags = set() + + # Check all the know font tags + for size in self._font_tags: + tag = self._font_tags[size] + + if start.has_tag(tag): + tags.add(tag) + else: + cp = start.copy() + + if cp.forward_to_tag_toggle(tag) and cp.compare(end) < 0: + tags.add(tag) + + return list(tags) + + def set_font_size(self, amount): + self.update_default_font() + + context = self.view.get_style_context() + description = context.get_font(context.get_state()).copy() + + buf = self.view.get_buffer() + bounds = buf.get_selection_bounds() + size = description.get_size() / Pango.SCALE + + if not bounds: + description.set_size(max(1, (size + amount)) * Pango.SCALE) + + self.view.override_font(description) + self._last_font = description + else: + start = bounds[0] + end = bounds[1] + + tags = self.get_font_tags(start, end) + + if not tags: + # Simply use the overall font size as the base + newsize = size + amount + elif len(tags) == 1: + newsize = tags[0].props.font_desc.get_size() / Pango.SCALE + amount + else: + newsize = 0 + + for tag in tags: + newsize += tag.props.font_desc.get_size() / Pango.SCALE + + newsize = round(newsize / len(tags)) + + newsize = int(max(1, newsize)) + + if not newsize in self._font_tags: + newtag = buf.create_tag(None) + + desc = description + desc.set_size(newsize * Pango.SCALE) + + newtag.props.font_desc = desc + self._font_tags[newsize] = newtag + else: + newtag = self._font_tags[newsize] + + # Remove all the previous mix of tags + for tag in tags: + buf.remove_tag(tag, start, end) + + buf.apply_tag(newtag, start, end) + + def larger_text(self): + self.set_font_size(1) + + def smaller_text(self): + self.set_font_size(-1) + + def normal_size(self): + self.update_default_font() + + buf = self.view.get_buffer() + bounds = buf.get_selection_bounds() + + if not bounds: + self.remove_font_tags() + + self.view.override_font(self._default_font) + self._last_font = self._default_font + else: + tags = self.get_font_tags(bounds[0], bounds[1]) + + for tag in tags: + buf.remove_tag(tag, bounds[0], bounds[1]) + + def on_scroll_event(self, view, event): + state = event.state & Gtk.accelerator_get_default_mod_mask() + + if state != Gdk.ModifierType.CONTROL_MASK: + return False + + # Scroll to the keep the line in the centre of the screen anchored + visible_rect = self.view.get_visible_rect() + central_line_y = visible_rect.height / 2 + visible_rect.y + central_line_iter, _ = self.view.get_line_at_y(central_line_y) + self.view.scroll_to_iter(central_line_iter, 0, True, 0.5, 0.5) + + if event.direction == Gdk.ScrollDirection.UP: + self.larger_text() + return True + elif event.direction == Gdk.ScrollDirection.DOWN: + self.smaller_text() + return True + elif event.direction == Gdk.ScrollDirection.SMOOTH: + if event.delta_y > 0: + self.smaller_text() + elif event.delta_y < 0: + self.larger_text() + + return False + + def on_button_press_event(self, view, event): + state = event.state & Gtk.accelerator_get_default_mod_mask() + + if state == Gdk.ModifierType.CONTROL_MASK and event.button == 2: + self.normal_size() + return True + else: + return False + +# ex:ts=4:et: