Commit b6dd6219 authored by Alexander's avatar Alexander

[stickers] Refactor StickersButton to work with models

parent b3e66ee0
......@@ -16,8 +16,6 @@
#
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GObject
from gajim.common.i18n import _
from gajim.common.helpers import open_file
......@@ -28,36 +26,6 @@ from gajim.gui.dialogs import DialogButton
from stickers.utils import sticker_data_path
class StickerPackObject(GObject.GObject):
def __init__(self, name, summary, amount, uploaded, id_):
self._name = name
self._summary = summary
self._amount = amount
self._uploaded = uploaded
self._id = id_
GObject.GObject.__init__(self)
@property
def name(self):
return self._name
@property
def summary(self):
return self._summary
@property
def amount(self):
return self._amount
@property
def uploaded(self):
return self._uploaded
@property
def id_(self):
return self._id
class StickersConfigDialog(GajimPluginConfigDialog):
def init(self):
path = self.plugin.local_file_path('gtk/config.ui')
......@@ -67,7 +35,7 @@ class StickersConfigDialog(GajimPluginConfigDialog):
box.pack_start(self._ui.stickers_config_dialog, True, True, 0)
self._ui.connect_signals(self)
self._list_model = Gio.ListStore()
self._list_model = self.plugin.sticker_pack_model
self._ui.sticker_width.set_range(0, 400)
self._ui.sticker_width.set_increments(1, -1)
self._ui.sticker_packs_list.bind_model(self._list_model, self._create_sticker_pack_row)
......@@ -86,30 +54,6 @@ class StickersConfigDialog(GajimPluginConfigDialog):
def on_sticker_packs_reload_clicked(self, button):
self.plugin.reload_sticker_packs()
def on_sticker_pack_added(self, pack):
'''
Called when a new sticker pack has been added.
'''
self._list_model.append(StickerPackObject(pack.name,
pack.summary,
len(pack.stickers),
pack.uploaded,
pack.id_))
def on_sticker_pack_removed(self, pack):
'''
Called when a sticker pack has been removed.
'''
# Find the correct index in the ListStore
index = -1
for index_ in range(self._list_model.get_n_items()):
if self._list_model.get_item(index_).id_ == pack.id_:
index = index_
break
if index != -1:
self._list_model.remove(index)
def on_run(self):
# Update all config settings
for setting in ('download_new', 'download_new_signin', 'upload_new_signin', 'show_animated_stickers'):
......
#
# Copyright (C) 2020 Alexander "PapaTutuWawa" <papatutuwawa AT polynom.me>
# This file is part of the Stickers plugin for Gajim.
#
# Gajim 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.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>..check_file_before_transfer(sticker)
#
from gi.repository import Gio
from gi.repository import GObject
class StickerObject(GObject.GObject):
@staticmethod
def from_sticker(sticker):
obj = StickerObject()
for field in sticker._fields:
setattr(obj, field, getattr(sticker, field))
setattr(obj, 'sticker_strings', sticker.suggests + [sticker.desc])
return obj
class StickerPackObject(GObject.GObject):
def __init__(self, id_, name, summary, amount, strings, uploaded, stickers_model):
super().__init__()
self._id = id_
self._name = name
self._summary = summary
self._amount = amount
self._strings = strings
self._uploaded = uploaded
self._stickers_model = stickers_model
@property
def id_(self):
return self._id
@property
def name(self):
return self._name
@property
def summary(self):
return self._summary
@property
def amount(self):
return self._amount
@property
def sticker_strings(self):
return self._strings
@property
def uploaded(self):
return self._uploaded
@property
def stickers_model(self):
return self._stickers_model
@staticmethod
def from_sticker_pack(pack):
strings = []
model = Gio.ListStore()
for sticker in pack.stickers:
strings += sticker.suggests + [sticker.desc]
model.append(StickerObject.from_sticker(sticker))
return StickerPackObject(pack.id_,
pack.name,
pack.summary,
len(pack.stickers),
strings,
pack.uploaded,
model)
#
# Copyright (C) 2020 Alexander "PapaTutuWawa" <papatutuwawa AT polynom.me>
# This file is part of the Stickers plugin for Gajim.
#
# Gajim 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.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
#
import weakref
from gi.repository import Gio
from gi.repository import GObject
class FilterListModel(GObject.GObject, Gio.ListModel):
'''
Since GTK (or GIO) do not provide us with a filterable proxy model
for ListStores, we just implement it ourselves until it somehow moves
into GTK (or GIO).
TODO: Remove once in GTK (or GIO)
'''
def __init__(self, parent):
super().__init__()
self._parent = parent
self._items_cache = []
self._n_items = 0
self._func = lambda x: True
self._parent.connect('items-changed', self._on_parent_changed)
def _refresh_cache(self):
before = self._n_items
self._items_cache = []
for index in range(self._parent.get_n_items()):
item = self._parent.get_item(index)
if self._func(item):
self._items_cache.append(weakref.ref(item))
self._n_items = len(self._items_cache)
self.items_changed(0, before, self._n_items)
def _on_parent_changed(self, model, position, removed, added):
self._refresh_cache()
def set_filter_func(self, func):
self._func = func
self.invalidate_filter()
def do_get_item(self, position):
if position >= self._n_items:
return None
return self._items_cache[position]()
def do_get_item_type(self):
return GObject.GType.children
def do_get_n_items(self):
return self._n_items
def invalidate_filter(self):
self._refresh_cache()
......@@ -34,6 +34,7 @@ import logging
from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import Soup
from gi.repository import Gio
from nbxmpp.namespaces import Namespace
from nbxmpp.simplexml import Node
......@@ -58,6 +59,9 @@ except ImportError as err:
from stickers.modules import stickers_module
from stickers.gtk.config import StickersConfigDialog
from stickers.gtk.utils import FilterListModel
from stickers.gtk.models import StickerPackObject
from stickers.gtk.models import StickerObject
from stickers.utils import sticker_data_path
from stickers.utils import sticker_path
from stickers.utils import find_one
......@@ -187,6 +191,15 @@ def verify_sticker_hashes(sticker, data):
class StickersPlugin(GajimPlugin):
def init(self):
self._session = Soup.Session()
self.controls = {}
self.sticker_packs = {} # Sticker pack id -> sticker pack
self.sticker_requests = [] # Sticker packs we requested
self.sticker_pack_model = Gio.ListStore()
self.signin_pack_requests = [] # List of accounts that we requested sticker packs on signin as
self.sticker_mentions = {} # Requested stickers that are mentioned
self.sticker_pack_retractions = {} # Sticker pack id -> [Accounts that we wait on]
self.description = _('Send stickers. Note that stickers are currently always sent unencrypted. Requires python-pillow.')
self.config_dialog = StickersConfigDialog(self)
self.config_default_values = {
......@@ -221,19 +234,40 @@ class StickersPlugin(GajimPlugin):
'print_real_text': (self._print_real_text, None),
}
self._session = Soup.Session()
self.controls = {}
self.sticker_packs = {} # Sticker pack id -> sticker pack
self.sticker_requests = [] # Sticker packs we requested
self.signin_pack_requests = [] # List of accounts that we requested sticker packs on signin as
self.sticker_mentions = {} # Requested stickers that are mentioned
self.sticker_pack_retractions = {} # Sticker pack id -> [Accounts that we wait on]
self.modules = [stickers_module]
self._load_sticker_packs()
self._load_icon()
def _model_append(self, pack):
'''
Appends the StickerPackObject pack to the model.
'''
self.sticker_pack_model.append(pack)
def _model_remove(self, pack_id):
'''
Remove the item with the id_ attribute of pack_id from the model.
Returns the index of this item, or -1 if not found.
'''
# Find the correct index in the ListStore
for index_ in range(model.get_n_items()):
if model.get_item(index_).id_ == id_:
model.remove(index)
return index
log.warning('Cannot remove %s from sticker_pack_model: Not found', pack_id)
return -1
def _model_replace(self, pack):
'''
Replaces the item with the id_ attribute of pack.id_ with the
StickerPackObject pack.
'''
index = self._model_remove(pack.id_)
if index >= 0:
self.sticker_pack_model.insert(index, pack)
def __has_sticker_pack(self, id_):
'''
Returns True if we know a sticker pack by this ID
......@@ -253,10 +287,9 @@ class StickersPlugin(GajimPlugin):
pack has been fully uploaded.
'''
pack = self.sticker_packs[event.id_]
self.sticker_packs[event.id_] = pack
self._model_replace(StickerPackObject.from_sticker_pack(pack))
write_sticker_pack_info(pack)
self.config_dialog.on_sticker_pack_removed(pack)
self.config_dialog.on_sticker_pack_added(pack)
self.__update_buttons()
def _on_signed_in(self, event):
if not self.config['UPLOAD_NEW_SIGNIN'] and not self.config['DOWNLOAD_NEW_SIGNIN']:
......@@ -279,19 +312,16 @@ class StickersPlugin(GajimPlugin):
'''
Called when a new sticker pack has been added
'''
self.config_dialog.on_sticker_pack_added(pack)
self._model_append(StickerPackObject.from_sticker_pack(pack))
def reload_sticker_packs(self):
'''
Searches again for sticker packs and loads them.
'''
log.debug('Reloading sticker packs')
# To prevent duplicate sticker packs in the config dialog, first remove
# them all
for sticker_pack in self.sticker_packs.values():
self.config_dialog.on_sticker_pack_removed(sticker_pack)
# To prevent duplicate sticker packs, first remove them all
self.sticker_pack_model.remove_all()
self._load_sticker_packs()
self.__update_buttons()
def _load_sticker_packs(self):
path = sticker_data_path()
......@@ -422,10 +452,9 @@ class StickersPlugin(GajimPlugin):
return
shutil.rmtree(path)
self.config_dialog.on_sticker_pack_removed(self.sticker_packs[event.id_])
del self.sticker_packs[event.id_]
for btn in self.controls.values():
btn.update_sticker_packs(self.sticker_packs.values())
btn.remove_sticker_pack(event.id_)
def _on_sticker_pack_received(self, event):
if event.pack.id_ not in self.sticker_requests:
......@@ -468,7 +497,6 @@ class StickersPlugin(GajimPlugin):
# pylint: disable=attribute-defined-outside-init
self.sticker_requests = list(filter(lambda x: x != event.pack.id_, self.sticker_requests))
self.__update_buttons()
self._on_sticker_pack_added(self.sticker_packs[event.pack.id_])
self.sticker_packs[event.pack.id_] = event.pack
......@@ -496,13 +524,6 @@ class StickersPlugin(GajimPlugin):
download_callback,
DownloadCallbackData(sticker, event.pack, publish))
def __update_buttons(self):
'''
Makes new sticker packs available to all sticker buttons
'''
for btn in self.controls.values():
btn.update_sticker_packs(self.sticker_packs.values())
def _on_sticker_packs_received(self, event):
if not event.account in self.signin_pack_requests:
log.warning('Received a stickers-received event although we did not ask for one')
......@@ -706,7 +727,7 @@ class StickersPlugin(GajimPlugin):
control.contact,
control,
chat_type,
self.sticker_packs.values(),
self.sticker_pack_model,
self.local_file_path,
self.upload_sticker_pack,
self._button_icon_pixbuf)
......@@ -723,7 +744,7 @@ class StickersPlugin(GajimPlugin):
self.controls.pop(control.control_id, None)
class StickersButton(Gtk.Button):
def __init__(self, conn, contact, chat_control, chat_type, sticker_packs, local_file_path, upload_sticker_pack, icon_pixbuf):
def __init__(self, conn, contact, chat_control, chat_type, model, local_file_path, upload_sticker_pack, icon_pixbuf):
Gtk.Button.__init__(self)
icon = Gtk.Image.new_from_pixbuf(icon_pixbuf)
self.set_image(icon)
......@@ -735,57 +756,54 @@ class StickersButton(Gtk.Button):
self._conn = conn
self._contact = contact
self._popover = None
self._sticker_packs = sticker_packs
self._model = FilterListModel(model)
self._submodels = {} # Sticker pack ID -> FilterListModel
self._local_file_path = local_file_path
self._upload_sticker_pack = upload_sticker_pack
self._query = ''
self._model.set_filter_func(self._listbox_filter_func)
self._create_popover()
self.connect('clicked', self._on_clicked)
def update_sticker_packs(self, sticker_packs):
def remove_sticker_pack(self, id_):
'''
When we get new sticker packs, recreate the popover
Since we keep an additional model per sticker pack, we need a way to
tell when we can dispose of it.
'''
# TODO: This may not be as performant. Maybe just add the new
# one
self._sticker_packs = sticker_packs
self._popover = None
self._create_popover()
log.debug('StickersButton: Removed submodel for %s', id_)
del self._submodels[id_]
def _create_sticker_button(self, sticker, *user_data):
sticker_pack = user_data[0]
img = image_from_pixbuf(sticker.pixbuf)
img.set_tooltip_text(sticker.desc)
img.show()
box = Gtk.Button.new()
box.add(img)
box.set_relief(Gtk.ReliefStyle.NONE)
box.connect('clicked',
self._send_sticker_lambda(sticker,
sticker_pack.id_,
sticker_pack.uploaded))
def _create_listitem(self, sticker_pack):
return box
def _create_listitem(self, sticker_pack, *user_data):
item = get_builder(self._local_file_path('gtk/popover_listitem.ui'))
item.pack_name.set_markup(f'<big>{sticker_pack.name}</big>')
item.stickers.set_filter_func(self._flowbox_filter_func, None)
stickers_strings = []
for sticker in sticker_pack.stickers:
img = image_from_pixbuf(sticker.pixbuf)
img.set_tooltip_text(sticker.desc)
box = Gtk.Button()
box.add(img)
box.set_relief(Gtk.ReliefStyle.NONE)
box.connect('clicked',
self._send_sticker_lambda(sticker,
sticker_pack.id_,
sticker_pack.uploaded))
# NOTE: We do this so we can later in the filter function check
# if a given query matches the specific sticker
wrapper = Gtk.FlowBoxChild.new()
wrapper.add(box)
wrapper.sticker_strings = [sticker.desc] + sticker.suggests
stickers_strings += wrapper.sticker_strings
item.stickers.add(wrapper)
row_item = Gtk.ListBoxRow.new()
row_item.add(item.sticker_pack_row)
# NOTE: See above NOTE
row_item.stickers_strings = stickers_strings
return row_item
model = FilterListModel(sticker_pack.stickers_model)
model.set_filter_func(self._flowbox_filter_func)
self._submodels[sticker_pack.id_] = model
item.stickers.bind_model(self._submodels[sticker_pack.id_],
self._create_sticker_button,
sticker_pack)
return item.sticker_pack_row
def _create_popover(self):
self._popover = Gtk.Popover.new()
......@@ -794,12 +812,9 @@ class StickersButton(Gtk.Button):
listbox = Gtk.ListBox.new()
listbox.set_selection_mode(Gtk.SelectionMode.NONE)
for sticker_pack in self._sticker_packs:
row = self._create_listitem(sticker_pack)
listbox.insert(row, -1)
listbox.bind_model(self._model, self._create_listitem, None)
listbox.show_all()
listbox.set_filter_func(self._listbox_filter_func, None)
listbox.set_vexpand(True)
self._listbox = listbox
scroll.add(listbox)
......@@ -817,27 +832,26 @@ class StickersButton(Gtk.Button):
def _on_search_changed(self, entry):
self._query = entry.get_text()
self._listbox.invalidate_filter()
def _flowbox_filter_func(self, child, *user_data):
# We don't need it, but it's there
# pylint: disable=unused-argument
if not self._query:
return True
self._model.invalidate_filter()
return any([x.startswith(self._query) for x in child.sticker_strings])
def _flowbox_filter_func(self, sticker):
'''
Used to perform filtering on a single sticker pack
'''
return (not self._query or
any([x.startswith(self._query) for x in sticker.sticker_strings]))
def _listbox_filter_func(self, row, *user_data):
# We don't need it, but it's there
# pylint: disable=unused-argument
grid = row.get_children()[0]
if not self._query:
grid.get_child_at(0, 1).invalidate_filter()
def _listbox_filter_func(self, pack):
'''
Used to perform filtering on all sticker packs
'''
if (not self._query or
any([x.startswith(self._query) for x in pack.sticker_strings])):
if pack.id_ in self._submodels:
# Update the search within the sticker pack
self._submodels[pack.id_].invalidate_filter()
return True
if any([x.startswith(self._query) for x in row.stickers_strings]):
grid.get_child_at(0, 1).invalidate_filter()
return True
return False
def _send_sticker_lambda(self, sticker, pack_id, uploaded):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment