From e753511a8b1f4f61b441e21bde0be0bb82bd29b1 Mon Sep 17 00:00:00 2001
From: inso <insomniak.fr@gmaiL.com>
Date: Sun, 7 Aug 2016 13:27:56 +0200
Subject: [PATCH] Add informations view

---
 src/sakia/gui/component/controller.py         |  13 +-
 src/sakia/gui/homescreen/controller.py        |   7 +
 src/sakia/gui/identities/controller.py        |   8 +
 src/sakia/gui/identities/model.py             |   3 +
 src/sakia/gui/informations/__init__.py        |   0
 src/sakia/gui/informations/controller.py      |  70 ++++
 .../sakia/gui/informations/informations.ui    |  10 +-
 src/sakia/gui/informations/model.py           | 148 +++++++++
 src/sakia/gui/informations/view.py            | 220 ++++++++++++
 src/sakia/gui/informations_tab.py             | 312 ------------------
 src/sakia/gui/main_window/controller.py       |  21 +-
 src/sakia/gui/navigation/controller.py        |  12 +-
 src/sakia/gui/navigation/model.py             |   3 +
 src/sakia/gui/network/controller.py           |   8 +
 src/sakia/gui/status_bar/controller.py        |  10 +-
 src/sakia/gui/toolbar/controller.py           |   8 +
 src/sakia/gui/txhistory/controller.py         |   8 +
 17 files changed, 529 insertions(+), 332 deletions(-)
 create mode 100644 src/sakia/gui/informations/__init__.py
 create mode 100644 src/sakia/gui/informations/controller.py
 rename res/ui/informations_tab.ui => src/sakia/gui/informations/informations.ui (95%)
 create mode 100644 src/sakia/gui/informations/model.py
 create mode 100644 src/sakia/gui/informations/view.py
 delete mode 100644 src/sakia/gui/informations_tab.py

diff --git a/src/sakia/gui/component/controller.py b/src/sakia/gui/component/controller.py
index aa1726fa..019c47cf 100644
--- a/src/sakia/gui/component/controller.py
+++ b/src/sakia/gui/component/controller.py
@@ -14,8 +14,17 @@ class ComponentController(QObject):
         :param sakia.gui.component.model.ComponentModel model: the model
         """
         super().__init__(parent)
-        self.view = view
-        self.model = model
+        self._view = view
+        self._model = model
+
+    @property
+    def view(self):
+        raise NotImplementedError("View property not implemented")
+
+    @property
+    def model(self):
+        raise NotImplementedError("Model property not implemented")
+
 
     @classmethod
     def create(cls, parent, app, **kwargs):
diff --git a/src/sakia/gui/homescreen/controller.py b/src/sakia/gui/homescreen/controller.py
index ae546f52..1a4b1297 100644
--- a/src/sakia/gui/homescreen/controller.py
+++ b/src/sakia/gui/homescreen/controller.py
@@ -31,3 +31,10 @@ class HomeScreenController(ComponentController):
         model.setParent(homescreen)
         return homescreen
 
+    @property
+    def view(self) -> HomeScreenView:
+        return self._view
+
+    @property
+    def model(self) -> HomeScreenModel:
+        return self._model
\ No newline at end of file
diff --git a/src/sakia/gui/identities/controller.py b/src/sakia/gui/identities/controller.py
index 74f425a6..99af1567 100644
--- a/src/sakia/gui/identities/controller.py
+++ b/src/sakia/gui/identities/controller.py
@@ -31,6 +31,14 @@ class IdentitiesController(ComponentController):
         table_model = self.model.init_table_model()
         self.view.set_table_identities_model(table_model)
 
+    @property
+    def view(self) -> IdentitiesView:
+        return self._view
+
+    @property
+    def model(self) -> IdentitiesModel:
+        return self._model
+
     @property
     def app(self):
         return self.model.app
diff --git a/src/sakia/gui/identities/model.py b/src/sakia/gui/identities/model.py
index a81bdc50..9c41c995 100644
--- a/src/sakia/gui/identities/model.py
+++ b/src/sakia/gui/identities/model.py
@@ -13,6 +13,9 @@ class IdentitiesModel(ComponentModel):
         Constructor of a model of Identities component
 
         :param sakia.gui.identities.controller.IdentitiesController parent: the controller
+        :param sakia.core.Application app: the app
+        :param sakia.core.Account account: the account
+        :param sakia.core.Community community: the communitys
         """
         super().__init__(parent)
         self.app = app
