diff --git a/src/sakia/gui/community_tile.py b/src/sakia/gui/community_tile.py deleted file mode 100644 index 3e59dc2574943482645ad20661f03ba739790945..0000000000000000000000000000000000000000 --- a/src/sakia/gui/community_tile.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -@author: inso -""" - -import enum - -from PyQt5.QtWidgets import QFrame, QLabel, QVBoxLayout, QLayout -from PyQt5.QtCore import QSize, pyqtSignal, QTime -from duniterpy.documents.block import Block -from duniterpy.api import errors - -from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task -from ..tools.exceptions import NoPeerAvailable -from .widgets.busy import Busy - - -@enum.unique -class CommunityState(enum.Enum): - NOT_INIT = 0 - OFFLINE = 1 - READY = 2 - - -class CommunityTile(QFrame): - clicked = pyqtSignal() - _hover_stylesheet = """QFrame#CommunityTile { -border-radius: 5px; -background-color: palette(midlight); -} -""" - _pressed_stylesheet = """QFrame#CommunityTile { -border-radius: 5px; -background-color: palette(dark); -} -""" - _standard_stylesheet = """QFrame#CommunityTile { -border-radius: 5px; -background-color: palette(base); -} -""" - - def __init__(self, parent, app, community): - super().__init__(parent) - self.setObjectName("CommunityTile") - self.app = app - self.community = community - self.community.network.nodes_changed.connect(self.handle_nodes_change) - self.text_label = QLabel() - self.setLayout(QVBoxLayout()) - self.layout().setSizeConstraint(QLayout.SetFixedSize) - self.layout().addWidget(self.text_label) - self.setFrameShape(QFrame.StyledPanel) - self.setFrameShadow(QFrame.Raised) - self.setStyleSheet(CommunityTile._standard_stylesheet) - self.busy = Busy(self) - self.busy.hide() - self._state = CommunityState.NOT_INIT - self.refresh() - - def sizeHint(self): - return QSize(250, 250) - - def handle_nodes_change(self): - if len(self.community.network.online_nodes) > 0: - if self.community.network.current_blockUID.sha_hash == Block.Empty_Hash: - state = CommunityState.NOT_INIT - else: - state = CommunityState.READY - else: - state = CommunityState.OFFLINE - - if state != self._state: - self.refresh() - - def cancel_refresh(self): - cancel_once_task(self, self.refresh) - - @once_at_a_time - @asyncify - async def refresh(self): - self.busy.show() - self.setFixedSize(QSize(150, 150)) - try: - current_block = await self.community.get_block() - members_pubkeys = await self.community.members_pubkeys() - amount = await self.app.current_account.amount(self.community) - localized_amount = await self.app.current_account.current_ref.instance(amount, - self.community, self.app).localized(units=True, - international_system=self.app.preferences['international_system_of_units']) - if current_block['monetaryMass']: - localized_monetary_mass = await self.app.current_account.current_ref.instance(current_block['monetaryMass'], - self.community, self.app).diff_localized(units=True, - international_system=self.app.preferences['international_system_of_units']) - else: - localized_monetary_mass = "" - status = self.app.current_account.pubkey in members_pubkeys - account_identity = await self.app.current_account.identity(self.community) - - mstime_remaining_text = self.tr("Expired or never published") - outdistanced_text = self.tr("Outdistanced") - - requirements = await account_identity.requirements(self.community) - mstime_remaining = 0 - nb_certs = 0 - if requirements: - mstime_remaining = requirements['membershipExpiresIn'] - nb_certs = len(requirements['certifications']) - if not requirements['outdistanced']: - outdistanced_text = self.tr("In WoT range") - - if mstime_remaining > 0: - days, remainder = divmod(mstime_remaining, 3600*24) - hours, remainder = divmod(remainder, 3600) - minutes, seconds = divmod(remainder, 60) - mstime_remaining_text = self.tr("Expires in ") - if days > 0: - mstime_remaining_text += "{days} days".format(days=days) - else: - mstime_remaining_text += "{hours} hours and {min} min.".format(hours=hours, - min=minutes) - - status_value = self.tr("Member") if status else self.tr("Non-Member") - status_color = '#00AA00' if status else self.tr('#FF0000') - description = """<html> - <body> - <p> - <span style=" font-size:16pt; font-weight:600;">{currency}</span> - </p> - <p>{nb_members} {members_label}</p> - <p><span style="font-weight:600;">{monetary_mass_label}</span> : {monetary_mass}</p> - <p><span style="font-weight:600;">{status_label}</span> : <span style="color:{status_color};">{status}</span></p> - <p><span style="font-weight:600;">{nb_certs_label}</span> : {nb_certs} ({outdistanced_text})</p> - <p><span style="font-weight:600;">{mstime_remaining_label}</span> : {mstime_remaining}</p> - <p><span style="font-weight:600;">{balance_label}</span> : {balance}</p> - </body> - </html>""".format(currency=self.community.currency, - nb_members=len(members_pubkeys), - members_label=self.tr("members"), - monetary_mass_label=self.tr("Monetary mass"), - monetary_mass=localized_monetary_mass, - status_color=status_color, - status_label=self.tr("Status"), - status=status_value, - nb_certs_label=self.tr("Certs. received"), - nb_certs=nb_certs, - outdistanced_text=outdistanced_text, - mstime_remaining_label=self.tr("Membership"), - mstime_remaining=mstime_remaining_text, - balance_label=self.tr("Balance"), - balance=localized_amount) - self.text_label.setText(description) - self._state = CommunityState.READY - except NoPeerAvailable: - description = """<html> - <body> - <p> - <span style=" font-size:16pt; font-weight:600;">{currency}</span> - </p> - <p>{message}</p> - </body> - </html>""".format(currency=self.community.currency, - message=self.tr("Not connected")) - self.text_label.setText(description) - self._state = CommunityState.OFFLINE - except errors.DuniterError as e: - if e.ucode == errors.BLOCK_NOT_FOUND: - description = """<html> - <body> - <p> - <span style=" font-size:16pt; font-weight:600;">{currency}</span> - </p> - <p>{message}</p> - </body> - </html>""".format(currency=self.community.currency, - message=self.tr("Community not initialized")) - self.text_label.setText(description) - self._state = CommunityState.NOT_INIT - else: - raise - - self.busy.hide() - - def mousePressEvent(self, event): - self.grabMouse() - self.setStyleSheet(CommunityTile._pressed_stylesheet) - return super().mousePressEvent(event) - - def mouseReleaseEvent(self, event): - self.releaseMouse() - self.setStyleSheet(CommunityTile._hover_stylesheet) - self.clicked.emit() - return super().mouseReleaseEvent(event) - - def resizeEvent(self, event): - self.busy.resize(event.size()) - super().resizeEvent(event) - - def enterEvent(self, event): - self.setStyleSheet(CommunityTile._hover_stylesheet) - return super().enterEvent(event) - - def leaveEvent(self, event): - self.setStyleSheet(CommunityTile._standard_stylesheet) - return super().leaveEvent(event) diff --git a/src/sakia/gui/informations/controller.py b/src/sakia/gui/informations/controller.py index df4eab8540612aba48e4340818e864e437785884..ee56073afb16ab91303ad38831b3b44a74f4b1b6 100644 --- a/src/sakia/gui/informations/controller.py +++ b/src/sakia/gui/informations/controller.py @@ -3,8 +3,10 @@ from .model import InformationsModel from .view import InformationsView from sakia.tools.decorators import asyncify from sakia.tools.exceptions import NoPeerAvailable +from duniterpy.api import errors import logging + class InformationsController(ComponentController): """ The informations component @@ -67,6 +69,19 @@ class InformationsController(ComponentController): Refresh localized data in view """ localized_data = await self.model.get_localized_data() - if localized_data: - self.view.set_general_text(localized_data) - self.view.set_rules_text(localized_data) \ No newline at end of file + try: + simple_data = await self.model.get_identity_data() + all_data = {**simple_data, **localized_data} + self.view.set_simple_informations(all_data, InformationsView.CommunityState.READY) + except NoPeerAvailable as e: + logging.debug(str(e)) + self.view.set_simple_informations(all_data, InformationsView.CommunityState.OFFLINE) + except errors.DuniterError as e: + if e.ucode == errors.BLOCK_NOT_FOUND: + self.view.set_simple_informations(all_data, InformationsView.CommunityState.NOT_INIT) + else: + raise + + self.view.set_general_text(localized_data) + self.view.set_rules_text(localized_data) + diff --git a/src/sakia/gui/informations/informations.ui b/src/sakia/gui/informations/informations.ui index 2396590f181c5c9de572f947fa6992c69132bc80..920da0a0356356bd02d576bc429b1841c01ac67e 100644 --- a/src/sakia/gui/informations/informations.ui +++ b/src/sakia/gui/informations/informations.ui @@ -13,11 +13,8 @@ <property name="windowTitle"> <string>Form</string> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QScrollArea" name="scrollArea"> - <property name="styleSheet"> - <string notr="true">QGroupBox { + <property name="styleSheet"> + <string notr="true">QGroupBox { border: 1px solid gray; border-radius: 9px; margin-top: 0.5em; @@ -29,6 +26,12 @@ QGroupBox::title { padding: 0 3px 0 3px; font-weight: bold; }</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="0"> + <widget class="QScrollArea" name="scrollarea"> + <property name="styleSheet"> + <string notr="true"/> </property> <property name="widgetResizable"> <bool>true</bool> @@ -39,7 +42,7 @@ QGroupBox::title { <x>0</x> <y>0</y> <width>522</width> - <height>721</height> + <height>308</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout_5"> @@ -148,6 +151,65 @@ QGroupBox::title { </widget> </widget> </item> + <item row="1" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="topMargin"> + <number>6</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <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="QPushButton" name="button_details"> + <property name="text"> + <string>Details</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="group_uid_state"> + <property name="title"> + <string>UID</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_simple"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> </layout> </widget> <resources> diff --git a/src/sakia/gui/informations/model.py b/src/sakia/gui/informations/model.py index db410380e801ac15012da289aa44436f8f6d8c06..94b930fd93b44859097f9b5d19fbba25446bb8aa 100644 --- a/src/sakia/gui/informations/model.py +++ b/src/sakia/gui/informations/model.py @@ -43,12 +43,11 @@ class InformationsModel(ComponentModel): block_ud = await self.community.get_ud_block() except NoPeerAvailable as e: logging.debug('community get_ud_block error : ' + str(e)) - return None + try: block_ud_minus_1 = await self.community.get_ud_block(x=1) except NoPeerAvailable as e: logging.debug('community get_ud_block error : ' + str(e)) - return None localized_data['units'] = self.account.current_ref.instance(0, self.community, self.app, None).units localized_data['diff_units'] = self.account.current_ref.instance(0, self.community, self.app, None).diff_units @@ -105,17 +104,45 @@ class InformationsModel(ComponentModel): QDateTime.fromTime_t(block_ud_minus_1['medianTime']), QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat) ) + return localized_data + + async def get_identity_data(self): + amount = await self.app.current_account.amount(self.community) + localized_amount = await self.app.current_account.current_ref.instance(amount, + self.community, self.app).localized( + units=True, + international_system=self.app.preferences['international_system_of_units']) + account_identity = await self.app.current_account.identity(self.community) + + mstime_remaining_text = self.tr("Expired or never published") + outdistanced_text = self.tr("Outdistanced") + + requirements = await account_identity.requirements(self.community) + mstime_remaining = 0 + nb_certs = 0 + if requirements: + mstime_remaining = requirements['membershipExpiresIn'] + nb_certs = len(requirements['certifications']) + if not requirements['outdistanced']: + outdistanced_text = self.tr("In WoT range") + + if mstime_remaining > 0: + days, remainder = divmod(mstime_remaining, 3600 * 24) + hours, remainder = divmod(remainder, 3600) + minutes, seconds = divmod(remainder, 60) + mstime_remaining_text = self.tr("Expires in ") + if days > 0: + mstime_remaining_text += "{days} days".format(days=days) else: - localized_data['mass_minus_1_per_member'] = QLocale().toString( - float(0), 'f', self.app.preferences['digits_after_comma'] - ) - localized_data['mass_minus_1'] = QLocale().toString( - float(0), 'f', self.app.preferences['digits_after_comma'] - ) - localized_data['actual_growth'] = float(0) - localized_data['ud_median_time_minus_1'] = "####" - return localized_data - return None + mstime_remaining_text += "{hours} hours and {min} min.".format(hours=hours, + min=minutes) + return { + 'amount': localized_amount, + 'outdistanced': outdistanced_text, + 'nb_certs': nb_certs, + 'mstime': mstime_remaining_text, + 'membership_state': mstime_remaining > 0 + } async def parameters(self): """ diff --git a/src/sakia/gui/informations/view.py b/src/sakia/gui/informations/view.py index f3989e23ce6896ea5aeacff76860f78ca56897f2..c75957718437ea851fa8b955f5beb4c068e16ac9 100644 --- a/src/sakia/gui/informations/view.py +++ b/src/sakia/gui/informations/view.py @@ -1,6 +1,7 @@ from PyQt5.QtWidgets import QWidget from PyQt5.QtCore import QEvent from .informations_uic import Ui_InformationsWidget +from enum import Enum class InformationsView(QWidget, Ui_InformationsWidget): @@ -8,9 +9,65 @@ class InformationsView(QWidget, Ui_InformationsWidget): The view of navigation panel """ + class CommunityState(Enum): + NOT_INIT = 0 + OFFLINE = 1 + READY = 2 + def __init__(self, parent): super().__init__(parent) self.setupUi(self) + self.scrollarea.hide() + self.button_details.clicked.connect(self.handle_details_click) + + def handle_details_click(self): + if self.button_details.isChecked(): + self.scrollarea.show() + else: + self.scrollarea.hide() + + def set_simple_informations(self, data, state): + if state in (InformationsView.CommunityState.NOT_INIT, InformationsView.CommunityState.OFFLINE): + self.label_simple.setText("""<html> + <body> + <p> + <span style=" font-size:16pt; font-weight:600;">{currency}</span> + </p> + <p>{message}</p> + </body> + </html>""".format(currency=data['currency'], + message=InformationsView.simple_message[state])) + else: + status_value = self.tr("Member") if data['membership_state'] else self.tr("Non-Member") + status_color = '#00AA00' if data['membership_state'] else self.tr('#FF0000') + description = """<html> + <body> + <p> + <span style=" font-size:16pt; font-weight:600;">{currency}</span> + </p> + <p>{nb_members} {members_label}</p> + <p><span style="font-weight:600;">{monetary_mass_label}</span> : {monetary_mass}</p> + <p><span style="font-weight:600;">{status_label}</span> : <span style="color:{status_color};">{status}</span></p> + <p><span style="font-weight:600;">{nb_certs_label}</span> : {nb_certs} ({outdistanced_text})</p> + <p><span style="font-weight:600;">{mstime_remaining_label}</span> : {mstime_remaining}</p> + <p><span style="font-weight:600;">{balance_label}</span> : {balance}</p> + </body> + </html>""".format(currency=data['units'], + nb_members=data['members_count'], + members_label=self.tr("members"), + monetary_mass_label=self.tr("Monetary mass"), + monetary_mass=data['mass'], + status_color=status_color, + status_label=self.tr("Status"), + status=status_value, + nb_certs_label=self.tr("Certs. received"), + nb_certs=data['nb_certs'], + outdistanced_text=data['outdistanced'], + mstime_remaining_label=self.tr("Membership"), + mstime_remaining=data['mstime'], + balance_label=self.tr("Balance"), + balance=data['amount']) + self.label_simple.setText(description) def set_general_text_no_dividend(self): """ @@ -37,25 +94,25 @@ class InformationsView(QWidget, Ui_InformationsWidget): <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> </table> """).format( - localized_data['ud'], + localized_data.get('ud', '####'), self.tr('Universal Dividend UD(t) in'), localized_data['diff_units'], localized_data['mass_minus_1'], self.tr('Monetary Mass M(t-1) in'), localized_data['units'], - localized_data['members_count'], + localized_data.get('members_count', '####'), self.tr('Members N(t)'), - localized_data['mass_minus_1_per_member'], + localized_data.get('mass_minus_1_per_member', '####'), self.tr('Monetary Mass per member M(t-1)/N(t) in'), localized_data['diff_units'], - localized_data['actual_growth'], - localized_data['days_per_dividend'], + localized_data.get('actual_growth', 0), + localized_data.get('days_per_dividend', '####'), self.tr('Actual growth c = UD(t)/[M(t-1)/N(t)]'), - localized_data['ud_median_time_minus_1'], + localized_data.get('ud_median_time_minus_1', '####'), self.tr('Penultimate UD date and time (t-1)'), - localized_data['ud_median_time'], + localized_data.get('ud_median_time', '####'), self.tr('Last UD date and time (t)'), - localized_data['next_ud_median_time'], + localized_data.get('next_ud_median_time', '####'), self.tr('Next UD date and time (t+1)') ) ) @@ -86,13 +143,13 @@ class InformationsView(QWidget, Ui_InformationsWidget): self.tr('UD(t+1) = MAX { UD(t) ; c × M(t) / N(t+1) }'), self.tr('Universal Dividend (formula)'), self.tr('{:} = MAX {{ {:} {:} ; {:2.0%} × {:} {:} / {:} }}').format( - localized_data['ud_plus_1'], - localized_data['ud'], + localized_data.get('ud_plus_1', '####'), + localized_data.get('ud', '####'), localized_data['diff_units'], - localized_data['growth'], - localized_data['mass'], + localized_data.get('growth', '####'), + localized_data.get('mass', '####'), localized_data['diff_units'], - localized_data['members_count'] + localized_data.get('members_count', '####') ), self.tr('Universal Dividend (computed)') ) diff --git a/src/sakia/gui/toolbar/view.py b/src/sakia/gui/toolbar/view.py index c53cadb2185b8509ad4dd5247b3badd8ccc50ee2..e9f3be10e3f76f1cf1b509dc5d9a45dadf1ea7ed 100644 --- a/src/sakia/gui/toolbar/view.py +++ b/src/sakia/gui/toolbar/view.py @@ -8,8 +8,6 @@ class ToolbarView(QFrame, Ui_SakiaToolbar): """ The model of Navigation component """ - _action_showinfo_text = QT_TRANSLATE_NOOP("ToolbarView", "Show informations") - _action_explore_text = QT_TRANSLATE_NOOP("ToolbarView", "Explore the Web of Trust") _action_publish_uid_text = QT_TRANSLATE_NOOP("ToolbarView", "Publish UID") _action_revoke_uid_text = QT_TRANSLATE_NOOP("ToolbarView", "Revoke UID") @@ -22,20 +20,6 @@ class ToolbarView(QFrame, Ui_SakiaToolbar): self.action_publish_uid = QAction(self.tr(ToolbarView._action_publish_uid_text), self) self.action_revoke_uid = QAction(self.tr(ToolbarView._action_revoke_uid_text), self) - self.action_showinfo = QAction(self.tr(ToolbarView._action_showinfo_text), self) - self.action_explorer = QAction(self.tr(ToolbarView._action_explore_text), self) - - action_showinfo = QAction(self.tr("Show informations"), self.toolbutton_menu) - action_showinfo.triggered.connect(lambda: self.show_closable_tab(self.tab_informations, - QIcon(":/icons/informations_icon"), - self.tr("Informations"))) - tool_menu.addAction(action_showinfo) - - action_showexplorer = QAction(self.tr("Show explorer"), self.toolbutton_menu) - action_showexplorer.triggered.connect(lambda: self.show_closable_tab(self.tab_explorer.widget, - QIcon(":/icons/explorer_icon"), - self.tr("Explorer"))) - tool_menu.addAction(action_showexplorer) menu_advanced = QMenu(self.tr("Advanced"), self.toolbutton_menu) self.action_gen_revokation = QAction(self.tr("Save revokation document"), menu_advanced)