Skip to content
Snippets Groups Projects
wot_tab.py 14.4 KiB
Newer Older
# -*- coding: utf-8 -*-

import logging
import asyncio
from PyQt5.QtWidgets import QWidget, QComboBox, QDialog
from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime, pyqtSignal
from ucoinpy.api import bma
from ..tools.exceptions import MembershipNotFoundError
from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
from ..core.graph import Graph
from ..core.registry import BlockchainState
from .member import MemberDialog
from .certification import CertificationDialog
from .transfer import TransferMoneyDialog
from .contact import ConfigureContactDialog
inso's avatar
inso committed
from ..gen_resources.wot_tab_uic import Ui_WotTabWidget
from cutecoin.gui.views.wot import NODE_STATUS_HIGHLIGHTED, NODE_STATUS_SELECTED, NODE_STATUS_OUT
from cutecoin.gui.widgets.busy import Busy
inso's avatar
inso committed
class WotTabWidget(QWidget, Ui_WotTabWidget):

    money_sent = pyqtSignal()

    def __init__(self, app):
        :param cutecoin.core.app.Application app:   Application instance
        super().__init__()
        # construct from qtDesigner
        self.setupUi(self)

        # Default text when combo lineEdit is empty
        self.comboBoxSearch.lineEdit().setPlaceholderText(self.tr('Research a pubkey, an uid...'))
        self.comboBoxSearch.lineEdit().returnPressed.connect(self.search)
        # To fix a recall of the same item with different case,
        # the edited text is not added in the item list
        self.comboBoxSearch.setInsertPolicy(QComboBox.NoInsert)
        self.busy = Busy(self.graphicsView)
        self.busy.hide()

        # add scene events
inso's avatar
inso committed
        self.graphicsView.scene().node_clicked.connect(self.handle_node_click)
        self.graphicsView.scene().node_signed.connect(self.sign_node)
        self.graphicsView.scene().node_transaction.connect(self.send_money_to_node)
        self.graphicsView.scene().node_contact.connect(self.add_node_as_contact)
inso's avatar
inso committed
        self.graphicsView.scene().node_member.connect(self.identity_informations)
inso's avatar
inso committed
        self.graphicsView.scene().node_copy_pubkey.connect(self.copy_node_pubkey)
        self.account = None
        self.community = None
        self.password_asker = None
inso's avatar
inso committed
        self.app = app
        self.draw_task = None
        # nodes list for menu from search
        self.nodes = list()
        # create node metadata from account
        self._current_identity = None
    def cancel_once_tasks(self):
        cancel_once_task(self, self.draw_graph)
        cancel_once_task(self, self.refresh_informations_frame)
        cancel_once_task(self, self.reset)

    def change_account(self, account, password_asker):
        self.account = account
        self.password_asker = password_asker

    def change_community(self, community):
        self._auto_refresh(community)
        self.community = community
        self.reset()
    def _auto_refresh(self, new_community):
        if self.community:
            try:
                self.community.network.new_block_mined.disconnect(self.refresh)
inso's avatar
inso committed
            except TypeError as e:
                if "connected" in str(e):
                    logging.debug("new block mined not connected")
        if self.app.preferences["auto_refresh"]:
            if new_community:
                new_community.network.new_block_mined.connect(self.refresh)
            elif self.community:
                self.community.network.new_block_mined.connect(self.refresh)

    @once_at_a_time
    @asyncify
    @asyncio.coroutine
    def refresh_informations_frame(self):
        parameters = self.community.parameters
        try:
            identity = yield from self.account.identity(self.community)
            membership = identity.membership(self.community)
            renew_block = membership['blockNumber']
            last_renewal = self.community.get_block(renew_block)['medianTime']
            expiration = last_renewal + parameters['sigValidity']
        except MembershipNotFoundError:
            last_renewal = None
            expiration = None

        certified = yield from identity.unique_valid_certified_by(self.app.identities_registry, self.community)
        certifiers = yield from identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
        if last_renewal and expiration:
            date_renewal = QLocale.toString(
                QLocale(),
                QDateTime.fromTime_t(last_renewal).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
            )
            date_expiration = QLocale.toString(
                QLocale(),
                QDateTime.fromTime_t(expiration).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
            )

            if self.account.pubkey in self.community.members_pubkeys():
                # set infos in label
                self.label_general.setText(
                    self.tr("""
                    <table cellpadding="5">
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    </table>
                    """).format(
                        self.account.name, self.account.pubkey,
                        self.tr("Membership"),
                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
                        self.tr("Your web of trust"),
                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
                                                                                             len(certified))
                    )
                )
            else:
                # set infos in label
                self.label_general.setText(
                    self.tr("""
                    <table cellpadding="5">
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    </table>
                    """).format(
                        self.account.name, self.account.pubkey,
                        self.tr("Not a member"),
                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
                        self.tr("Your web of trust"),
                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
                                                                                             len(certified))
                    )
                )
        else:
            # set infos in label
            self.label_general.setText(
                self.tr("""
                <table cellpadding="5">
                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                <tr><td align="right"><b>{:}</b></td></tr>
                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                </table>
                """).format(
                    self.account.name, self.account.pubkey,
                    self.tr("Not a member"),
                    self.tr("Your web of trust"),
                    self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
                                                                                         len(certified))
                )
            )

