diff --git a/src/sakia/core/net/network.py b/src/sakia/core/net/network.py index eec21ff4c601e49a2b2bdd31b16904ec70ef8644..d28357f19db7823c2ef187a840953e7c25e17bc0 100644 --- a/src/sakia/core/net/network.py +++ b/src/sakia/core/net/network.py @@ -313,11 +313,11 @@ class Network(QObject): self._root_nodes.append(node) self.root_nodes_changed.emit() - def remove_root_node(self, index): + def remove_root_node(self, node): """ Remove a node from the root nodes list """ - self._root_nodes.pop(index) + self._root_nodes.remove(node) self.root_nodes_changed.emit() def is_root_node(self, node): diff --git a/src/sakia/gui/component/controller.py b/src/sakia/gui/component/controller.py index a98e7c0a910c0e69c13d2516c2e1d4cdf0bd6295..95689da4ced45e75952fed2498d9e75f551bdb80 100644 --- a/src/sakia/gui/component/controller.py +++ b/src/sakia/gui/component/controller.py @@ -10,7 +10,7 @@ class ComponentController(QObject): """ Constructor of the navigation component - :param PyQt5.QtWidgets.QWidget presentation: the presentation + :param PyQt5.QtWidgets.QWidget view: the presentation :param sakia.core.gui.navigation.model.NavigationModel model: the model """ super().__init__(parent) diff --git a/src/sakia/gui/navigation/controller.py b/src/sakia/gui/navigation/controller.py index ef7f5b554e9d25b95f84e0a773a8cdad5e430de0..350cad60a5a524d06679cfb6d6323ecaf0b8b92d 100644 --- a/src/sakia/gui/navigation/controller.py +++ b/src/sakia/gui/navigation/controller.py @@ -3,6 +3,7 @@ from ..component.controller import ComponentController from .view import NavigationView from ..txhistory.controller import TxHistoryController from ..homescreen.controller import HomeScreenController +from ..network.controller import NetworkController class NavigationController(ComponentController): @@ -21,7 +22,7 @@ class NavigationController(ComponentController): self.components = { 'TxHistory': TxHistoryController, 'HomeScreen': HomeScreenController, - 'Network': TxHistoryController, + 'Network': NetworkController, 'Identities': TxHistoryController } diff --git a/src/sakia/gui/network/__init__.py b/src/sakia/gui/network/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/sakia/gui/network/controller.py b/src/sakia/gui/network/controller.py new file mode 100644 index 0000000000000000000000000000000000000000..e0203aec6fe1e238d6c039f564c7e5cdb5e2c19c --- /dev/null +++ b/src/sakia/gui/network/controller.py @@ -0,0 +1,83 @@ +from ..component.controller import ComponentController +from .model import NetworkModel +from .view import NetworkView +from PyQt5.QtWidgets import QAction, QMenu +from PyQt5.QtGui import QCursor, QDesktopServices +from PyQt5.QtCore import pyqtSlot, QUrl +from duniterpy.api import bma + + +class NetworkController(ComponentController): + """ + The network panel + """ + + def __init__(self, parent, view, model): + """ + Constructor of the navigation component + + :param sakia.gui.network.view.NetworkView: the view + :param sakia.gui.network.model.NetworkModel model: the model + """ + super().__init__(parent, view, model) + table_model = self.model.init_network_table_model() + self.view.set_network_table_model(table_model) + self.view.manual_refresh_clicked.connect(self.refresh_nodes_manually) + + @classmethod + def create(cls, parent, app, **kwargs): + account = kwargs['account'] + community = kwargs['community'] + + view = NetworkView(parent.view) + model = NetworkModel(None, app, account, community) + txhistory = cls(parent, view, model) + model.setParent(txhistory) + return txhistory + + def refresh_nodes_manually(self): + self.model.refresh_nodes_once() + + def node_context_menu(self, point): + index = self.view.table_network.indexAt(point) + valid, node, is_root = self.model.table_model_data(index) + if valid: + self.view.show_menu(point, is_root) + menu = QMenu() + if is_root: + unset_root = QAction(self.tr("Unset root node"), self) + unset_root.triggered.connect(self.unset_root_node) + unset_root.setData(node) + if len(self.community.network.root_nodes) > 1: + menu.addAction(unset_root) + else: + set_root = QAction(self.tr("Set as root node"), self) + set_root.triggered.connect(self.set_root_node) + set_root.setData(node) + menu.addAction(set_root) + + if self.app.preferences['expert_mode']: + open_in_browser = QAction(self.tr("Open in browser"), self) + open_in_browser.triggered.connect(self.open_node_in_browser) + open_in_browser.setData(node) + menu.addAction(open_in_browser) + + # Show the context menu. + menu.exec_(QCursor.pos()) + + @pyqtSlot() + def set_root_node(self): + node = self.sender().data() + self.model.add_root_node(node) + + @pyqtSlot() + def unset_root_node(self): + node = self.sender().data() + self.model.unset_root_node(node) + + @pyqtSlot() + def open_node_in_browser(self): + node = self.sender().data() + peering = bma.network.Peering(node.endpoint.conn_handler()) + url = QUrl(peering.reverse_url("http", "/peering")) + QDesktopServices.openUrl(url) \ No newline at end of file diff --git a/src/sakia/gui/network/model.py b/src/sakia/gui/network/model.py new file mode 100644 index 0000000000000000000000000000000000000000..1acbc14f91fbcc5b09558153ddadb16132d1e582 --- /dev/null +++ b/src/sakia/gui/network/model.py @@ -0,0 +1,61 @@ +from ..component.model import ComponentModel +from .table_model import NetworkTableModel, NetworkFilterProxyModel +from PyQt5.QtCore import QModelIndex, Qt + + +class NetworkModel(ComponentModel): + """ + A network model + """ + + def __init__(self, parent, app, account, community): + """ + Constructor of an network model + + :param sakia.core.gui.component.controller.NetworkController parent: the controller + :param sakia.core.Application app: the app + :param sakia.core.Account account: the account + :param sakia.core.Community community: the community + """ + super().__init__(parent) + self.app = app + self.account = account + self.community = community + self.table_model = None + + def init_network_table_model(self): + model = NetworkTableModel(self.community) + proxy = NetworkFilterProxyModel() + proxy.setSourceModel(model) + self.table_model = proxy + model.refresh_nodes() + return self.table_model + + def refresh_nodes_once(self): + """ + Start the refresh of the nodes + :return: + """ + self.community.network.refresh_once() + + def table_model_data(self, index): + """ + Get data at given index + :param PyQt5.QtCore.QModelIndex index: + :return: + """ + if index.isValid() and index.row() < self.table_model.rowCount(QModelIndex()): + source_index = self.table_model.mapToSource(index) + is_root_col = self.table_model.sourceModel().columns_types.index('is_root') + is_root_index = self.table_model.sourceModel().index(source_index.row(), is_root_col) + is_root = self.table_model.sourceModel().data(is_root_index, Qt.DisplayRole) + node = self.community.network.nodes(source_index.row()) + return True, node, is_root + return False, None, None + + def add_root_node(self, node): + self.community.network.add_root_node(node) + + def unset_root_node(self, node): + self.community.network.remove_root_node(node) + diff --git a/res/ui/network_tab.ui b/src/sakia/gui/network/network.ui similarity index 82% rename from res/ui/network_tab.ui rename to src/sakia/gui/network/network.ui index 25edc66568e46a69ea74f9ce6314559b6fa5075b..c36278aa0ca3ec6518b57fe9550007c86fb40d39 100644 --- a/res/ui/network_tab.ui +++ b/src/sakia/gui/network/network.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>NetworkTabWidget</class> - <widget class="QWidget" name="NetworkTabWidget"> + <class>NetworkWidget</class> + <widget class="QWidget" name="NetworkWidget"> <property name="geometry"> <rect> <x>0</x> @@ -40,7 +40,7 @@ <string/> </property> <property name="icon"> - <iconset resource="../icons/icons.qrc"> + <iconset resource="../../../../res/icons/icons.qrc"> <normaloff>:/icons/refresh_icon</normaloff>:/icons/refresh_icon</iconset> </property> <property name="iconSize"> @@ -86,29 +86,13 @@ </layout> </widget> <resources> - <include location="../icons/icons.qrc"/> + <include location="../../../../res/icons/icons.qrc"/> </resources> <connections> - <connection> - <sender>table_network</sender> - <signal>customContextMenuRequested(QPoint)</signal> - <receiver>NetworkTabWidget</receiver> - <slot>node_context_menu()</slot> - <hints> - <hint type="sourcelabel"> - <x>199</x> - <y>149</y> - </hint> - <hint type="destinationlabel"> - <x>199</x> - <y>149</y> - </hint> - </hints> - </connection> <connection> <sender>button_manual_refresh</sender> <signal>clicked()</signal> - <receiver>NetworkTabWidget</receiver> + <receiver>NetworkWidget</receiver> <slot>manual_nodes_refresh()</slot> <hints> <hint type="sourcelabel"> diff --git a/src/sakia/models/network.py b/src/sakia/gui/network/table_model.py similarity index 94% rename from src/sakia/models/network.py rename to src/sakia/gui/network/table_model.py index 07c6c20b3cc1f967a1196e488df65cec6b296d4b..6bb77668b8084d08425166e57ec441287fdcb535 100644 --- a/src/sakia/models/network.py +++ b/src/sakia/gui/network/table_model.py @@ -10,8 +10,8 @@ import asyncio from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, QDateTime, QLocale from PyQt5.QtGui import QColor, QFont, QIcon -from ..tools.exceptions import NoPeerAvailable -from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task +from sakia.tools.exceptions import NoPeerAvailable +from sakia.tools.decorators import asyncify, once_at_a_time, cancel_once_task from sakia.core.net.node import Node @@ -164,17 +164,6 @@ class NetworkTableModel(QAbstractTableModel): } self.nodes_data = [] - def change_community(self, community): - """ - Change current community displayed in network and refresh the nodes - :param sakia.core.Community community: the new community - :return: the refresh task - :rtype: asyncio.Task - """ - cancel_once_task(self, self.refresh_nodes) - self.community = community - return self.refresh_nodes() - async def data_node(self, node: Node) -> tuple: """ Return node data tuple diff --git a/src/sakia/gui/network/view.py b/src/sakia/gui/network/view.py new file mode 100644 index 0000000000000000000000000000000000000000..1c03ac66f60ed228a3f3275ce06453547ec1ca32 --- /dev/null +++ b/src/sakia/gui/network/view.py @@ -0,0 +1,46 @@ +from PyQt5.QtWidgets import QWidget +from PyQt5.QtCore import Qt, QEvent, pyqtSignal +from .network_uic import Ui_NetworkWidget +import asyncio + + +class NetworkView(QWidget, Ui_NetworkWidget): + """ + The view of Network component + """ + manual_refresh_clicked = pyqtSignal() + + def __init__(self, parent): + """ + + :param sakia.gui.network.controller parent: + """ + super().__init__(parent) + self.setupUi(self) + + def set_network_table_model(self, model): + """ + Set the table view model + :param PyQt5.QtCore.QAbstractItemModel model: the model of the table view + """ + self.table_network.setModel(model) + self.table_network.sortByColumn(2, Qt.DescendingOrder) + self.table_network.resizeColumnsToContents() + model.modelAboutToBeReset.connect(lambda: self.table_network.setEnabled(False)) + model.modelReset.connect(lambda: self.table_network.setEnabled(True)) + + def manual_nodes_refresh(self): + self.button_manual_refresh.setEnabled(False) + asyncio.get_event_loop().call_later(15, lambda: self.button_manual_refresh.setEnabled(True)) + self.manual_refresh_clicked.emit() + + def changeEvent(self, event): + """ + Intercepte LanguageChange event to translate UI + :param QEvent QEvent: Event + :return: + """ + if event.type() == QEvent.LanguageChange: + self.retranslateUi(self) + self.refresh_nodes() + return super().changeEvent(event) diff --git a/src/sakia/gui/network_tab.py b/src/sakia/gui/network_tab.py deleted file mode 100644 index 95ba687e44af709c1ad61cddd41f832b60b7ce80..0000000000000000000000000000000000000000 --- a/src/sakia/gui/network_tab.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Created on 20 févr. 2015 - -@author: inso -""" - -import logging -import asyncio - -from PyQt5.QtGui import QCursor, QDesktopServices -from PyQt5.QtWidgets import QWidget, QMenu, QAction -from PyQt5.QtCore import Qt, QModelIndex, pyqtSlot, QUrl, QEvent -from ..models.network import NetworkTableModel, NetworkFilterProxyModel -from duniterpy.api import bma -from ..presentation.network_tab_uic import Ui_NetworkTabWidget - - -class NetworkTabWidget(QWidget, Ui_NetworkTabWidget): - """ - classdocs - """ - - def __init__(self, app): - """ - Constructore of a network tab. - - :param sakia.core.Application app: The application - :return: A new network tab. - :rtype: NetworkTabWidget - """ - super().__init__() - self.app = app - self.community = None - - self.setupUi(self) - model = NetworkTableModel(self.community) - proxy = NetworkFilterProxyModel() - proxy.setSourceModel(model) - self.table_network.setModel(proxy) - self.table_network.sortByColumn(2, Qt.DescendingOrder) - self.table_network.resizeColumnsToContents() - model.modelAboutToBeReset.connect(lambda: self.table_network.setEnabled(False)) - model.modelReset.connect(lambda: self.table_network.setEnabled(True)) - - def change_community(self, community): - if self.community: - self.community.network.nodes_changed.disconnect(self.refresh_nodes) - if community: - community.network.nodes_changed.connect(self.refresh_nodes) - - self.community = community - refresh_task = self.table_network.model().change_community(community) - refresh_task.add_done_callback(lambda fut: self.table_network.resizeColumnsToContents()) - - @pyqtSlot() - def refresh_nodes(self): - logging.debug("Refresh nodes") - refresh_task = self.table_network.model().sourceModel().refresh_nodes() - refresh_task.add_done_callback(lambda fut: self.table_network.resizeColumnsToContents()) - - def node_context_menu(self, point): - index = self.table_network.indexAt(point) - model = self.table_network.model() - if index.isValid() and index.row() < model.rowCount(QModelIndex()): - source_index = model.mapToSource(index) - is_root_col = model.sourceModel().columns_types.index('is_root') - is_root_index = model.sourceModel().index(source_index.row(), is_root_col) - is_root = model.sourceModel().data(is_root_index, Qt.DisplayRole) - - menu = QMenu() - if is_root: - unset_root = QAction(self.tr("Unset root node"), self) - unset_root.triggered.connect(self.unset_root_node) - unset_root.setData(self.community.network.root_node_index(source_index.row())) - if len(self.community.network.root_nodes) > 1: - menu.addAction(unset_root) - else: - set_root = QAction(self.tr("Set as root node"), self) - set_root.triggered.connect(self.set_root_node) - set_root.setData(self.community.network.nodes[source_index.row()]) - menu.addAction(set_root) - - if self.app.preferences['expert_mode']: - open_in_browser = QAction(self.tr("Open in browser"), self) - open_in_browser.triggered.connect(self.open_node_in_browser) - open_in_browser.setData(self.community.network.nodes[source_index.row()]) - menu.addAction(open_in_browser) - - # Show the context menu. - menu.exec_(QCursor.pos()) - - @pyqtSlot() - def set_root_node(self): - node = self.sender().data() - self.community.network.add_root_node(node) - self.table_network.model().sourceModel().refresh_nodes() - - @pyqtSlot() - def unset_root_node(self): - index = self.sender().data() - self.community.network.remove_root_node(index) - self.table_network.model().sourceModel().refresh_nodes() - - @pyqtSlot() - def open_node_in_browser(self): - node = self.sender().data() - peering = bma.network.Peering(node.endpoint.conn_handler()) - url = QUrl(peering.reverse_url("http", "/peering")) - QDesktopServices.openUrl(url) - - def manual_nodes_refresh(self): - self.community.network.refresh_once() - self.button_manual_refresh.setEnabled(False) - asyncio.get_event_loop().call_later(15, lambda: self.button_manual_refresh.setEnabled(True)) - - def changeEvent(self, event): - """ - Intercepte LanguageChange event to translate UI - :param QEvent QEvent: Event - :return: - """ - if event.type() == QEvent.LanguageChange: - self.retranslateUi(self) - self.refresh_nodes() - return super(NetworkTabWidget, self).changeEvent(event) diff --git a/src/sakia/gui/txhistory/controller.py b/src/sakia/gui/txhistory/controller.py index 3e8ceba4d30ef03d841d48462c441a41fbcb309a..be500771cdff959e3ad4bc62e59961dfb711ace1 100644 --- a/src/sakia/gui/txhistory/controller.py +++ b/src/sakia/gui/txhistory/controller.py @@ -83,14 +83,15 @@ class TxHistoryController(ComponentController): @asyncify async def history_context_menu(self, point): index = self.view.table_history.indexAt(point) - identity, transfer = await self.model.table_data(index) - menu = ContextMenu.from_data(self.view, self.model.app, self.model.account, self.model.community, - self.password_asker, - (identity, transfer)) - menu.view_identity_in_wot.connect(self.view_in_wot) - - # Show the context menu. - menu.qmenu.popup(QCursor.pos()) + valid, identity, transfer = await self.model.table_data(index) + if valid: + menu = ContextMenu.from_data(self.view, self.model.app, self.model.account, self.model.community, + self.password_asker, + (identity, transfer)) + menu.view_identity_in_wot.connect(self.view_in_wot) + + # Show the context menu. + menu.qmenu.popup(QCursor.pos()) def dates_changed(self): logging.debug("Changed dates") diff --git a/src/sakia/gui/txhistory/model.py b/src/sakia/gui/txhistory/model.py index a96a7ae275a09ada00372d886c1f2f77c7772da1..fd6bf345f9336427c118c4a6c30cc29eed3bb934 100644 --- a/src/sakia/gui/txhistory/model.py +++ b/src/sakia/gui/txhistory/model.py @@ -17,9 +17,9 @@ class TxHistoryModel(ComponentModel): """ :param sakia.gui.txhistory.TxHistoryParent parent: the parent controller - :param app: - :param account: - :param community: + :param sakia.core.Application app: the app + :param sakia.core.Account account: the account + :param sakia.core.Community community: the community """ super().__init__(parent) self.app = app @@ -62,8 +62,8 @@ class TxHistoryModel(ComponentModel): identity = await self.app.identities_registry.future_find(pubkey, self.community) transfer = self.table_model.sourceModel().transfers()[source_index.row()] - return identity, transfer - return None + return True, identity, transfer + return False, None, None def connect_progress(self): def progressing(community, value, maximum): diff --git a/src/sakia/gui/txhistory/view.py b/src/sakia/gui/txhistory/view.py index 4f94b7831ad7c0a7be2cf10b174818f8554e401a..3f969bd1d26d67b335905e2a4af60dd4340122f9 100644 --- a/src/sakia/gui/txhistory/view.py +++ b/src/sakia/gui/txhistory/view.py @@ -1,14 +1,11 @@ from PyQt5.QtWidgets import QWidget, QAbstractItemView, QHeaderView -from PyQt5.QtGui import QCursor -from PyQt5.QtCore import Qt, QModelIndex, QDateTime, QEvent +from PyQt5.QtCore import QDateTime, QEvent from .txhistory_uic import Ui_TxHistoryWidget -from ...tools.decorators import asyncify, once_at_a_time -from ..widgets.context_menu import ContextMenu class TxHistoryView(QWidget, Ui_TxHistoryWidget): """ - The model of Navigation component + The view of TxHistory component """ def __init__(self, parent):