Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • cebash/sakia
  • santiago/sakia
  • jonas/sakia
3 results
Show changes
Showing
with 1944 additions and 0 deletions
from PyQt5.QtCore import QLocale, pyqtSlot, QDateTime, QTimer, QObject, QCoreApplication
from .model import StatusBarModel
from .view import StatusBarView
from sakia.data.processors import BlockchainProcessor
import logging
class StatusBarController(QObject):
"""
The navigation panel
"""
def __init__(self, view, model):
"""
Constructor of the navigation component
:param sakia.gui.main_window.status_bar.view.StatusBarView view: the presentation
:param sakia.gui.main_window.status_bar.model.StatusBarModel model: the model
"""
super().__init__()
self.view = view
self.model = model
view.combo_referential.currentIndexChanged[int].connect(
self.referential_changed
)
self.update_time()
@classmethod
def create(cls, app):
"""
Instanciate a navigation component
:param sakia.app.Application app:
:return: a new Navigation controller
:rtype: NavigationController
"""
view = StatusBarView(None)
model = StatusBarModel(None, app, BlockchainProcessor.instanciate(app))
status_bar = cls(view, model)
app.new_blocks_handled.connect(status_bar.new_blocks_handled)
if app.connection_exists():
status_bar.new_blocks_handled()
return status_bar
@pyqtSlot()
def update_time(self):
dateTime = QDateTime.currentDateTime()
self.view.label_time.setText(
"{0}".format(
QLocale.toString(
QLocale(),
QDateTime.currentDateTime(),
QLocale.dateTimeFormat(QLocale(), QLocale.NarrowFormat),
)
)
)
timer = QTimer()
timer.timeout.connect(self.update_time)
timer.start(1000)
def start_loading(self):
self.view.start_loading()
def stop_loading(self):
self.view.stop_loading()
def new_blocks_handled(self):
current_block = self.model.current_block()
current_time = self.model.current_time()
str_time = QLocale.toString(
QLocale(),
QDateTime.fromTime_t(current_time),
QLocale.dateTimeFormat(QLocale(), QLocale.NarrowFormat),
)
self.view.status_label.setText(
QCoreApplication.translate(
"StatusBarController", "Blockchain sync: {0} BAT ({1})"
).format(str_time, str(current_block)[:15])
)
def refresh(self):
"""
Refresh main window
When the selected account changes, all the widgets
in the window have to be refreshed
"""
logging.debug("Refresh started")
for ref in self.model.referentials():
self.view.combo_referential.addItem(ref.translated_name())
self.view.combo_referential.setCurrentIndex(self.model.default_referential())
@pyqtSlot(int)
def referential_changed(self, index):
self.model.app.change_referential(index)
from PyQt5.QtCore import QObject
from sakia.money import Referentials
class StatusBarModel(QObject):
"""
The model of status bar component
"""
def __init__(self, parent, app, blockchain_processor):
"""
The status bar model
:param parent:
:param sakia.app.Application app: the app
:param sakia.data.processors.BlockchainProcessor blockchain_processor:
"""
super().__init__(parent)
self.app = app
self.blockchain_processor = blockchain_processor
def referentials(self):
return Referentials
def default_referential(self):
return self.app.parameters.referential
def current_block(self):
return self.blockchain_processor.current_buid(self.app.currency)
def current_time(self):
time = self.blockchain_processor.time(self.app.currency)
return self.blockchain_processor.adjusted_ts(self.app.currency, time)
from PyQt5.QtWidgets import QStatusBar
from PyQt5.QtWidgets import QLabel, QComboBox, QSpacerItem, QSizePolicy
from PyQt5.QtGui import QMovie
from PyQt5.QtCore import Qt, QSize
class StatusBarView(QStatusBar):
"""
The model of Navigation component
"""
def __init__(self, parent):
super().__init__(parent)
self.status_label = QLabel("", parent)
self.status_label.setTextFormat(Qt.RichText)
self.label_time = QLabel("", parent)
self.combo_referential = QComboBox(parent)
self.movie_loader = QMovie(":/icons/loader")
self.label_loading = QLabel(parent)
self.label_loading.setMovie(self.movie_loader)
self.label_loading.setMaximumHeight(self.height())
self.movie_loader.setScaledSize(QSize(16, 16))
self.movie_loader.start()
self.movie_loader.setPaused(True)
self.addPermanentWidget(self.label_loading)
self.addPermanentWidget(self.status_label, 2)
self.addPermanentWidget(self.label_time)
self.addPermanentWidget(self.combo_referential)
def start_loading(self):
self.movie_loader.setPaused(False)
def stop_loading(self):
self.movie_loader.setPaused(True)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutPopup</class>
<widget class="QDialog" name="AboutPopup">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>297</width>
<height>264</height>
</rect>
</property>
<property name="windowTitle">
<string>About</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>label</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AboutPopup</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutMoney</class>
<widget class="QWidget" name="AboutMoney">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>509</width>
<height>406</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="group_general">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="title">
<string>General</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_general">
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="group_rules">
<property name="title">
<string>Rules</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="label_rules">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="group_money">
<property name="title">
<string>Money</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_money">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutWot</class>
<widget class="QWidget" name="AboutWot">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>509</width>
<height>406</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="group_wot">
<property name="title">
<string>WoT</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_wot">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
from PyQt5.QtCore import QObject, pyqtSignal, QCoreApplication
from PyQt5.QtWidgets import QDialog
from sakia.gui.dialogs.connection_cfg.controller import ConnectionConfigController
from sakia.gui.dialogs.revocation.controller import RevocationController
from sakia.gui.dialogs.contact.controller import ContactController
from sakia.gui.dialogs.plugins_manager.controller import PluginsManagerController
from sakia.gui.preferences import PreferencesDialog
from .model import ToolbarModel
from .view import ToolbarView
from sakia.data.processors import BlockchainProcessor
import sys
class ToolbarController(QObject):
"""
The navigation panel
"""
exit_triggered = pyqtSignal()
def __init__(self, view, model):
"""
:param sakia.gui.component.controller.ComponentController parent: the parent
:param sakia.gui.main_window.toolbar.view.ToolbarView view:
:param sakia.gui.main_window.toolbar.model.ToolbarModel model:
"""
super().__init__()
self.view = view
self.model = model
self.view.action_add_connection.triggered.connect(
self.open_add_connection_dialog
)
self.view.action_parameters.triggered.connect(self.open_settings_dialog)
self.view.action_plugins.triggered.connect(self.open_plugins_manager_dialog)
self.view.action_about.triggered.connect(self.open_about_dialog)
self.view.action_about_wot.triggered.connect(self.open_about_wot_dialog)
self.view.action_about_money.triggered.connect(self.open_about_money_dialog)
self.view.action_about_referentials.triggered.connect(
self.open_about_referentials_dialog
)
self.view.action_revoke_uid.triggered.connect(self.open_revocation_dialog)
self.view.button_contacts.clicked.connect(self.open_contacts_dialog)
self.view.action_exit.triggered.connect(self.exit_triggered)
@classmethod
def create(cls, app, navigation):
"""
Instanciate a navigation component
:param sakia.app.Application app:
:param sakia.gui.navigation.controller.NavigationController navigation:
:return: a new Navigation controller
:rtype: NavigationController
"""
view = ToolbarView(None)
model = ToolbarModel(
app,
navigation.model,
app.blockchain_service,
BlockchainProcessor.instanciate(app),
)
toolbar = cls(view, model)
return toolbar
def open_contacts_dialog(self):
ContactController.open_dialog(self, self.model.app)
def open_revocation_dialog(self):
RevocationController.open_dialog(
self, self.model.app, self.model.navigation_model.current_connection()
)
def open_settings_dialog(self):
PreferencesDialog(self.model.app).exec()
def open_plugins_manager_dialog(self):
PluginsManagerController.open_dialog(self, self.model.app)
def open_add_connection_dialog(self):
connection_config = ConnectionConfigController.create_connection(
self, self.model.app
)
connection_config.exec()
if connection_config.view.result() == QDialog.Accepted:
self.model.app.instanciate_services()
self.model.app.start_coroutines()
self.model.app.new_connection.emit(connection_config.model.connection)
def open_about_dialog(self):
text = self.model.about_text()
self.view.show_about(text)
def open_about_wot_dialog(self):
params = self.model.parameters()
self.view.show_about_wot(params)
def open_about_money_dialog(self):
params = self.model.parameters()
currency = self.model.app.currency
localized_data = self.model.get_localized_data()
referentials = self.model.referentials()
self.view.show_about_money(params, currency, localized_data)
def open_about_referentials_dialog(self):
referentials = self.model.referentials()
self.view.show_about_referentials(referentials)
def retranslateUi(self, widget):
"""
Method to complete translations missing from generated code
:param widget:
:return:
"""
self.action_publish_uid.setText(
QCoreApplication.translate(
"ToolbarController", ToolbarController.action_publish_uid_text
)
)
self.action_revoke_uid.setText(
QCoreApplication.translate(
"ToolbarController", ToolbarController.action_revoke_uid_text
)
)
self.action_showinfo.setText(
QCoreApplication.translate(
"ToolbarController", ToolbarController.action_showinfo_text
)
)
super().retranslateUi(self)
from PyQt5.QtCore import QObject, QLocale, QDateTime, QCoreApplication
from sakia.data.processors import ConnectionsProcessor
import attr
import math
from sakia import __version__
from sakia.money import Referentials
@attr.s()
class ToolbarModel(QObject):
"""
The model of Navigation component
:param sakia.app.Application app: the application
:param sakia.gui.navigation.model.NavigationModel navigation_model: The navigation model
:param sakia.services.BlockchainService blockchain_service: The blockchain service
"""
app = attr.ib()
navigation_model = attr.ib()
blockchain_service = attr.ib()
blockchain_processor = attr.ib()
def __attrs_post_init__(self):
super().__init__()
def notifications(self):
return self.app.parameters.notifications
def connections_with_uids(self):
return ConnectionsProcessor.instanciate(self.app).connections_with_uids()
def connections(self):
return ConnectionsProcessor.instanciate(self.app).connections()
def about_text(self):
latest = self.app.available_version
version_info = ""
version_url = ""
url_text = ""
if not latest[0]:
version_info = "Latest release: {version}".format(version=latest[1])
version_url = latest[2]
url_text = QCoreApplication.translate("ToolbarView", "Download page")
new_version_text = """
<p><b>{version_info}</b></p>
<p><a href={version_url}>{url_text}</a></p>
""".format(
version_info=version_info, version_url=version_url, url_text=url_text
)
return """
<h1>Sakia</h1>
<p>Python/Qt Duniter client</p>
<p>Version: {:}</p>
{new_version_text}
<p>License: GPLv3</p>
<p><b>Authors</b></p>
<p>inso</p>
<p>vit</p>
<p>canercandan</p>
<p>Moul</p>
""".format(
__version__, new_version_text=new_version_text
)
def get_localized_data(self):
localized_data = {}
# try to request money parameters
params = self.blockchain_service.parameters()
localized_data["currency"] = self.app.root_servers[self.app.currency]["display"]
localized_data["growth"] = params.c
localized_data["growth_per_dt"] = QLocale().toString(
params.c / (params.dt_reeval / params.dt), "f", 8
)
localized_data["dt_reeval_in_days"] = QLocale().toString(
params.dt_reeval / 86400, "f", 2
)
last_mass = self.blockchain_service.last_monetary_mass()
last_ud, last_ud_base = self.blockchain_service.last_ud()
last_members_count = self.blockchain_service.last_members_count()
last_ud_time = self.blockchain_service.last_ud_time()
localized_data["units"] = self.app.current_ref.instance(
0, self.app.currency, self.app, None
).units
localized_data["diff_units"] = self.app.current_ref.instance(
0, self.app.currency, self.app, None
).diff_units
if last_ud:
# display float values
localized_data["ud"] = self.app.current_ref.instance(
last_ud * math.pow(10, last_ud_base), self.app.currency, self.app
).diff_localized(False, True)
localized_data[
"members_count"
] = self.blockchain_service.last_members_count()
computed_dividend = self.blockchain_service.computed_dividend()
# display float values
localized_data["ud_plus_1"] = self.app.current_ref.instance(
computed_dividend, self.app.currency, self.app
).diff_localized(False, True)
localized_data["mass"] = self.app.current_ref.instance(
self.blockchain_service.last_monetary_mass(),
self.app.currency,
self.app,
).localized(False, True)
ud_median_time = self.blockchain_service.last_ud_time()
ud_median_time = self.blockchain_processor.adjusted_ts(
self.app.currency, ud_median_time
)
localized_data["ud_median_time"] = QLocale.toString(
QLocale(),
QDateTime.fromTime_t(ud_median_time),
QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat),
)
next_ud_median_time = self.blockchain_service.last_ud_time() + params.dt
next_ud_median_time = self.blockchain_processor.adjusted_ts(
self.app.currency, next_ud_median_time
)
localized_data["next_ud_median_time"] = QLocale.toString(
QLocale(),
QDateTime.fromTime_t(next_ud_median_time),
QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat),
)
next_ud_reeval = self.blockchain_service.next_ud_reeval()
next_ud_reeval = self.blockchain_processor.adjusted_ts(
self.app.currency, next_ud_reeval
)
localized_data["next_ud_reeval"] = QLocale.toString(
QLocale(),
QDateTime.fromTime_t(next_ud_reeval),
QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat),
)
if last_ud:
mass_per_member = (
float(0)
if last_ud == 0 or last_members_count == 0
else last_mass / last_members_count
)
localized_data["members_count"] = last_members_count
localized_data["mass_per_member"] = self.app.current_ref.instance(
mass_per_member, self.app.currency, self.app
).localized(False, True)
# avoid divide by zero !
if last_members_count == 0:
localized_data["actual_growth"] = float(0)
else:
localized_data["actual_growth"] = (
last_ud * math.pow(10, last_ud_base)
) / (last_mass / last_members_count)
last_ud_time = self.blockchain_processor.adjusted_ts(
self.app.currency, last_ud_time
)
localized_data["ud_median_time_minus_1"] = QLocale.toString(
QLocale(),
QDateTime.fromTime_t(last_ud_time),
QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat),
)
return localized_data
def parameters(self):
"""
Get community parameters
"""
return self.blockchain_service.parameters()
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.app.currency, self.app, None))
return refs_instances
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SakiaToolbar</class>
<widget class="QFrame" name="SakiaToolbar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>241</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>1000</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Frame</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="button_network">
<property name="text">
<string>Network</string>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/wot_icon</normaloff>:/icons/wot_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_identity">
<property name="text">
<string>Search an identity</string>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/explorer_icon</normaloff>:/icons/explorer_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>221</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_contacts">
<property name="text">
<string>Contacts</string>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/connect_icon</normaloff>:/icons/connect_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolbutton_menu">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/menu_icon</normaloff>:/icons/menu_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
<property name="autoRaise">
<bool>false</bool>
</property>
<property name="arrowType">
<enum>Qt::NoArrow</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="maximumSize">
<size>
<width>40</width>
<height>40</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../../../../res/icons/sakia.icons.qrc">:/icons/sakia_logo</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../../../res/icons/sakia.icons.qrc"/>
</resources>
<connections/>
</ui>
from PyQt5.QtWidgets import (
QFrame,
QAction,
QMenu,
QSizePolicy,
QInputDialog,
QDialog,
QVBoxLayout,
QTabWidget,
QWidget,
QLabel,
)
from sakia.gui.widgets.dialogs import dialog_async_exec
from PyQt5.QtCore import QObject, QT_TRANSLATE_NOOP, Qt, QLocale, QCoreApplication
from .toolbar_uic import Ui_SakiaToolbar
from .about_uic import Ui_AboutPopup
from .about_money_uic import Ui_AboutMoney
from .about_wot_uic import Ui_AboutWot
from sakia.helpers import timestamp_to_dhms, dpi_ratio
class ToolbarView(QFrame, Ui_SakiaToolbar):
"""
The model of Navigation component
"""
_action_revoke_uid_text = QT_TRANSLATE_NOOP(
"ToolbarView", "Publish a revocation document"
)
def __init__(self, parent):
super().__init__(parent)
self.setupUi(self)
tool_menu = QMenu(
QCoreApplication.translate("ToolbarView", "Tools"), self.toolbutton_menu
)
self.toolbutton_menu.setMenu(tool_menu)
self.action_add_connection = QAction(
QCoreApplication.translate("ToolbarView", "Add an Sakia account"), tool_menu
)
tool_menu.addAction(self.action_add_connection)
self.action_revoke_uid = QAction(
QCoreApplication.translate(
"ToolbarView", ToolbarView._action_revoke_uid_text
),
self,
)
tool_menu.addAction(self.action_revoke_uid)
self.action_parameters = QAction(
QCoreApplication.translate("ToolbarView", "Settings"), tool_menu
)
tool_menu.addAction(self.action_parameters)
self.action_plugins = QAction(
QCoreApplication.translate("ToolbarView", "Plugins manager"), tool_menu
)
tool_menu.addAction(self.action_plugins)
tool_menu.addSeparator()
about_menu = QMenu(
QCoreApplication.translate("ToolbarView", "About"), tool_menu
)
tool_menu.addMenu(about_menu)
self.action_about_money = QAction(
QCoreApplication.translate("ToolbarView", "About Money"), about_menu
)
about_menu.addAction(self.action_about_money)
self.action_about_referentials = QAction(
QCoreApplication.translate("ToolbarView", "About Referentials"), about_menu
)
about_menu.addAction(self.action_about_referentials)
self.action_about_wot = QAction(
QCoreApplication.translate("ToolbarView", "About Web of Trust"), about_menu
)
about_menu.addAction(self.action_about_wot)
self.action_about = QAction(
QCoreApplication.translate("ToolbarView", "About Sakia"), about_menu
)
about_menu.addAction(self.action_about)
self.action_exit = QAction(
QCoreApplication.translate("ToolbarView", "Quit"), tool_menu
)
tool_menu.addAction(self.action_exit)
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Minimum)
self.setMaximumHeight(60)
self.button_network.setIconSize(self.button_network.iconSize() * dpi_ratio())
self.button_contacts.setIconSize(self.button_contacts.iconSize() * dpi_ratio())
self.button_identity.setIconSize(self.button_identity.iconSize() * dpi_ratio())
self.toolbutton_menu.setIconSize(self.toolbutton_menu.iconSize() * dpi_ratio())
self.button_network.setFixedHeight(
self.button_network.height() * dpi_ratio() + 5 * dpi_ratio()
)
self.button_contacts.setFixedHeight(
self.button_contacts.height() * dpi_ratio() + 5 * dpi_ratio()
)
self.button_identity.setFixedHeight(
self.button_identity.height() * dpi_ratio() + 5 * dpi_ratio()
)
self.toolbutton_menu.setFixedHeight(
self.toolbutton_menu.height() * dpi_ratio() + 5 * dpi_ratio()
)
async def ask_for_connection(self, connections):
connections_titles = [c.title() for c in connections]
input_dialog = QInputDialog()
input_dialog.setComboBoxItems(connections_titles)
input_dialog.setWindowTitle(
QCoreApplication.translate("ToolbarView", "Membership")
)
input_dialog.setLabelText(
QCoreApplication.translate("ToolbarView", "Select an account")
)
await dialog_async_exec(input_dialog)
result = input_dialog.textValue()
if input_dialog.result() == QDialog.Accepted:
for c in connections:
if c.title() == result:
return c
def show_about_wot(self, params):
"""
Set wot text from currency parameters
:param sakia.data.entities.BlockchainParameters params: Parameters of the currency
:return:
"""
dialog = QDialog(self)
about_dialog = Ui_AboutWot()
about_dialog.setupUi(dialog)
# set infos in label
about_dialog.label_wot.setText(
QCoreApplication.translate(
"ToolbarView",
"""
<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(
QLocale().toString(params.sig_period / 86400, "f", 2),
QCoreApplication.translate(
"ToolbarView", "Minimum delay between 2 certifications (days)"
),
QLocale().toString(params.sig_validity / 86400, "f", 2),
QCoreApplication.translate(
"ToolbarView", "Maximum validity time of a certification (days)"
),
params.sig_qty,
QCoreApplication.translate(
"ToolbarView",
"Minimum quantity of certifications to be part of the WoT",
),
params.sig_stock,
QCoreApplication.translate(
"ToolbarView",
"Maximum quantity of active certifications per member",
),
QLocale().toString(params.sig_window / 86400, "f", 2),
QCoreApplication.translate(
"ToolbarView",
"Maximum time a certification can wait before being in blockchain (days)",
),
params.xpercent * 100,
QCoreApplication.translate(
"ToolbarView",
"Minimum percent of sentries to reach to match the distance rule",
),
params.ms_validity / 86400,
QCoreApplication.translate(
"ToolbarView", "Maximum validity time of a membership (days)"
),
params.step_max,
QCoreApplication.translate(
"ToolbarView",
"Maximum distance between each WoT member and a newcomer",
),
)
)
dialog.setWindowTitle(
QCoreApplication.translate("ToolbarView", "Web of Trust rules")
)
dialog.exec()
def show_about_money(self, params, currency, localized_data):
dialog = QDialog(self)
about_dialog = Ui_AboutMoney()
about_dialog.setupUi(dialog)
about_dialog.label_general.setText(self.general_text(localized_data))
about_dialog.label_rules.setText(self.rules_text(localized_data))
about_dialog.label_money.setText(self.money_text(params, currency))
dialog.setWindowTitle(QCoreApplication.translate("ToolbarView", "Money rules"))
dialog.exec()
def show_about_referentials(self, referentials):
dialog = QDialog(self)
layout = QVBoxLayout(dialog)
tabwidget = QTabWidget(dialog)
layout.addWidget(tabwidget)
for ref in referentials:
widget = QWidget()
layout = QVBoxLayout(widget)
label = QLabel()
label.setText(self.text_referential(ref))
layout.addWidget(label)
tabwidget.addTab(widget, ref.translated_name())
dialog.setWindowTitle(QCoreApplication.translate("ToolbarView", "Referentials"))
dialog.exec()
def general_text(self, localized_data):
"""
Fill the general text with given informations
:return:
"""
# set infos in label
return QCoreApplication.translate(
"ToolbarView",
"""
<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%} / {:}</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.get("ud", "####"),
QCoreApplication.translate("ToolbarView", "Universal Dividend UD(t) in"),
localized_data["diff_units"],
localized_data.get("mass", "###"),
QCoreApplication.translate("ToolbarView", "Monetary Mass M(t) in"),
localized_data["units"],
localized_data.get("members_count", "####"),
QCoreApplication.translate("ToolbarView", "Members N(t)"),
localized_data.get("mass_per_member", "####"),
QCoreApplication.translate(
"ToolbarView", "Monetary Mass per member M(t)/N(t) in"
),
localized_data["diff_units"],
localized_data.get("actual_growth", 0),
QCoreApplication.translate("ToolbarView", "day"),
QCoreApplication.translate(
"ToolbarView", "Actual growth c = UD(t)/[M(t)/N(t)]"
),
# todo: wait for accurate datetime of reevaluation
# localized_data.get("ud_median_time_minus_1", "####"),
# QCoreApplication.translate("ToolbarView", "Penultimate UD date and time (t-1)"),
localized_data.get("ud_median_time", "####") + " BAT",
QCoreApplication.translate("ToolbarView", "Last UD date and time (t)"),
localized_data.get("next_ud_median_time", "####") + " BAT",
QCoreApplication.translate("ToolbarView", "Next UD date and time (t+1)"),
localized_data.get("next_ud_reeval", "####") + " BAT",
QCoreApplication.translate("ToolbarView", "Next UD reevaluation (t+1)"),
)
def rules_text(self, localized_data):
"""
Set text in rules
:param dict localized_data:
:return:
"""
# set infos in label
return QCoreApplication.translate(
"ToolbarView",
"""
<table cellpadding="5">
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
</table>
""",
).format(
QCoreApplication.translate("ToolbarView", "{:2.2%} / {:} days").format(
localized_data["growth"], localized_data["dt_reeval_in_days"]
),
QCoreApplication.translate(
"ToolbarView",
"Fundamental growth (c) / Reevaluation delta time (dt_reeval)",
),
QCoreApplication.translate(
"ToolbarView", "UDĞ(t) = UDĞ(t-1) + c²*M(t-1)/N(t)"
),
QCoreApplication.translate("ToolbarView", "Universal Dividend (formula)"),
# fixme: re-display when the computed dividend will be accurate (need accurate previous monetary mass,
# last mass just before reevaluation)
# QCoreApplication.translate("ToolbarView", "{:} = {:} + {:}² * {:} / {:}").format(
# localized_data.get("ud_plus_1", "####"),
# localized_data.get("ud", "####"),
# localized_data.get("growth_per_dt", "##########"),
# localized_data.get("mass", "####"),
# localized_data.get("members_count", "####"),
# ),
# QCoreApplication.translate("ToolbarView", "Universal Dividend (computed)"),
)
def text_referential(self, ref):
"""
Set text from 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>
"""
return ref_template.format(
QCoreApplication.translate("ToolbarView", "Name"),
ref.translated_name(),
QCoreApplication.translate("ToolbarView", "Units"),
ref.units,
QCoreApplication.translate("ToolbarView", "Formula"),
ref.formula,
QCoreApplication.translate("ToolbarView", "Description"),
ref.description,
)
def money_text(self, params, currency):
"""
Set text from money parameters
:param sakia.data.entities.BlockchainParameters params: Parameters of the currency
:param str currency: The currency
"""
dt_dhms = timestamp_to_dhms(params.dt)
if dt_dhms[0] > 0:
dt_as_str = QCoreApplication.translate(
"ToolbarView", "{:} day(s) {:} hour(s)"
).format(*dt_dhms)
else:
dt_as_str = QCoreApplication.translate("ToolbarView", "{:} hour(s)").format(
dt_dhms[1]
)
if dt_dhms[2] > 0 or dt_dhms[3] > 0:
dt_dhms += ", {:} minute(s) and {:} second(s)".format(*dt_dhms[1:])
dt_reeval_dhms = timestamp_to_dhms(params.dt_reeval)
dt_reeval_as_str = QCoreApplication.translate(
"ToolbarView", "{:} day(s) {:} hour(s)"
).format(*dt_reeval_dhms)
# set infos in label
return QCoreApplication.translate(
"ToolbarView",
"""
<table cellpadding="5">
<tr><td align="right"><b>{:2.2%}</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,
QCoreApplication.translate("ToolbarView", "Fundamental growth (c)"),
params.ud0,
QCoreApplication.translate(
"ToolbarView", "Initial Universal Dividend UD(0) in"
),
currency,
dt_as_str,
QCoreApplication.translate("ToolbarView", "Time period between two UD"),
dt_reeval_as_str,
QCoreApplication.translate(
"ToolbarView", "Time period between two UD reevaluation"
),
params.median_time_blocks,
QCoreApplication.translate(
"ToolbarView", "Number of blocks used for calculating median time"
),
params.avg_gen_time,
QCoreApplication.translate(
"ToolbarView",
"The average time in seconds for writing 1 block (wished time)",
),
params.dt_diff_eval,
QCoreApplication.translate(
"ToolbarView",
"The number of blocks required to evaluate again PoWMin value",
),
params.percent_rot,
QCoreApplication.translate(
"ToolbarView",
"The percent of previous issuers to reach for personalized difficulty",
),
)
def show_about(self, text):
dialog = QDialog(self)
about_dialog = Ui_AboutPopup()
about_dialog.setupUi(dialog)
about_dialog.label.setText(text)
dialog.exec()
from PyQt5.QtWidgets import QMainWindow
from .mainwindow_uic import Ui_MainWindow
class MainWindowView(QMainWindow, Ui_MainWindow):
"""
The model of Navigation component
"""
def __init__(self):
super().__init__(None)
self.setupUi(self)
from PyQt5.QtCore import pyqtSignal, QObject, Qt, QCoreApplication
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QMenu, QAction, QMessageBox
from duniterpy.key import SigningKey
from sakia.data.entities import Connection
from sakia.decorators import asyncify
from sakia.gui.sub.password_input import PasswordInputController
from sakia.gui.widgets import toast
from sakia.gui.widgets.dialogs import (
QAsyncMessageBox,
dialog_async_exec,
QAsyncFileDialog,
)
from sakia.models.generic_tree import GenericTreeModel
from .graphs.wot.controller import WotController
from .homescreen.controller import HomeScreenController
from .identities.controller import IdentitiesController
from .identity.controller import IdentityController
from .model import NavigationModel
from .network.controller import NetworkController
from .txhistory.controller import TxHistoryController
from .view import NavigationView
class NavigationController(QObject):
"""
The navigation panel
"""
currency_changed = pyqtSignal(str)
connection_changed = pyqtSignal(Connection)
def __init__(self, parent, view, model):
"""
Constructor of the navigation component
:param sakia.gui.navigation.view.NavigationView view: the view
:param sakia.gui.navigation.model.NavigationModel model: the model
"""
super().__init__(parent)
self.view = view
self.model = model
self.components = {
"TxHistory": TxHistoryController,
"HomeScreen": HomeScreenController,
"Network": NetworkController,
"Identities": IdentitiesController,
"Informations": IdentityController,
"Wot": WotController,
}
self.view.current_view_changed.connect(self.handle_view_change)
self.view.setContextMenuPolicy(Qt.CustomContextMenu)
self.view.customContextMenuRequested.connect(self.tree_context_menu)
self._components_controllers = []
@classmethod
def create(cls, parent, app):
"""
Instanciate a navigation component
:param sakia.app.Application app: the application
:return: a new Navigation controller
:rtype: NavigationController
"""
view = NavigationView(None)
model = NavigationModel(None, app)
navigation = cls(parent, view, model)
model.setParent(navigation)
navigation.init_navigation()
app.new_connection.connect(navigation.add_connection)
app.view_in_wot.connect(navigation.open_wot_view)
app.identity_changed.connect(navigation.handle_identity_change)
return navigation
def open_network_view(self, _):
raw_data = self.model.get_raw_data("Network")
if raw_data:
widget = raw_data["widget"]
if self.view.stacked_widget.indexOf(widget) != -1:
self.view.stacked_widget.setCurrentWidget(widget)
self.view.current_view_changed.emit(raw_data)
return
def open_wot_view(self, _):
raw_data = self.model.get_raw_data("Wot")
if raw_data:
widget = raw_data["widget"]
if self.view.stacked_widget.indexOf(widget) != -1:
self.view.stacked_widget.setCurrentWidget(widget)
self.view.current_view_changed.emit(raw_data)
return
def open_identities_view(self, _):
raw_data = self.model.get_raw_data("Identities")
if raw_data:
widget = raw_data["widget"]
if self.view.stacked_widget.indexOf(widget) != -1:
self.view.stacked_widget.setCurrentWidget(widget)
self.view.current_view_changed.emit(raw_data)
return
def parse_node(self, node_data):
if "component" in node_data:
component_class = self.components[node_data["component"]]
component = component_class.create(
self, self.model.app, **node_data["dependencies"]
)
self._components_controllers.append(component)
widget = self.view.add_widget(component.view)
node_data["widget"] = widget
if "children" in node_data:
for child in node_data["children"]:
self.parse_node(child)
def init_navigation(self):
self.model.init_navigation_data()
for node in self.model.navigation:
self.parse_node(node)
self.view.set_model(self.model)
def handle_identity_change(self, identity):
node = self.model.handle_identity_change(identity)
if node:
self.view.update_connection(node)
def handle_view_change(self, raw_data):
"""
Handle view change
:param dict raw_data:
:return:
"""
user_identity = raw_data.get("user_identity", None)
currency = raw_data.get("currency", None)
if user_identity != self.model.current_data("user_identity"):
self.account_changed.emit(user_identity)
if currency != self.model.current_data("currency"):
self.currency_changed.emit(currency)
self.model.set_current_data(raw_data)
def add_connection(self, connection):
raw_node = self.model.add_connection(connection)
self.view.add_connection(raw_node)
self.parse_node(raw_node)
def tree_context_menu(self, point):
mapped = self.view.splitter.mapFromParent(point)
index = self.view.tree_view.indexAt(mapped)
raw_data = self.view.tree_view.model().data(
index, GenericTreeModel.ROLE_RAW_DATA
)
if raw_data:
menu = QMenu(self.view)
if raw_data["misc"]["connection"].uid:
action_view_in_wot = QAction(
QCoreApplication.translate(
"NavigationController", "View in Web of Trust"
),
menu,
)
menu.addAction(action_view_in_wot)
action_view_in_wot.triggered.connect(
lambda c: self.model.view_in_wot(raw_data["misc"]["connection"])
)
action_gen_revocation = QAction(
QCoreApplication.translate(
"NavigationController", "Save revocation document"
),
menu,
)
menu.addAction(action_gen_revocation)
action_gen_revocation.triggered.connect(
lambda c: self.action_save_revocation(
raw_data["misc"]["connection"]
)
)
action_publish_uid = QAction(
QCoreApplication.translate("NavigationController", "Publish UID"),
menu,
)
menu.addAction(action_publish_uid)
action_publish_uid.triggered.connect(
lambda c: self.publish_uid(raw_data["misc"]["connection"])
)
identity_published = self.model.identity_published(
raw_data["misc"]["connection"]
)
action_publish_uid.setEnabled(not identity_published)
action_export_identity = QAction(
QCoreApplication.translate(
"NavigationController", "Export identity document"
),
menu,
)
menu.addAction(action_export_identity)
action_export_identity.triggered.connect(
lambda c: self.export_identity_document(
raw_data["misc"]["connection"]
)
)
action_leave = QAction(
QCoreApplication.translate(
"NavigationController", "Leave the currency"
),
menu,
)
menu.addAction(action_leave)
action_leave.triggered.connect(
lambda c: self.send_leave(raw_data["misc"]["connection"])
)
action_leave.setEnabled(
self.model.identity_is_member(raw_data["misc"]["connection"])
)
copy_pubkey = QAction(
QCoreApplication.translate(
"NavigationController", "Copy pubkey to clipboard"
),
menu.parent(),
)
copy_pubkey.triggered.connect(
lambda checked, c=raw_data["misc"][
"connection"
]: NavigationModel.copy_pubkey_to_clipboard(c)
)
menu.addAction(copy_pubkey)
copy_pubkey_crc = QAction(
QCoreApplication.translate(
"NavigationController", "Copy pubkey to clipboard (with CRC)"
),
menu.parent(),
)
copy_pubkey_crc.triggered.connect(
lambda checked, c=raw_data["misc"][
"connection"
]: NavigationModel.copy_pubkey_to_clipboard_with_crc(c)
)
menu.addAction(copy_pubkey_crc)
action_remove = QAction(
QCoreApplication.translate(
"NavigationController", "Remove the Sakia account"
),
menu,
)
menu.addAction(action_remove)
action_remove.triggered.connect(
lambda c: self.remove_connection(raw_data["misc"]["connection"])
)
# Show the context menu.
menu.popup(QCursor.pos())
@asyncify
async def publish_uid(self, connection):
identity = self.model.generate_identity(connection)
identity_doc = identity.document()
if not identity_doc.signatures:
secret_key, password = await PasswordInputController.open_dialog(
self, connection
)
if not password or not secret_key:
return
key = SigningKey.from_credentials(
secret_key, password, connection.scrypt_params
)
identity_doc.sign([key])
identity.signature = identity_doc.signatures[0]
self.model.update_identity(identity)
result = await self.model.send_identity(connection, identity_doc)
if result[0]:
if self.model.notifications():
toast.display(
QCoreApplication.translate("NavigationController", "UID"),
QCoreApplication.translate(
"NavigationController", "Success publishing your UID"
),
)
else:
await QAsyncMessageBox.information(
self.view,
QCoreApplication.translate("NavigationController", "UID"),
QCoreApplication.translate(
"NavigationController", "Success publishing your UID"
),
)
else:
if not self.model.notifications():
toast.display(
QCoreApplication.translate("NavigationController", "UID"), result[1]
)
else:
await QAsyncMessageBox.critical(
self.view,
QCoreApplication.translate("NavigationController", "UID"),
result[1],
)
@asyncify
async def send_leave(self):
reply = await QAsyncMessageBox.warning(
self,
QCoreApplication.translate("NavigationController", "Warning"),
QCoreApplication.translate(
"NavigationController",
"""Are you sure?
Sending a leaving demand cannot be canceled.
The process to join back the community later will have to be done again.""",
).format(self.account.pubkey),
QMessageBox.Ok | QMessageBox.Cancel,
)
if reply == QMessageBox.Ok:
connection = self.model.navigation_model.navigation.current_connection()
secret_key, password = await PasswordInputController.open_dialog(
self, connection
)
if not password or not secret_key:
return
result = await self.model.send_leave(connection, secret_key, password)
if result[0]:
if self.app.preferences["notifications"]:
toast.display(
QCoreApplication.translate("NavigationController", "Revoke"),
QCoreApplication.translate(
"NavigationController", "Success sending Revoke demand"
),
)
else:
await QAsyncMessageBox.information(
self,
QCoreApplication.translate("NavigationController", "Revoke"),
QCoreApplication.translate(
"NavigationController", "Success sending Revoke demand"
),
)
else:
if self.app.preferences["notifications"]:
toast.display(
QCoreApplication.translate("NavigationController", "Revoke"),
result[1],
)
else:
await QAsyncMessageBox.critical(
self,
QCoreApplication.translate("NavigationController", "Revoke"),
result[1],
)
@asyncify
async def remove_connection(self, connection):
reply = await QAsyncMessageBox.question(
self.view,
QCoreApplication.translate(
"NavigationController", "Removing the Sakia account"
),
QCoreApplication.translate(
"NavigationController",
"""Are you sure? This won't remove your money
neither your identity from the network.""",
),
QMessageBox.Ok | QMessageBox.Cancel,
)
if reply == QMessageBox.Ok:
await self.model.remove_connection(connection)
self.init_navigation()
@asyncify
async def action_save_revocation(self, connection):
secret_key, password = await PasswordInputController.open_dialog(
self, connection
)
if not password or not secret_key:
return
raw_document, _ = self.model.generate_revocation(
connection, secret_key, password
)
# Testable way of using a QFileDialog
selected_files = await QAsyncFileDialog.get_save_filename(
self.view,
QCoreApplication.translate(
"NavigationController", "Save a revocation document"
),
"revocation-{uid}-{pubkey}-{currency}.txt".format(
uid=connection.uid,
pubkey=connection.pubkey[:8],
currency=connection.currency,
),
QCoreApplication.translate(
"NavigationController", "All text files (*.txt)"
),
)
if selected_files:
path = selected_files[0]
if not path.endswith(".txt"):
path = "{0}.txt".format(path)
with open(path, "w") as save_file:
save_file.write(raw_document)
dialog = QMessageBox(
QMessageBox.Information,
QCoreApplication.translate("NavigationController", "Revocation file"),
QCoreApplication.translate(
"NavigationController",
"""<div>Your revocation document has been saved.</div>
<div><b>Please keep it in a safe place.</b></div>
The publication of this document will revoke your identity on the network.</p>""",
),
QMessageBox.Ok,
)
dialog.setTextFormat(Qt.RichText)
await dialog_async_exec(dialog)
@asyncify
async def export_identity_document(self, connection):
identity = self.model.generate_identity(connection)
identity_doc = identity.document()
if not identity_doc.signatures[0]:
secret_key, password = await PasswordInputController.open_dialog(
self, connection
)
if not password or not secret_key:
return
key = SigningKey.from_credentials(
secret_key, password, connection.scrypt_params
)
identity_doc.sign([key])
identity.signature = identity_doc.signatures[0]
self.model.update_identity(identity)
selected_files = await QAsyncFileDialog.get_save_filename(
self.view,
QCoreApplication.translate(
"NavigationController", "Save an identity document"
),
"identity-{uid}-{pubkey}-{currency}.txt".format(
uid=connection.uid,
pubkey=connection.pubkey[:8],
currency=connection.currency,
),
QCoreApplication.translate(
"NavigationController", "All text files (*.txt)"
),
)
if selected_files:
path = selected_files[0]
if not path.endswith(".txt"):
path = "{0}.txt".format(path)
with open(path, "w") as save_file:
save_file.write(identity_doc.signed_raw())
dialog = QMessageBox(
QMessageBox.Information,
QCoreApplication.translate("NavigationController", "Identity file"),
QCoreApplication.translate(
"NavigationController",
"""<div>Your identity document has been saved.</div>
Share this document to your friends for them to certify you.</p>""",
),
QMessageBox.Ok,
)
dialog.setTextFormat(Qt.RichText)
await dialog_async_exec(dialog)
import asyncio
from PyQt5.QtCore import pyqtSlot, QObject
from PyQt5.QtGui import QCursor
from sakia.decorators import asyncify, once_at_a_time
from sakia.gui.widgets.context_menu import ContextMenu
class BaseGraphController(QObject):
"""
The homescreen view
"""
def __init__(self, parent, view, model, password_asker):
"""
Constructor of the homescreen component
:param sakia.gui.homescreen.view.HomeScreenView: the view
:param sakia.gui.homescreen.model.HomeScreenModel model: the model
"""
super().__init__(parent)
self.view = view
self.model = model
self.password_asker = password_asker
def set_scene(self, scene):
"""
Set the scene and connects the signals
:param sakia.gui.views.scenes.base_scene.BaseScene scene: the scene
:return:
"""
# add scene events
scene.node_context_menu_requested.connect(self.node_context_menu)
scene.node_clicked.connect(self.handle_node_click)
@pyqtSlot(str, dict)
def handle_node_click(self, pubkey, metadata):
asyncio.ensure_future(self.draw_graph(metadata["identity"]))
async def draw_graph(self, identity):
"""
Draw community graph centered on the identity
:param sakia.core.registry.Identity identity: Graph node identity
"""
raise NotImplementedError("draw_graph not implemented")
@once_at_a_time
@asyncify
async def reset(self, checked=False):
"""
Reset graph scene to wallet identity
"""
raise NotImplementedError("reset not implemented")
@once_at_a_time
@asyncify
def refresh(self):
"""
Refresh graph scene to current metadata
"""
raise NotImplementedError("refresh not implemented")
def node_context_menu(self, identity):
"""
Open the node context menu
:param sakia.data.entities.Identity identity: the identity of the node to open
"""
menu = ContextMenu.from_data(self.view, self.model.app, None, (identity,))
menu.view_identity_in_wot.connect(self.draw_graph)
# Show the context menu.
menu.qmenu.popup(QCursor.pos())
from PyQt5.QtWidgets import QGraphicsLineItem
from PyQt5.QtCore import Qt, QPointF
class BaseEdge(QGraphicsLineItem):
def __init__(self, source_node, destination_node, metadata, nx_pos):
"""
Create an arc between two nodes
:param str source_node: Source node id of the arc
:param str destination_node: Destination node id of the arc
:param dict metadata: Edge metadata
:param dict nx_pos: The position generated by nx_graph
"""
super().__init__()
self.metadata = metadata
self.source = source_node
self.destination = destination_node
self.status = self.metadata["status"]
self.source_point = QPointF(nx_pos[self.source][0], nx_pos[self.source][1])
self.destination_point = QPointF(
nx_pos[self.destination][0], nx_pos[self.destination][1]
)
self.setAcceptedMouseButtons(Qt.NoButton)
from PyQt5.QtCore import QObject
class BaseGraphModel(QObject):
"""
The model of Navigation component
"""
def __init__(self, parent, app, blockchain_service, identities_service):
"""
Constructor of a model of WoT component
:param sakia.gui.identities.controller.IdentitiesController parent: the controller
:param sakia.app.Application app: the app
:param sakia.services.BlockchainService blockchain_service: the blockchain service
:param sakia.services.IdentitiesService identities_service: the identities service
"""
super().__init__(parent)
self.app = app
self.blockchain_service = blockchain_service
self.identities_service = identities_service
def get_identity(self, pubkey):
"""
Get identity from pubkey
:param str pubkey: Identity pubkey
:rtype: sakia.core.registry.Identity
"""
return self.identities_service.get_identity(pubkey, self.app.currency)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import (
QGraphicsEllipseItem,
QGraphicsSceneHoverEvent,
QGraphicsSceneContextMenuEvent,
)
from sakia.data.graphs.constants import NodeStatus
class BaseNode(QGraphicsEllipseItem):
def __init__(self, nx_node, pos):
"""
Create node in the graph scene
:param tuple nx_node: Node info
:param x_y: Position of the node
"""
super().__init__()
self.metadata = nx_node[1]["attr_dict"]
self.id = nx_node[0]
# unpack tuple
x, y = pos[nx_node[0]]
self.setPos(x, y)
self.status_wallet = self.metadata["status"] & NodeStatus.HIGHLIGHTED
self.status_member = not self.metadata["status"] & NodeStatus.OUT
self.text = self.metadata["text"]
self.setToolTip(self.text + " - " + self.metadata["tooltip"])
self.arcs = []
self.menu = None
self.action_sign = None
self.action_transaction = None
self.action_contact = None
self.action_show_member = None
def update_metadata(self, metadata):
self.metadata = metadata
self.status_wallet = self.metadata["status"] & NodeStatus.HIGHLIGHTED
self.status_member = not self.metadata["status"] & NodeStatus.OUT
self.text = self.metadata["text"]
self.setToolTip(self.text + " - " + self.metadata["tooltip"])
def mousePressEvent(self, event: QMouseEvent):
"""
Click on mouse button
:param event: mouse event
"""
if event.button() == Qt.LeftButton:
# trigger scene signal
self.scene().node_clicked.emit(self.id, self.metadata)
def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
"""
Mouse enter on node zone
:param event: scene hover event
"""
self.setCursor(Qt.ArrowCursor)
self.scene().node_hovered.emit(self.id)
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent):
"""
Right click on node to show node menu
Except on wallet node
:param event: scene context menu event
"""
self.scene().node_context_menu_requested.emit(self.metadata["identity"])