diff --git a/res/ui/community_tab.ui b/res/ui/community_tab.ui index 357db655d70e47599331f4609bba2cfdf9fa0ea4..287487dfb05c7e8842ab521e35f189f4f507d9e2 100644 --- a/res/ui/community_tab.ui +++ b/res/ui/community_tab.ui @@ -37,13 +37,34 @@ <normaloff>:/icons/members_icon</normaloff>:/icons/members_icon</iconset> </attribute> <attribute name="title"> - <string>Members</string> + <string>Identities</string> </attribute> <layout class="QVBoxLayout" name="verticalLayout"> <item> <layout class="QVBoxLayout" name="verticalLayout_6"> <item> - <widget class="QTableView" name="table_community_members"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="topMargin"> + <number>0</number> + </property> + <item> + <widget class="QLineEdit" name="edit_textsearch"> + <property name="placeholderText"> + <string>Research a pubkey, an uid...</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="button_search"> + <property name="text"> + <string>Search...</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTableView" name="table_identities"> <property name="contextMenuPolicy"> <enum>Qt::CustomContextMenu</enum> </property> @@ -152,10 +173,43 @@ </hint> </hints> </connection> + <connection> + <sender>edit_textsearch</sender> + <signal>returnPressed()</signal> + <receiver>CommunityTabWidget</receiver> + <slot>search()</slot> + <hints> + <hint type="sourcelabel"> + <x>170</x> + <y>62</y> + </hint> + <hint type="destinationlabel"> + <x>215</x> + <y>184</y> + </hint> + </hints> + </connection> + <connection> + <sender>button_search</sender> + <signal>clicked()</signal> + <receiver>CommunityTabWidget</receiver> + <slot>search()</slot> + <hints> + <hint type="sourcelabel"> + <x>370</x> + <y>62</y> + </hint> + <hint type="destinationlabel"> + <x>215</x> + <y>184</y> + </hint> + </hints> + </connection> </connections> <slots> - <slot>member_context_menu(QPoint)</slot> + <slot>identity_context_menu(QPoint)</slot> <slot>send_membership_demand()</slot> <slot>send_membership_leaving()</slot> + <slot>search()</slot> </slots> </ui> diff --git a/res/ui/member.ui b/res/ui/member.ui index dc90dca871a4ae014f539cf79a5cfb1166419f97..ca9b92019f0ad59dbb5e57fa2061ac2c2c5ef38b 100644 --- a/res/ui/member.ui +++ b/res/ui/member.ui @@ -83,7 +83,7 @@ QGroupBox::title { </widget> <resources> <include location="../icons/icons.qrc"/> - <include location="../../../../../../../../home/vit/.designer/icons/icons.qrc"/> + <include location="../icons/icons.qrc"/> </resources> <connections/> </ui> diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py index da9fd989b4aa7aed4b54933bee811cb69a07eaf3..be5e70adf47c298e1c9ad62eafcad290fb0669ef 100644 --- a/src/cutecoin/core/community.py +++ b/src/cutecoin/core/community.py @@ -343,6 +343,7 @@ class Community(QObject): if number is None: data = self.request(bma.blockchain.Current) else: + logging.debug("Requesting block {0}".format(number)) data = self.request(bma.blockchain.Block, req_args={'number': number}) diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py index 5f0f89f842065f2ac18829075b45a2ab33376a9a..8f5de09f26972f5a9eeefbcd8f6b1c6dff37aa82 100644 --- a/src/cutecoin/gui/community_tab.py +++ b/src/cutecoin/gui/community_tab.py @@ -9,7 +9,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QIcon, QCursor from PyQt5.QtWidgets import QWidget, QMessageBox, QAction, QMenu, QDialog, \ QAbstractItemView -from ..models.members import MembersFilterProxyModel, MembersTableModel +from cutecoin.models.identities import IdentitiesFilterProxyModel, IdentitiesTableModel from ..gen_resources.community_tab_uic import Ui_CommunityTabWidget from cutecoin.gui.contact import ConfigureContactDialog from cutecoin.gui.member import MemberDialog @@ -19,6 +19,7 @@ from .password_asker import PasswordAskerDialog from .certification import CertificationDialog from ..tools.exceptions import PersonNotFoundError, NoPeerAvailable from ..core.person import Person +from ucoinpy.api import bma class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): @@ -30,7 +31,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): def __init__(self, app, account, community, password_asker, parent): """ Init - :param cutecoin.core.account.Account account: Accoun instance + :param cutecoin.core.account.Account account: Account instance :param cutecoin.core.community.Community community: Community instance :param cutecoin.gui.password_asker.PasswordAskerDialog password_asker: Password asker dialog :param cutecoin.gui.currency_tab.CurrencyTabWidget parent: TabWidget instance @@ -42,13 +43,13 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): self.community = community self.account = account self.password_asker = password_asker - members_model = MembersTableModel(community) - proxy_members = MembersFilterProxyModel() - proxy_members.setSourceModel(members_model) - self.table_community_members.setModel(proxy_members) - self.table_community_members.setSelectionBehavior(QAbstractItemView.SelectRows) - self.table_community_members.customContextMenuRequested.connect(self.member_context_menu) - self.table_community_members.sortByColumn(0, Qt.AscendingOrder) + identities_model = IdentitiesTableModel(community) + proxy = IdentitiesFilterProxyModel() + proxy.setSourceModel(identities_model) + self.table_identities.setModel(proxy) + self.table_identities.setSelectionBehavior(QAbstractItemView.SelectRows) + self.table_identities.customContextMenuRequested.connect(self.identity_context_menu) + self.table_identities.sortByColumn(0, Qt.AscendingOrder) if self.account.member_of(self.community): self.button_membership.setText("Renew membership") @@ -58,38 +59,39 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): self.wot_tab = WotTabWidget(app, account, community, password_asker, self) self.tabs_information.addTab(self.wot_tab, QIcon(':/icons/wot_icon'), "WoT") + self.refresh() - def member_context_menu(self, point): - index = self.table_community_members.indexAt(point) - model = self.table_community_members.model() + def identity_context_menu(self, point): + index = self.table_identities.indexAt(point) + model = self.table_identities.model() if index.row() < model.rowCount(): source_index = model.mapToSource(index) pubkey_col = model.sourceModel().columns_ids.index('pubkey') pubkey_index = model.sourceModel().index(source_index.row(), pubkey_col) pubkey = model.sourceModel().data(pubkey_index, Qt.DisplayRole) - member = Person.lookup(pubkey, self.community) + identity = Person.lookup(pubkey, self.community) menu = QMenu(self) informations = QAction("Informations", self) informations.triggered.connect(self.menu_informations) - informations.setData(member) + informations.setData(identity) add_contact = QAction("Add as contact", self) add_contact.triggered.connect(self.menu_add_as_contact) - add_contact.setData(member) + add_contact.setData(identity) send_money = QAction("Send money", self) send_money.triggered.connect(self.menu_send_money) - send_money.setData(member) + send_money.setData(identity) certify = QAction("Certify identity", self) certify.triggered.connect(self.menu_certify_member) - certify.setData(member) + certify.setData(identity) view_wot = QAction("View in WoT", self) view_wot.triggered.connect(self.view_wot) - view_wot.setData(member) + view_wot.setData(identity) menu.addAction(informations) menu.addAction(add_contact) @@ -102,32 +104,32 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): def menu_informations(self): person = self.sender().data() - self.member_informations(person) + self.identity_informations(person) def menu_add_as_contact(self): person = self.sender().data() - self.add_member_as_contact({'name': person.uid, + self.add_identity_as_contact({'name': person.uid, 'pubkey': person.pubkey}) def menu_send_money(self): person = self.sender().data() - self.send_money_to_member(person) + self.send_money_to_identity(person) def menu_certify_member(self): person = self.sender().data() - self.certify_member(person) + self.certify_identity(person) - def member_informations(self, person): + def identity_informations(self, person): dialog = MemberDialog(self.account, self.community, person) dialog.exec_() - def add_member_as_contact(self, person): + def add_identity_as_contact(self, person): dialog = ConfigureContactDialog(self.account, self.window(), person) result = dialog.exec_() if result == QDialog.Accepted: self.window().refresh_contacts() - def send_money_to_member(self, person): + def send_money_to_identity(self, person): if isinstance(person, str): pubkey = person else: @@ -140,7 +142,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): currency_tab = self.window().currencies_tabwidget.currentWidget() currency_tab.tab_history.table_history.model().invalidate() - def certify_member(self, person): + def certify_identity(self, person): dialog = CertificationDialog(self.account, self.password_asker) dialog.combo_community.setCurrentText(self.community.name) dialog.edit_pubkey.setText(person.pubkey) @@ -149,7 +151,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): def view_wot(self): person = self.sender().data() - # redraw WoT with this member selected + # redraw WoT with this identity selected self.wot_tab.draw_graph({'text': person.uid, 'id': person.pubkey}) # change page to WoT index_community_tab = self.parent.tabs_account.indexOf(self) @@ -210,9 +212,51 @@ The process to join back the community later will have to be done again.""" "{0}".format(e), QMessageBox.Ok) + def search(self): + """ + Search nodes when return is pressed in combobox lineEdit + """ + text = self.edit_textsearch.text() + + if len(text) < 2: + return False + try: + response = self.community.request(bma.wot.Lookup, {'search': text}) + except Exception as e: + logging.debug('bma.wot.Lookup request error : ' + str(e)) + return False + + persons = [] + for identity in response['results']: + persons.append(identity['pubkey']) + + self.edit_textsearch.clear() + self.refresh(persons) + + + def refresh(self, persons=None): + ''' + Refresh the table with specified identities. + If no identities is passed, use the account connections. + ''' + if persons is None: + self_identity = Person.lookup(self.account.pubkey, self.community) + account_connections = [] + persons = [] + for p in self_identity.unique_valid_certifiers_of(self.community): + account_connections.append(Person.lookup(p['pubkey'], self.community)) + persons = [p for p in account_connections + if p.pubkey not in [i.pubkey for i in persons]] + for p in self_identity.unique_valid_certified_by(self.community): + account_connections.append(Person.lookup(p['pubkey'], self.community)) + persons = persons + [p for p in account_connections + if p.pubkey not in [i.pubkey for i in persons]] + + self.table_identities.model().sourceModel().refresh_identities(persons) + def refresh_person(self, pubkey): if self is None: logging.error("community_tab self is None in refresh_person. Watcher connected to a destroyed tab") else: - index = self.table_community_members.model().sourceModel().person_index(pubkey) - self.table_community_members.model().sourceModel().dataChanged.emit(index[0], index[1]) + index = self.table_identities.model().sourceModel().person_index(pubkey) + self.table_identities.model().sourceModel().dataChanged.emit(index[0], index[1]) diff --git a/src/cutecoin/models/members.py b/src/cutecoin/models/identities.py similarity index 74% rename from src/cutecoin/models/members.py rename to src/cutecoin/models/identities.py index 7e648a1dbdd4717e51b904617978a9d9386d865f..8e80d44b1d4bd97abe78061d7834649e41de2714 100644 --- a/src/cutecoin/models/members.py +++ b/src/cutecoin/models/identities.py @@ -6,13 +6,14 @@ Created on 5 févr. 2014 from ucoinpy.api import bma from ..core.person import Person +from ..tools.exceptions import NoPeerAvailable from PyQt5.QtCore import QAbstractTableModel, QSortFilterProxyModel, Qt, \ QDateTime, QModelIndex from PyQt5.QtGui import QColor import logging -class MembersFilterProxyModel(QSortFilterProxyModel): +class IdentitiesFilterProxyModel(QSortFilterProxyModel): def __init__(self, parent=None): super().__init__(parent) self.community = None @@ -25,8 +26,9 @@ class MembersFilterProxyModel(QSortFilterProxyModel): """ Sort table by given column number. """ - left_data = self.sourceModel().data(left, Qt.DisplayRole) - right_data = self.sourceModel().data(right, Qt.DisplayRole) + 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 data(self, index, role): @@ -56,7 +58,7 @@ class MembersFilterProxyModel(QSortFilterProxyModel): return source_data -class MembersTableModel(QAbstractTableModel): +class IdentitiesTableModel(QAbstractTableModel): ''' A Qt abstract item model to display communities in a tree @@ -74,35 +76,47 @@ class MembersTableModel(QAbstractTableModel): 'renewed': 'Renewed', 'expiration': 'Expiration'} self.columns_ids = ('uid', 'pubkey', 'renewed', 'expiration') + self.identities_data = [] @property def pubkeys(self): - return self.community.members_pubkeys() + return [i.pubkey for i in self.identities_data] + + def identity_data(self, person): + join_block = person.membership(self.community)['blockNumber'] + try: + join_date = self.community.get_block(join_block).mediantime + except NoPeerAvailable: + join_date = 0 + parameters = self.community.parameters + expiration_date = join_date + parameters['sigValidity'] + logging.debug((person.uid, person.pubkey, join_date, expiration_date)) + return (person.uid, person.pubkey, join_date, expiration_date) + + def refresh_identities(self, persons): + logging.debug("Refresh {0} identities".format(len(persons))) + self.identities_data = [] + self.beginResetModel() + for person in persons: + self.identities_data.append(self.identity_data(person)) + self.endResetModel() def rowCount(self, parent): - return len(self.pubkeys) + return len(self.identities_data) def columnCount(self, parent): return len(self.columns_ids) def headerData(self, section, orientation, role): if role == Qt.DisplayRole: - id = self.columns_ids[section] - return self.columns_titles[id] - - def member_data(self, pubkey): - person = Person.lookup(pubkey, self.community) - join_block = person.membership(self.community)['blockNumber'] - join_date = self.community.get_block(join_block).mediantime - parameters = self.community.parameters - expiration_date = join_date + parameters['sigValidity'] - return (person.uid, pubkey, join_date, expiration_date) + col_id = self.columns_ids[section] + return self.columns_titles[col_id] def data(self, index, role): if role == Qt.DisplayRole: row = index.row() col = index.column() - return self.member_data(self.pubkeys[row])[col] + return self.identities_data[row][col] def person_index(self, pubkey): try: diff --git a/src/cutecoin/models/txhistory.py b/src/cutecoin/models/txhistory.py index 2e336430622d50b95d3e7089008bdb4f331619a0..f5da92ef4e98092d485a5f2d440c8bbe74c18022 100644 --- a/src/cutecoin/models/txhistory.py +++ b/src/cutecoin/models/txhistory.py @@ -206,15 +206,14 @@ class HistoryTableModel(QAbstractTableModel): "", comment, transfer.state) def refresh_transfers(self): + self.beginResetModel() self.transfers_data = [] for transfer in self.transfers: if type(transfer) is Received: self.transfers_data.append(self.data_received(transfer)) else: self.transfers_data.append(self.data_sent(transfer)) - self.dataChanged.emit(QModelIndex(), - QModelIndex(), - []) + self.endResetModel() def rowCount(self, parent): return len(self.transfers)