diff --git a/src/sakia/gui/informations/__init__.py b/src/sakia/gui/informations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/sakia/gui/informations/controller.py b/src/sakia/gui/informations/controller.py
new file mode 100644
index 00000000..a54b5581
--- /dev/null
+++ b/src/sakia/gui/informations/controller.py
@@ -0,0 +1,70 @@
+from ..component.controller import ComponentController
+from .model import InformationsModel
+from .view import InformationsView
+from sakia.tools.decorators import asyncify
+
+
+class InformationsController(ComponentController):
+    """
+    The informations component
+    """
+
+    def __init__(self, parent, view, model):
+        """
+        Constructor of the informations component
+
+        :param sakia.gui.informations.view.InformationsView view: the view
+        :param sakia.gui.informations.model.InformationsModel model: the model
+        """
+        super().__init__(parent, view, model)
+        self.init_view_text()
+
+    @property
+    def informations_view(self):
+        """
+        :rtype: sakia.gui.informations.view.InformationsView
+        """
+        return self.view
+
+    @classmethod
+    def create(cls, parent, app, **kwargs):
+        account = kwargs['account']
+        community = kwargs['community']
+
+        view = InformationsView(parent.view)
+        model = InformationsModel(None, app, account, community)
+        informations = cls(parent, view, model)
+        model.setParent(informations)
+        return informations
+
+    @property
+    def view(self) -> InformationsView:
+        return self._view
+
+    @property
+    def model(self) -> InformationsModel:
+        return self._model
+
+    @asyncify
+    async def init_view_text(self):
+        """
+        Initialization of text in informations view
+        """
+        referentials = self.model.referentials()
+        self.view.set_rules_text_no_dividend()
+        self.view.set_general_text_no_dividend()
+        self.view.set_text_referentials(referentials)
+        params = await self.model.parameters()
+        self.view.set_money_text(params, self.model.short_currency())
+        self.view.set_wot_text(params)
+        self.refresh_localized_data()
+
+    @asyncify
+    async def refresh_localized_data(self):
+        """
+        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
diff --git a/res/ui/informations_tab.ui b/src/sakia/gui/informations/informations.ui
similarity index 95%
rename from res/ui/informations_tab.ui
rename to src/sakia/gui/informations/informations.ui
index 46ed1d42..2396590f 100644
--- a/res/ui/informations_tab.ui
+++ b/src/sakia/gui/informations/informations.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>InformationsTabWidget</class>
- <widget class="QWidget" name="InformationsTabWidget">
+ <class>InformationsWidget</class>
+ <widget class="QWidget" name="InformationsWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -38,8 +38,8 @@ QGroupBox::title {
        <rect>
         <x>0</x>
         <y>0</y>
-        <width>518</width>
-        <height>717</height>
+        <width>522</width>
+        <height>721</height>
        </rect>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_5">
@@ -151,7 +151,7 @@ QGroupBox::title {
   </layout>
  </widget>
  <resources>
-  <include location="../icons/icons.qrc"/>
+  <include location="../../../../res/icons/icons.qrc"/>
  </resources>
  <connections/>
 </ui>
diff --git a/src/sakia/gui/informations/model.py b/src/sakia/gui/informations/model.py
new file mode 100644
index 00000000..db410380
--- /dev/null
+++ b/src/sakia/gui/informations/model.py
@@ -0,0 +1,148 @@
+from ..component.model import ComponentModel
+from sakia.tools.exceptions import NoPeerAvailable
+from PyQt5.QtCore import QLocale, QDateTime, pyqtSignal
+from sakia.core.money import Referentials
+
+import logging
+import math
+
+
+class InformationsModel(ComponentModel):
+    """
+    An component
+    """
+    localized_data_changed = pyqtSignal(dict)
+
+    def __init__(self, parent, app, account, community):
+        """
+        Constructor of an component
+
+        :param sakia.gui.informations.controller.InformationsController parent: the controller
+        :param sakia.core.Application app: the app
+        :param sakia.core.Account account: the account
+        :param sakia.core.Community community: the community
+        """
+        super().__init__(parent)
+        self.app = app
+        self.account = account
+        self.community = community
+
+    async def get_localized_data(self):
+        localized_data = {}
+        #  try to request money parameters
+        try:
+            params = await self.community.parameters()
+        except NoPeerAvailable as e:
+            logging.debug('community parameters error : ' + str(e))
+            return None
+
+        localized_data['growth'] = params['c']
+        localized_data['days_per_dividend'] = params['dt'] / 86400
+
+        try:
+            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
+
+        if block_ud:
+            # display float values
+            localized_data['ud'] = await self.account.current_ref.instance(block_ud['dividend'] * math.pow(10, block_ud['unitbase']),
+                                              self.community,
+                                              self.app) \
+                .diff_localized(True, self.app.preferences['international_system_of_units'])
+
+            localized_data['members_count'] = block_ud['membersCount']
+
+            computed_dividend = await self.community.computed_dividend()
+            # display float values
+            localized_data['ud_plus_1'] = await self.account.current_ref.instance(computed_dividend,
+                                              self.community, self.app) \
+                .diff_localized(True, self.app.preferences['international_system_of_units'])
+
+            localized_data['mass'] = await self.account.current_ref.instance(block_ud['monetaryMass'],
+                                              self.community, self.app) \
+                .diff_localized(True, self.app.preferences['international_system_of_units'])
+
+            localized_data['ud_median_time'] = QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(block_ud['medianTime']),
+                QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
+            )
+
+            localized_data['next_ud_median_time'] = QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(block_ud['medianTime'] + params['dt']),
+                QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
+            )
+
+            if block_ud_minus_1:
+                mass_minus_1 = (float(0) if block_ud['membersCount'] == 0 else
+                                block_ud_minus_1['monetaryMass'] / block_ud['membersCount'])
+                localized_data['mass_minus_1_per_member'] = await self.account.current_ref.instance(mass_minus_1,
+                                                  self.community, self.app) \
+                                                .diff_localized(True, self.app.preferences['international_system_of_units'])
+                localized_data['mass_minus_1'] = await self.account.current_ref.instance(block_ud_minus_1['monetaryMass'],
+                                                  self.community, self.app) \
+                                                    .diff_localized(True, self.app.preferences['international_system_of_units'])
+                # avoid divide by zero !
+                if block_ud['membersCount'] == 0 or block_ud_minus_1['monetaryMass'] == 0:
+                    localized_data['actual_growth'] = float(0)
+                else:
+                    localized_data['actual_growth'] = (block_ud['dividend'] * math.pow(10, block_ud['unitbase'])) / (
+                    block_ud_minus_1['monetaryMass'] / block_ud['membersCount'])
+
+                localized_data['ud_median_time_minus_1'] = QLocale.toString(
+                    QLocale(),
+                    QDateTime.fromTime_t(block_ud_minus_1['medianTime']),
+                    QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
+                )
+            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
+
+    async def parameters(self):
+        """
+        Get community parameters
+        """
+        #  try to request money parameters
+        try:
+            params = await self.community.parameters()
+        except NoPeerAvailable as e:
+            logging.debug('community parameters error : ' + str(e))
+            return None
+        return params
+
+    def referentials(self):
+        """
+        Get referentials
+        :return: The list of instances of all referentials
+        :rtype: list
+        """
+        refs_instances = []
+        for ref_class in Referentials:
+             refs_instances.append(ref_class(0, self.community, self.app, None))
+        return refs_instances
+
+    def short_currency(self):
+        """
+        Get community currency
+        :return: the community in short currency format
+        """
+        return self.community.short_currency
\ No newline at end of file
diff --git a/src/sakia/gui/informations/view.py b/src/sakia/gui/informations/view.py
new file mode 100644
index 00000000..f3989e23
--- /dev/null
+++ b/src/sakia/gui/informations/view.py
@@ -0,0 +1,220 @@
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtCore import QEvent
+from .informations_uic import Ui_InformationsWidget
+
+
+class InformationsView(QWidget, Ui_InformationsWidget):
+    """
+    The view of navigation panel
+    """
+
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.setupUi(self)
+
+    def set_general_text_no_dividend(self):
+        """
+        Set the general text when there is no dividend
+        """
+        self.label_general.setText(self.tr('No Universal Dividend created yet.'))
+
+    def set_general_text(self, localized_data):
+        """
+        Fill the general text with given informations
+        :return:
+        """
+        # set infos in label
+        self.label_general.setText(
+            self.tr("""
+            <table cellpadding="5">
+            <tr><td align="right"><b>{:}</b></div></td><td>{:} {:}</td></tr>
+            <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>
+            <tr><td align="right"><b>{:2.2%} / {:} days</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>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            </table>
+            """).format(
+                localized_data['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'],
+                self.tr('Members N(t)'),
+                localized_data['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'],
+                self.tr('Actual growth c = UD(t)/[M(t-1)/N(t)]'),
+                localized_data['ud_median_time_minus_1'],
+                self.tr('Penultimate UD date and time (t-1)'),
+                localized_data['ud_median_time'],
+                self.tr('Last UD date and time (t)'),
+                localized_data['next_ud_median_time'],
+                self.tr('Next UD date and time (t+1)')
+            )
+        )
+
+    def set_rules_text_no_dividend(self):
+        """
+        Set text when no dividends was generated yet
+        """
+        self.label_rules.setText(self.tr('No Universal Dividend created yet.'))
+
+    def set_rules_text(self, localized_data):
+        """
+        Set text in rules
+        :param dict localized_data:
+        :return:
+        """
+        # set infos in label
+        self.label_rules.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.tr('{:2.0%} / {:} days').format(localized_data['growth'], localized_data['days_per_dividend']),
+                self.tr('Fundamental growth (c) / Delta time (dt)'),
+                self.tr('UD(t+1) = MAX { UD(t) ; c &#215; M(t) / N(t+1) }'),
+                self.tr('Universal Dividend (formula)'),
+                self.tr('{:} = MAX {{ {:} {:} ; {:2.0%} &#215; {:} {:} / {:} }}').format(
+                    localized_data['ud_plus_1'],
+                    localized_data['ud'],
+                    localized_data['diff_units'],
+                    localized_data['growth'],
+                    localized_data['mass'],
+                    localized_data['diff_units'],
+                    localized_data['members_count']
+                ),
+                self.tr('Universal Dividend (computed)')
+            )
+        )
+
+    def set_text_referentials(self, referentials):
+        """
+        Set text from referentials
+        :param list referentials: list of referentials
+        """
+        # set infos in label
+        ref_template = """
+                <table cellpadding="5">
+                <tr><th>{:}</th><td>{:}</td></tr>
+                <tr><th>{:}</th><td>{:}</td></tr>
+                <tr><th>{:}</th><td>{:}</td></tr>
+                <tr><th>{:}</th><td>{:}</td></tr>
+                </table>
+                """
+        templates = []
+        for ref in referentials:
+            # print(ref_class.__class__.__name__)
+            # if ref_class.__class__.__name__ == 'RelativeToPast':
+            #     continue
+            templates.append(ref_template.format(self.tr('Name'), ref.translated_name(),
+                                                 self.tr('Units'), ref.units,
+                                                 self.tr('Formula'), ref.formula,
+                                                 self.tr('Description'), ref.description
+                                                 )
+                             )
+
+        self.label_referentials.setText('<hr>'.join(templates))
+
+    def set_money_text(self, params, currency):
+        """
+        Set text from money parameters
+        :param dict params: Parameters of the currency
+        :param str currency: The currency
+        """
+
+        # set infos in label
+        self.label_money.setText(
+                self.tr("""
+            <table cellpadding="5">
+            <tr><td align="right"><b>{:2.0%} / {:} days</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>
+            <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>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:2.0%}</b></td><td>{:}</td></tr>
+            </table>
+            """).format(
+                        params['c'],
+                        params['dt'] / 86400,
+                        self.tr('Fundamental growth (c)'),
+                        params['ud0'],
+                        self.tr('Initial Universal Dividend UD(0) in'),
+                        currency,
+                        params['dt'] / 86400,
+                        self.tr('Time period (dt) in days (86400 seconds) between two UD'),
+                        params['medianTimeBlocks'],
+                        self.tr('Number of blocks used for calculating median time'),
+                        params['avgGenTime'],
+                        self.tr('The average time in seconds for writing 1 block (wished time)'),
+                        params['dtDiffEval'],
+                        self.tr('The number of blocks required to evaluate again PoWMin value'),
+                        params['blocksRot'],
+                        self.tr('The number of previous blocks to check for personalized difficulty'),
+                        params['percentRot'],
+                        self.tr('The percent of previous issuers to reach for personalized difficulty')
+                )
+        )
+
+    def set_wot_text(self, params):
+        """
+        Set wot text from currency parameters
+        :param dict parameters:
+        :return:
+        """
+
+        # set infos in label
+        self.label_wot.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>
+            <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>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            </table>
+            """).format(
+                        params['sigPeriod'] / 86400,
+                        self.tr('Minimum delay between 2 certifications (in days)'),
+                        params['sigValidity'] / 86400,
+                        self.tr('Maximum age of a valid signature (in days)'),
+                        params['sigQty'],
+                        self.tr('Minimum quantity of signatures to be part of the WoT'),
+                        params['sigStock'],
+                        self.tr('Maximum quantity of active certifications made by member.'),
+                        params['sigWindow'],
+                        self.tr('Maximum delay a certification can wait before being expired for non-writing.'),
+                        params['xpercent'],
+                        self.tr('Minimum percent of sentries to reach to match the distance rule'),
+                        params['msValidity'] / 86400,
+                        self.tr('Maximum age of a valid membership (in days)'),
+                        params['stepMax'],
+                        self.tr('Maximum distance between each WoT member and a newcomer'),
+                )
+        )
+
+    def changeEvent(self, event):
+        """
+        Intercepte LanguageChange event to translate UI
+        :param QEvent QEvent: Event
+        :return:
+        """
+        if event.type() == QEvent.LanguageChange:
+            self.retranslateUi(self)
+            self.refresh()
+        return super().changeEvent(event)
diff --git a/src/sakia/gui/informations_tab.py b/src/sakia/gui/informations_tab.py
deleted file mode 100644
index c46342aa..00000000
--- a/src/sakia/gui/informations_tab.py
+++ /dev/null
@@ -1,312 +0,0 @@
-"""
-Created on 31 janv. 2015
-
-@author: vit
-"""
-
-import logging
-import math
-from PyQt5.QtCore import QLocale, QDateTime, QEvent
-from PyQt5.QtWidgets import QWidget
-from ..presentation.informations_tab_uic import Ui_InformationsTabWidget
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from ..tools.exceptions import NoPeerAvailable
-from .widgets import Busy
-from ..core.money import Referentials
-
-
-class InformationsTabWidget(QWidget, Ui_InformationsTabWidget):
-    """
-    classdocs
-    """
-
-    def __init__(self, app):
-        """
-        Constructor of the InformationsTabWidget
-
-        :param sakia.core.app.Application app: Application instance
-
-        :return:
-        """
-        super().__init__()
-        self.setupUi(self)
-        self.app = app
-        self.account = None
-        self.community = None
-        self.busy = Busy(self.scrollArea)
-        self.busy.hide()
-
-    def change_account(self, account):
-        """
-
-        :param sakia.core.app.Account account: Account instance selected
-        """
-        cancel_once_task(self, self.refresh_labels)
-        self.account = account
-
-    def change_community(self, community):
-        cancel_once_task(self, self.refresh_labels)
-        self.community = community
-        self.refresh()
-
-    def refresh(self):
-        if self.account and self.community:
-            self.refresh_labels()
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_labels(self):
-        self.busy.show()
-        #  try to request money parameters
-        try:
-            params = await self.community.parameters()
-        except NoPeerAvailable as e:
-            logging.debug('community parameters error : ' + str(e))
-            return False
-
-        #  try to request money variables from last ud block
-        try:
-            block_ud = await self.community.get_ud_block()
-        except NoPeerAvailable as e:
-            logging.debug('community get_ud_block error : ' + str(e))
-            return False
-        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 False
-
-        if block_ud:
-            # display float values
-            localized_ud = await self.account.current_ref.instance(block_ud['dividend'] * math.pow(10, block_ud['unitbase']),
-                                                               self.community,
-                                                               self.app) \
-                .diff_localized(True, self.app.preferences['international_system_of_units'])
-
-            computed_dividend = await self.community.computed_dividend()
-            # display float values
-            localized_ud_plus_1 = await self.account.current_ref.instance(computed_dividend,
-                                                    self.community, self.app)\
-                .diff_localized(True, self.app.preferences['international_system_of_units'])
-
-            localized_mass = await self.account.current_ref.instance(block_ud['monetaryMass'],
-                                                    self.community, self.app)\
-                .diff_localized(True, self.app.preferences['international_system_of_units'])
-
-            localized_ud_median_time = QLocale.toString(
-                        QLocale(),
-                        QDateTime.fromTime_t(block_ud['medianTime']),
-                        QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
-                    )
-
-            localized_next_ud_median_time = QLocale.toString(
-                        QLocale(),
-                        QDateTime.fromTime_t(block_ud['medianTime'] + params['dt']),
-                        QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
-                    )
-
-            if block_ud_minus_1:
-                mass_minus_1 = (float(0) if block_ud['membersCount'] == 0 else
-                        block_ud_minus_1['monetaryMass'] / block_ud['membersCount'])
-                localized_mass_minus_1_per_member = await self.account.current_ref.instance(mass_minus_1,
-                                                                  self.community, self.app)\
-                    .diff_localized(True, self.app.preferences['international_system_of_units'])
-                localized_mass_minus_1 = await self.account.current_ref.instance(block_ud_minus_1['monetaryMass'],
-                                                                  self.community, self.app)\
-                    .diff_localized(True, self.app.preferences['international_system_of_units'])
-                # avoid divide by zero !
-                if block_ud['membersCount'] == 0 or block_ud_minus_1['monetaryMass'] == 0:
-                    actual_growth = float(0)
-                else:
-                    actual_growth = (block_ud['dividend'] * math.pow(10, block_ud['unitbase'])) / (block_ud_minus_1['monetaryMass'] / block_ud['membersCount'])
-
-                localized_ud_median_time_minus_1 = QLocale.toString(
-                    QLocale(),
-                    QDateTime.fromTime_t(block_ud_minus_1['medianTime']),
-                    QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
-                )
-            else:
-                localized_mass_minus_1_per_member = QLocale().toString(
-                        float(0), 'f', self.app.preferences['digits_after_comma']
-                )
-                localized_mass_minus_1 = QLocale().toString(
-                        float(0), 'f', self.app.preferences['digits_after_comma']
-                )
-                actual_growth = float(0)
-                localized_ud_median_time_minus_1 = "####"
-
-            # set infos in label
-            self.label_general.setText(
-                    self.tr("""
-                <table cellpadding="5">
-                <tr><td align="right"><b>{:}</b></div></td><td>{:} {:}</td></tr>
-                <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>
-                <tr><td align="right"><b>{:2.2%} / {:} days</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>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                </table>
-                """).format(
-                    localized_ud,
-                    self.tr('Universal Dividend UD(t) in'),
-                    self.account.current_ref.instance(0, self.community, self.app, None).diff_units,
-                    localized_mass_minus_1,
-                    self.tr('Monetary Mass M(t-1) in'),
-                    self.account.current_ref.instance(0, self.community, self.app, None).units,
-                    block_ud['membersCount'],
-                    self.tr('Members N(t)'),
-                    localized_mass_minus_1_per_member,
-                    self.tr('Monetary Mass per member M(t-1)/N(t) in'),
-                    self.account.current_ref.instance(0, self.community, self.app, None).diff_units,
-                    actual_growth,
-                    params['dt'] / 86400,
-                    self.tr('Actual growth c = UD(t)/[M(t-1)/N(t)]'),
-                    localized_ud_median_time_minus_1,
-                    self.tr('Penultimate UD date and time (t-1)'),
-                    localized_ud_median_time,
-                    self.tr('Last UD date and time (t)'),
-                    localized_next_ud_median_time,
-                    self.tr('Next UD date and time (t+1)')
-                )
-            )
-        else:
-            self.label_general.setText(self.tr('No Universal Dividend created yet.'))
-
-        if block_ud:
-            # set infos in label
-            self.label_rules.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.tr('{:2.0%} / {:} days').format(params['c'], params['dt'] / 86400),
-                    self.tr('Fundamental growth (c) / Delta time (dt)'),
-                    self.tr('UD(t+1) = MAX { UD(t) ; c &#215; M(t) / N(t+1) }'),
-                    self.tr('Universal Dividend (formula)'),
-                    self.tr('{:} = MAX {{ {:} {:} ; {:2.0%} &#215; {:} {:} / {:} }}').format(
-                        localized_ud_plus_1,
-                        localized_ud,
-                        self.account.current_ref.instance(0, self.community, self.app, None).diff_units,
-                        params['c'],
-                        localized_mass,
-                        self.account.current_ref.instance(0, self.community, self.app, None).diff_units,
-                        block_ud['membersCount']
-                    ),
-                    self.tr('Universal Dividend (computed)')
-                )
-            )
-        else:
-            self.label_rules.setText(self.tr('No Universal Dividend created yet.'))
-
-        # set infos in label
-        ref_template = """
-        <table cellpadding="5">
-        <tr><th>{:}</th><td>{:}</td></tr>
-        <tr><th>{:}</th><td>{:}</td></tr>
-        <tr><th>{:}</th><td>{:}</td></tr>
-        <tr><th>{:}</th><td>{:}</td></tr>
-        </table>
-        """
-        templates = []
-        for ref_class in Referentials:
-            ref = ref_class(0, self.community, self.app, None)
-            # print(ref_class.__class__.__name__)
-            # if ref_class.__class__.__name__ == 'RelativeToPast':
-            #     continue
-            templates.append(ref_template.format(self.tr('Name'), ref.translated_name(),
-                                        self.tr('Units'), ref.units,
-                                        self.tr('Formula'), ref.formula,
-                                        self.tr('Description'), ref.description
-                                        )
-                             )
-
-        self.label_referentials.setText('<hr>'.join(templates))
-
-        # set infos in label
-        self.label_money.setText(
-                self.tr("""
-            <table cellpadding="5">
-            <tr><td align="right"><b>{:2.0%} / {:} days</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>
-            <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>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:2.0%}</b></td><td>{:}</td></tr>
-            </table>
-            """).format(
-                        params['c'],
-                        params['dt'] / 86400,
-                        self.tr('Fundamental growth (c)'),
-                        params['ud0'],
-                        self.tr('Initial Universal Dividend UD(0) in'),
-                        self.community.short_currency,
-                        params['dt'] / 86400,
-                        self.tr('Time period (dt) in days (86400 seconds) between two UD'),
-                        params['medianTimeBlocks'],
-                        self.tr('Number of blocks used for calculating median time'),
-                        params['avgGenTime'],
-                        self.tr('The average time in seconds for writing 1 block (wished time)'),
-                        params['dtDiffEval'],
-                        self.tr('The number of blocks required to evaluate again PoWMin value'),
-                        params['blocksRot'],
-                        self.tr('The number of previous blocks to check for personalized difficulty'),
-                        params['percentRot'],
-                        self.tr('The percent of previous issuers to reach for personalized difficulty')
-                )
-        )
-
-        # set infos in label
-        self.label_wot.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>
-            <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>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            </table>
-            """).format(
-                        params['sigPeriod'] / 86400,
-                        self.tr('Minimum delay between 2 certifications (in days)'),
-                        params['sigValidity'] / 86400,
-                        self.tr('Maximum age of a valid signature (in days)'),
-                        params['sigQty'],
-                        self.tr('Minimum quantity of signatures to be part of the WoT'),
-                        params['sigStock'],
-                        self.tr('Maximum quantity of active certifications made by member.'),
-                        params['sigWindow'],
-                        self.tr('Maximum delay a certification can wait before being expired for non-writing.'),
-                        params['xpercent'],
-                        self.tr('Minimum percent of sentries to reach to match the distance rule'),
-                        params['msValidity'] / 86400,
-                        self.tr('Maximum age of a valid membership (in days)'),
-                        params['stepMax'],
-                        self.tr('Maximum distance between each WoT member and a newcomer'),
-                )
-        )
-        self.busy.hide()
-
-    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.refresh()
-        return super(InformationsTabWidget, self).changeEvent(event)
diff --git a/src/sakia/gui/main_window/controller.py b/src/sakia/gui/main_window/controller.py
index 5fdab5f3..b4735244 100644
--- a/src/sakia/gui/main_window/controller.py
+++ b/src/sakia/gui/main_window/controller.py
@@ -59,13 +59,13 @@ class MainWindowController(ComponentController):
         main_window = cls(view, model, password_asker, None, None, None)
 
         main_window.status_bar = main_window.attach(StatusBarController.create(main_window, app))
-        view.setStatusBar(main_window.status_bar.view)
+        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)
+        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)
+        view.bottom_layout.insertWidget(0, main_window.navigation._view)
 
         #app.version_requested.connect(main_window.latest_version_requested)
         #app.account_imported.connect(main_window.import_account_accepted)
@@ -75,14 +75,13 @@ class MainWindowController(ComponentController):
         main_window.refresh()
         return main_window
 
-    def add_to_stack(self, index, widget):
-        """
-        Add a view to the stack
-        :param int index: the index of the page
-        :param PyQt5.QtWidgets.QWidget widget: the view
-        """
-        self.stack.addWidget(widget)
-        self.stacked_widgets[index] = widget
+    @property
+    def view(self) -> MainWindowView:
+        return self._view
+
+    @property
+    def model(self) -> MainWindowModel:
+        return self._model
 
     @pyqtSlot(str)
     def display_error(self, error):
diff --git a/src/sakia/gui/navigation/controller.py b/src/sakia/gui/navigation/controller.py
index f2ce02b6..3f454438 100644
--- a/src/sakia/gui/navigation/controller.py
+++ b/src/sakia/gui/navigation/controller.py
@@ -5,6 +5,7 @@ from ..txhistory.controller import TxHistoryController
 from ..homescreen.controller import HomeScreenController
 from ..network.controller import NetworkController
 from ..identities.controller import IdentitiesController
+from ..informations.controller import InformationsController
 
 
 class NavigationController(ComponentController):
@@ -24,7 +25,8 @@ class NavigationController(ComponentController):
             'TxHistory': TxHistoryController,
             'HomeScreen': HomeScreenController,
             'Network': NetworkController,
-            'Identities': IdentitiesController
+            'Identities': IdentitiesController,
+            'Informations': InformationsController
         }
 
     @classmethod
@@ -42,6 +44,14 @@ class NavigationController(ComponentController):
         navigation.init_navigation()
         return navigation
 
+    @property
+    def view(self) -> NavigationView:
+        return self._view
+
+    @property
+    def model(self) -> NavigationModel:
+        return self._model
+
     def init_navigation(self):
         def parse_node(node_data):
             if 'component' in node_data['node']:
diff --git a/src/sakia/gui/navigation/model.py b/src/sakia/gui/navigation/model.py
index ea51af90..3d2d5313 100644
--- a/src/sakia/gui/navigation/model.py
+++ b/src/sakia/gui/navigation/model.py
@@ -33,6 +33,9 @@ class NavigationModel(ComponentModel):
             self.navigation[0]['children'].append({
                 'node': {
                     'title': c.currency,
+                    'component': "Informations",
+                    'community': c,
+                    'account': self.app.current_account
                 },
                 'children': [
                     {
diff --git a/src/sakia/gui/network/controller.py b/src/sakia/gui/network/controller.py
index e0203aec..7da84978 100644
--- a/src/sakia/gui/network/controller.py
+++ b/src/sakia/gui/network/controller.py
@@ -35,6 +35,14 @@ class NetworkController(ComponentController):
         model.setParent(txhistory)
         return txhistory
 
+    @property
+    def view(self) -> NetworkView:
+        return self._view
+
+    @property
+    def model(self) -> NetworkModel:
+        return self._model
+    
     def refresh_nodes_manually(self):
         self.model.refresh_nodes_once()
 
diff --git a/src/sakia/gui/status_bar/controller.py b/src/sakia/gui/status_bar/controller.py
index 2791714d..3b38490b 100644
--- a/src/sakia/gui/status_bar/controller.py
+++ b/src/sakia/gui/status_bar/controller.py
@@ -29,13 +29,21 @@ class StatusBarController(ComponentController):
         :return: a new Navigation controller
         :rtype: NavigationController
         """
