diff --git a/src/sakia/gui/navigation/txhistory/model.py b/src/sakia/gui/navigation/txhistory/model.py index 5fea05231bcb7a163092caf66bb5a3fff910df79..3b0cd432ca3a642823c76f9e5f7747b3d6cacacf 100644 --- a/src/sakia/gui/navigation/txhistory/model.py +++ b/src/sakia/gui/navigation/txhistory/model.py @@ -3,6 +3,7 @@ from .table_model import HistoryTableModel, TxFilterProxyModel from PyQt5.QtCore import Qt, QDateTime, QTime, pyqtSignal, QModelIndex from sakia.errors import NoPeerAvailable from duniterpy.api import errors +from sakia.data.entities import Identity import logging @@ -66,11 +67,15 @@ class TxHistoryModel(QObject): pubkey_col = self.table_model.sourceModel().columns_types.index('pubkey') pubkey_index = self.table_model.sourceModel().index(source_index.row(), pubkey_col) pubkeys = self.table_model.sourceModel().data(pubkey_index, Qt.DisplayRole) - identities = [] + identities_or_pubkeys = [] for pubkey in pubkeys: - identities.append(self.identities_service.get_identity(pubkey)) + identity = self.identities_service.get_identity(pubkey) + if identity: + identities_or_pubkeys.append(identity) + else: + identities_or_pubkeys.append(pubkey) transfer = self._model.transfers_data[source_index.row()][self._model.columns_types.index('raw_data')] - return True, identities, transfer + return True, identities_or_pubkeys, transfer return False, [], None def minimum_maximum_datetime(self): diff --git a/src/sakia/gui/navigation/txhistory/txhistory.ui b/src/sakia/gui/navigation/txhistory/txhistory.ui index 89e7d39b626c0f1684247ded52bbd5602760ba32..98d997ee6e1b20315aaaacb0d8c2ca28fbd97185 100644 --- a/src/sakia/gui/navigation/txhistory/txhistory.ui +++ b/src/sakia/gui/navigation/txhistory/txhistory.ui @@ -20,33 +20,62 @@ <string>Balance</string> </property> <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QLabel" name="label_balance"> - <property name="font"> - <font> - <pointsize>22</pointsize> - <weight>75</weight> - <bold>true</bold> - </font> - </property> - <property name="text"> - <string>loading...</string> - </property> - <property name="alignment"> - <set>Qt::AlignHCenter|Qt::AlignTop</set> - </property> - </widget> - </item> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <property name="topMargin"> <number>6</number> </property> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_balance"> + <property name="font"> + <font> + <pointsize>22</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>loading...</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter|Qt::AlignTop</set> + </property> + </widget> + </item> <item> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> diff --git a/src/sakia/gui/sub/transfer/controller.py b/src/sakia/gui/sub/transfer/controller.py index e1cc33d3c60f3e8c0f70ad926042b17498018bb3..8f76eae7f262518cf935e605722b184ed5ddfbdb 100644 --- a/src/sakia/gui/sub/transfer/controller.py +++ b/src/sakia/gui/sub/transfer/controller.py @@ -2,7 +2,7 @@ import asyncio import logging from PyQt5.QtCore import Qt, QObject, pyqtSignal -from PyQt5.QtWidgets import QApplication +from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout from sakia.data.processors import ConnectionsProcessor from sakia.decorators import asyncify @@ -10,6 +10,7 @@ from sakia.gui.sub.password_input import PasswordInputController from sakia.gui.sub.search_user.controller import SearchUserController from sakia.gui.sub.user_information.controller import UserInformationController from sakia.money import Quantitative +from sakia.gui.widgets.dialogs import dialog_async_exec from .model import TransferModel from .view import TransferView @@ -71,41 +72,73 @@ class TransferController(QObject): return transfer @classmethod - async def send_money_to_identity(cls, parent, app, connection, identity): - dialog = cls.create(parent, app) - dialog.view.groupbox_connection.show() - dialog.view.combo_connections.setCurrentText(connection.title()) - dialog.user_information.change_identity(identity) - dialog.view.edit_pubkey.setText(identity.pubkey) - dialog.view.radio_pubkey.setChecked(True) - - dialog.refresh() + def open_transfer_with_pubkey(cls, parent, app, connection, pubkey): + transfer = cls.create(parent, app) + transfer.view.groupbox_connection.show() + transfer.view.combo_connections.setCurrentText(connection.title()) + transfer.view.edit_pubkey.setText(pubkey) + transfer.view.radio_pubkey.setChecked(True) + + transfer.refresh() + return transfer @classmethod - def send_transfer_again(cls, parent, app, connection, resent_transfer): - dialog = cls.create(parent, app) - dialog.view.groupbox_connection.show() - dialog.view.label_total.show() - dialog.view.combo_connections.setCurrentText(connection.title()) - dialog.view.edit_pubkey.setText(resent_transfer.receivers[0]) - dialog.view.radio_pubkey.setChecked(True) + def send_money_to_pubkey(cls, parent, app, connection, pubkey): + dialog = QDialog(parent) + dialog.setWindowTitle(dialog.tr("Transfer")) + dialog.setLayout(QVBoxLayout(dialog)) + transfer = cls.open_transfer_with_pubkey(parent, app, connection, pubkey) - dialog.refresh() + dialog.layout().addWidget(transfer.view) + transfer.accepted.connect(dialog.accept) + transfer.rejected.connect(dialog.reject) + return dialog.exec() - current_base = dialog.model.current_base() + @classmethod + def send_money_to_identity(cls, parent, app, connection, identity): + dialog = QDialog(parent) + dialog.setWindowTitle(dialog.tr("Transfer")) + dialog.setLayout(QVBoxLayout(dialog)) + transfer = cls.open_transfer_with_pubkey(parent, app, connection, identity.pubkey) + + transfer.user_information.change_identity(identity) + dialog.layout().addWidget(transfer.view) + transfer.accepted.connect(dialog.accept) + transfer.rejected.connect(dialog.reject) + return dialog.exec() + + @classmethod + def send_transfer_again(cls, parent, app, connection, resent_transfer): + dialog = QDialog(parent) + dialog.setWindowTitle(dialog.tr("Transfer")) + dialog.setLayout(QVBoxLayout(dialog)) + transfer = cls.create(parent, app) + transfer.view.groupbox_connection.show() + transfer.view.label_total.show() + transfer.view.combo_connections.setCurrentText(connection.title()) + transfer.view.edit_pubkey.setText(resent_transfer.receivers[0]) + transfer.view.radio_pubkey.setChecked(True) + + transfer.refresh() + + current_base = transfer.model.current_base() current_base_amount = resent_transfer.amount / pow(10, resent_transfer.amount_base - current_base) - relative = dialog.model.quant_to_rel(current_base_amount / 100) - dialog.view.set_spinboxes_parameters(current_base_amount / 100, relative) - dialog.view.change_relative_amount(relative) - dialog.view.change_quantitative_amount(current_base_amount / 100) + relative = transfer.model.quant_to_rel(current_base_amount / 100) + transfer.view.set_spinboxes_parameters(current_base_amount / 100, relative) + transfer.view.change_relative_amount(relative) + transfer.view.change_quantitative_amount(current_base_amount / 100) connections_processor = ConnectionsProcessor.instanciate(app) wallet_index = connections_processor.connections().index(connection) - dialog.view.combo_connections.setCurrentIndex(wallet_index) - dialog.view.edit_pubkey.setText(resent_transfer.receivers[0]) - dialog.view.radio_pubkey.setChecked(True) - dialog.view.edit_message.setText(resent_transfer.comment) + transfer.view.combo_connections.setCurrentIndex(wallet_index) + transfer.view.edit_pubkey.setText(resent_transfer.receivers[0]) + transfer.view.radio_pubkey.setChecked(True) + transfer.view.edit_message.setText(resent_transfer.comment) + dialog.layout().addWidget(transfer.view) + transfer.accepted.connect(dialog.accept) + transfer.rejected.connect(dialog.reject) + return dialog.exec() def integrate_to_main_view(self, connection): self.view.combo_connections.setCurrentText(connection.title()) @@ -210,14 +243,3 @@ class TransferController(QObject): self.model.set_connection(index) self.password_input.set_connection(self.model.connection) 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/widgets/context_menu.py b/src/sakia/gui/widgets/context_menu.py index 460e1b804a3c836d556f76f54915b589686d1c52..fa260abb287a65db7aaa29cd38ddb99201903890 100644 --- a/src/sakia/gui/widgets/context_menu.py +++ b/src/sakia/gui/widgets/context_menu.py @@ -1,8 +1,9 @@ import logging - +import re from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtWidgets import QMenu, QAction, QApplication, QMessageBox +from duniterpy.documents.constants import pubkey_regex from sakia.data.entities import Identity, Transaction, Dividend from sakia.data.processors import BlockchainProcessor, TransactionsProcessor from sakia.decorators import asyncify @@ -32,25 +33,24 @@ class ContextMenu(QObject): :param ContextMenu menu: the qmenu to add actions to :param Identity identity: the identity """ - menu.qmenu.addSeparator().setText(identity.uid if identity.uid else "Pubkey") + menu.qmenu.addSeparator().setText(identity.uid if identity.uid else identity.pubkey[:7]) informations = QAction(menu.qmenu.tr("Informations"), menu.qmenu.parent()) informations.triggered.connect(lambda checked, i=identity: menu.informations(i)) menu.qmenu.addAction(informations) - if menu._connection.pubkey != identity.pubkey: - send_money = QAction(menu.qmenu.tr("Send money"), menu.qmenu.parent()) - send_money.triggered.connect(lambda checked, i=identity: menu.send_money(i)) - menu.qmenu.addAction(send_money) - if identity.uid and menu._connection.pubkey != identity.pubkey: certify = QAction(menu.tr("Certify identity"), menu.qmenu.parent()) certify.triggered.connect(lambda checked, i=identity: menu.certify_identity(i)) menu.qmenu.addAction(certify) - view_wot = QAction(menu.qmenu.tr("View in Web of Trust"), menu.qmenu.parent()) - view_wot.triggered.connect(lambda checked, i=identity: menu.view_wot(i)) - menu.qmenu.addAction(view_wot) + view_wot = QAction(menu.qmenu.tr("View in Web of Trust"), menu.qmenu.parent()) + view_wot.triggered.connect(lambda checked, i=identity: menu.view_wot(i)) + menu.qmenu.addAction(view_wot) + + send_money = QAction(menu.qmenu.tr("Send money"), menu.qmenu.parent()) + send_money.triggered.connect(lambda checked, i=identity: menu.send_money(i)) + menu.qmenu.addAction(send_money) copy_pubkey = QAction(menu.qmenu.tr("Copy pubkey to clipboard"), menu.qmenu.parent()) copy_pubkey.triggered.connect(lambda checked, i=identity: ContextMenu.copy_pubkey_to_clipboard(i)) @@ -88,6 +88,19 @@ class ContextMenu(QObject): menu.copy_block_to_clipboard(transfer.blockstamp.number)) menu.qmenu.addAction(copy_doc) + @staticmethod + def _add_string_actions(menu, str_value): + if re.match(pubkey_regex, str_value): + menu.qmenu.addSeparator().setText(str_value[:7]) + copy_pubkey = QAction(menu.qmenu.tr("Copy pubkey to clipboard"), menu.qmenu.parent()) + copy_pubkey.triggered.connect(lambda checked, p=str_value: ContextMenu.copy_pubkey_to_clipboard(p)) + menu.qmenu.addAction(copy_pubkey) + + if menu._connection.pubkey != str_value: + send_money = QAction(menu.qmenu.tr("Send money"), menu.qmenu.parent()) + send_money.triggered.connect(lambda checked, p=str_value: menu.send_money(p)) + menu.qmenu.addAction(send_money) + @classmethod def from_data(cls, parent, app, connection, data): """ @@ -105,6 +118,7 @@ class ContextMenu(QObject): Identity: ContextMenu._add_identity_actions, Transaction: ContextMenu._add_transfers_actions, Dividend: lambda m, d: None, + str: ContextMenu._add_string_actions, dict: lambda m, d: None, type(None): lambda m, d: None } @@ -114,9 +128,12 @@ class ContextMenu(QObject): return menu @staticmethod - def copy_pubkey_to_clipboard(identity): + def copy_pubkey_to_clipboard(identity_or_pubkey): clipboard = QApplication.clipboard() - clipboard.setText(identity.pubkey) + if isinstance(identity_or_pubkey, Identity): + clipboard.setText(identity_or_pubkey.pubkey) + else: + clipboard.setText(identity_or_pubkey) def informations(self, identity): if identity.uid: @@ -126,9 +143,11 @@ class ContextMenu(QObject): UserInformationController.search_and_show_pubkey(self.parent(), self._app, identity.pubkey) - @asyncify - async def send_money(self, identity): - await TransferController.send_money_to_identity(None, self._app, self._connection, identity) + def send_money(self, identity_or_pubkey): + if isinstance(identity_or_pubkey, Identity): + TransferController.send_money_to_identity(None, self._app, self._connection, identity_or_pubkey) + else: + TransferController.send_money_to_pubkey(None, self._app, self._connection, identity_or_pubkey) def view_wot(self, identity): self.view_identity_in_wot.emit(identity) diff --git a/src/sakia/services/identities.py b/src/sakia/services/identities.py index e4a2b4d801a91caecd92ffd6e7ba968614bfced4..e25fb87af8e5d3882bebd4183476bb8a05e6dd00 100644 --- a/src/sakia/services/identities.py +++ b/src/sakia/services/identities.py @@ -72,6 +72,9 @@ class IdentitiesService(QObject): for c in connections: identities.append(self._identities_processor.get_identity(self.currency, c.pubkey)) return identities + + def is_identity_of_connection(self, identity): + return identity.pubkey in self._connections_processor.pubkeys() async def load_memberships(self, identity): """ @@ -97,14 +100,14 @@ class IdentitiesService(QObject): identity.membership_written_on = ms["written"] identity = await self.load_requirements(identity) # We save connections pubkeys - if identity.pubkey in self._connections_processor.pubkeys(): - identity.written = True + identity.written = True + if self.is_identity_of_connection(identity): self._identities_processor.insert_or_update_identity(identity) except errors.DuniterError as e: logging.debug(str(e)) if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID): identity.written = False - if identity.pubkey in self._connections_processor.pubkeys(): + if self.is_identity_of_connection(identity): self._identities_processor.insert_or_update_identity(identity) except NoPeerAvailable as e: logging.debug(str(e)) @@ -136,7 +139,7 @@ class IdentitiesService(QObject): cert.block) certifiers.append(cert) # We save connections pubkeys - if identity.pubkey in self._connections_processor.pubkeys(): + if self.is_identity_of_connection(identity): self._certs_processor.insert_or_update_certification(cert) for signed_data in result["signed"]: cert = Certification(currency=self.currency, @@ -148,7 +151,7 @@ class IdentitiesService(QObject): if cert not in certified: certified.append(cert) # We save connections pubkeys - if identity.pubkey in self._connections_processor.pubkeys(): + if self.is_identity_of_connection(identity): cert.timestamp = await self._blockchain_processor.timestamp(self.currency, cert.block) self._certs_processor.insert_or_update_certification(cert) @@ -183,7 +186,7 @@ class IdentitiesService(QObject): self._certs_processor.insert_or_update_certification(cert) identity.written = True - if identity.pubkey in self._connections_processor.pubkeys(): + if self.is_identity_of_connection(identity): self._identities_processor.insert_or_update_identity(identity) except errors.DuniterError as e: if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID): @@ -219,7 +222,7 @@ class IdentitiesService(QObject): self._certs_processor.insert_or_update_certification(cert) identity.written = True - if identity.pubkey in self._connections_processor.pubkeys(): + if self.is_identity_of_connection(identity): self._identities_processor.insert_or_update_identity(identity) except errors.DuniterError as e: if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):