diff --git a/qtribu/gui/dck_qchat.py b/qtribu/gui/dck_qchat.py index c5f15764..13b8a511 100644 --- a/qtribu/gui/dck_qchat.py +++ b/qtribu/gui/dck_qchat.py @@ -5,7 +5,6 @@ from typing import Any, Optional # PyQGIS -# from PyQt5 import QtWebSockets # noqa QGS103 from qgis.core import Qgis, QgsApplication from qgis.gui import QgisInterface, QgsDockWidget @@ -28,7 +27,7 @@ INTERNAL_MESSAGE_AUTHOR, QCHAT_NICKNAME_MINLENGTH, ) -from qtribu.gui.dlg_emoji_picker import QEmojiPicker +from qtribu.gui.dlg_emoji_picker import EmojiPicker from qtribu.logic.qchat_client import QChatApiClient from qtribu.tasks.dizzy import DizzyTask from qtribu.toolbelt import PlgLogger, PlgOptionsManager @@ -58,13 +57,7 @@ def __init__(self, iface: QgisInterface, parent: QWidget = None): self.task_manager = QgsApplication.taskManager() self.log = PlgLogger().log self.plg_settings = PlgOptionsManager() - self.emoji_picker = QEmojiPicker( - parent=self, - # this option can say how many emojis are in a row - items_per_row=8, - # with this enabled, the emoji search will be faster but less accurate - performance_search=True, - ) + self.emoji_picker_new = EmojiPicker(self) uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) @@ -638,10 +631,11 @@ def on_emoji_picker_pressed(self) -> Optional[str]: :return: selected emoji :rtype: str """ - selected_emoji = self.emoji_picker.select() - self.lne_message.insert(selected_emoji) + self.emoji_picker_new.show() + # selected_emoji = self.emoji_picker.select() + # self.lne_message.insert(selected_emoji) - return selected_emoji + # return selected_emoji def on_renew_clicked(self) -> None: msg_box = QMessageBox() diff --git a/qtribu/gui/dlg_emoji_picker.py b/qtribu/gui/dlg_emoji_picker.py index 48095301..5869f302 100644 --- a/qtribu/gui/dlg_emoji_picker.py +++ b/qtribu/gui/dlg_emoji_picker.py @@ -10,10 +10,14 @@ # standard import json -import typing +from pathlib import Path +from typing import Optional, Union # PyQGIS -from qgis.PyQt import QtCore, QtGui +from qgis.core import QgsApplication +from qgis.PyQt import QtCore, QtGui, uic +from qgis.PyQt.QtCore import QUrl +from qgis.PyQt.QtGui import QFontDatabase from qgis.PyQt.QtWidgets import ( QDialog, QGridLayout, @@ -31,6 +35,190 @@ # plugin from qtribu.__about__ import DIR_PLUGIN_ROOT +from qtribu.toolbelt import PlgLogger, PlgOptionsManager + +# ############################################################################ +# ########## Classes ############### +# ################################## + + +class QHoverPushButton(QPushButton): + """A custom QPushButton which detects when a mouse hovers it""" + + def __init__(self, text: str, parent_emoji_picker): + """ + Args: + text: The button text + parent_emoji_picker (QEmojiPicker): The parent emoji picker + """ + super().__init__(text) + self.clicked.connect(self.on_click) + + self.parent_emoji_picker = parent_emoji_picker + + def enterEvent(self, a0: QtCore.QEvent) -> None: + """On mouse hover / when the mouse is over the button""" + self.parent_emoji_picker.emoji_image_label.setText(self.text()) + group_title = self.parentWidget().title() + # when the group title is 'Search results' the user has used the search input + if group_title == "Search results": + self.parent_emoji_picker.emoji_name_label.setText( + self.parent_emoji_picker.total_emojis[self.text()] + ) + else: + self.parent_emoji_picker.emoji_name_label.setText( + self.parent_emoji_picker.emojis[group_title][self.text()].get("name") + ) + + def leaveEvent(self, a0: QtCore.QEvent) -> None: + """When the mouse leaves the button""" + self.parent_emoji_picker.emoji_image_label.setText("") + self.parent_emoji_picker.emoji_name_label.setText("") + + def on_click(self): + """Gets called if the button is pressed. Closes the emoji picker and if it + was called via `QEmojiPicker.select()` the current button emoji will be + returned. + """ + self.parent_emoji_picker.selected_emoji = self.text() + self.parent_emoji_picker.close() + + +class EmojiPicker(QDialog): + """Emoji Picker. + + :param QDialog: parent widget + :type QDialog: + """ + + def __init__(self, parent: Optional[QWidget] = None): + """Mini dialog to pick up an emoji.""" + # init module and ui + super().__init__(parent, QtCore.Qt.WindowCloseButtonHint) + + uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) + + self.log = PlgLogger().log + self.plg_settings = PlgOptionsManager().get_plg_settings() + + self.items_per_row: int = 8 + + self.check_emoji_font() + self.emoji_font = QtGui.QFont("Noto Color Emoji") + + self.emojis = self.load_emojis() + self.total_emojis = {} + + self.setupUi() + + def setupUi(self): + for group, items in self.emojis.items(): + box = QGroupBox(group) + layout = QGridLayout() + for i, (emoji, name) in enumerate(items.items()): + # for every emoji, build a button + button = QHoverPushButton(text=emoji, parent_emoji_picker=self) + button.setFont(self.emoji_font) + button.setFlat(True) + button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + button.setFixedSize(30, 30) + # the button style + button.setStyleSheet( + "QPushButton {" + " font-size: 20px;" + " border-radius: 50%%;" + "}" + "QPushButton:hover {" + " background-color: %s" + "}" % button.palette().button().color().darker().name() + ) + layout.addWidget( + button, int(i / self.items_per_row), i % self.items_per_row + ) + + # adds the current emoji with its name to a dict where are all emojis without groups are listed + self.total_emojis[emoji] = name + + box.setLayout(layout) + self.scr_emojis.addWidget(box) + + def check_emoji_font(self): + """Check if the font used for displaying emojis is installed. If not, try to + download it. + + :return: _description_ + :rtype: _type_ + """ + if self.is_font_available(font_family=self.plg_settings.font_emoji_family): + self.log( + message=self.tr( + "Required font to display emojis is already installed: {}".format( + self.plg_settings.font_emoji_family + ) + ), + push=False, + log_level=4, + ) + return True + else: + self.log( + message="Required font for emojis needs to be installed: {}".format( + self.plg_settings.font_emoji_family + ), + push=False, + ) + font_manager = QgsApplication.fontManager() + font_manager.fontDownloadErrorOccurred.connect(self.on_font_download_failed) + auto_downloaded = font_manager.tryToDownloadFontFamily( + self.plg_settings.font_emoji_family + ) + if not auto_downloaded: + self.log(message="not downloaded", log_level=1) + + font_manager.downloadAndInstallFont( + url=QUrl(self.plg_settings.font_emoji_download_url), + identifier="qtribu-emoji-font", + ) + + def on_font_download_failed(self, error_message: Optional[str] = None): + """Handle pyqtsignal emitted by QgsFontManager when font downloading failed. + + :param error_message: error message, defaults to None + :type error_message: Optional[str], optional + """ + self.log( + message=self.tr( + "Downloading the font {} from {} failed. Since it's required to " + "correctly display emojis, consider to add it manually to your system. " + "Trace: {}".format( + self.EMOJI_FONT_FAMILY, self.EMOJI_FONT_DOWNLOAD_URL, error_message + ) + ) + ) + + def is_font_available(self, font_family: str) -> bool: + """Check if the given font family if among the Qt font database. + + :param font_family: font family name + :type font_family: str + + :return: True if the font family is available. + :rtype: bool + """ + available_fonts = QFontDatabase().families() + return font_family in available_fonts + + def load_emojis(self) -> dict: + """Load emojis from JSON file. + + :return: _description_ + :rtype: dict + """ + with DIR_PLUGIN_ROOT.joinpath("resources/emojis/selection.json").open( + mode="r", encoding="UTF-8" + ) as in_json: + emojis = json.load(in_json) + return emojis class QEmojiPicker(QDialog): @@ -38,8 +226,8 @@ class QEmojiPicker(QDialog): def __init__( self, - parent: typing.Optional[QWidget] = None, - flags: typing.Union[None, QtCore.Qt.WindowFlags, QtCore.Qt.WindowType] = None, + parent: Optional[QWidget] = None, + flags: Union[None, QtCore.Qt.WindowFlags, QtCore.Qt.WindowType] = None, items_per_row: int = 8, performance_search: bool = True, ): @@ -54,9 +242,13 @@ def __init__( super().__init__(parent, flags) else: super().__init__(parent) + + self.check_emoji_font() + # initializes the ui self.setupUi(self) - self.retranslateUi(self) + + self.font_manager = QgsApplication.fontManager() self.items_per_row = items_per_row self.performance_search = performance_search @@ -77,7 +269,7 @@ def __init__( for i, (emoji, name) in enumerate(items.items()): # uses a little modified push button which recognizes when the mouse is over the button button = self.__QHoverPushButton(text=emoji, parent_emoji_picker=self) - + button.setFont(emoji_font) button.setFlat(True) button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) button.setFixedSize(30, 30) @@ -134,44 +326,46 @@ def setupUi(self, Form): self.emoji_image_label.sizePolicy().hasHeightForWidth() ) self.emoji_image_label.setSizePolicy(size_policy) - font = QtGui.QFont() - font.setPointSize(22) - self.emoji_image_label.setFont(font) + self.emoji_image_label.setFont(emoji_font) self.emoji_image_label.setText("") self.emoji_image_label.setObjectName("emoji_image_label") self.emoji_information_hlayout.addWidget(self.emoji_image_label) self.emoji_name_label = QLabel(Form) - size_policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth( self.emoji_name_label.sizePolicy().hasHeightForWidth() ) self.emoji_name_label.setSizePolicy(size_policy) - font = QtGui.QFont() - font.setPointSize(10) - self.emoji_name_label.setFont(font) + self.emoji_name_label.setFont(emoji_font) self.emoji_name_label.setText("") self.emoji_name_label.setObjectName("emoji_name_label") self.emoji_information_hlayout.addWidget(self.emoji_name_label) self.verticalLayout.addLayout(self.emoji_information_hlayout) - self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) + def check_emoji_font(self): + print(self.is_font_available(font_family="Noto Color Emoji")) + + def is_font_available(self, font_family: str) -> bool: + available_fonts = QFontDatabase().families() + return font_family in available_fonts + def load_emojis(self) -> dict: - with DIR_PLUGIN_ROOT.joinpath("resources/emojis2.json").open( + """Load emojis from JSON file. + + :return: _description_ + :rtype: dict + """ + with DIR_PLUGIN_ROOT.joinpath("resources/emojis/selection.json").open( mode="r", encoding="UTF-8" ) as in_json: emojis = json.load(in_json) return emojis - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "Form")) - self.search_line_edit.setPlaceholderText(_translate("Form", "Search...")) - - def select(self) -> typing.Union[str, None]: + def select(self) -> Union[str, None]: """Shows this window and returns the selected emoji if a button was pressed or none, if the window was closed without choosing an emoji""" self.exec() return self.selected_emoji @@ -275,7 +469,9 @@ def enterEvent(self, a0: QtCore.QEvent) -> None: ) else: self.parent_emoji_picker.emoji_name_label.setText( - self.parent_emoji_picker.emojis[group_title][self.text()] + self.parent_emoji_picker.emojis[group_title][self.text()].get( + "name" + ) ) def leaveEvent(self, a0: QtCore.QEvent) -> None: diff --git a/qtribu/gui/dlg_emoji_picker.ui b/qtribu/gui/dlg_emoji_picker.ui new file mode 100644 index 00000000..5750eac8 --- /dev/null +++ b/qtribu/gui/dlg_emoji_picker.ui @@ -0,0 +1,138 @@ + + + dlg_emoji_picker + + + + 0 + 0 + 379 + 162 + + + + + 100 + 150 + + + + Emojis Picker + + + 0.800000000000000 + + + true + + + + + + true + + + + + + + + + + + Qt::ImhPreferLatin + + + search for filtering... + + + true + + + + + + + Qt::NoContextMenu + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 359 + 86 + + + + + + + + + + + QLayout::SetDefaultConstraint + + + 10 + + + + + + Noto Color Emoji + + + + Qt::NoContextMenu + + + true + + + 🙂 + + + true + + + Qt::NoTextInteraction + + + + + + + + Ubuntu + + + + Slightly Smiling Face + + + + + + + :slightly_smiling_face: + + + + + + + + + + diff --git a/qtribu/resources/emojis.json b/qtribu/resources/emojis/emojis.json similarity index 100% rename from qtribu/resources/emojis.json rename to qtribu/resources/emojis/emojis.json diff --git a/qtribu/resources/emojis2.json b/qtribu/resources/emojis/emojis2.json similarity index 100% rename from qtribu/resources/emojis2.json rename to qtribu/resources/emojis/emojis2.json diff --git a/qtribu/resources/emojis/selection.json b/qtribu/resources/emojis/selection.json new file mode 100644 index 00000000..b66ae350 --- /dev/null +++ b/qtribu/resources/emojis/selection.json @@ -0,0 +1,39 @@ +{ + "Gestures": { + "🤲": { + "name": "Palms Up Together", + "codes": [ + "palms" + ] + }, + "🤝": { + "name": "Handshake", + "codes": [ + "handshake", + "shake" + ] + } + }, + "Smileys & People": { + "🙂": { + "name": "Slightly Smiling Face", + "codes": [ + "palms" + ] + }, + "😂": { + "name": "Handshake", + "codes": [ + "handshake", + "shake" + ] + }, + "😇": { + "name": "Handshake", + "codes": [ + "handshake", + "shake" + ] + } + } +} diff --git a/qtribu/toolbelt/preferences.py b/qtribu/toolbelt/preferences.py index be130249..a3c1377a 100644 --- a/qtribu/toolbelt/preferences.py +++ b/qtribu/toolbelt/preferences.py @@ -47,6 +47,10 @@ class PlgSettingsStructure: qchat_color_mention: str = "#4169e1" qchat_color_self: str = "#00cc00" qchat_color_admin: str = "#ffa500" + font_emoji_family: str = "Noto Color Emoji" + font_emoji_download_url: str = ( + "https://github.com/google/fonts/raw/main/ofl/notocoloremoji/NotoColorEmoji-Regular.ttf" + ) # QChat qchat_instance_uri: str = "https://gischat.geotribu.net"