-        view = StatusBarView(parent.view)
+        view = StatusBarView(parent._view)
 
         model = StatusBarModel(None, app)
         status_bar = cls(parent, view, model)
         model.setParent(status_bar)
         return status_bar
 
+    @property
+    def view(self) -> StatusBarView:
+        return self._view
+
+    @property
+    def model(self) -> StatusBarModel:
+        return self._model
+
     @pyqtSlot()
     def update_time(self):
         dateTime = QDateTime.currentDateTime()
diff --git a/src/sakia/gui/toolbar/controller.py b/src/sakia/gui/toolbar/controller.py
index 6edb59a9..00e2c077 100644
--- a/src/sakia/gui/toolbar/controller.py
+++ b/src/sakia/gui/toolbar/controller.py
@@ -43,6 +43,14 @@ class ToolbarController(ComponentController):
         model.setParent(toolbar)
         return toolbar
 
+    @property
+    def view(self) -> ToolbarView:
+        return self._view
+
+    @property
+    def model(self) -> ToolbarModel:
+        return self._model
+    
     def cancel_once_tasks(self):
         cancel_once_task(self, self.refresh_block)
         cancel_once_task(self, self.refresh_status)
diff --git a/src/sakia/gui/txhistory/controller.py b/src/sakia/gui/txhistory/controller.py
index be500771..fcf33cf0 100644
--- a/src/sakia/gui/txhistory/controller.py
+++ b/src/sakia/gui/txhistory/controller.py
@@ -49,6 +49,14 @@ class TxHistoryController(ComponentController):
         model.setParent(txhistory)
         return txhistory
 
+    @property
+    def view(self) -> TxHistoryView:
+        return self._view
+
+    @property
+    def model(self) -> TxHistoryModel:
+        return self._model
+    
     @once_at_a_time
     @asyncify
     async def refresh_minimum_maximum(self):
-- 
GitLab