diff --git a/src/sakia/gui/certification.py b/src/sakia/gui/certification.py deleted file mode 100644 index b50d9d5e110d141b6ca7eb2931f0e17d5a29cad4..0000000000000000000000000000000000000000 --- a/src/sakia/gui/certification.py +++ /dev/null @@ -1,264 +0,0 @@ -""" -Created on 24 dec. 2014 - -@author: inso -""" -import asyncio -import logging -from duniterpy.api import errors -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QApplication, QMessageBox -from PyQt5.QtCore import Qt, QObject, QLocale, QDateTime - -from .widgets import toast -from .widgets.dialogs import QAsyncMessageBox -from .member import MemberDialog -from ..tools.decorators import asyncify, once_at_a_time -from ..tools.exceptions import NoPeerAvailable -from ..presentation.certification_uic import Ui_CertificationDialog - - -class CertificationDialog(QObject): - """ - A dialog to certify individuals - """ - - def __init__(self, app, account, password_asker, widget, ui): - """ - Constructor if a certification dialog - - :param sakia.core.Application app: - :param sakia.core.Account account: - :param sakia.gui.password_asker.PasswordAsker password_asker: - :param PyQt5.QtWidgets widget: the widget of the dialog - :param sakia.gen_resources.certification_uic.Ui_CertificationDialog view: the view of the certification dialog - :return: - """ - super().__init__() - self.widget = widget - self.ui = ui - self.ui.setupUi(self.widget) - self.app = app - self.account = account - self.password_asker = password_asker - self.community = self.account.communities[0] - - self.ui.radio_contact.toggled.connect(lambda c, radio="contact": self.recipient_mode_changed(radio)) - self.ui.radio_pubkey.toggled.connect(lambda c, radio="pubkey": self.recipient_mode_changed(radio)) - self.ui.radio_search.toggled.connect(lambda c, radio="search": self.recipient_mode_changed(radio)) - self.ui.button_box.accepted.connect(self.accept) - self.ui.button_box.rejected.connect(self.widget.reject) - - for community in self.account.communities: - self.ui.combo_community.addItem(community.currency) - - for contact_name in sorted([c['name'] for c in account.contacts], key=str.lower): - self.ui.combo_contact.addItem(contact_name) - - if len(account.contacts) == 0: - self.ui.radio_pubkey.setChecked(True) - self.ui.radio_contact.setEnabled(False) - - self.ui.member_widget = MemberDialog.as_widget(self.ui.groupBox, self.app, self.account, self.community, None) - self.ui.horizontalLayout_5.addWidget(self.ui.member_widget.widget) - - self.ui.search_user.button_reset.hide() - self.ui.search_user.init(self.app) - self.ui.search_user.change_account(self.account) - self.ui.search_user.change_community(self.community) - self.ui.combo_contact.currentIndexChanged.connect(self.refresh_member) - self.ui.edit_pubkey.textChanged.connect(self.refresh_member) - self.ui.search_user.identity_selected.connect(self.refresh_member) - self.ui.radio_contact.toggled.connect(self.refresh_member) - self.ui.radio_search.toggled.connect(self.refresh_member) - self.ui.radio_pubkey.toggled.connect(self.refresh_member) - self.ui.combo_community.currentIndexChanged.connect(self.change_current_community) - - @classmethod - def open_dialog(cls, app, account, community, password_asker): - """ - Certify and identity - :param sakia.core.Application app: the application - :param sakia.core.Account account: the account certifying the identity - :param sakia.core.Community community: the community - :param sakia.gui.password_asker.PasswordAsker password_asker: the password asker - :return: - """ - dialog = cls(app, account, password_asker, QDialog(), Ui_CertificationDialog()) - if community: - dialog.ui.combo_community.setCurrentText(community.name) - dialog.refresh() - return dialog.exec() - - @classmethod - async def certify_identity(cls, app, account, password_asker, community, identity): - """ - Certify and identity - :param sakia.core.Application app: the application - :param sakia.core.Account account: the account certifying the identity - :param sakia.gui.password_asker.PasswordAsker password_asker: the password asker - :param sakia.core.Community community: the community - :param sakia.core.registry.Identity identity: the identity certified - :return: - """ - dialog = cls(app, account, password_asker, QDialog(), Ui_CertificationDialog()) - dialog.ui.combo_community.setCurrentText(community.name) - dialog.ui.edit_pubkey.setText(identity.pubkey) - dialog.ui.radio_pubkey.setChecked(True) - dialog.refresh() - return await dialog.async_exec() - - @asyncify - async def accept(self): - """ - Validate the dialog - """ - pubkey = self.selected_pubkey() - if pubkey: - password = await self.password_asker.async_exec() - if password == "": - self.ui.button_box.setEnabled(True) - return - QApplication.setOverrideCursor(Qt.WaitCursor) - result = await self.account.certify(password, self.community, pubkey) - if result[0]: - if self.app.preferences['notifications']: - toast.display(self.tr("Certification"), - self.tr("Success sending certification")) - else: - await QAsyncMessageBox.information(self.widget, self.tr("Certification"), - self.tr("Success sending certification")) - QApplication.restoreOverrideCursor() - self.widget.accept() - else: - if self.app.preferences['notifications']: - toast.display(self.tr("Certification"), self.tr("Could not broadcast certification : {0}" - .format(result[1]))) - else: - await QAsyncMessageBox.critical(self.widget, self.tr("Certification"), - self.tr("Could not broadcast certification : {0}" - .format(result[1]))) - QApplication.restoreOverrideCursor() - self.ui.button_box.setEnabled(True) - - def change_current_community(self, index): - self.community = self.account.communities[index] - self.ui.search_user.change_community(self.community) - self.ui.member_widget.change_community(self.community) - if self.widget.isVisible(): - self.refresh() - - def selected_pubkey(self): - """ - Get selected pubkey in the widgets of the window - :return: the current pubkey - :rtype: str - """ - pubkey = None - if self.ui.radio_contact.isChecked(): - for contact in self.account.contacts: - if contact['name'] == self.ui.combo_contact.currentText(): - pubkey = contact['pubkey'] - break - elif self.ui.radio_search.isChecked(): - if self.ui.search_user.current_identity(): - pubkey = self.ui.search_user.current_identity().pubkey - else: - pubkey = self.ui.edit_pubkey.text() - return pubkey - - @asyncify - async def refresh_member(self, checked=False): - """ - Refresh the member widget - """ - current_pubkey = self.selected_pubkey() - if current_pubkey: - identity = await self.app.identities_registry.future_find(current_pubkey, self.community) - else: - identity = None - self.ui.member_widget.identity = identity - self.ui.member_widget.refresh() - - @once_at_a_time - @asyncify - async def refresh(self): - account_identity = await self.account.identity(self.community) - is_member = await account_identity.is_member(self.community) - try: - block_0 = await self.community.get_block(0) - except errors.DuniterError as e: - if e.ucode == errors.BLOCK_NOT_FOUND: - block_0 = None - except NoPeerAvailable as e: - logging.debug(str(e)) - block_0 = None - - params = await self.community.parameters() - certifications = await account_identity.unique_valid_certified_by(self.app.identities_registry, self.community) - nb_certifications = len([c for c in certifications if c['block_number']]) - nb_cert_pending = len([c for c in certifications if not c['block_number']]) - remaining_time = await account_identity.cert_issuance_delay(self.app.identities_registry, self.community) - cert_text = self.tr("Certifications sent : {nb_certifications}/{stock}").format( - nb_certifications=nb_certifications, - stock=params['sigStock']) - if nb_cert_pending > 0: - cert_text += " (+{nb_cert_pending} certifications pending)".format(nb_cert_pending=nb_cert_pending) - if remaining_time > 0: - cert_text += "\n" - days, remainder = divmod(remaining_time, 3600*24) - hours, remainder = divmod(remainder, 3600) - minutes, seconds = divmod(remainder, 60) - if days > 0: - remaining_localized = self.tr("{days} days").format(days=days) - else: - remaining_localized = self.tr("{hours} hours and {min} min.").format(hours=hours, - min=minutes) - cert_text += self.tr("Remaining time before next certification validation : {0}".format(remaining_localized)) - self.ui.label_cert_stock.setText(cert_text) - - if is_member or not block_0: - if nb_certifications < params['sigStock'] or params['sigStock'] == 0: - self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(True) - if remaining_time > 0: - self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("&Ok") + - self.tr(" (Not validated before ") - + remaining_localized + ")") - else: - self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("&Ok")) - else: - self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(False) - self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("No more certifications")) - else: - self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(False) - self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("Not a member")) - - def showEvent(self, event): - super().showEvent(event) - self.first_certification_check() - - def first_certification_check(self): - if self.account.notifications['warning_certifying_first_time']: - self.account.notifications['warning_certifying_first_time'] = False - QMessageBox.warning(self, "Certifying individuals", """Please follow the following guidelines : -1.) Don't certify an account if you believe the issuers identity might be faked. -2.) Don't certify an account if you believe the issuer already has another certified account. -3.) Don't certify an account if you believe the issuer purposely or carelessly violates rule 1 or 2 (the issuer certifies faked or double accounts -""") - - def recipient_mode_changed(self, radio): - """ - :param str radio: - """ - self.ui.edit_pubkey.setEnabled(radio == "pubkey") - self.ui.combo_contact.setEnabled(radio == "contact") - self.ui.search_user.setEnabled(radio == "search") - - def async_exec(self): - future = asyncio.Future() - self.widget.finished.connect(lambda r: future.set_result(r)) - self.widget.open() - self.refresh() - return future - - def exec(self): - self.widget.exec() diff --git a/src/sakia/gui/search_user/__init__.py b/src/sakia/gui/certification/__init__.py similarity index 100% rename from src/sakia/gui/search_user/__init__.py rename to src/sakia/gui/certification/__init__.py diff --git a/res/ui/certification.ui b/src/sakia/gui/certification/certification.ui similarity index 94% rename from res/ui/certification.ui rename to src/sakia/gui/certification/certification.ui index cd0c6b75abc4b98e6f27c6b98ac265d1f71e99fd..b45c093068b4febb01ff1c0c71c1793e26a3217e 100644 --- a/res/ui/certification.ui +++ b/src/sakia/gui/certification/certification.ui @@ -55,16 +55,16 @@ </property> <layout class="QHBoxLayout" name="horizontalLayout_5"> <item> - <layout class="QVBoxLayout" name="verticalLayout_3"> + <layout class="QVBoxLayout" name="layout_target_choice"> <property name="topMargin"> <number>6</number> </property> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> + <layout class="QHBoxLayout" name="layout_mode_contact"> <item> <widget class="QRadioButton" name="radio_contact"> <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> @@ -109,7 +109,7 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QHBoxLayout" name="layout_mode_pubkey"> <item> <widget class="QRadioButton" name="radio_pubkey"> <property name="text"> @@ -161,14 +161,14 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="layout_mode_search"> <property name="topMargin"> <number>6</number> </property> <item> <widget class="QRadioButton" name="radio_search"> <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> diff --git a/src/sakia/gui/certification/controller.py b/src/sakia/gui/certification/controller.py new file mode 100644 index 0000000000000000000000000000000000000000..eee867cb04de40643dfdb064e3d2976a8903383f --- /dev/null +++ b/src/sakia/gui/certification/controller.py @@ -0,0 +1,178 @@ +from ..component.controller import ComponentController +from .view import CertificationView +from .model import CertificationModel +from ..search_user.controller import SearchUserController +from PyQt5.QtWidgets import QApplication +from PyQt5.QtCore import Qt +from ...tools.decorators import asyncify, once_at_a_time +import asyncio + + +class CertificationController(ComponentController): + """ + The Certification view + """ + + def __init__(self, parent, view, model, search_user): + """ + Constructor of the Certification component + + :param sakia.gui.Certification.view.CertificationView: the view + :param sakia.gui.Certification.model.CertificationModel model: the model + :param sakia.gui.SearchUser.controller.SearchUserController search_user: the search user component + """ + super().__init__(parent, view, model) + self.view.button_box.accepted.connect(self.accept) + self.view.button_box.rejected.connect(self.reject) + self.view.combo_community.currentIndexChanged.connect(self.change_current_community) + self.search_user = search_user + + @classmethod + def create(cls, parent, app, **kwargs): + """ + Instanciate a Certification component + :param sakia.gui.component.controller.ComponentController parent: + :return: a new Certification controller + :rtype: CertificationController + """ + account = kwargs['account'] + community = kwargs['community'] + communities_names = [c.name for c in account.communities] + contacts_names = [c['name'] for c in account.contacts] + + view = CertificationView(parent.view, None, communities_names, contacts_names) + model = CertificationModel(None, app, account, community) + certification = cls(parent, view, model, None) + + search_user = SearchUserController.create(certification, app, account=model.account, community=model.community) + view.set_search_user(search_user.view) + model.setParent(certification) + return certification + + @classmethod + def open_dialog(cls, parent, app, account, community, password_asker): + """ + Certify and identity + :param sakia.gui.component.controller.ComponentController parent: the parent + :param sakia.core.Application app: the application + :param sakia.core.Account account: the account certifying the identity + :param sakia.core.Community community: the community + :param sakia.gui.password_asker.PasswordAsker password_asker: the password asker + :return: + """ + dialog = cls.create(parent, app, account=account, community=community, password_asker=password_asker) + if community: + dialog.ui.combo_community.setCurrentText(community.name) + dialog.refresh() + return dialog.exec() + + @classmethod + async def certify_identity(cls, parent, app, account, password_asker, community, identity): + """ + Certify and identity + :param sakia.gui.component.controller.ComponentController parent: the parent + :param sakia.core.Application app: the application + :param sakia.core.Account account: the account certifying the identity + :param sakia.gui.password_asker.PasswordAsker password_asker: the password asker + :param sakia.core.Community community: the community + :param sakia.core.registry.Identity identity: the identity certified + :return: + """ + dialog = cls.create(parent, app, account=account, community=community, password_asker=password_asker) + dialog.view.combo_community.setCurrentText(community.name) + dialog.view.edit_pubkey.setText(identity.pubkey) + dialog.view.radio_pubkey.setChecked(True) + dialog.refresh() + return await dialog.async_exec() + + @property + def view(self) -> CertificationView: + return self._view + + @property + def model(self) -> CertificationModel: + return self._model + + @asyncify + async def accept(self): + """ + Validate the dialog + """ + pubkey = self.selected_pubkey() + if pubkey: + password = await self.password_asker.async_exec() + if password == "": + self.view.enable_button_box() + return + QApplication.setOverrideCursor(Qt.WaitCursor) + result = await self.account.certify(password, self.community, pubkey) + if result[0]: + QApplication.restoreOverrideCursor() + await self.view.show_success() + self.view.accept() + else: + await self.view.show_error(result[1]) + QApplication.restoreOverrideCursor() + self.view.enable_button_box() + + def reject(self): + self.view.reject() + + def selected_pubkey(self): + """ + Get selected pubkey in the widgets of the window + :return: the current pubkey + :rtype: str + """ + pubkey = None + + if self.view.recipient_mode() == CertificationView.RecipientMode.CONTACT: + contact_name = self.view.selected_contact() + pubkey = self.model.contact_name_pubkey(contact_name) + elif self.view.recipient_mode() == CertificationView.RecipientMode.SEARCH: + if self.search_user.current_identity(): + pubkey = self.search_user.current_identity().pubkey + else: + pubkey = self.view.pubkey_value() + return pubkey + + @once_at_a_time + @asyncify + async def refresh(self): + stock = await self.model.get_cert_stock() + written, pending = await self.model.nb_certifications() + days, hours, minutes, seconds = await self.model.remaining_time() + self.view.display_cert_stock(written, pending, stock, days, hours, minutes) + + if await self.model.could_certify(): + if written < stock or stock == 0: + if days+hours+minutes > 0: + if days > 0: + remaining_localized = self.tr("{days} days").format(days=days) + else: + remaining_localized = self.tr("{hours}h {min}min").format(hours=hours, min=minutes) + self.view.set_button_box(CertificationView.ButtonBoxState.REMAINING_TIME_BEFORE_VALIDATION, + remaining=remaining_localized) + else: + self.view.set_button_box(CertificationView.ButtonBoxState.OK) + else: + self.view.set_button_box(CertificationView.ButtonBoxState.NO_MORE_CERTIFICATION) + else: + self.view.set_button_box(CertificationView.ButtonBoxState.NOT_A_MEMBER) + + def change_current_community(self, index): + self.model.change_community(index) + self.search_user.set_community(self.community) + #self.member_widget.change_community(self.community) + self.refresh() + + def async_exec(self): + future = asyncio.Future() + self.view.finished.connect(lambda r: future.set_result(r)) + self.view.open() + self.refresh() + return future + + def exec(self): + self.refresh() + self.view.exec() diff --git a/src/sakia/gui/certification/model.py b/src/sakia/gui/certification/model.py new file mode 100644 index 0000000000000000000000000000000000000000..94ac1aba768990d147f42273dc004a19da2dba23 --- /dev/null +++ b/src/sakia/gui/certification/model.py @@ -0,0 +1,90 @@ +from ..component.model import ComponentModel +from duniterpy.api import errors +from sakia.tools.exceptions import NoPeerAvailable + +import logging + + +class CertificationModel(ComponentModel): + """ + The model of Certification component + """ + + def __init__(self, parent, app, account, community): + super().__init__(parent) + self.app = app + self.account = account + self.community = community + if self.community is None: + self.community = self.account.communities[0] + + def contact_name_pubkey(self, name): + """ + Get the pubkey of a contact from its name + :param str name: + :return: + :rtype: str + """ + for contact in self.account.contacts: + if contact['name'] == name: + return contact['pubkey'] + + def change_community(self, index): + """ + Change current community + :param int index: index of the community in the account list + """ + self.community = self.account.communities[index] + + async def get_cert_stock(self): + """ + + :return: the certifications stock + :rtype: int + """ + params = await self.community.parameters() + return params['sigStock'] + + async def remaining_time(self): + """ + Get remaining time as a tuple to display + :return: a tuple containing (days, hours, minutes, seconds) + :rtype: tuple[int] + """ + account_identity = await self.account.identity(self.community) + remaining_time = await account_identity.cert_issuance_delay(self.app.identities_registry, self.community) + + days, remainder = divmod(remaining_time, 3600 * 24) + hours, remainder = divmod(remainder, 3600) + minutes, seconds = divmod(remainder, 60) + return days, hours, minutes, seconds + + async def nb_certifications(self): + """ + Get + :return: a tuple containing (written valid certifications, pending certifications) + :rtype: tuple[int] + """ + account_identity = await self.account.identity(self.community) + certifications = await account_identity.unique_valid_certified_by(self.app.identities_registry, self.community) + nb_certifications = len([c for c in certifications if c['block_number']]) + nb_cert_pending = len([c for c in certifications if not c['block_number']]) + return nb_certifications, nb_cert_pending + + async def could_certify(self): + """ + Check if the user could theorically certify + :return: true if the user can certifiy + :rtype: bool + """ + account_identity = await self.account.identity(self.community) + is_member = await account_identity.is_member(self.community) + try: + block_0 = await self.community.get_block(0) + except errors.DuniterError as e: + if e.ucode == errors.BLOCK_NOT_FOUND: + block_0 = None + except NoPeerAvailable as e: + logging.debug(str(e)) + block_0 = None + return is_member or not block_0 \ No newline at end of file diff --git a/src/sakia/gui/certification/view.py b/src/sakia/gui/certification/view.py new file mode 100644 index 0000000000000000000000000000000000000000..3afba6ff21a7368ba48151ff8e0c5a284034ca33 --- /dev/null +++ b/src/sakia/gui/certification/view.py @@ -0,0 +1,153 @@ +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox +from PyQt5.QtCore import QT_TRANSLATE_NOOP +from .certification_uic import Ui_CertificationDialog +from ..widgets import toast +from ..widgets.dialogs import QAsyncMessageBox +from enum import Enum + + +class CertificationView(QDialog, Ui_CertificationDialog): + """ + The view of the certification component + """ + + class ButtonBoxState(Enum): + NO_MORE_CERTIFICATION = 0 + NOT_A_MEMBER = 1 + REMAINING_TIME_BEFORE_VALIDATION = 2 + OK = 3 + + class RecipientMode(Enum): + CONTACT = 0 + PUBKEY = 1 + SEARCH = 2 + + _button_box_values = { + ButtonBoxState.NO_MORE_CERTIFICATION: (False, + QT_TRANSLATE_NOOP("CertificationView", "No more certifications")), + ButtonBoxState.NOT_A_MEMBER: (False, QT_TRANSLATE_NOOP("CertificationView", "Not a member")), + ButtonBoxState.REMAINING_TIME_BEFORE_VALIDATION: (True, + QT_TRANSLATE_NOOP("CertificationView", + "&Ok (Not validated before {remaining})")), + ButtonBoxState.OK: (True, QT_TRANSLATE_NOOP("CertificationView", "&Ok")) + } + + def __init__(self, parent, search_user_view, communities_names, contacts_names): + """ + Constructor + """ + super().__init__(parent) + self.setupUi(self) + + self.radio_contact.toggled.connect(lambda c, radio=CertificationView.RecipientMode.CONTACT: self.recipient_mode_changed(radio)) + self.radio_pubkey.toggled.connect(lambda c, radio=CertificationView.RecipientMode.PUBKEY: self.recipient_mode_changed(radio)) + self.radio_search.toggled.connect(lambda c, radio=CertificationView.RecipientMode.SEARCH: self.recipient_mode_changed(radio)) + + for name in communities_names: + self.combo_community.addItem(name) + + for name in sorted(contacts_names): + self.combo_contact.addItem(name) + + if len(contacts_names) == 0: + self.radio_pubkey.setChecked(True) + self.radio_contact.setEnabled(False) + + self.search_user = search_user_view + + #self.combo_contact.currentIndexChanged.connect(self.refresh_member) + #self.edit_pubkey.textChanged.connect(self.refresh_member) + #self.search_user.identity_selected.connect(self.refresh_member) + #self.radio_contact.toggled.connect(self.refresh_member) + #self.radio_search.toggled.connect(self.refresh_member) + #self.radio_pubkey.toggled.connect(self.refresh_member) + + def set_search_user(self, search_user_view): + """ + + :param sakia.gui.search_user.view.SearchUserView search_user_view: + :return: + """ + self.search_user = search_user_view + self.layout_mode_search.addWidget(search_user_view) + self.search_user.button_reset.hide() + + def set_member_widget(self, member_widget): + self.horizontalLayout_5.addWidget(member_widget) + + def recipient_mode(self): + if self.radio_contact.isChecked(): + return CertificationView.RecipientMode.CONTACT + elif self.radio_search.isChecked(): + return CertificationView.RecipientMode.SEARCH + else: + return CertificationView.RecipientMode.PUBKEY + + def selected_contact(self): + return self.combo_contact.currentText() + + def pubkey_value(self): + return self.edit_pubkey.text() + + async def show_success(self): + if self.app.preferences['notifications']: + toast.display(self.tr("Certification"), + self.tr("Success sending certification")) + else: + await QAsyncMessageBox.information(self.widget, self.tr("Certification"), + self.tr("Success sending certification")) + + async def show_error(self, error_txt): + + if self.app.preferences['notifications']: + toast.display(self.tr("Certification"), self.tr("Could not broadcast certification : {0}" + .format(error_txt))) + else: + await QAsyncMessageBox.critical(self.widget, self.tr("Certification"), + self.tr("Could not broadcast certification : {0}" + .format(error_txt))) + + def display_cert_stock(self, written, pending, stock, + remaining_days, remaining_hours, remaining_minutes): + """ + Display values in informations label + :param int written: number of written certifications + :param int pending: number of pending certifications + :param int stock: maximum certifications + :param int remaining_days: + :param int remaining_hours: + :param int remaining_minutes: + """ + cert_text = self.tr("Certifications sent : {nb_certifications}/{stock}").format( + nb_certifications=written, + stock=stock) + if pending > 0: + cert_text += " (+{nb_cert_pending} certifications pending)".format(nb_cert_pending=pending) + + if remaining_days > 0: + remaining_localized = self.tr("{days} days").format(days=remaining_days) + else: + remaining_localized = self.tr("{hours} hours and {min} min.").format(hours=remaining_hours, + min=remaining_minutes) + cert_text += self.tr("Remaining time before next certification validation : {0}".format(remaining_localized)) + self.label_cert_stock.setText(cert_text) + + def set_button_box(self, state, **kwargs): + """ + Set button box state + :param sakia.gui.certification.view.CertificationView.ButtonBoxState state: the state of te button box + :param dict kwargs: the values to replace from the text in the state + :return: + """ + button_box_state = CertificationView._button_box_values[state] + self.button_box.button(QDialogButtonBox.Ok).setEnabled(button_box_state[0]) + self.button_box.button(QDialogButtonBox.Ok).setText(button_box_state[1].format(**kwargs)) + + def recipient_mode_changed(self, radio): + """ + :param str radio: + """ + self.edit_pubkey.setEnabled(radio == CertificationView.RecipientMode.PUBKEY) + self.combo_contact.setEnabled(radio == CertificationView.RecipientMode.CONTACT) + self.search_user.setEnabled(radio == CertificationView.RecipientMode.SEARCH) + diff --git a/src/sakia/gui/homescreen/controller.py b/src/sakia/gui/homescreen/controller.py index 7f1dfdd9b2d25ed03de0cb4ec66b3ad6acc488ce..3a63a3a989f40ab65a51a310f051dc448536b8c0 100644 --- a/src/sakia/gui/homescreen/controller.py +++ b/src/sakia/gui/homescreen/controller.py @@ -21,9 +21,10 @@ class HomeScreenController(ComponentController): def create(cls, parent, app, **kwargs): """ Instanciate a homescreen component - :param sakia.gui.agent.controller.AgentController parent: + :param sakia.gui.component.controller.ComponentController parent: + :param sakia.core.Application app: :return: a new Homescreen controller - :rtype: NavigationController + :rtype: HomeScreenController """ view = HomeScreenView(parent.view) model = HomeScreenModel(None, app) diff --git a/src/sakia/gui/homescreen/model.py b/src/sakia/gui/homescreen/model.py index c306fb2b2a860edb18d955c2f017f2212a32bc68..66db3c5cf7d1a234625d5a942824c9aa54fb5b30 100644 --- a/src/sakia/gui/homescreen/model.py +++ b/src/sakia/gui/homescreen/model.py @@ -3,7 +3,7 @@ from sakia.gui.component.model import ComponentModel class HomeScreenModel(ComponentModel): """ - The model of Navigation component + The model of HomeScreen component """ def __init__(self, parent, app): diff --git a/src/sakia/gui/homescreen/view.py b/src/sakia/gui/homescreen/view.py index dd4e7040a527151254a7dae2f1bc6ad583425967..5f8f92acc6c2f6d4b42f14382356b5ce090a2241 100644 --- a/src/sakia/gui/homescreen/view.py +++ b/src/sakia/gui/homescreen/view.py @@ -7,10 +7,9 @@ class HomeScreenView(QWidget, Ui_HomescreenWidget): classdocs """ - def __init__(self, app): + def __init__(self, parent): """ Constructor """ - super().__init__() + super().__init__(parent) self.setupUi(self) - self.app = app diff --git a/src/sakia/gui/identities/view.py b/src/sakia/gui/identities/view.py index cbb4ce5b425bb4297a815115191b348788c56e71..24e6bb955d2f0a5a01838d65a5ae8f7328f74163 100644 --- a/src/sakia/gui/identities/view.py +++ b/src/sakia/gui/identities/view.py @@ -12,8 +12,8 @@ class IdentitiesView(QWidget, Ui_IdentitiesWidget): search_by_text_requested = pyqtSignal(str) search_directly_connected_requested = pyqtSignal() - _direct_connections_text = QT_TRANSLATE_NOOP("IdentitiesTabWidget", "Search direct certifications") - _search_placeholder = QT_TRANSLATE_NOOP("IdentitiesTabWidget", "Research a pubkey, an uid...") + _direct_connections_text = QT_TRANSLATE_NOOP("IdentitiesView", "Search direct certifications") + _search_placeholder = QT_TRANSLATE_NOOP("IdentitiesView", "Research a pubkey, an uid...") def __init__(self, parent): super().__init__(parent) diff --git a/src/sakia/gui/main_window/controller.py b/src/sakia/gui/main_window/controller.py index b4735244dcc8b5842dd819f83e42f196291b4ca4..bdde74da84c7f5465b4b5d65acabff7349e1ddfa 100644 --- a/src/sakia/gui/main_window/controller.py +++ b/src/sakia/gui/main_window/controller.py @@ -61,11 +61,15 @@ class MainWindowController(ComponentController): main_window.status_bar = main_window.attach(StatusBarController.create(main_window, app)) view.setStatusBar(main_window.status_bar._view) - main_window.toolbar = main_window.attach(ToolbarController.create(main_window, password_asker)) - view.top_layout.addWidget(main_window.toolbar._view) - main_window.navigation = main_window.attach(NavigationController.create(main_window, app)) view.bottom_layout.insertWidget(0, main_window.navigation._view) + main_window.navigation.community_changed.connect(main_window.handle_community_change) + main_window.navigation.account_changed.connect(main_window.handle_community_change) + + main_window.toolbar = main_window.attach(ToolbarController.create(main_window, app, + app.current_account, None, + password_asker)) + view.top_layout.addWidget(main_window.toolbar._view) #app.version_requested.connect(main_window.latest_version_requested) #app.account_imported.connect(main_window.import_account_accepted) @@ -107,6 +111,20 @@ class MainWindowController(ComponentController): version_info=version_info, version_url=version_url)) + def handle_account_change(self, account): + """ + Set current account + :param sakia.core.Account account: + """ + self.toolbar.set_account(account) + + def handle_community_change(self, community): + """ + Set current community + :param sakia.core.Community community: + """ + self.toolbar.set_community(community) + def refresh(self): """ Refresh main window diff --git a/src/sakia/gui/navigation/controller.py b/src/sakia/gui/navigation/controller.py index ac01f3bbd9ec9506dc2ed642d02468cb3aa63724..568f049a753c62d1f4eda62c9aa4713404f5b4a2 100644 --- a/src/sakia/gui/navigation/controller.py +++ b/src/sakia/gui/navigation/controller.py @@ -8,12 +8,16 @@ from ..identities.controller import IdentitiesController from ..informations.controller import InformationsController from ..graphs.wot.controller import WotController from ..graphs.explorer.controller import ExplorerController +from sakia.core import Account, Community +from PyQt5.QtCore import pyqtSignal class NavigationController(ComponentController): """ The navigation panel """ + community_changed = pyqtSignal(Community) + account_changed = pyqtSignal(Account) def __init__(self, parent, view, model): """ @@ -32,6 +36,7 @@ class NavigationController(ComponentController): 'Wot': WotController, 'Explorer': ExplorerController } + self.view.current_view_changed.connect(self.handle_view_change) @classmethod def create(cls, parent, app): @@ -74,3 +79,16 @@ class NavigationController(ComponentController): self.view.set_model(self.model) + def handle_view_change(self, raw_data): + """ + Handle view change + :param dict raw_data: + :return: + """ + account = raw_data.get('account', None) + community = raw_data.get('community', None) + if account != self.model.current_data('account'): + self.account_changed.emit(account) + if community != self.model.current_data('community'): + self.community_changed.emit(community) + self.model.set_current_data(raw_data) diff --git a/src/sakia/gui/navigation/model.py b/src/sakia/gui/navigation/model.py index d487b5c129a154ac62c1b70e294e79978b079849..8a7f49ee67ce95416a308c9eaa0e830153270181 100644 --- a/src/sakia/gui/navigation/model.py +++ b/src/sakia/gui/navigation/model.py @@ -18,17 +18,20 @@ class NavigationModel(ComponentModel): super().__init__(parent) self.app = app self.navigation = {} + self._current_data = None def init_navigation_data(self): self.navigation = [ { 'node': { 'title': self.app.current_account.name, - 'component': "HomeScreen" + 'component': "HomeScreen", + 'account': self.app.current_account }, 'children': [] } ] + self._current_data = self.navigation[0] for c in self.app.current_account.communities: self.navigation[0]['children'].append({ 'node': { @@ -89,3 +92,9 @@ class NavigationModel(ComponentModel): def generic_tree(self): return GenericTreeModel.create("Navigation", self.navigation) + + def set_current_data(self, raw_data): + self._current_data = raw_data + + def current_data(self, key): + return self._current_data.get(key, None) \ No newline at end of file diff --git a/src/sakia/gui/navigation/view.py b/src/sakia/gui/navigation/view.py index 178e1dabb484e87b76125926f570c84d11d7feb1..4518017f0b25ef779e55641a3b9d3b2762b6fe1a 100644 --- a/src/sakia/gui/navigation/view.py +++ b/src/sakia/gui/navigation/view.py @@ -1,5 +1,5 @@ from PyQt5.QtWidgets import QFrame, QSizePolicy -from PyQt5.QtCore import QModelIndex +from PyQt5.QtCore import pyqtSignal from .navigation_uic import Ui_Navigation from ...models.generic_tree import GenericTreeModel @@ -8,6 +8,7 @@ class NavigationView(QFrame, Ui_Navigation): """ The view of navigation panel """ + current_view_changed = pyqtSignal(dict) def __init__(self, parent): super().__init__(parent) @@ -40,4 +41,5 @@ class NavigationView(QFrame, Ui_Navigation): if 'widget' in raw_data: widget = raw_data['widget'] if self.stacked_widget.indexOf(widget): - self.stacked_widget.setCurrentWidget(widget) \ No newline at end of file + self.stacked_widget.setCurrentWidget(widget) + self.current_view_changed.emit(raw_data) diff --git a/src/sakia/gui/search_user/controller.py b/src/sakia/gui/search_user/controller.py index 8b7cde236406e65a4589c514bd827deb6de819ab..ba26cc77be7f6d8a77443c20338d8c3dfb6e49a8 100644 --- a/src/sakia/gui/search_user/controller.py +++ b/src/sakia/gui/search_user/controller.py @@ -60,4 +60,12 @@ class SearchUserController(ComponentController): Select node in graph when item is selected in combobox """ self.model.select_identity(index) - self.identity_selected.emit(self.model.identity()) \ No newline at end of file + self.identity_selected.emit(self.model.identity()) + + def set_community(self, community): + """ + Set community + :param sakia.core.Community community: + :return: + """ + self.model.community = community diff --git a/src/sakia/gui/toolbar/controller.py b/src/sakia/gui/toolbar/controller.py index 6e2ebd9f6544ad06b469e53c5dd4d2fe9e132d5d..b9622b26b719ce238c9ebeba69fcbf0c6462dd5a 100644 --- a/src/sakia/gui/toolbar/controller.py +++ b/src/sakia/gui/toolbar/controller.py @@ -6,6 +6,7 @@ from .view import ToolbarView from ...tools.decorators import asyncify, once_at_a_time, cancel_once_task from ..widgets.dialogs import QAsyncMessageBox, QAsyncFileDialog, dialog_async_exec from ..widgets import toast +from ..certification.controller import CertificationController import logging @@ -16,7 +17,7 @@ class ToolbarController(ComponentController): def __init__(self, parent, view, model, password_asker): """ - :param sakia.gui.agent.controller.AgentController parent: the parent + :param sakia.gui.component.controller.ComponentController parent: the parent :param sakia.gui.toolbar.view.ToolbarView view: :param sakia.gui.toolbar.model.ToolbarModel model: """ @@ -30,7 +31,7 @@ class ToolbarController(ComponentController): self.view.button_membership.clicked.connect(self.send_membership_demand) @classmethod - def create(cls, parent, password_asker): + def create(cls, parent, app, account, community, password_asker): """ Instanciate a navigation component :param sakia.gui.agent.controller.AgentController parent: @@ -38,7 +39,7 @@ class ToolbarController(ComponentController): :rtype: ToolbarController """ view = ToolbarView(parent.view) - model = ToolbarModel(None) + model = ToolbarModel(None, app, account, community) toolbar = cls(parent, view, model, password_asker) model.setParent(toolbar) return toolbar @@ -173,10 +174,24 @@ The process to join back the community later will have to be done again.""") self.button_certification.setEnabled(False) self.action_publish_uid.setEnabled(False) + def set_account(self, account): + """ + Set current account + :param sakia.core.Account account: + """ + self.model.account = account + + def set_community(self, community): + """ + Set current community + :param sakia.core.Community community: + """ + self.model.community = community + def open_certification_dialog(self): - CertificationDialog.open_dialog(self.app, - self.account, - self.community_view.community, + CertificationController.open_dialog(self, self.model.app, + self.model.account, + self.model.community, self.password_asker) def open_revocation_dialog(self): diff --git a/src/sakia/gui/toolbar/model.py b/src/sakia/gui/toolbar/model.py index 6d8d4ce41e719462c5cc561d52632b3ca093fdd4..68a011601e8983feca9a374be0fca00e7ab81715 100644 --- a/src/sakia/gui/toolbar/model.py +++ b/src/sakia/gui/toolbar/model.py @@ -6,5 +6,8 @@ class ToolbarModel(ComponentModel): The model of Navigation component """ - def __init__(self, parent): + def __init__(self, parent, app, account, community): super().__init__(parent) + self.app = app + self.account = account + self.community = community diff --git a/src/sakia/gui/widgets/context_menu.py b/src/sakia/gui/widgets/context_menu.py index 60e7b4a14ca899b66231c30894fe693978ee6b76..5124c826abd2e433a1ce5016e8a3541e87e7884f 100644 --- a/src/sakia/gui/widgets/context_menu.py +++ b/src/sakia/gui/widgets/context_menu.py @@ -6,7 +6,7 @@ import logging from ..member import MemberDialog from ..contact import ConfigureContactDialog from ..transfer import TransferMoneyDialog -from ..certification import CertificationDialog +from ..certification.controller import CertificationController from ...tools.decorators import asyncify from ...core.transfer import Transfer, TransferState from ...core.registry import Identity @@ -152,7 +152,7 @@ class ContextMenu(QObject): @asyncify async def certify_identity(self, identity): - await CertificationDialog.certify_identity(self._app, self._account, self._password_asker, + await CertificationController.certify_identity(None, self._app, self._account, self._password_asker, self._community, identity) @asyncify