[quick_replies] Complete rework

This commit is contained in:
Daniel Brötzmann
2020-04-24 20:49:41 +02:00
parent 4098a77add
commit 63484c5b59
5 changed files with 286 additions and 120 deletions

View File

@@ -0,0 +1,85 @@
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)

103
quick_replies/gtk/config.ui Normal file
View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkListStore" id="replies_store">
<columns>
<!-- column-name reply -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">18</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="replies_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">replies_store</property>
<property name="search_column">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="selection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="resizable">True</property>
<property name="title" translatable="yes">Quick Reply</property>
<property name="clickable">True</property>
<property name="sort_indicator">True</property>
<property name="sort_column_id">0</property>
<child>
<object class="GtkCellRendererText">
<property name="editable">True</property>
<signal name="edited" handler="_on_reply_edited" swapped="no"/>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToolbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="toolbar_style">icons</property>
<property name="icon_size">4</property>
<child>
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Add</property>
<property name="icon_name">list-add-symbolic</property>
<signal name="clicked" handler="_on_add_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Remove</property>
<property name="icon_name">list-remove-symbolic</property>
<signal name="clicked" handler="_on_remove_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<style>
<class name="inline-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@@ -1,140 +1,113 @@
from gi.repository import Gtk import json
from gi.repository import GdkPixbuf 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 import GajimPlugin
from gajim.plugins.gui import GajimPluginConfigDialog
from gajim.plugins.helpers import log_calls
from gajim.plugins.plugins_i18n import _ from gajim.plugins.plugins_i18n import _
from quick_replies.quick_replies import DEFAULT_DATA
from quick_replies.gtk.config import ConfigDialog
class QuickRepliesPlugin(GajimPlugin): class QuickRepliesPlugin(GajimPlugin):
@log_calls('QuickRepliesPlugin')
def init(self): def init(self):
self.description = _('Adds a menu with customizable quick replies')
self.description = _('Plugin for quick replies') self.config_dialog = partial(ConfigDialog, self)
self.config_dialog = QuickRepliesPluginConfigDialog(self)
self.chat_control = None
self.gui_extension_points = { self.gui_extension_points = {
'chat_control_base': (self.connect_with_chat_control, 'chat_control_base': (self._connect_chat_control,
self.disconnect_from_chat_control), self._disconnect_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': ('', ''),
} }
self.controls = [] self._buttons = {}
self.quick_replies = self._load_quick_replies()
@log_calls('QuickRepliesPlugin') def _connect_chat_control(self, chat_control):
def connect_with_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 def _disconnect_chat_control(self, chat_control):
base = Base(self, chat_control) button = self._buttons.get(chat_control.control_id)
self.controls.append(base) if button is not None:
button.destroy()
self._buttons.pop(chat_control.control_id, None)
@log_calls('QuickRepliesPlugin') @staticmethod
def disconnect_from_chat_control(self, chat_control): 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: path = data_path / 'quick_replies' / 'quick_replies'
control.disconnect_from_chat_control() if not path.exists():
self.controls = [] return DEFAULT_DATA
@log_calls('QuickRepliesPlugin') with path.open('r') as file:
def update_button_state(self, chat_control): quick_replies = json.load(file)
for base in self.controls: return quick_replies
if base.chat_control != chat_control:
continue @staticmethod
base.button.set_sensitive( def _save_quick_replies(quick_replies):
app.account_is_connected(chat_control.account)) 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.update_menu(replies)
self.chat_control = chat_control
self.create_button()
self.create_menu()
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') def _on_insert(self, widget, text):
self.button = Gtk.MenuButton(label=None, stock=None, use_underline=True) message_buffer = self._chat_control.msg_textview.get_buffer()
self.button.get_style_context().add_class( self._chat_control.msg_textview.remove_placeholder()
'chatcontrol-actionbar-button') message_buffer.insert_at_cursor(text.rstrip() + ' ')
self.button.set_property('relief', Gtk.ReliefStyle.NONE) self._chat_control.msg_textview.grab_focus()
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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,5 @@
DEFAULT_DATA = [
'Hello!',
'How are you?',
'Good bye.',
]