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 1665 additions and 0 deletions
import asyncio
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtCore import QObject, QCoreApplication
from .model import PluginsManagerModel
from .view import PluginsManagerView
import attr
@attr.s()
class PluginsManagerController(QObject):
"""
The PluginManager view
"""
view = attr.ib()
model = attr.ib()
def __attrs_post_init__(self):
super().__init__()
self.view.button_box.rejected.connect(self.view.close)
@classmethod
def create(cls, parent, app):
"""
Instanciate a PluginManager component
:param sakia.gui.component.controller.ComponentController parent:
:param sakia.app.Application app: sakia application
:return: a new PluginManager controller
:rtype: PluginManagerController
"""
view = PluginsManagerView(parent.view if parent else None)
model = PluginsManagerModel(app)
plugin = cls(view, model)
view.set_table_plugins_model(model.init_plugins_table())
view.button_delete.clicked.connect(plugin.delete_plugin)
view.button_install.clicked.connect(plugin.install_plugin)
return plugin
@classmethod
def open_dialog(cls, parent, app):
"""
Certify and identity
:param sakia.gui.component.controller.ComponentController parent: the parent
:param sakia.core.Application app: the application
:param sakia.core.Account account: the account certifying the identity
:param sakia.core.Community community: the community
:return:
"""
dialog = cls.create(parent, app)
return dialog.exec()
def delete_plugin(self):
plugin_index = self.view.selected_plugin_index()
plugin = self.model.plugin(plugin_index)
self.model.delete_plugin(plugin)
def install_plugin(self):
filename = QFileDialog.getOpenFileName(
self.view,
QCoreApplication.translate("PluginsManagerController", "Open File"),
"",
QCoreApplication.translate(
"PluginsManagerController", "Sakia module (*.zip)"
),
)
if filename[0]:
self.model.install_plugin(filename[0])
def async_exec(self):
future = asyncio.Future()
self.view.finished.connect(lambda r: future.set_result(r))
self.view.open()
return future
def exec(self):
self.view.exec()
from PyQt5.QtCore import QObject, Qt
from .table_model import PluginsTableModel, PluginsFilterProxyModel
import attr
@attr.s()
class PluginsManagerModel(QObject):
"""
The model of Plugin component
:param sakia.app.Application app: The application
"""
app = attr.ib()
def __attrs_post_init__(self):
super().__init__()
def init_plugins_table(self):
"""
Generates a plugins table model
:return:
"""
self._model = PluginsTableModel(self, self.app)
self._proxy = PluginsFilterProxyModel(self)
self._proxy.setSourceModel(self._model)
self._proxy.setDynamicSortFilter(True)
self._proxy.setSortRole(Qt.DisplayRole)
self._model.init_plugins()
return self._proxy
def delete_plugin(self, plugin):
self.app.plugins_dir.uninstall_plugin(plugin)
self._model.remove_plugin(plugin)
def plugin(self, index):
plugin_name = self._proxy.plugin_name(index)
if plugin_name:
try:
return next(
p for p in self.app.plugins_dir.plugins if p.name == plugin_name
)
except StopIteration:
pass
return None
def install_plugin(self, filename):
try:
plugin = self.app.plugins_dir.install_plugin(filename)
self._model.add_plugin(plugin)
except OSError as e:
self.view.show_error(str(e))
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CertificationDialog</class>
<widget class="QDialog" name="CertificationDialog">
<class>PluginDialog</class>
<widget class="QDialog" name="PluginDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>399</width>
<height>216</height>
<width>629</width>
<height>316</height>
</rect>
</property>
<property name="windowTitle">
<string>Certification</string>
<string>Plugins manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Community</string>
<string>Installed plugins list</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QComboBox" name="combo_community"/>
<widget class="QTableView" name="table_plugins">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Certify user</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="topMargin">
<number>6</number>
</property>
<item>
<widget class="QRadioButton" name="radio_contact">
<property name="text">
<string>Contact</string>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="checked">
<bool>true</bool>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</widget>
</spacer>
</item>
<item>
<widget class="QComboBox" name="combo_contact">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="radio_pubkey">
<widget class="QPushButton" name="button_install">
<property name="text">
<string>User public key</string>
<string>Install a new plugin</string>
</property>
<property name="checked">
<bool>false</bool>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_pubkey">
<property name="enabled">
<bool>false</bool>
</property>
<property name="inputMask">
<string/>
</property>
<widget class="QPushButton" name="button_delete">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Key</string>
<string>Uninstall selected plugin</string>
</property>
</widget>
</item>
......@@ -88,83 +75,23 @@
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="enabled">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>CertificationDialog</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>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>CertificationDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>radio_pubkey</sender>
<signal>toggled(bool)</signal>
<receiver>CertificationDialog</receiver>
<slot>recipient_mode_changed(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>87</x>
<y>51</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>244</y>
</hint>
</hints>
</connection>
<connection>
<sender>combo_community</sender>
<signal>currentIndexChanged(int)</signal>
<receiver>CertificationDialog</receiver>
<slot>change_current_community(int)</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>50</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>165</y>
</hint>
</hints>
</connection>
</connections>
<resources>
<include location="../../../../../res/icons/sakia.icons.qrc"/>
</resources>
<connections/>
<slots>
<slot>open_manage_wallet_coins()</slot>
<slot>change_displayed_wallet(int)</slot>
......
from PyQt5.QtCore import (
QAbstractTableModel,
Qt,
QVariant,
QSortFilterProxyModel,
QModelIndex,
QT_TRANSLATE_NOOP,
QCoreApplication,
)
from sakia.data.entities import Plugin
class PluginsFilterProxyModel(QSortFilterProxyModel):
def __init__(self, parent):
"""
History of all transactions
:param PyQt5.QtWidgets.QWidget parent: parent widget
"""
super().__init__(parent)
self.app = None
def columnCount(self, parent):
return self.sourceModel().columnCount(None) - 1
def setSourceModel(self, source_model):
self.app = source_model.app
super().setSourceModel(source_model)
def lessThan(self, left, right):
"""
Sort table by given column number.
"""
source_model = self.sourceModel()
left_data = source_model.data(left, Qt.DisplayRole)
right_data = source_model.data(right, Qt.DisplayRole)
return left_data < right_data
def plugin_name(self, index):
"""
Gets available table data at given index
:param index:
:return: tuple containing (Identity, Transfer)
"""
if index.isValid() and index.row() < self.rowCount(QModelIndex()):
source_index = self.mapToSource(index)
plugin_name_col = PluginsTableModel.columns_types.index("name")
plugin_name = self.sourceModel().plugins_data[source_index.row()][
plugin_name_col
]
return plugin_name
return None
def data(self, index, role):
source_index = self.mapToSource(index)
model = self.sourceModel()
source_data = model.data(source_index, role)
return source_data
class PluginsTableModel(QAbstractTableModel):
"""
A Qt abstract item model to display plugins in a table view
"""
columns_types = ("name", "description", "version", "imported")
columns_headers = (
QT_TRANSLATE_NOOP("PluginsTableModel", "Name"),
QT_TRANSLATE_NOOP("PluginsTableModel", "Description"),
QT_TRANSLATE_NOOP("PluginsTableModel", "Version"),
QT_TRANSLATE_NOOP("PluginsTableModel", "Imported"),
)
def __init__(self, parent, app):
"""
History of all transactions
:param PyQt5.QtWidgets.QWidget parent: parent widget
:param sakia.app.Application app: the main application
"""
super().__init__(parent)
self.app = app
self.plugins_data = []
def add_plugin(self, plugin):
self.beginInsertRows(
QModelIndex(), len(self.plugins_data), len(self.plugins_data)
)
self.plugins_data.append(self.data_plugin(plugin))
self.endInsertRows()
def remove_plugin(self, plugin):
for i, data in enumerate(self.plugins_data):
if data[PluginsTableModel.columns_types.index("name")] == plugin.name:
self.beginRemoveRows(QModelIndex(), i, i)
self.plugins_data.pop(i)
self.endRemoveRows()
return
def data_plugin(self, plugin):
"""
Converts a plugin to table data
:param sakia.data.entities.Plugin plugin: the plugin
:return: data as tuple
"""
return plugin.name, plugin.description, plugin.version, plugin.imported
def init_plugins(self):
self.beginResetModel()
self.plugins_data = []
for plugin in self.app.plugins_dir.plugins:
self.plugins_data.append(self.data_plugin(plugin))
self.endResetModel()
def rowCount(self, parent):
return len(self.plugins_data)
def columnCount(self, parent):
return len(PluginsTableModel.columns_types)
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QCoreApplication.translate(
"PluginsTableModel", PluginsTableModel.columns_headers[section]
)
def data(self, index, role):
row = index.row()
col = index.column()
if not index.isValid():
return QVariant()
if role in (Qt.DisplayRole, Qt.ForegroundRole, Qt.ToolTipRole):
return self.plugins_data[row][col]
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
from PyQt5.QtWidgets import QDialog, QAbstractItemView, QHeaderView, QMessageBox
from PyQt5.QtCore import QModelIndex, QCoreApplication
from .plugins_manager_uic import Ui_PluginDialog
class PluginsManagerView(QDialog, Ui_PluginDialog):
"""
The view of the plugins manager component
"""
def __init__(self, parent):
"""
:param parent:
"""
super().__init__(parent)
self.setupUi(self)
def set_table_plugins_model(self, model):
"""
Define the table history model
:param QAbstractItemModel model:
:return:
"""
self.table_plugins.setModel(model)
self.table_plugins.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_plugins.setSortingEnabled(True)
self.table_plugins.horizontalHeader().setSectionResizeMode(
QHeaderView.Interactive
)
self.table_plugins.resizeRowsToContents()
self.table_plugins.verticalHeader().setSectionResizeMode(
QHeaderView.ResizeToContents
)
def selected_plugin_index(self):
indexes = self.table_plugins.selectedIndexes()
if indexes:
return indexes[0]
return QModelIndex()
def show_error(self, error_txt):
QMessageBox.critical(
self,
QCoreApplication.translate("PluginsManagerView", "Plugin import"),
QCoreApplication.translate(
"PluginsManagerView", "Could not import plugin: {0}".format(error_txt)
),
)
import asyncio
from PyQt5.QtCore import QObject
from duniterpy.documents import MalformedDocumentError
from sakia.decorators import asyncify
from .model import RevocationModel
from .view import RevocationView
class RevocationController(QObject):
"""
The revocation view
"""
def __init__(self, view, model):
"""
Constructor of the revocation component
:param sakia.gui.dialogs.revocation.view.revocationView: the view
:param sakia.gui.dialogs.revocation.model.revocationModel model: the model
"""
super().__init__()
self.view = view
self.model = model
self.view.button_next.clicked.connect(
lambda checked: self.handle_next_step(False)
)
self.view.button_load.clicked.connect(self.load_from_file)
self.view.spinbox_port.valueChanged.connect(self.refresh_revocation_info)
self.view.edit_address.textChanged.connect(self.refresh_revocation_info)
self.view.radio_address.toggled.connect(self.refresh_revocation_info)
self.view.radio_currency.toggled.connect(self.refresh_revocation_info)
self._steps = (
{
"page": self.view.page_load_file,
"init": lambda: None,
"next": lambda: None,
},
{
"page": self.view.page_destination,
"init": self.init_publication_page,
"next": self.publish,
},
)
self._current_step = 0
@classmethod
def create(cls, parent, app, connection):
"""
Instanciate a revocation component
:param sakia.app.Application app:
:return: a new revocation controller
:rtype: revocationController
"""
view = RevocationView(parent.view)
model = RevocationModel(app, connection)
revocation = cls(view, model)
return revocation
@classmethod
def open_dialog(cls, parent, app, connection):
"""
Certify and identity
:param sakia.gui.component.controller.ComponentController parent: the parent
:param sakia.app.Application app: the application
:param sakia.data.entities.Connection connection: the connection certifying the identity
:return:
"""
dialog = cls.create(parent, app, connection=connection)
dialog.handle_next_step(init=True)
return dialog.exec()
def handle_next_step(self, init=False):
if self._current_step < len(self._steps) - 1:
if not init:
self.view.button_next.clicked.disconnect(
self._steps[self._current_step]["next"]
)
self._current_step += 1
self._steps[self._current_step]["init"]()
self.view.stackedWidget.setCurrentWidget(
self._steps[self._current_step]["page"]
)
self.view.button_next.clicked.connect(
self._steps[self._current_step]["next"]
)
def load_from_file(self):
selected_file = self.view.select_revocation_file()
try:
self.model.load_revocation(selected_file)
self.view.show_revoked_selfcert(self.model.revoked_identity)
self.view.button_next.setEnabled(True)
except FileNotFoundError:
pass
except MalformedDocumentError:
self.view.malformed_file_error()
self.button_next.setEnabled(False)
def refresh_revocation_info(self):
self.view.refresh_revocation_label(self.model.revoked_identity)
def init_publication_page(self):
communities_names = self.model.currencies_names()
self.view.set_currencies_names(communities_names)
def publish(self):
self.view.button_next.setEnabled(False)
if self.view.ask_for_confirmation():
self.accept()
else:
self.view.button_next.setEnabled(True)
@asyncify
async def accept(self):
if self.view.radio_currency.isChecked():
index = self.view.combo_currency.currentIndex()
result, error = await self.model.broadcast_to_network(index)
else:
server = self.view.edit_address.text()
port = self.view.spinbox_port.value()
secured = self.view.checkbox_secured.isChecked()
result, error = await self.model.send_to_node(server, port, secured)
if result:
self.view.accept()
else:
await self.view.revocation_broadcast_error(error)
def async_exec(self):
future = asyncio.Future()
self.view.finished.connect(lambda r: future.set_result(r))
self.view.open()
self.refresh()
return future
def exec(self):
self.view.exec()
from duniterpy.documents import Revocation
from duniterpy.api.endpoint import BMAEndpoint, SecuredBMAEndpoint
from duniterpy.api import bma, errors
from sakia.data.connectors import NodeConnector
from asyncio import TimeoutError
from socket import gaierror
import jsonschema
from aiohttp import ClientError
from PyQt5.QtCore import QObject
import logging
class RevocationModel(QObject):
"""
The model of HomeScreen component
"""
def __init__(self, app, connection):
super().__init__()
self.app = app
self.connection = connection
self.revocation_document = None
self.revoked_identity = None
self._logger = logging.getLogger("sakia")
def load_revocation(self, path):
"""
Load a revocation document from a file
:param str path:
"""
with open(path, "r") as file:
file_content = file.read()
self.revocation_document = Revocation.from_signed_raw(file_content)
self.revoked_identity = Revocation.extract_self_cert(file_content)
def currencies_names(self):
return [c for c in self.app.db.connections_repo.get_currencies()]
async def broadcast_to_network(self, index):
currency = self.currencies_names()[index]
return await self.app.documents_service.broadcast_revocation(
currency, self.revoked_identity, self.revocation_document
)
async def send_to_node(self, server, port, secured):
signed_raw = self.revocation_document.signed_raw(self.revoked_identity)
node_connector = await NodeConnector.from_address(
None, secured, server, port, self.app.parameters
)
for endpoint in [
e
for e in node_connector.node.endpoints
if isinstance(e, BMAEndpoint) or isinstance(e, SecuredBMAEndpoint)
]:
try:
self._logger.debug("Broadcasting: \n" + signed_raw)
conn_handler = endpoint.conn_handler(
node_connector.session, proxy=self.app.parameters.proxy()
)
result = await bma.wot.revoke(conn_handler, signed_raw)
if result.status == 200:
return True, ""
else:
return False, bma.api.parse_error(await result.text())["message"]
except errors.DuniterError as e:
return False, e.message
except (
jsonschema.ValidationError,
ClientError,
gaierror,
TimeoutError,
ConnectionRefusedError,
ValueError,
) as e:
return False, str(e)
finally:
node_connector.session.close()
return True, ""
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CommunityConfigurationDialog</class>
<widget class="QDialog" name="CommunityConfigurationDialog">
<class>RevocationDialog</class>
<widget class="QDialog" name="RevocationDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>452</width>
<height>358</height>
</rect>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="windowTitle">
<string>Add a community</string>
<string>Revoke an identity</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QStackedWidget" name="stacked_pages">
<widget class="QStackedWidget" name="stackedWidget">
<property name="styleSheet">
<string notr="true">QGroupBox {
border: 1px solid gray;
border-radius: 9px;
margin-top: 0.5em;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 3px 0 3px;
font-weight: bold;
}</string>
</property>
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="page_init">
<layout class="QVBoxLayout" name="verticalLayout_4">
<widget class="QWidget" name="page_load_file">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
<property name="text">
<string>&lt;h2&gt;Select a revocation document&lt;/h1&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</spacer>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<widget class="QPushButton" name="button_load">
<property name="text">
<string>Please enter the address of a node :</string>
<string>Load from file</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="rightMargin">
<number>5</number>
<widget class="QGroupBox" name="groupBox">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="title">
<string>Revocation document</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_revocation_content">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_destination">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:x-large; font-weight:600;&quot;&gt;Select publication destination&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="lineedit_server"/>
<widget class="QRadioButton" name="radio_currency">
<property name="text">
<string>To a co&amp;mmunity</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_currency"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="label_double_dot">
<widget class="QRadioButton" name="radio_address">
<property name="text">
<string>:</string>
<string>&amp;To an address</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_address"/>
</item>
<item>
<widget class="QSpinBox" name="spinbox_port">
<property name="maximum">
<number>25000</number>
<number>65535</number>
</property>
<property name="value">
<number>8081</number>
<number>8201</number>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_secured">
<property name="text">
<string>SSL/TLS</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_add_nodes">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Communities nodes</string>
<string>Revocation information</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QTreeView" name="tree_peers">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
<widget class="QLabel" name="label_revocation_info">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="lineedit_add_address">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Server</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinbox_add_port">
<property name="minimum">
<number>1025</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
<property name="value">
<number>8081</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_add">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
</layout>
<widget class="QLabel" name="label_target">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
......@@ -149,19 +201,12 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="button_previous">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Previous</string>
</property>
</widget>
</item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>6</number>
</property>
<item>
<spacer name="horizontalSpacer_2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
......@@ -175,9 +220,6 @@
</item>
<item>
<widget class="QPushButton" name="button_next">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Next</string>
</property>
......@@ -188,78 +230,19 @@
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>button_add</sender>
<signal>clicked()</signal>
<receiver>CommunityConfigurationDialog</receiver>
<slot>add_node()</slot>
<hints>
<hint type="sourcelabel">
<x>337</x>
<y>236</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>tree_peers</sender>
<signal>customContextMenuRequested(QPoint)</signal>
<receiver>CommunityConfigurationDialog</receiver>
<slot>showContextMenu(QPoint)</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>128</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_next</sender>
<signal>clicked()</signal>
<receiver>CommunityConfigurationDialog</receiver>
<slot>next()</slot>
<hints>
<hint type="sourcelabel">
<x>349</x>
<y>278</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_previous</sender>
<signal>clicked()</signal>
<receiver>CommunityConfigurationDialog</receiver>
<slot>previous()</slot>
<hints>
<hint type="sourcelabel">
<x>49</x>
<y>278</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
<slots>
<slot>add_node()</slot>
<slot>showContextMenu(QPoint)</slot>
<slot>check()</slot>
<slot>open_process_add_community()</slot>
<slot>key_changed(int)</slot>
<slot>action_remove_community()</slot>
<slot>open_process_edit_community(QModelIndex)</slot>
<slot>next()</slot>
<slot>previous()</slot>
<slot>current_wallet_changed(int)</slot>
<slot>open_import_key()</slot>
<slot>open_generate_account_key()</slot>
<slot>action_edit_account_key()</slot>
<slot>action_edit_account_parameters()</slot>
<slot>action_show_pubkey()</slot>
<slot>action_delete_account()</slot>
</slots>
</ui>
from enum import Enum
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QDialog, QFileDialog, QMessageBox
from sakia.decorators import asyncify
from sakia.gui.widgets.dialogs import QAsyncMessageBox
from .revocation_uic import Ui_RevocationDialog
class RevocationView(QDialog, Ui_RevocationDialog):
"""
Home screen view
"""
class PublicationMode(Enum):
ADDRESS = 0
COMMUNITY = 1
def __init__(self, parent):
"""
Constructor
"""
super().__init__(parent)
self.setupUi(self)
self.button_next.setEnabled(False)
self.radio_address.toggled.connect(
lambda c: self.publication_mode_changed(
RevocationView.PublicationMode.ADDRESS
)
)
self.radio_currency.toggled.connect(
lambda c: self.publication_mode_changed(
RevocationView.PublicationMode.COMMUNITY
)
)
def publication_mode_changed(self, radio):
self.edit_address.setEnabled(radio == RevocationView.PublicationMode.ADDRESS)
self.spinbox_port.setEnabled(radio == RevocationView.PublicationMode.ADDRESS)
self.combo_currency.setEnabled(
radio == RevocationView.PublicationMode.COMMUNITY
)
def refresh_target(self):
if self.radio_currency.isChecked():
target = QCoreApplication.translate(
"RevocationView",
"All nodes of currency {name}".format(
name=self.combo_currency.currentText()
),
)
elif self.radio_address.isChecked():
target = QCoreApplication.translate(
"RevocationView",
"Address {address}:{port}".format(
address=self.edit_address.text(), port=self.spinbox_port.value()
),
)
else:
target = ""
self.label_target.setText(
"""
<h4>Publication address</h4>
<div>{target}</div>
""".format(
target=target
)
)
def refresh_revocation_label(self, revoked_identity):
if revoked_identity:
text = QCoreApplication.translate(
"RevocationView",
"""
<div>Identity revoked: {uid} (public key: {pubkey}...)</div>
<div>Identity signed on block: {timestamp}</div>
""".format(
uid=revoked_identity.uid,
pubkey=revoked_identity.pubkey[:12],
timestamp=revoked_identity.timestamp,
),
)
self.label_revocation_content.setText(text)
if self.radio_currency.isChecked():
target = QCoreApplication.translate(
"RevocationView",
"All nodes of currency {name}".format(
name=self.combo_currency.currentText()
),
)
elif self.radio_address.isChecked():
target = QCoreApplication.translate(
"RevocationView",
"Address {address}:{port}".format(
address=self.edit_address.text(), port=self.spinbox_port.value()
),
)
else:
target = ""
self.label_revocation_info.setText(
"""
<h4>Revocation document</h4>
<div>{text}</div>
<h4>Publication address</h4>
<div>{target}</div>
""".format(
text=text, target=target
)
)
else:
self.label_revocation_content.setText("")
def select_revocation_file(self):
"""
Get a revocation file using a file dialog
:rtype: str
"""
selected_files = QFileDialog.getOpenFileName(
self,
QCoreApplication.translate("RevocationView", "Load a revocation file"),
"",
QCoreApplication.translate("RevocationView", "All text files (*.txt)"),
)
selected_file = selected_files[0]
return selected_file
def malformed_file_error(self):
QMessageBox.critical(
self,
QCoreApplication.translate("RevocationView", "Error loading document"),
QCoreApplication.translate(
"RevocationView", "Loaded document is not a revocation document"
),
buttons=QMessageBox.Ok,
)
async def revocation_broadcast_error(self, error):
await QAsyncMessageBox.critical(
self,
QCoreApplication.translate("RevocationView", "Error broadcasting document"),
error,
)
def show_revoked_selfcert(self, selfcert):
text = QCoreApplication.translate(
"RevocationView",
"""
<div>Identity revoked: {uid} (public key: {pubkey}...)</div>
<div>Identity signed on block: {timestamp}</div>
""".format(
uid=selfcert.uid,
pubkey=selfcert.pubkey[:12],
timestamp=selfcert.timestamp,
),
)
self.label_revocation_content.setText(text)
def set_currencies_names(self, names):
self.combo_currency.clear()
for name in names:
self.combo_currency.addItem(name)
self.radio_currency.setChecked(True)
def ask_for_confirmation(self):
answer = QMessageBox.warning(
self,
QCoreApplication.translate("RevocationView", "Revocation"),
QCoreApplication.translate(
"RevocationView",
"""<h4>The publication of this document will revoke your identity on the network.</h4>
<li>
<li> <b>This identity won't be able to join the WoT anymore.</b> </li>
<li> <b>This identity won't be able to generate Universal Dividends anymore.</b> </li>
<li> <b>This identity won't be able to certify identities anymore.</b> </li>
</li>
Please think twice before publishing this document.
""",
),
QMessageBox.Ok | QMessageBox.Cancel,
)
return answer == QMessageBox.Ok
@asyncify
async def accept(self):
await QAsyncMessageBox.information(
self,
QCoreApplication.translate("RevocationView", "Revocation broadcast"),
QCoreApplication.translate(
"RevocationView", "The document was successfully broadcasted."
),
)
super().accept()
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StartupDialog</class>
<widget class="QDialog" name="StartupDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>362</width>
<height>95</height>
</rect>
</property>
<property name="windowTitle">
<string>Sakia</string>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>341</width>
<height>81</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_3">
<property name="maximumSize">
<size>
<width>50</width>
<height>50</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>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>Connecting to the network
please wait...</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QPushButton" name="cancelButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources>
<include location="../../../../res/icons/sakia.icons.qrc"/>
</resources>
<connections/>
</ui>
import logging
from PyQt5.QtCore import QEvent, pyqtSlot, QObject, QCoreApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QMessageBox, QApplication
from .model import MainWindowModel
from .status_bar.controller import StatusBarController
from .toolbar.controller import ToolbarController
from .view import MainWindowView
from ..navigation.controller import NavigationController
from ..widgets import toast
from ...__init__ import __version__
class MainWindowController(QObject):
"""
classdocs
"""
def __init__(self, view, model, status_bar, toolbar, navigation):
"""
Init
:param MainWindowView view: the ui of the mainwindow component
:param sakia.gui.main_window.model.MainWindowModel: the model of the mainwindow component
:param sakia.gui.main_window.status_bar.controller.StatusBarController status_bar: the controller of the status bar component
:param sakia.gui.main_window.toolbar.controller.ToolbarController toolbar: the controller of the toolbar component
:param sakia.gui.navigation.controller.NavigationController navigation: the controller of the navigation
:param PasswordAsker password_asker: the password asker of the application
:type: sakia.core.app.Application
"""
# Set up the user interface from Designer.
super().__init__()
self.view = view
self.model = model
self.initialized = False
self.status_bar = status_bar
self.toolbar = toolbar
self.navigation = navigation
self.stacked_widgets = {}
self.view.bottom_layout.insertWidget(0, self.navigation.view)
self.view.top_layout.addWidget(self.toolbar.view)
self.view.setStatusBar(self.status_bar.view)
QApplication.setWindowIcon(QIcon(":/icons/sakia_logo"))
@classmethod
def create(cls, app, status_bar, toolbar, navigation):
"""
Instanciate a navigation component
:param sakia.gui.status_bar.controller.StatusBarController status_bar: the controller of the status bar component
:param sakia.gui.toolbar.controller.ToolbarController toolbar: the controller of the toolbar component
:param sakia.gui.navigation.contoller.NavigationController navigation: the controller of the navigation
:return: a new Navigation controller
:rtype: MainWindowController
"""
view = MainWindowView()
model = MainWindowModel(None, app)
main_window = cls(view, model, status_bar, toolbar, navigation)
model.setParent(main_window)
return main_window
@classmethod
def startup(cls, app):
"""
:param sakia.app.Application app:
:return:
"""
navigation = NavigationController.create(None, app)
toolbar = ToolbarController.create(app, navigation)
main_window = cls.create(
app,
status_bar=StatusBarController.create(app),
navigation=navigation,
toolbar=toolbar,
)
toolbar.view.button_network.clicked.connect(navigation.open_network_view)
toolbar.view.button_identity.clicked.connect(navigation.open_identities_view)
toolbar.exit_triggered.connect(main_window.view.close)
# app.version_requested.connect(main_window.latest_version_requested)
# app.account_imported.connect(main_window.import_account_accepted)
# app.account_changed.connect(main_window.change_account)
if app.parameters.maximized:
main_window.view.showMaximized()
else:
main_window.view.show()
app.refresh_started.connect(main_window.status_bar.start_loading)
app.refresh_finished.connect(main_window.status_bar.stop_loading)
main_window.model.load_plugins(main_window)
main_window.refresh(app.currency)
return main_window
@pyqtSlot(str)
def display_error(self, error):
QMessageBox.critical(self, ":(", error, QMessageBox.Ok)
@pyqtSlot(int)
def referential_changed(self, index):
pass
@pyqtSlot()
def latest_version_requested(self):
latest = self.app.available_version
logging.debug("Latest version requested")
if not latest[0]:
version_info = QCoreApplication.translate(
"MainWindowController", "Please get the latest release {version}"
).format(version=latest[1])
version_url = latest[2]
if self.app.preferences["notifications"]:
toast.display(
"sakia",
"""{version_info}""".format(
version_info=version_info, version_url=version_url
),
)
def refresh(self, currency):
"""
Refresh main window
When the selected account changes, all the widgets
in the window have to be refreshed
"""
self.status_bar.refresh()
display_name = self.model.app.root_servers[currency]["display"]
self.view.setWindowTitle(
QCoreApplication.translate(
"MainWindowController", "sakia {0} - {1}"
).format(__version__, display_name)
)
def eventFilter(self, target, event):
"""
Event filter on the widget
:param QObject target: the target of the event
:param QEvent event: the event
:return: bool
"""
if target == self.widget:
if event.type() == QEvent.LanguageChange:
self.ui.retranslateUi(self)
self.refresh()
return self.widget.eventFilter(target, event)
return False
......@@ -6,82 +6,23 @@
<rect>
<x>0</x>
<y>0</y>
<width>681</width>
<height>549</height>
<width>982</width>
<height>584</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">CuteCoin</string>
<string notr="true">Sakia</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QTabWidget" name="currencies_tabwidget">
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
<layout class="QHBoxLayout" name="top_layout"/>
</item>
<item>
<layout class="QHBoxLayout" name="bottom_layout"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>681</width>
<height>25</height>
</rect>
</property>
<widget class="QMenu" name="menu_account">
<property name="title">
<string>Account</string>
</property>
<widget class="QMenu" name="menu_change_account">
<property name="title">
<string>Open</string>
</property>
</widget>
<addaction name="action_add_account"/>
<addaction name="menu_change_account"/>
<addaction name="action_configure_parameters"/>
<addaction name="action_set_as_default"/>
<addaction name="separator"/>
<addaction name="action_export"/>
<addaction name="action_import"/>
<addaction name="separator"/>
<addaction name="actionAbout"/>
<addaction name="action_quit"/>
</widget>
<widget class="QMenu" name="menu_contacts">
<property name="title">
<string>Contacts</string>
</property>
<widget class="QMenu" name="menu_contacts_list">
<property name="title">
<string>Contacts</string>
</property>
<addaction name="separator"/>
</widget>
<addaction name="separator"/>
<addaction name="action_add_a_contact"/>
<addaction name="menu_contacts_list"/>
</widget>
<widget class="QMenu" name="menu_actions">
<property name="title">
<string>Actions</string>
</property>
<addaction name="actionTransfer_money"/>
<addaction name="actionCertification"/>
</widget>
<addaction name="menu_account"/>
<addaction name="menu_contacts"/>
<addaction name="menu_actions"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionManage_accounts">
<property name="text">
<string>Manage accounts</string>
......@@ -94,7 +35,7 @@
</action>
<action name="action_add_a_contact">
<property name="text">
<string>Add a contact</string>
<string>A&amp;dd a contact</string>
</property>
</action>
<action name="actionSend_a_message">
......@@ -127,11 +68,6 @@
<string>Remove contact</string>
</property>
</action>
<action name="action_add_account">
<property name="text">
<string>Add</string>
</property>
</action>
<action name="action_save">
<property name="text">
<string>Save</string>
......@@ -139,7 +75,7 @@
</action>
<action name="action_quit">
<property name="text">
<string>Quit</string>
<string>&amp;Quit</string>
</property>
</action>
<action name="actionAccount">
......@@ -149,203 +85,64 @@
</action>
<action name="actionTransfer_money">
<property name="text">
<string>Transfer money</string>
<string>&amp;Transfer money</string>
</property>
</action>
<action name="action_configure_parameters">
<property name="text">
<string>Configure</string>
<string>&amp;Configure</string>
</property>
</action>
<action name="action_import">
<property name="text">
<string>Import</string>
<string>&amp;Import</string>
</property>
</action>
<action name="action_export">
<property name="text">
<string>Export</string>
<string>&amp;Export</string>
</property>
</action>
<action name="actionCertification">
<property name="text">
<string>Certification</string>
<string>C&amp;ertification</string>
</property>
</action>
<action name="action_set_as_default">
<property name="text">
<string>Set as default</string>
<string>&amp;Set as default</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
<string>A&amp;bout</string>
</property>
</action>
<action name="actionPreferences">
<property name="text">
<string>&amp;Preferences</string>
</property>
</action>
<action name="action_add_account">
<property name="text">
<string>&amp;Add account</string>
</property>
</action>
<action name="actionManage_local_node">
<property name="text">
<string>&amp;Manage local node</string>
</property>
</action>
<action name="action_revoke_identity">
<property name="text">
<string>&amp;Revoke an identity</string>
</property>
</action>
</widget>
<resources/>
<connections>
<connection>
<sender>action_add_account</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>open_add_account_dialog()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>225</x>
<y>199</y>
</hint>
</hints>
</connection>
<connection>
<sender>action_quit</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>225</x>
<y>199</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionTransfer_money</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>open_transfer_money_dialog()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>248</x>
<y>218</y>
</hint>
</hints>
</connection>
<connection>
<sender>action_add_a_contact</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>open_add_contact_dialog()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>248</x>
<y>218</y>
</hint>
</hints>
</connection>
<connection>
<sender>action_configure_parameters</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>open_configure_account_dialog()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>248</x>
<y>218</y>
</hint>
</hints>
</connection>
<connection>
<sender>action_import</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>import_account()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>248</x>
<y>218</y>
</hint>
</hints>
</connection>
<connection>
<sender>action_export</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>export_account()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>248</x>
<y>218</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionCertification</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>open_certification_dialog()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>248</x>
<y>218</y>
</hint>
</hints>
</connection>
<connection>
<sender>action_set_as_default</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>set_as_default_account()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>340</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionAbout</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>open_about_popup()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>340</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<resources>
<include location="../../../../res/icons/sakia.icons.qrc"/>
</resources>
<connections/>
<slots>
<slot>open_add_account_dialog()</slot>
<slot>import_account()</slot>
......@@ -356,7 +153,7 @@
<slot>refresh_wallet_content(QModelIndex)</slot>
<slot>export_account()</slot>
<slot>open_certification_dialog()</slot>
<slot>set_as_default_account()</slot>
<slot>open_preferences_dialog()</slot>
<slot>open_about_popup()</slot>
</slots>
</ui>
from PyQt5.QtCore import QObject
class MainWindowModel(QObject):
"""
The model of Navigation component
"""
def __init__(self, parent, app):
super().__init__(parent)
self.app = app
def load_plugins(self, main_window):
for plugin in self.app.plugins_dir.plugins:
if plugin.imported:
plugin.module.plugin_exec(self.app, main_window)
if self.app.plugins_dir.with_plugin and self.app.plugins_dir.with_plugin.module:
self.app.plugins_dir.with_plugin.module.plugin_exec(self.app, main_window)
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)