inso's avatar
inso committed
    @pyqtSlot(dict)
    def handle_node_click(self, metadata):
        self.draw_graph(
            self.app.identities_registry.from_handled_data(
                metadata['text'],
                metadata['id'],
                BlockchainState.VALIDATED,
                self.community
inso's avatar
inso committed

    @once_at_a_time
    @asyncify
    @asyncio.coroutine
    def draw_graph(self, identity):
        Draw community graph centered on the identity
        :param cutecoin.core.registry.Identity identity: Graph node identity
        logging.debug("Draw graph - " + identity.uid)
        self.busy.show()
        if self.community:
            identity_account = yield from self.account.identity(self.community)

            #Connect new identity
            if self._current_identity != identity:
                self._current_identity = identity

            # create Identity from node metadata
            certifier_list = yield from identity.unique_valid_certifiers_of(self.app.identities_registry,
                                                                            self.community)
            certified_list = yield from identity.unique_valid_certified_by(self.app.identities_registry,
                                                                           self.community)

            # create empty graph instance
            graph = Graph(self.app, self.community)

            # add wallet node
            node_status = 0
            if identity == identity_account:
                node_status += NODE_STATUS_HIGHLIGHTED
            is_member = yield from identity.is_member(self.community)
            if is_member is False:
                node_status += NODE_STATUS_OUT
            node_status += NODE_STATUS_SELECTED
            graph.add_identity(identity, node_status)

            # populate graph with certifiers-of
            yield from graph.add_certifier_list(certifier_list, identity, identity_account)
            # populate graph with certified-by
            yield from graph.add_certified_list(certified_list, identity, identity_account)

            # draw graph in qt scene
            self.graphicsView.scene().update_wot(graph.get())

            # if selected member is not the account member...
            if identity.pubkey != identity_account.pubkey:
                # add path from selected member to account member
                path = yield from graph.get_shortest_path_between_members(identity, identity_account)
                if path:
                    self.graphicsView.scene().update_path(path)
        self.busy.hide()
    @once_at_a_time
    @asyncify
    @asyncio.coroutine
    def reset(self, checked=False):
        """
        Reset graph scene to wallet identity
        """
inso's avatar
inso committed
        if self.account and self.community:
            identity = yield from self.account.identity(self.community)
            self.draw_graph(identity)
inso's avatar
inso committed
    def refresh(self):
        """
        Refresh graph scene to current metadata
        """
        if self._current_identity:
            self.draw_graph(self._current_identity)
        else:
            self.reset()
inso's avatar
inso committed

inso's avatar
inso committed
    @asyncify
    @asyncio.coroutine
        """
        Search nodes when return is pressed in combobox lineEdit
        """
        text = self.comboBoxSearch.lineEdit().text()

        if len(text) < 2:
            return False
inso's avatar
inso committed
        response = yield from self.community.bma_access.future_request(bma.wot.Lookup, {'search': text})

        nodes = {}
        for identity in response['results']:
            nodes[identity['pubkey']] = identity['uids'][0]['uid']

        if nodes:
            self.nodes = list()
            self.comboBoxSearch.clear()
            self.comboBoxSearch.lineEdit().setText(text)
            for pubkey, uid in nodes.items():
                self.nodes.append({'pubkey': pubkey, 'uid': uid})
                self.comboBoxSearch.addItem(uid)
            self.comboBoxSearch.showPopup()

    def select_node(self, index):
        """
        Select node in graph when item is selected in combobox
        """
        if index < 0 or index >= len(self.nodes):
            return False
        node = self.nodes[index]
        metadata = {'id': node['pubkey'], 'text': node['uid']}
        self.draw_graph(
            self.app.identities_registry.from_handled_data(
                metadata['text'],
                metadata['id'],
                BlockchainState.VALIDATED,
                self.community
inso's avatar
inso committed
    def identity_informations(self, metadata):
        identity = self.app.identities_registry.from_handled_data(
            metadata['text'],
            metadata['id'],
            BlockchainState.VALIDATED,
            self.community
        dialog = MemberDialog(self.app, self.account, self.community, identity)
        dialog.exec_()
inso's avatar
inso committed
    @asyncify
    @asyncio.coroutine
    def sign_node(self, metadata):
        identity = self.app.identities_registry.from_handled_data(
            metadata['text'],
            metadata['id'],
            BlockchainState.VALIDATED,
            self.community
inso's avatar
inso committed
        yield from CertificationDialog.certify_identity(self.app, self.account, self.password_asker,
                                             self.community, identity)
inso's avatar
inso committed
    @asyncify
    @asyncio.coroutine
    def send_money_to_node(self, metadata):
        identity = self.app.identities_registry.from_handled_data(
            metadata['text'],
            metadata['id'],
            BlockchainState.VALIDATED,
            self.community
inso's avatar
inso committed
        result = yield from TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker,
                                                            self.community, identity)
        if result == QDialog.Accepted:
            self.money_sent.emit()
inso's avatar
inso committed
    def copy_node_pubkey(self, metadata):
        cb = self.app.qapp.clipboard()
        cb.clear(mode=cb.Clipboard)
        cb.setText(metadata['id'], mode=cb.Clipboard)

    def add_node_as_contact(self, metadata):
        # check if contact already exists...
inso's avatar
inso committed
        if metadata['id'] == self.account.pubkey \
                or metadata['id'] in [contact['pubkey'] for contact in self.account.contacts]:
        dialog = ConfigureContactDialog(self.account, self.window(), {'name': metadata['text'],
                                                                      'pubkey': metadata['id']})
        result = dialog.exec_()
        if result == QDialog.Accepted:
            self.window().refresh_contacts()
    def resizeEvent(self, event):
        self.busy.resize(event.size())
        super().resizeEvent(event)

    def changeEvent(self, event):
        """
        Intercepte LanguageChange event to translate UI
        :param QEvent QEvent: Event
        :return:
        """
        if event.type() == QEvent.LanguageChange:
            self.retranslateUi(self)
            self._auto_refresh(None)
            self.refresh()
        return super(WotTabWidget, self).changeEvent(event)