From 63484c5b59b2f1943f5f3ef6e971eab0d6c07f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Br=C3=B6tzmann?= Date: Fri, 24 Apr 2020 20:49:41 +0200 Subject: [PATCH] [quick_replies] Complete rework --- quick_replies/gtk/config.py | 85 +++++++++++++ quick_replies/gtk/config.ui | 103 ++++++++++++++++ quick_replies/plugin.py | 213 ++++++++++++++------------------- quick_replies/qicon.png | Bin 1590 -> 0 bytes quick_replies/quick_replies.py | 5 + 5 files changed, 286 insertions(+), 120 deletions(-) create mode 100644 quick_replies/gtk/config.py create mode 100644 quick_replies/gtk/config.ui delete mode 100644 quick_replies/qicon.png create mode 100644 quick_replies/quick_replies.py diff --git a/quick_replies/gtk/config.py b/quick_replies/gtk/config.py new file mode 100644 index 0000000..a0844c0 --- /dev/null +++ b/quick_replies/gtk/config.py @@ -0,0 +1,85 @@ +# Copyright (C) 2018 Philipp Hörist +# +# This file is part of Quick Replies. +# +# Quick Replies 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; version 3 only. +# +# Quick Replies 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 Quick Replies. If not, see . + +from pathlib import Path + +from gi.repository import Gtk +from gi.repository import Gdk + +from gajim.common import app + +from gajim.plugins.plugins_i18n import _ +from gajim.plugins.helpers import get_builder + + +class ConfigDialog(Gtk.ApplicationWindow): + def __init__(self, plugin, transient): + Gtk.ApplicationWindow.__init__(self) + self.set_application(app.app) + self.set_show_menubar(False) + self.set_title(_('Quick Replies Configuration')) + self.set_transient_for(transient) + self.set_default_size(400, 400) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + self.set_modal(True) + self.set_destroy_with_parent(True) + + ui_path = Path(__file__).parent + self._ui = get_builder(ui_path.resolve() / 'config.ui') + + self._plugin = plugin + + self.add(self._ui.box) + + self._fill_list() + self.show_all() + + self._ui.connect_signals(self) + self.connect('destroy', self._on_destroy) + + def _fill_list(self): + for reply in self._plugin.quick_replies: + self._ui.replies_store.append([reply]) + + def _on_reply_edited(self, _renderer, path, new_text): + iter_ = self._ui.replies_store.get_iter(path) + self._ui.replies_store.set_value(iter_, 0, new_text) + + def _on_add_clicked(self, _button): + self._ui.replies_store.append([_('New Quick Reply')]) + row = self._ui.replies_store[-1] + self._ui.replies_treeview.scroll_to_cell( + row.path, None, False, 0, 0) + self._ui.selection.unselect_all() + self._ui.selection.select_path(row.path) + + def _on_remove_clicked(self, _button): + model, paths = self._ui.selection.get_selected_rows() + references = [] + for path in paths: + references.append(Gtk.TreeRowReference.new(model, path)) + + for ref in references: + iter_ = model.get_iter(ref.get_path()) + self._ui.replies_store.remove(iter_) + + def _on_destroy(self, *args): + replies = [] + for row in self._ui.replies_store: + if row[0] == '': + continue + replies.append(row[0]) + self._plugin.set_quick_replies(replies) diff --git a/quick_replies/gtk/config.ui b/quick_replies/gtk/config.ui new file mode 100644 index 0000000..7683f8d --- /dev/null +++ b/quick_replies/gtk/config.ui @@ -0,0 +1,103 @@ + + + + + + + + + + + + True + False + 18 + vertical + + + True + True + True + in + + + True + True + replies_store + 1 + + + multiple + + + + + True + Quick Reply + True + True + 0 + + + True + + + + 0 + + + + + + + + + False + True + 0 + + + + + True + False + icons + 4 + + + True + False + Add + list-add-symbolic + + + + False + True + + + + + True + False + Remove + list-remove-symbolic + + + + False + True + + + + + + False + True + 1 + + + + diff --git a/quick_replies/plugin.py b/quick_replies/plugin.py index 799558d..683bb52 100644 --- a/quick_replies/plugin.py +++ b/quick_replies/plugin.py @@ -1,140 +1,113 @@ -from gi.repository import Gtk -from gi.repository import GdkPixbuf +import json +from pathlib import Path +from functools import partial -from gajim.common import app +from gi.repository import Gtk + +from gajim.common import configpaths from gajim.plugins import GajimPlugin -from gajim.plugins.gui import GajimPluginConfigDialog -from gajim.plugins.helpers import log_calls from gajim.plugins.plugins_i18n import _ +from quick_replies.quick_replies import DEFAULT_DATA +from quick_replies.gtk.config import ConfigDialog + + class QuickRepliesPlugin(GajimPlugin): - - @log_calls('QuickRepliesPlugin') def init(self): - - self.description = _('Plugin for quick replies') - self.config_dialog = QuickRepliesPluginConfigDialog(self) - self.chat_control = None + self.description = _('Adds a menu with customizable quick replies') + self.config_dialog = partial(ConfigDialog, self) self.gui_extension_points = { - 'chat_control_base': (self.connect_with_chat_control, - self.disconnect_from_chat_control), - 'chat_control_base_update_toolbar': (self.update_button_state, - None)} - self.config_default_values = { - 'entry1': ('Hello!', ''), - 'entry2': ('How are you?', ''), - 'entry3': ('Good bye.', ''), - 'entry4': ('', ''), - 'entry5': ('', ''), - 'entry6': ('', ''), - 'entry7': ('', ''), - 'entry8': ('', ''), - 'entry9': ('', ''), - 'entry10': ('', ''), + 'chat_control_base': (self._connect_chat_control, + self._disconnect_chat_control), } - self.controls = [] + self._buttons = {} + self.quick_replies = self._load_quick_replies() - @log_calls('QuickRepliesPlugin') - def connect_with_chat_control(self, chat_control): + def _connect_chat_control(self, chat_control): + button = QuickRepliesButton(chat_control, self.quick_replies) + self._buttons[chat_control.control_id] = button + actions_hbox = chat_control.xml.get_object('hbox') + actions_hbox.pack_start(button, False, False, 0) + actions_hbox.reorder_child( + button, len(actions_hbox.get_children()) - 2) + button.show() - self.chat_control = chat_control - base = Base(self, chat_control) - self.controls.append(base) + def _disconnect_chat_control(self, chat_control): + button = self._buttons.get(chat_control.control_id) + if button is not None: + button.destroy() + self._buttons.pop(chat_control.control_id, None) - @log_calls('QuickRepliesPlugin') - def disconnect_from_chat_control(self, chat_control): + @staticmethod + def _load_quick_replies(): + try: + data_path = Path(configpaths.get('PLUGINS_DATA')) + except KeyError: + # PLUGINS_DATA was added in 1.0.99.1 + return DEFAULT_DATA - for control in self.controls: - control.disconnect_from_chat_control() - self.controls = [] + path = data_path / 'quick_replies' / 'quick_replies' + if not path.exists(): + return DEFAULT_DATA - @log_calls('QuickRepliesPlugin') - def update_button_state(self, chat_control): - for base in self.controls: - if base.chat_control != chat_control: - continue - base.button.set_sensitive( - app.account_is_connected(chat_control.account)) + with path.open('r') as file: + quick_replies = json.load(file) + return quick_replies + + @staticmethod + def _save_quick_replies(quick_replies): + try: + data_path = Path(configpaths.get('PLUGINS_DATA')) + except KeyError: + # PLUGINS_DATA was added in 1.0.99.1 + return + + path = data_path / 'quick_replies' + if not path.exists(): + path.mkdir(parents=True) + + filepath = path / 'quick_replies' + with filepath.open('w') as file: + json.dump(quick_replies, file) + + def set_quick_replies(self, quick_replies): + self.quick_replies = quick_replies + self._save_quick_replies(quick_replies) + self._update_buttons() + + def _update_buttons(self): + for button in self._buttons.values(): + button.update_menu(self.quick_replies) -class Base(object): +class QuickRepliesButton(Gtk.MenuButton): + def __init__(self, chat_control, replies): + Gtk.MenuButton.__init__(self) + self.get_style_context().add_class('chatcontrol-actionbar-button') + self.set_property('relief', Gtk.ReliefStyle.NONE) + self.set_can_focus(False) + plugin_path = Path(__file__).parent + img_path = plugin_path.resolve() / 'quick_replies.png' + img = Gtk.Image.new_from_file(str(img_path)) + self.set_image(img) + self.set_tooltip_text(_('Quick Replies')) - def __init__(self, plugin, chat_control): + self._chat_control = chat_control - self.plugin = plugin - self.chat_control = chat_control - self.create_button() - self.create_menu() + self.update_menu(replies) - def create_button(self): + def update_menu(self, replies): + self._menu = Gtk.Menu() + for reply in replies: + item = Gtk.MenuItem.new_with_label(label=reply) + item.connect('activate', self._on_insert, reply) + self._menu.append(item) + self._menu.show_all() + self.set_popup(self._menu) - actions_hbox = self.chat_control.xml.get_object('hbox') - self.button = Gtk.MenuButton(label=None, stock=None, use_underline=True) - self.button.get_style_context().add_class( - 'chatcontrol-actionbar-button') - self.button.set_property('relief', Gtk.ReliefStyle.NONE) - self.button.set_property('can-focus', False) - img = Gtk.Image() - img_path = self.plugin.local_file_path('quick_replies.png') - pixbuf = GdkPixbuf.Pixbuf.new_from_file(img_path) - img.set_from_pixbuf(pixbuf) - self.button.set_image(img) - self.button.set_tooltip_text(_('Quick replies')) - actions_hbox.pack_start(self.button, False, False , 0) - actions_hbox.reorder_child(self.button, - len(actions_hbox.get_children()) - 2) - self.button.show() - - def on_insert(self, widget, text): - - text = text.rstrip() + ' ' - message_buffer = self.chat_control.msg_textview.get_buffer() - self.chat_control.msg_textview.remove_placeholder() - message_buffer.insert_at_cursor(text) - self.chat_control.msg_textview.grab_focus() - - def create_menu(self): - - self.menu = Gtk.Menu() - - for count in range(1, 11): - text = self.plugin.config['entry' + str(count)] - if not text: - continue - item = Gtk.MenuItem(text) - item.connect('activate', self.on_insert, text) - self.menu.append(item) - self.menu.show_all() - self.button.set_popup(self.menu) - - def disconnect_from_chat_control(self): - actions_hbox = self.chat_control.xml.get_object('hbox') - actions_hbox.remove(self.button) - - -class QuickRepliesPluginConfigDialog(GajimPluginConfigDialog): - - def init(self): - - self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path( - 'config_dialog.ui') - self.xml = Gtk.Builder() - self.xml.set_translation_domain('gajim_plugins') - self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, ['table1']) - hbox = self.xml.get_object('table1') - self.get_child().pack_start(hbox, True, True, 0) - self.xml.connect_signals(self) - - def on_run(self): - - for count in range(1, 11): - self.xml.get_object('entry' + str(count)).set_text( - self.plugin.config['entry' + str(count)]) - - def entry_changed(self, widget): - - name = Gtk.Buildable.get_name(widget) - self.plugin.config[name] = widget.get_text() - for control in self.plugin.controls: - control.create_menu() + def _on_insert(self, widget, text): + message_buffer = self._chat_control.msg_textview.get_buffer() + self._chat_control.msg_textview.remove_placeholder() + message_buffer.insert_at_cursor(text.rstrip() + ' ') + self._chat_control.msg_textview.grab_focus() diff --git a/quick_replies/qicon.png b/quick_replies/qicon.png deleted file mode 100644 index bc9da4bed44d12549522ab01b040645a468206cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1590 zcmV-62Fdw}P){MRq*}6~A%WLwSJGbguB@%ZAIq%3?(~kwgGD^?J2oN(8 zBKSatAR|Fwqy(+eAPDEwlryxT$@FB*|Mi$a^!Hb)er~DJ0y@%%MQj8BW&^b`2?4)M z0dP-)>F*!3RbDQak;rACxck#B#78-uvu8b{L_zhzGQg%_Mhh8+;IMce;K!@w zS~6-8Qf1(3^9@K&-3m{i_JCYw4`EA!z;lWRbkgf9jm^;8HvkHS98P_80HPwocx0L~ zBT)!&7K*5cMd67WoITrH)zVm^vy2fg&D-twKozxyi#j1T0uJumN!(mrY#9veN62UU zaxgcqh}hfPp|+|J<_7q23P45-B8iMrO({YHgs4`jIe_NNXDEP$A~2YcycsTCX(n-R zEyZt6>;r~j2(J@9O1_GS8M_aW+`M88S~wSKD)I>kDiTTMv^=aJjj&jqq{dmZl&WhD zMOw4T40k&oz?bR1;YZu{z@nf)a;D&bb!Lzk zQ21*q$2IR~VB^)FVE^aaXa-S&4Iv_t6vcK@Shixl8fVT_sxDRM>x}v#($(FE!RocN z)JPiZ3vrUWEA1Hzv2tT|%^thDdPr#GIvk<3s6C%ce7vXG=8{MxL8-kxM8t030klXQ)B@i(^^9WQ{UnYx#=V5O4E|5yZEZu0;I_qG}Y{s;V!{lOp6D|mx4Mq9;362Lr zL=qN24pH$N)#&T1RMk~}tsNQa=X`W`_d(n{Y0%l#18>Zq1G{#t2jvV;p&Sf6eAEe9 z*#)3&xDE~q8I%-c!D}FI~t?SIm7Jc?ApBzu1v2Kf7MqM}S9wFRSbl-S!zQRd(b zu}NFh96)v13EgmiH{UG6R38~JV)3~esGxLFAX|~$T%Ad5R4Aq-MUx4R3hq92PVx%R zW6**CsIG4!3+MUciWSQtE_tgOy}gyHs&mJ+!+o94i$!>|vCUInbqS80EMX@rI5@~5 zJY+tE1OxQkj&7j$FWXz``PZQU#!H_a5v1kDExg-tpSR&`_G oJNyr;zrVlJXf*ylFfcIoH``{ge=*}4FaQ7m07*qoM6N<$g3XNN`Tzg` diff --git a/quick_replies/quick_replies.py b/quick_replies/quick_replies.py new file mode 100644 index 0000000..502dd01 --- /dev/null +++ b/quick_replies/quick_replies.py @@ -0,0 +1,5 @@ +DEFAULT_DATA = [ + 'Hello!', + 'How are you?', + 'Good bye.', +]