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 0 additions and 3133 deletions
'''
Created on 2 févr. 2014
@author: inso
'''
import logging
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 ..gen_resources.community_tab_uic import Ui_CommunityTabWidget
from cutecoin.gui.contact import ConfigureContactDialog
from cutecoin.gui.member import MemberDialog
from .wot_tab import WotTabWidget
from .transfer import TransferMoneyDialog
from .password_asker import PasswordAskerDialog
from .certification import CertificationDialog
from ..tools.exceptions import PersonNotFoundError, NoPeerAvailable
from ..core.person import Person
class CommunityTabWidget(QWidget, Ui_CommunityTabWidget):
'''
classdocs
'''
def __init__(self, account, community, password_asker):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
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)
if self.account.member_of(self.community):
self.button_membership.setText("Renew membership")
else:
self.button_membership.setText("Send membership demand")
self.button_leaving.hide()
self.wot_tab = WotTabWidget(account, community, password_asker, self)
self.tabs_information.addTab(self.wot_tab, QIcon(':/icons/wot_icon'), "WoT")
def member_context_menu(self, point):
index = self.table_community_members.indexAt(point)
model = self.table_community_members.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)
menu = QMenu(self)
informations = QAction("Informations", self)
informations.triggered.connect(self.menu_informations)
informations.setData(member)
add_contact = QAction("Add as contact", self)
add_contact.triggered.connect(self.menu_add_as_contact)
add_contact.setData(member)
send_money = QAction("Send money", self)
send_money.triggered.connect(self.menu_send_money)
send_money.setData(member)
certify = QAction("Certify identity", self)
certify.triggered.connect(self.menu_certify_member)
certify.setData(member)
view_wot = QAction("View in WoT", self)
view_wot.triggered.connect(self.view_wot)
view_wot.setData(member)
menu.addAction(informations)
menu.addAction(add_contact)
menu.addAction(send_money)
menu.addAction(certify)
menu.addAction(view_wot)
# Show the context menu.
menu.exec_(QCursor.pos())
def menu_informations(self):
person = self.sender().data()
self.member_informations(person)
def menu_add_as_contact(self):
person = self.sender().data()
self.add_member_as_contact(person)
def menu_send_money(self):
person = self.sender().data()
self.send_money_to_member(person)
def menu_certify_member(self):
person = self.sender().data()
self.certify_member(person)
def member_informations(self, person):
dialog = MemberDialog(self.account, self.community, person)
dialog.exec_()
def add_member_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):
dialog = TransferMoneyDialog(self.account, self.password_asker)
dialog.edit_pubkey.setText(person.pubkey)
dialog.combo_community.setCurrentText(self.community.name)
dialog.radio_pubkey.setChecked(True)
if dialog.exec_() == QDialog.Accepted:
currency_tab = self.window().currencies_tabwidget.currentWidget()
currency_tab.table_history.model().invalidate()
def certify_member(self, person):
dialog = CertificationDialog(self.account, self.password_asker)
dialog.combo_community.setCurrentText(self.community.name)
dialog.edit_pubkey.setText(person.pubkey)
dialog.radio_pubkey.setChecked(True)
dialog.exec_()
def view_wot(self):
person = self.sender().data()
index_wot_tab = self.tabs_information.indexOf(self.wot_tab)
# redraw WoT with this member selected
self.wot_tab.draw_graph(person.pubkey)
# change page to WoT
self.tabs_information.setCurrentIndex(index_wot_tab)
def send_membership_demand(self):
password = self.password_asker.exec_()
if self.password_asker.result() == QDialog.Rejected:
return
try:
self.account.send_membership(password, self.community, 'IN')
QMessageBox.information(self, "Membership",
"Success sending membership demand")
except ValueError as e:
QMessageBox.critical(self, "Join demand error",
str(e))
except PersonNotFoundError as e:
QMessageBox.critical(self, "Key not sent to community",
"Your key wasn't sent in the community. \
You can't request a membership.")
except NoPeerAvailable as e:
QMessageBox.critical(self, "Network error",
"Couldn't connect to network : {0}".format(e),
QMessageBox.Ok)
except Exception as e:
QMessageBox.critical(self, "Error",
"{0}".format(e),
QMessageBox.Ok)
def send_membership_leaving(self):
reply = QMessageBox.warning(self, "Warning",
"""Are you sure ?
Sending a membership 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:
password_asker = PasswordAskerDialog(self.app.current_account)
password = password_asker.exec_()
if password_asker.result() == QDialog.Rejected:
return
try:
self.account.send_membership(password, self.community, 'OUT')
QMessageBox.information(self, "Membership",
"Success sending leaving demand")
except ValueError as e:
QMessageBox.critical(self, "Leaving demand error",
e.message)
except NoPeerAvailable as e:
QMessageBox.critical(self, "Network error",
"Couldn't connect to network : {0}".format(e),
QMessageBox.Ok)
except Exception as e:
QMessageBox.critical(self, "Error",
"{0}".format(e),
QMessageBox.Ok)
def refresh_person(self, pubkey):
index = self.table_community_members.model().sourceModel().person_index(pubkey)
self.table_community_members.model().sourceModel().dataChanged.emit(index[0], index[1])
'''
Created on 2 févr. 2014
@author: inso
'''
import re
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox
from ..core.person import Person
from ..tools.exceptions import ContactAlreadyExists
from ..gen_resources.contact_uic import Ui_ConfigureContactDialog
class ConfigureContactDialog(QDialog, Ui_ConfigureContactDialog):
'''
classdocs
'''
def __init__(self, account, parent=None, contact=None, edit=False):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
self.account = account
self.main_window = parent
self.contact = contact
if contact:
self.edit_name.setText(contact.name)
self.edit_pubkey.setText(contact.pubkey)
if edit:
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
def accept(self):
name = self.edit_name.text()
pubkey = self.edit_pubkey.text()
if self.contact:
self.contact.name = name
self.contact.pubkey = pubkey
else:
try:
self.account.add_contact(Person.from_metadata(name, pubkey))
except ContactAlreadyExists as e:
QMessageBox.critical(self, "Contact already exists",
str(e),
QMessageBox.Ok)
return
self.main_window.app.save(self.account)
super().accept()
def name_edited(self, new_name):
name_ok = len(new_name) > 0
self.button_box.button(QDialogButtonBox.Ok).setEnabled(name_ok)
def pubkey_edited(self, new_pubkey):
pattern = re.compile("([1-9A-Za-z][^OIl]{42,45})")
self.button_box.button(
QDialogButtonBox.Ok).setEnabled(
pattern.match(new_pubkey)is not None)
'''
Created on 2 févr. 2014
@author: inso
'''
import time
from PyQt5.QtWidgets import QWidget, QMenu, QAction, QApplication, \
QMessageBox, QDialog, QAbstractItemView, QHeaderView
from PyQt5.QtCore import QModelIndex, Qt, pyqtSlot, \
QThread, QDateTime
from PyQt5.QtGui import QIcon, QCursor
from ..gen_resources.currency_tab_uic import Ui_CurrencyTabWidget
from .community_tab import CommunityTabWidget
from .transfer import TransferMoneyDialog
from .wallets_tab import WalletsTabWidget
from .network_tab import NetworkTabWidget
from ..models.txhistory import HistoryTableModel, TxFilterProxyModel
from .informations_tab import InformationsTabWidget
from ..tools.exceptions import MembershipNotFoundError
from ..core.wallet import Wallet
from ..core.person import Person
from ..core.transfer import Transfer
from ..core.watchers.blockchain import BlockchainWatcher
from ..core.watchers.persons import PersonsWatcher
class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget):
'''
classdocs
'''
def __init__(self, app, community, password_asker, status_label):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
self.app = app
self.community = community
self.password_asker = password_asker
self.status_label = status_label
self.tab_community = CommunityTabWidget(self.app.current_account,
self.community,
self.password_asker)
self.tab_wallets = WalletsTabWidget(self.app,
self.app.current_account,
self.community)
self.tab_network = NetworkTabWidget(self.community)
self.bc_watcher = BlockchainWatcher(self.app.current_account,
community)
self.bc_watcher.new_block_mined.connect(self.refresh_block)
self.bc_watcher.connection_error.connect(self.display_error)
self.watcher_thread = QThread()
self.bc_watcher.moveToThread(self.watcher_thread)
self.watcher_thread.started.connect(self.bc_watcher.watch)
self.watcher_thread.start()
self.persons_watcher = PersonsWatcher(self.community)
self.persons_watcher.person_changed.connect(self.tab_community.refresh_person)
self.persons_watcher_thread = QThread()
self.persons_watcher.moveToThread(self.persons_watcher_thread)
self.persons_watcher_thread.started.connect(self.persons_watcher.watch)
self.persons_watcher.end_watching.connect(self.persons_watcher_thread.finished)
self.persons_watcher_thread.start()
person = Person.lookup(self.app.current_account.pubkey, self.community)
try:
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']
current_time = time.time()
sig_validity = self.community.parameters['sigValidity']
warning_expiration_time = int(sig_validity / 3)
will_expire_soon = (current_time > expiration_date - warning_expiration_time)
if will_expire_soon:
days = QDateTime().currentDateTime().daysTo(QDateTime.fromTime_t(expiration_date))
QMessageBox.warning(
self,
"Membership expiration",
"Warning : Membership expiration in {0} days".format(days),
QMessageBox.Ok
)
except MembershipNotFoundError as e:
pass
def refresh(self):
if self.app.current_account is None:
self.tabs_account.setEnabled(False)
else:
self.tabs_account.setEnabled(True)
self.refresh_wallets()
blockchain_init = QDateTime()
blockchain_init.setTime_t(self.community.get_block(1).mediantime)
self.date_from.setMinimumDateTime(blockchain_init)
self.date_from.setDateTime(blockchain_init)
self.date_from.setMaximumDateTime(QDateTime().currentDateTime())
self.date_to.setMinimumDateTime(blockchain_init)
tomorrow_datetime = QDateTime().currentDateTime().addDays(1)
self.date_to.setDateTime(tomorrow_datetime)
self.date_to.setMaximumDateTime(tomorrow_datetime)
ts_from = self.date_from.dateTime().toTime_t()
ts_to = self.date_to.dateTime().toTime_t()
model = HistoryTableModel(self.app.current_account, self.community)
proxy = TxFilterProxyModel(ts_from, ts_to)
proxy.setSourceModel(model)
proxy.setDynamicSortFilter(True)
proxy.setSortRole(Qt.DisplayRole)
self.table_history.setModel(proxy)
self.table_history.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_history.setSortingEnabled(True)
self.table_history.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.tab_community = CommunityTabWidget(self.app.current_account,
self.community,
self.password_asker)
self.tabs_account.addTab(self.tab_community,
QIcon(':/icons/community_icon'),
"Community")
self.tab_wallets = WalletsTabWidget(self.app,
self.app.current_account,
self.community)
self.tabs_account.addTab(self.tab_wallets,
QIcon(':/icons/wallet_icon'),
"Wallets")
self.tab_informations = InformationsTabWidget(self.app.current_account,
self.community)
self.tabs_account.addTab(self.tab_informations,
QIcon(':/icons/informations_icon'),
"Informations")
self.tab_network = NetworkTabWidget(self.community)
self.tabs_account.addTab(self.tab_network,
QIcon(":/icons/network_icon"),
"Network")
self.tab_informations.refresh()
blockid = self.community.current_blockid()
block_number = blockid['number']
self.status_label.setText("Connected : Block {0}"
.format(block_number))
@pyqtSlot(str)
def display_error(self, error):
QMessageBox.critical(self, ":(",
error,
QMessageBox.Ok)
@pyqtSlot(int)
def refresh_block(self, block_number):
if self.tab_wallets:
self.tab_wallets.refresh()
if self.table_history.model():
self.table_history.model().dataChanged.emit(
QModelIndex(),
QModelIndex(),
[])
self.persons_watcher_thread.start()
text = "Connected : Block {0}".format(block_number)
self.status_label.setText(text)
def refresh_wallets(self):
if self.app.current_account:
self.tab_wallets.refresh()
def wallet_context_menu(self, point):
index = self.list_wallets.indexAt(point)
model = self.list_wallets.model()
if index.row() < model.rowCount(QModelIndex()):
wallet = model.wallets[index.row()]
menu = QMenu(model.data(index, Qt.DisplayRole), self)
rename = QAction("Rename", self)
rename.triggered.connect(self.rename_wallet)
rename.setData(index)
copy_pubkey = QAction("Copy pubkey to clipboard", self)
copy_pubkey.triggered.connect(self.copy_pubkey_to_clipboard)
copy_pubkey.setData(wallet)
menu.addAction(rename)
menu.addAction(copy_pubkey)
# Show the context menu.
menu.exec_(QCursor.pos())
def history_context_menu(self, point):
index = self.table_history.indexAt(point)
model = self.table_history.model()
if index.row() < model.rowCount(QModelIndex()):
menu = QMenu("Actions", self)
source_index = model.mapToSource(index)
state_col = model.sourceModel().column_types.index('state')
state_index = model.sourceModel().index(source_index.row(),
state_col)
state_data = model.sourceModel().data(state_index, Qt.DisplayRole)
pubkey_col = model.sourceModel().column_types.index('uid')
person_index = model.sourceModel().index(source_index.row(),
pubkey_col)
person = model.sourceModel().data(person_index, Qt.DisplayRole)
transfer = model.sourceModel().transfers[source_index.row()]
if state_data == Transfer.REFUSED or state_data == Transfer.TO_SEND:
send_back = QAction("Send again", self)
send_back.triggered.connect(self.send_again)
send_back.setData(transfer)
menu.addAction(send_back)
cancel = QAction("Cancel", self)
cancel.triggered.connect(self.cancel_transfer)
cancel.setData(transfer)
menu.addAction(cancel)
copy_pubkey = QAction("Copy pubkey to clipboard", self)
copy_pubkey.triggered.connect(self.copy_pubkey_to_clipboard)
copy_pubkey.setData(person)
menu.addAction(copy_pubkey)
# Show the context menu.
menu.exec_(QCursor.pos())
def copy_pubkey_to_clipboard(self):
data = self.sender().data()
clipboard = QApplication.clipboard()
if data.__class__ is Wallet:
clipboard.setText(data.pubkey)
elif data.__class__ is Person:
clipboard.setText(data.pubkey)
elif data.__class__ is str:
clipboard.setText(data)
def send_again(self):
transfer = self.sender().data()
dialog = TransferMoneyDialog(self.app.current_account,
self.password_asker)
dialog.accepted.connect(self.refresh_wallets)
sender = transfer.metadata['issuer']
wallet_index = [w.pubkey for w in self.app.current_account.wallets].index(sender)
dialog.combo_wallets.setCurrentIndex(wallet_index)
dialog.edit_pubkey.setText(transfer.metadata['receiver'])
dialog.combo_community.setCurrentText(self.community.name)
dialog.spinbox_amount.setValue(transfer.metadata['amount'])
dialog.radio_pubkey.setChecked(True)
dialog.edit_message.setText(transfer.metadata['comment'])
result = dialog.exec_()
if result == QDialog.Accepted:
transfer.drop()
self.table_history.model().invalidate()
def cancel_transfer(self):
reply = QMessageBox.warning(self, "Warning",
"""Are you sure ?
This money transfer will be removed and not sent.""",
QMessageBox.Ok | QMessageBox.Cancel)
if reply == QMessageBox.Ok:
transfer = self.sender().data()
transfer.drop()
self.table_history.model().invalidate()
def showEvent(self, event):
blockid = self.community.current_blockid()
block_number = blockid['number']
self.status_label.setText("Connected : Block {0}"
.format(block_number))
def closeEvent(self, event):
super().closeEvent(event)
self.bc_watcher.deleteLater()
self.watcher_thread.deleteLater()
def dates_changed(self, datetime):
ts_from = self.date_from.dateTime().toTime_t()
ts_to = self.date_to.dateTime().toTime_t()
if self.table_history.model():
self.table_history.model().set_period(ts_from, ts_to)
self.table_history.model().invalidate()
def referential_changed(self):
if self.table_history.model():
self.table_history.model().dataChanged.emit(
QModelIndex(),
QModelIndex(),
[])
if self.tab_wallets:
self.tab_wallets.refresh()
if self.tab_informations:
self.tab_informations.refresh()
'''
Created on 22 mai 2014
@author: inso
'''
import re
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox, QFileDialog
from cutecoin.tools.exceptions import Error
from cutecoin.gen_resources.import_account_uic import Ui_ImportAccountDialog
class ImportAccountDialog(QDialog, Ui_ImportAccountDialog):
'''
classdocs
'''
def __init__(self, app, parent=None):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
self.app = app
self.main_window = parent
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
def accept(self):
account_name = self.edit_name.text()
try:
self.app.import_account(self.selected_file, account_name)
except Exception as e:
QMessageBox.critical(self, "Error",
"{0}".format(e),
QMessageBox.Ok)
return
QMessageBox.information(self, "Account import",
"Account imported succefully !")
super().accept()
def import_account(self):
self.selected_file = QFileDialog.getOpenFileName(self,
"Import an account file",
"",
"All account files (*.acc)")
self.selected_file = self.selected_file[0]
self.edit_file.setText(self.selected_file)
self.check()
def name_changed(self):
self.check()
def check(self):
name = self.edit_name.text()
if name == "":
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
self.label_errors.setText("Please enter a name")
return
for account in self.app.accounts:
if name == account.name:
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
self.label_errors.setText("Name already exists")
return
if self.selected_file[-4:] != ".acc":
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
self.label_errors.setText("File is not an account format")
return
self.label_errors.setText("")
self.button_box.button(QDialogButtonBox.Ok).setEnabled(True)
"""
Created on 31 janv. 2015
@author: vit
"""
import logging
import datetime
import math
from PyQt5.QtWidgets import QWidget
from ..gen_resources.informations_tab_uic import Ui_InformationsTabWidget
class InformationsTabWidget(QWidget, Ui_InformationsTabWidget):
"""
classdocs
"""
def __init__(self, account, community):
"""
Constructor
"""
super().__init__()
self.setupUi(self)
self.community = community
self.account = account
self.refresh()
def refresh(self):
# try to request money parameters
try:
params = self.community.parameters
except Exception as e:
logging.debug('community parameters error : ' + str(e))
return False
# try to request money variables from last ud block
try:
block = self.community.get_ud_block()
except Exception as e:
logging.debug('community get_ud_block error : ' + str(e))
return False
try:
block_t_minus_1 = self.community.get_ud_block(1)
except Exception as e:
logging.debug('community get_ud_block error : ' + str(e))
return False
# set infos in label
self.label_general.setText(
"""
<table cellpadding="5">
<tr><td align="right"><b>{:}</b></div></td><td>{:} {:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
<tr><td align="right"><b>{:2.2%} / {:} days</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
</table>
""".format(
round(self.get_referential_diff_value(block['dividend'])),
'Universal Dividend UD(t) in',
self.get_referential_name(),
round(self.get_referential_value(block['monetaryMass'])),
'Monetary Mass M(t) in',
self.get_referential_name(),
block['membersCount'],
'Members N(t)',
round(self.get_referential_value(block['monetaryMass'] / block['membersCount'])),
'Monetary Mass per member M(t)/N(t) in',
self.get_referential_name(),
block['dividend'] / (block_t_minus_1['monetaryMass'] / block['membersCount']),
params['dt'] / 86400,
'Actual growth c = UD(t)/[M(t-1)/N(t)]',
datetime.datetime.fromtimestamp(block['medianTime'] + params['dt']).strftime("%d/%m/%Y %I:%M"),
'Next UD date and time (t+1)'
)
)
# set infos in label
self.label_money.setText(
"""
<table cellpadding="5">
<tr><td align="right"><b>{:2.0%} / {:} days</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:2.0%}</b></td><td>{:}</td></tr>
</table>
""".format(
params['c'],
params['dt'] / 86400,
'Fundamental growth (c)',
params['ud0'],
'Initial Universal Dividend UD(0) in',
self.community.short_currency,
params['dt'] / 86400,
'Time period (dt) in days (86400 seconds) between two UD',
params['medianTimeBlocks'],
'Number of blocks used for calculating median time',
params['avgGenTime'],
'The average time in seconds for writing 1 block (wished time)',
params['dtDiffEval'],
'The number of blocks required to evaluate again PoWMin value',
params['blocksRot'],
'The number of previous blocks to check for personalized difficulty',
params['percentRot'],
'The percent of previous issuers to reach for personalized difficulty'
)
)
# set infos in label
self.label_rules.setText(
"""
<table cellpadding="5">
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
</table>
""".format(
'{:2.0%} / {:} days'.format(params['c'], params['dt'] / 86400),
'Fundamental growth (c) / Delta time (dt)',
'UD(t+1) = MAX { UD(t) ; c * M(t) / N(t) }',
'Universal Dividend (formula)',
'{:} = MAX {{ {:} {:} ; {:2.0%} * {:} {:} / {:} }}'.format(
math.ceil(max(block['dividend'], params['c'] * block['monetaryMass'] / block['membersCount'])),
round(self.get_referential_diff_value(block['dividend'])),
self.get_referential_name(),
params['c'],
round(self.get_referential_value(block['monetaryMass'])),
self.get_referential_name(),
block['membersCount']
),
'Universal Dividend (computed)'
)
)
# set infos in label
self.label_wot.setText(
"""
<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>
</table>
""".format(
params['sigDelay'] / 86400,
'Minimum delay between 2 identical certifications (in days)',
params['sigValidity'] / 86400,
'Maximum age of a valid signature (in days)',
params['sigQty'],
'Minimum quantity of signatures to be part of the WoT',
params['sigWoT'],
'Minimum quantity of valid made certifications to be part of the WoT for distance rule',
params['msValidity'] / 86400,
'Maximum age of a valid membership (in days)',
params['stepMax'],
'Maximum distance between each WoT member and a newcomer',
)
)
def get_referential_value(self, value):
return self.account.units_to_ref(value, self.community)
def get_referential_diff_value(self, value):
return self.account.units_to_diff_ref(value, self.community)
def get_referential_name(self):
return self.account.ref_name(self.community.short_currency)
'''
Created on 1 févr. 2014
@author: inso
'''
from ..gen_resources.mainwindow_uic import Ui_MainWindow
from ..gen_resources.about_uic import Ui_AboutPopup
from ..gen_resources.homescreen_uic import Ui_HomeScreenWidget
from PyQt5.QtWidgets import QMainWindow, QAction, QFileDialog, QProgressBar, \
QMessageBox, QLabel, QComboBox, QDialog
from PyQt5.QtCore import QSignalMapper, QObject, QThread, \
pyqtSlot, pyqtSignal, QDate, QDateTime, QTimer, QUrl
from PyQt5.QtGui import QIcon, QDesktopServices
from .process_cfg_account import ProcessConfigureAccount
from .transfer import TransferMoneyDialog
from .currency_tab import CurrencyTabWidget
from .contact import ConfigureContactDialog
from .import_account import ImportAccountDialog
from .certification import CertificationDialog
from .password_asker import PasswordAskerDialog
from ..tools.exceptions import NoPeerAvailable
from .homescreen import HomeScreenWidget
from ..core.account import Account
from ..__init__ import __version__
import logging
import requests
class Loader(QObject):
def __init__(self, app):
super().__init__()
self.app = app
self.account_name = ""
loaded = pyqtSignal()
connection_error = pyqtSignal(str)
def set_account_name(self, name):
self.account_name = name
@pyqtSlot()
def load(self):
if self.account_name != "":
try:
account = self.app.get_account(self.account_name)
self.app.change_current_account(account)
except requests.exceptions.RequestException as e:
self.connection_error.emit(str(e))
self.loaded.emit()
except NoPeerAvailable as e:
self.connection_error.emit(str(e))
self.loaded.emit()
self.loaded.emit()
class MainWindow(QMainWindow, Ui_MainWindow):
'''
classdocs
'''
def __init__(self, app):
'''
Constructor
'''
# Set up the user interface from Designer.
super().__init__()
self.setupUi(self)
self.app = app
self.password_asker = None
self.initialized = False
self.busybar = QProgressBar(self.statusbar)
self.busybar.setMinimum(0)
self.busybar.setMaximum(0)
self.busybar.setValue(-1)
self.statusbar.addWidget(self.busybar)
self.busybar.hide()
self.combo_referential = QComboBox(self)
self.combo_referential.setEnabled(False)
self.combo_referential.currentTextChanged.connect(self.referential_changed)
self.status_label = QLabel("", self)
self.label_time = QLabel("", self)
self.statusbar.addPermanentWidget(self.status_label, 1)
self.statusbar.addPermanentWidget(self.label_time)
self.statusbar.addPermanentWidget(self.combo_referential)
self.update_time()
self.loader_thread = QThread()
self.loader = Loader(self.app)
self.loader.moveToThread(self.loader_thread)
self.loader.loaded.connect(self.loader_finished)
self.loader.loaded.connect(self.loader_thread.quit)
self.loader.connection_error.connect(self.display_error)
self.loader_thread.started.connect(self.loader.load)
self.homescreen = HomeScreenWidget()
self.centralWidget().layout().addWidget(self.homescreen)
self.homescreen.button_new.clicked.connect(self.open_add_account_dialog)
self.homescreen.button_import.clicked.connect(self.import_account)
self.open_ucoin_info = lambda: QDesktopServices.openUrl(QUrl("http://ucoin.io/theoretical/"))
self.homescreen.button_info.clicked.connect(self.open_ucoin_info)
#TODO: There are too much refresh() calls on startup
self.refresh()
def open_add_account_dialog(self):
dialog = ProcessConfigureAccount(self.app, None)
result = dialog.exec_()
if result == QDialog.Accepted:
self.action_change_account(self.app.current_account.name)
@pyqtSlot()
def loader_finished(self):
self.refresh()
self.busybar.hide()
self.app.disconnect()
@pyqtSlot(str)
def display_error(self, error):
QMessageBox.critical(self, ":(",
error,
QMessageBox.Ok)
@pyqtSlot(str)
def referential_changed(self, text):
if self.app.current_account:
self.app.current_account.set_display_referential(text)
if self.currencies_tabwidget.currentWidget():
self.currencies_tabwidget.currentWidget().referential_changed()
@pyqtSlot()
def update_time(self):
date = QDate.currentDate()
self.label_time.setText("{0}".format(date.toString("dd/MM/yyyy")))
next_day = date.addDays(1)
current_time = QDateTime().currentDateTime().toMSecsSinceEpoch()
next_time = QDateTime(next_day).toMSecsSinceEpoch()
timer = QTimer()
timer.timeout.connect(self.update_time)
timer.start(next_time - current_time)
@pyqtSlot()
def delete_contact(self):
contact = self.sender().data()
self.app.current_account.contacts.remove(contact)
self.refresh_contacts()
@pyqtSlot()
def edit_contact(self):
contact = self.sender().data()
dialog = ConfigureContactDialog(self.app.current_account, self, contact, True)
result = dialog.exec_()
if result == QDialog.Accepted:
self.window().refresh_contacts()
def action_change_account(self, account_name):
def loading_progressed(value, maximum):
logging.debug("Busybar : {:} : {:}".format(value, maximum))
self.busybar.setValue(value)
self.busybar.setMaximum(maximum)
self.app.loading_progressed.connect(loading_progressed)
self.busybar.show()
self.status_label.setText("Loading account {0}".format(account_name))
self.loader.set_account_name(account_name)
self.loader_thread.start(QThread.LowPriority)
self.homescreen.setEnabled(False)
def open_transfer_money_dialog(self):
dialog = TransferMoneyDialog(self.app.current_account,
self.password_asker)
dialog.accepted.connect(self.refresh_wallets)
if dialog.exec_() == QDialog.Accepted:
currency_tab = self.currencies_tabwidget.currentWidget()
currency_tab.table_history.model().invalidate()
def open_certification_dialog(self):
dialog = CertificationDialog(self.app.current_account,
self.password_asker)
dialog.exec_()
def open_add_contact_dialog(self):
dialog = ConfigureContactDialog(self.app.current_account, self)
result = dialog.exec_()
if result == QDialog.Accepted:
self.window().refresh_contacts()
def open_configure_account_dialog(self):
dialog = ProcessConfigureAccount(self.app, self.app.current_account)
dialog.accepted.connect(self.refresh)
result = dialog.exec_()
if result == QDialog.Accepted:
self.action_change_account(self.app.current_account.name)
def open_about_popup(self):
"""
Open about popup window
"""
aboutDialog = QDialog(self)
aboutUi = Ui_AboutPopup()
aboutUi.setupUi(aboutDialog)
text = """
<h1>Cutecoin</h1>
<p>Python/Qt uCoin client</p>
<p>Version : {:}</p>
<p>License : MIT</p>
<p><b>Authors</b></p>
<p>inso</p>
<p>vit</p>
<p>canercandan</p>
""".format(__version__)
aboutUi.label.setText(text)
aboutDialog.show()
def refresh_wallets(self):
currency_tab = self.currencies_tabwidget.currentWidget()
if currency_tab:
currency_tab.refresh_wallets()
def refresh_communities(self):
self.currencies_tabwidget.clear()
if self.app.current_account:
for community in self.app.current_account.communities:
try:
tab_currency = CurrencyTabWidget(self.app, community,
self.password_asker,
self.status_label)
tab_currency.refresh()
self.currencies_tabwidget.addTab(tab_currency,
QIcon(":/icons/currency_icon"),
community.name)
except NoPeerAvailable as e:
QMessageBox.critical(self, "Could not join {0}".format(community.currency),
str(e),
QMessageBox.Ok)
continue
except requests.exceptions.RequestException as e:
QMessageBox.critical(self, ":(",
str(e),
QMessageBox.Ok)
def refresh_contacts(self):
self.menu_contacts_list.clear()
if self.app.current_account:
for contact in self.app.current_account.contacts:
contact_menu = self.menu_contacts_list.addMenu(contact.name)
edit_action = contact_menu.addAction("Edit")
edit_action.triggered.connect(self.edit_contact)
edit_action.setData(contact)
delete_action = contact_menu.addAction("Delete")
delete_action.setData(contact)
delete_action.triggered.connect(self.delete_contact)
def set_as_default_account(self):
self.app.default_account = self.app.current_account.name
logging.debug(self.app.current_account)
self.app.save(self.app.current_account)
self.action_set_as_default.setEnabled(False)
'''
Refresh main window
When the selected account changes, all the widgets
in the window have to be refreshed
'''
def refresh(self):
logging.debug("Refresh finished")
self.menu_change_account.clear()
signal_mapper = QSignalMapper(self)
for account_name in sorted(self.app.accounts.keys()):
action = QAction(account_name, self)
self.menu_change_account.addAction(action)
signal_mapper.setMapping(action, account_name)
action.triggered.connect(signal_mapper.map)
signal_mapper.mapped[str].connect(self.action_change_account)
if self.app.current_account is None:
self.currencies_tabwidget.hide()
self.homescreen.show()
self.setWindowTitle("CuteCoin {0}".format(__version__))
self.menu_contacts.setEnabled(False)
self.menu_actions.setEnabled(False)
self.action_configure_parameters.setEnabled(False)
self.action_set_as_default.setEnabled(False)
self.combo_referential.setEnabled(False)
self.status_label.setText("")
self.password_asker = None
else:
self.currencies_tabwidget.show()
self.homescreen.hide()
self.action_set_as_default.setEnabled(self.app.current_account.name
!= self.app.default_account)
self.password_asker = PasswordAskerDialog(self.app.current_account)
self.combo_referential.blockSignals(True)
self.combo_referential.clear()
self.combo_referential.addItems(sorted(Account.referentials.keys()))
self.combo_referential.setEnabled(True)
self.combo_referential.blockSignals(False)
self.combo_referential.setCurrentText(self.app.current_account.referential)
self.menu_contacts.setEnabled(True)
self.action_configure_parameters.setEnabled(True)
self.menu_actions.setEnabled(True)
self.password_asker = PasswordAskerDialog(self.app.current_account)
self.setWindowTitle("CuteCoin {0} - Account : {1}".format(__version__,
self.app.current_account.name))
self.refresh_communities()
self.refresh_wallets()
self.refresh_contacts()
def import_account(self):
dialog = ImportAccountDialog(self.app, self)
dialog.accepted.connect(self.refresh)
dialog.exec_()
def export_account(self):
selected_file = QFileDialog.getSaveFileName(self,
"Export an account",
"",
"All account files (*.acc)")
path = ""
if selected_file[0][-4:] == ".acc":
path = selected_file[0]
else:
path = selected_file[0] + ".acc"
self.app.export_account(path, self.app.current_account)
def closeEvent(self, event):
if self.app.current_account:
self.app.save_cache(self.app.current_account)
self.app.save_persons()
self.loader.deleteLater()
self.loader_thread.deleteLater()
super().closeEvent(event)
def showEvent(self, event):
super().showEvent(event)
if not self.initialized:
if self.app.default_account != "":
logging.debug("Loading default account")
self.action_change_account(self.app.default_account)
self.initialized = True
from cutecoin.core.graph import Graph
from PyQt5.QtWidgets import QDialog
from ..gen_resources.member_uic import Ui_DialogMember
class MemberDialog(QDialog, Ui_DialogMember):
"""
classdocs
"""
def __init__(self, account, community, person):
"""
Constructor
"""
super().__init__()
self.setupUi(self)
self.community = community
self.account = account
self.person = person
self.label_uid.setText(person.name)
join_date = self.person.get_join_date(self.community)
if join_date is None:
join_date = 'not a member'
# calculate path to account member
graph = Graph(self.community)
path = None
# if selected member is not the account member...
if person.pubkey != self.account.pubkey:
# add path from selected member to account member
path = graph.get_shortest_path_between_members(person, self.account.get_person())
text = """
<table cellpadding="5">
<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>
""".format(
'Public key',
self.person.pubkey,
'Join date',
join_date
)
if path:
distance = len(path) - 1
text += """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""".format('Distance', distance)
if distance > 1:
index = 0
for node in path:
if index == 0:
text += """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""".format('Path', node['text'])
else:
text += """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""".format('', node['text'])
if index == distance and node['id'] != self.account.pubkey:
text += """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""".format('', self.account.name)
index += 1
# close html text
text += "</table>"
# set text in label
self.label_properties.setText(text)
'''
Created on 20 févr. 2015
@author: inso
'''
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import QModelIndex, QThread
from ..core.watchers.network import NetworkWatcher
from ..models.network import NetworkTableModel, NetworkFilterProxyModel
from ..gen_resources.network_tab_uic import Ui_NetworkTabWidget
class NetworkTabWidget(QWidget, Ui_NetworkTabWidget):
'''
classdocs
'''
def __init__(self, community):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
model = NetworkTableModel(community)
proxy = NetworkFilterProxyModel(self)
proxy.setSourceModel(model)
self.table_network.setModel(proxy)
self.network_watcher = NetworkWatcher(community)
self.watcher_thread = QThread()
self.network_watcher.moveToThread(self.watcher_thread)
self.watcher_thread.started.connect(self.network_watcher.watch)
community.network.nodes_changed.connect(self.refresh_nodes)
def refresh_nodes(self):
self.table_network.model().sourceModel().modelReset.emit()
def closeEvent(self, event):
super().closeEvent(event)
self.network_watcher.deleteLater()
self.watcher_thread.deleteLater()
def showEvent(self, event):
super().showEvent(event)
self.watcher_thread.start()
'''
Created on 24 dec. 2014
@author: inso
'''
import logging
import re
from PyQt5.QtWidgets import QDialog, QMessageBox
from ..gen_resources.password_asker_uic import Ui_PasswordAskerDialog
class PasswordAskerDialog(QDialog, Ui_PasswordAskerDialog):
'''
A dialog to get password.
'''
def __init__(self, account):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
self.account = account
self.password = ""
self.remember = False
def exec_(self):
if not self.remember:
super().exec_()
pwd = self.password
if not self.remember:
self.password = ""
return pwd
else:
self.setResult(QDialog.Accepted)
return self.password
def accept(self):
password = self.edit_password.text()
if detect_non_printable(password):
QMessageBox.warning(self, "Bad password",
"Non printable characters in password",
QMessageBox.Ok)
return False
if not self.account.check_password(password):
QMessageBox.warning(self, "Failed to get private key",
"Wrong password typed. Cannot open the private key",
QMessageBox.Ok)
return False
self.remember = self.check_remember.isChecked()
self.password = password
self.edit_password.setText("")
logging.debug("Password is valid")
super().accept()
def reject(self):
self.edit_password.setText("")
logging.debug("Cancelled")
self.setResult(QDialog.Accepted)
self.password = ""
super().reject()
def detect_non_printable(data):
control_chars = ''.join(map(chr, list(range(0, 32)) + list(range(127, 160))))
control_char_re = re.compile('[%s]' % re.escape(control_chars))
if control_char_re.search(data):
return True
'''
Created on 6 mars 2014
@author: inso
'''
import logging
import requests
from ucoinpy.documents.peer import Peer
from ucoinpy.key import SigningKey
from ..gen_resources.account_cfg_uic import Ui_AccountConfigurationDialog
from ..gui.process_cfg_community import ProcessConfigureCommunity
from ..gui.password_asker import PasswordAskerDialog, detect_non_printable
from ..models.communities import CommunitiesListModel
from ..tools.exceptions import KeyAlreadyUsed, Error, NoPeerAvailable
from PyQt5.QtWidgets import QDialog, QMessageBox
class Step():
def __init__(self, config_dialog, previous_step=None, next_step=None):
self.previous_step = previous_step
self.next_step = next_step
self.config_dialog = config_dialog
class StepPageInit(Step):
'''
First step when adding a community
'''
def __init__(self, config_dialog):
super().__init__(config_dialog)
def is_valid(self):
if len(self.config_dialog.edit_account_name.text()) > 2:
return True
else:
return False
def process_next(self):
if self.config_dialog.account is None:
name = self.config_dialog.edit_account_name.text()
self.config_dialog.account = self.config_dialog.app.create_account(name)
else:
name = self.config_dialog.edit_account_name.text()
self.config_dialog.account.name = name
def display_page(self):
if self.config_dialog.account is not None:
self.config_dialog.edit_account_name.setText(self.config_dialog.account.name)
model = CommunitiesListModel(self.config_dialog.account)
self.config_dialog.list_communities.setModel(model)
nb_wallets = len(self.config_dialog.account.wallets)
self.config_dialog.spinbox_wallets.setValue(nb_wallets)
self.config_dialog.password_asker = PasswordAskerDialog(self.config_dialog.account)
self.config_dialog.button_previous.setEnabled(False)
self.config_dialog.button_next.setEnabled(False)
class StepPageKey(Step):
'''
First step when adding a community
'''
def __init__(self, config_dialog):
super().__init__(config_dialog)
def is_valid(self):
if len(self.config_dialog.edit_salt.text()) < 6:
self.config_dialog.label_info.setText("Warning : salt is too short")
return False
if len(self.config_dialog.edit_password.text()) < 6:
self.config_dialog.label_info.setText("Warning : password is too short")
return False
if detect_non_printable(self.config_dialog.edit_salt.text()):
self.config_dialog.label_info.setText("Warning : Invalid characters in salt field")
return False
if detect_non_printable(self.config_dialog.edit_password.text()):
self.config_dialog.label_info.setText("Warning : Invalid characters in password field")
return False
if self.config_dialog.edit_password.text() != \
self.config_dialog.edit_password_repeat.text():
self.config_dialog.label_info.setText("Error : passwords are different")
return False
self.config_dialog.label_info.setText("")
return True
def process_next(self):
salt = self.config_dialog.edit_salt.text()
password = self.config_dialog.edit_password.text()
self.config_dialog.account.salt = salt
self.config_dialog.account.pubkey = SigningKey(salt, password).pubkey
self.config_dialog.password_asker = PasswordAskerDialog(self.config_dialog.account)
model = CommunitiesListModel(self.config_dialog.account)
self.config_dialog.list_communities.setModel(model)
def display_page(self):
self.config_dialog.button_previous.setEnabled(False)
self.config_dialog.button_next.setEnabled(False)
class StepPageCommunities(Step):
'''
First step when adding a community
'''
def __init__(self, config_dialog):
super().__init__(config_dialog)
def is_valid(self):
return True
def process_next(self):
password = self.config_dialog.password_asker.exec_()
if self.config_dialog.password_asker.result() == QDialog.Rejected:
return
nb_wallets = self.config_dialog.spinbox_wallets.value()
self.config_dialog.account.set_walletpool_size(nb_wallets, password)
self.config_dialog.app.add_account(self.config_dialog.account)
if len(self.config_dialog.app.accounts) == 1:
self.config_dialog.app.default_account = self.config_dialog.account.name
self.config_dialog.app.save(self.config_dialog.account)
self.config_dialog.app.current_account = self.config_dialog.account
def display_page(self):
logging.debug("Communities DISPLAY")
self.config_dialog.button_previous.setEnabled(False)
self.config_dialog.button_next.setText("Ok")
list_model = CommunitiesListModel(self.config_dialog.account)
self.config_dialog.list_communities.setModel(list_model)
class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog):
'''
classdocs
'''
def __init__(self, app, account):
'''
Constructor
'''
# Set up the user interface from Designer.
super().__init__()
self.setupUi(self)
self.account = account
self.password_asker = None
self.app = app
step_init = StepPageInit(self)
step_key = StepPageKey(self)
step_communities = StepPageCommunities(self)
step_init.next_step = step_key
step_key.next_step = step_communities
self.step = step_init
self.step.display_page()
if self.account is None:
self.setWindowTitle("New account")
self.button_delete.hide()
else:
self.stacked_pages.removeWidget(self.stacked_pages.widget(1))
step_init.next_step = step_communities
self.button_next.setEnabled(True)
self.stacked_pages.currentWidget()
self.setWindowTitle("Configure " + self.account.name)
def open_process_add_community(self):
logging.debug("Opening configure community dialog")
logging.debug(self.password_asker)
dialog = ProcessConfigureCommunity(self.account, None,
self.password_asker)
dialog.accepted.connect(self.action_add_community)
dialog.exec_()
def action_add_community(self):
logging.debug("Action add community : done")
self.list_communities.setModel(CommunitiesListModel(self.account))
self.button_next.setEnabled(True)
self.button_next.setText("Ok")
def action_remove_community(self):
for index in self.list_communities.selectedIndexes():
self.account.communities.pop(index.row())
self.list_communities.setModel(CommunitiesListModel(self.account))
def action_edit_community(self):
self.list_communities.setModel(CommunitiesListModel(self.account))
def action_edit_account_key(self):
if self.step.is_valid():
self.button_next.setEnabled(True)
else:
self.button_next.setEnabled(False)
def action_show_pubkey(self):
salt = self.edit_salt.text()
password = self.edit_password.text()
pubkey = SigningKey(salt, password).pubkey
QMessageBox.information(self, "Public key",
"These parameters pubkeys are : {0}".format(pubkey))
def action_edit_account_parameters(self):
if self.step.is_valid():
self.button_next.setEnabled(True)
else:
self.button_next.setEnabled(False)
def open_process_edit_community(self, index):
community = self.account.communities[index.row()]
try:
dialog = ProcessConfigureCommunity(self.account, community, self.password_asker)
except NoPeerAvailable as e:
QMessageBox.critical(self, "Error",
str(e), QMessageBox.Ok)
return
except requests.exceptions.RequestException as e:
QMessageBox.critical(self, "Error",
str(e), QMessageBox.Ok)
return
dialog.accepted.connect(self.action_edit_community)
dialog.exec_()
def action_delete_account(self):
reply = QMessageBox.question(self, "Warning",
"""This action will delete your account locally.
Please note your key parameters (salt and password) if you wish to recover it later.
Your account won't be removed from the networks it joined.
Are you sure ?""")
if reply == QMessageBox.Yes:
account = self.app.current_account
self.app.delete_account(account)
self.app.save(account)
self.accept()
def next(self):
if self.step.is_valid():
try:
self.step.process_next()
if self.step.next_step is not None:
self.step = self.step.next_step
next_index = self.stacked_pages.currentIndex() + 1
self.stacked_pages.setCurrentIndex(next_index)
self.step.display_page()
else:
self.accept()
except Error as e:
QMessageBox.critical(self, "Error",
str(e), QMessageBox.Ok)
def previous(self):
if self.step.previous_step is not None:
self.step = self.step.previous_step
previous_index = self.stacked_pages.currentIndex() - 1
self.stacked_pages.setCurrentIndex(previous_index)
self.step.display_page()
def accept(self):
super().accept()
'''
Created on 8 mars 2014
@author: inso
'''
import logging
import requests
from ucoinpy.api import bma
from ucoinpy.api.bma import ConnectionHandler
from ucoinpy.documents.peer import Peer
from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox
from ..gen_resources.community_cfg_uic import Ui_CommunityConfigurationDialog
from ..models.peering import PeeringTreeModel
from ..core.community import Community
from ..core.person import Person
from ..core.net.node import Node
from ..tools.exceptions import PersonNotFoundError, NoPeerAvailable
class Step():
def __init__(self, config_dialog, previous_step=None, next_step=None):
self.previous_step = previous_step
self.next_step = next_step
self.config_dialog = config_dialog
class StepPageInit(Step):
'''
First step when adding a community
'''
def __init__(self, config_dialog):
super().__init__(config_dialog)
self.node = None
logging.debug("Init")
def is_valid(self):
server = self.config_dialog.lineedit_server.text()
port = self.config_dialog.spinbox_port.value()
logging.debug("Is valid ? ")
try:
self.node = Node.from_address(None, server, port)
except Exception as e:
QMessageBox.critical(self.config_dialog, ":(",
str(e),
QMessageBox.Ok)
return True
def process_next(self):
'''
We create the community
'''
account = self.config_dialog.account
logging.debug("Account : {0}".format(account))
self.config_dialog.community = Community.create(self.node)
def display_page(self):
self.config_dialog.button_previous.setEnabled(False)
class StepPageAddpeers(Step):
'''
The step where the user add peers
'''
def __init__(self, config_dialog):
super().__init__(config_dialog)
def is_valid(self):
return True
def process_next(self):
pass
def display_page(self):
# We add already known peers to the displayed list
self.config_dialog.nodes = self.config_dialog.community.nodes
try:
tree_model = PeeringTreeModel(self.config_dialog.community)
except requests.exceptions.RequestException:
raise
self.config_dialog.tree_peers.setModel(tree_model)
self.config_dialog.button_previous.setEnabled(False)
self.config_dialog.button_next.setText("Ok")
class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog):
'''
Dialog to configure or add a community
'''
def __init__(self, account, community, password_asker, default_node=None):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
self.community = community
self.account = account
self.password_asker = password_asker
self.step = None
self.nodes = []
step_init = StepPageInit(self)
step_add_peers = StepPageAddpeers(self)
step_init.next_step = step_add_peers
if self.community is not None:
self.stacked_pages.removeWidget(self.page_init)
self.step = step_add_peers
self.setWindowTitle("Configure community "
+ self.community.currency)
else:
self.step = step_init
self.setWindowTitle("Add a community")
self.step.display_page()
def next(self):
if self.step.next_step is not None:
if self.step.is_valid():
try:
self.step.process_next()
self.step = self.step.next_step
next_index = self.stacked_pages.currentIndex() + 1
self.stacked_pages.setCurrentIndex(next_index)
self.step.display_page()
except NoPeerAvailable:
return
except requests.exceptions.RequestException as e:
QMessageBox.critical(self.config_dialog, ":(",
str(e),
QMessageBox.Ok)
return
else:
self.accept()
def previous(self):
if self.step.previous_step is not None:
self.step = self.step.previous_step
previous_index = self.stacked_pages.currentIndex() - 1
self.stacked_pages.setCurrentIndex(previous_index)
self.step.display_page()
def add_node(self):
'''
Add node slot
'''
server = self.lineedit_server.text()
port = self.spinbox_port.value()
try:
node = Node.from_address(self.community.currency, server, port)
self.community.add_node(node)
except Exception as e:
QMessageBox.critical(self, "Error",
str(e))
self.tree_peers.setModel(PeeringTreeModel(self.community))
def showContextMenu(self, point):
if self.stacked_pages.currentIndex() == 1:
menu = QMenu()
action = menu.addAction("Delete", self.removeNode)
if self.community is not None:
if len(self.nodes) == 1:
action.setEnabled(False)
menu.exec_(self.mapToGlobal(point))
def accept(self):
try:
Person.lookup(self.account.pubkey, self.community, cached=False)
except PersonNotFoundError as e:
reply = QMessageBox.question(self, "Pubkey not found",
"""The public key of your account wasn't found in the community. :\n
{0}\n
Would you like to publish the key ?""".format(self.account.pubkey))
if reply == QMessageBox.Yes:
password = self.password_asker.exec_()
if self.password_asker.result() == QDialog.Rejected:
return
try:
self.account.send_selfcert(password, self.community)
except ValueError as e:
QMessageBox.critical(self, "Pubkey publishing error",
e.message)
except NoPeerAvailable as e:
QMessageBox.critical(self, "Network error",
"Couldn't connect to network : {0}".format(e),
QMessageBox.Ok)
except Exception as e:
QMessageBox.critical(self, "Error",
"{0}".format(e),
QMessageBox.Ok)
else:
return
if self.community not in self.account.communities:
self.account.add_community(self.community)
super().accept()
'''
Created on 2 févr. 2014
@author: inso
'''
from PyQt5.QtWidgets import QDialog, QMessageBox
from PyQt5.QtCore import QRegExp
from PyQt5.QtGui import QRegExpValidator, QValidator
from ..tools.exceptions import NotEnoughMoneyError, NoPeerAvailable
from ..gen_resources.transfer_uic import Ui_TransferMoneyDialog
import logging
class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog):
'''
classdocs
'''
def __init__(self, sender, password_asker):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
self.account = sender
self.password_asker = password_asker
self.recipient_trusts = []
self.wallet = None
self.community = self.account.communities[0]
self.wallet = self.account.wallets[0]
self.dividend = self.community.dividend
regexp = QRegExp('^([ a-zA-Z0-9-_:/;*?\[\]\(\)\\\?!^+=@&~#{}|<>%.]{0,255})$')
validator = QRegExpValidator(regexp)
self.edit_message.setValidator(validator)
for community in self.account.communities:
self.combo_community.addItem(community.currency)
for wallet in self.account.wallets:
self.combo_wallets.addItem(wallet.name)
for contact in sender.contacts:
self.combo_contact.addItem(contact.name)
if len(self.account.contacts) == 0:
self.combo_contact.setEnabled(False)
self.radio_contact.setEnabled(False)
self.radio_pubkey.setChecked(True)
def accept(self):
comment = self.edit_message.text()
if self.radio_contact.isChecked():
index = self.combo_contact.currentIndex()
recipient = self.account.contacts[index].pubkey
else:
recipient = self.edit_pubkey.text()
amount = self.spinbox_amount.value()
if not amount:
QMessageBox.critical(self, "Money transfer",
"No amount. Please give the transfert amount",
QMessageBox.Ok)
return
password = self.password_asker.exec_()
if self.password_asker.result() == QDialog.Rejected:
return
try:
self.wallet.send_money(self.account.salt, password, self.community,
recipient, amount, comment)
QMessageBox.information(self, "Money transfer",
"Success transfering {0} {1} to {2}".format(amount,
self.community.currency,
recipient))
except ValueError as e:
QMessageBox.critical(self, "Money transfer",
"Something wrong happened : {0}".format(e),
QMessageBox.Ok)
return
except NotEnoughMoneyError as e:
QMessageBox.warning(self, "Money transfer",
"""This transaction could not be sent on this block
Please try again later""")
except NoPeerAvailable as e:
QMessageBox.critical(self, "Money transfer",
"Couldn't connect to network : {0}".format(e),
QMessageBox.Ok)
return
except Exception as e:
QMessageBox.critical(self, "Error",
"{0}".format(str(e)),
QMessageBox.Ok)
return
super().accept()
def amount_changed(self):
amount = self.spinbox_amount.value()
relative = amount / self.dividend
self.spinbox_relative.blockSignals(True)
self.spinbox_relative.setValue(relative)
self.spinbox_relative.blockSignals(False)
def relative_amount_changed(self):
relative = self.spinbox_relative.value()
amount = relative * self.dividend
self.spinbox_amount.blockSignals(True)
self.spinbox_amount.setValue(amount)
self.spinbox_amount.blockSignals(False)
def change_current_community(self, index):
self.community = self.account.communities[index]
self.dividend = self.community.dividend
amount = self.wallet.value(self.community)
ref_amount = self.account.units_to_ref(amount, self.community)
ref_name = self.account.ref_name(self.community.currency)
self.label_total.setText("{0} {1}".format(ref_amount, ref_name))
self.spinbox_amount.setSuffix(" " + self.community.currency)
self.spinbox_amount.setValue(0)
amount = self.wallet.value(self.community)
relative = amount / self.dividend
self.spinbox_amount.setMaximum(amount)
self.spinbox_relative.setMaximum(relative)
def change_displayed_wallet(self, index):
self.wallet = self.account.wallets[index]
amount = self.wallet.value(self.community)
ref_amount = self.account.units_to_ref(amount, self.community)
ref_name = self.account.ref_name(self.community.currency)
self.label_total.setText("{0} {1}".format(ref_amount, ref_name))
self.spinbox_amount.setValue(0)
amount = self.wallet.value(self.community)
relative = amount / self.dividend
self.spinbox_amount.setMaximum(amount)
self.spinbox_relative.setMaximum(relative)
def recipient_mode_changed(self, pubkey_toggled):
self.edit_pubkey.setEnabled(pubkey_toggled)
self.combo_contact.setEnabled(not pubkey_toggled)
# -*- coding: utf-8 -*-
import math
from PyQt5.QtGui import QPainter, QBrush, QPen, QPolygonF, QColor, QRadialGradient, \
QPainterPath, QMouseEvent, QWheelEvent, QTransform, QCursor
from PyQt5.QtCore import Qt, QRectF, QLineF, QPoint, QPointF, QSizeF, qFuzzyCompare, pyqtSignal
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsEllipseItem, \
QGraphicsSimpleTextItem, QGraphicsLineItem, QMenu, QAction, QGraphicsSceneHoverEvent, \
QGraphicsSceneContextMenuEvent
NODE_STATUS_HIGHLIGHTED = 1
NODE_STATUS_SELECTED = 2
NODE_STATUS_OUT = 4
ARC_STATUS_STRONG = 1
ARC_STATUS_WEAK = 2
class WotView(QGraphicsView):
def __init__(self, parent=None):
"""
Create View to display scene
:param parent: [Optional, default=None] Parent widget
"""
super(WotView, self).__init__(parent)
self.setScene(Scene(self))
self.setCacheMode(QGraphicsView.CacheBackground)
self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
self.setRenderHint(QPainter.Antialiasing)
self.setRenderHint(QPainter.SmoothPixmapTransform)
def wheelEvent(self, event: QWheelEvent):
"""
Zoom in/out on the mouse cursor
"""
# zoom only when CTRL key pressed
if (event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier:
steps = event.angleDelta().y() / 15 / 8
if steps == 0:
event.ignore()
return
# scale factor 1.25
sc = pow(1.25, steps)
self.scale(sc, sc)
self.centerOn(self.mapToScene(event.pos()))
event.accept()
# act normally on scrollbar
else:
# transmit event to parent class wheelevent
super(QGraphicsView, self).wheelEvent(event)
class Scene(QGraphicsScene):
# This defines signals taking string arguments
node_clicked = pyqtSignal(dict, name='nodeClicked')
node_signed = pyqtSignal(dict, name='nodeSigned')
node_transaction = pyqtSignal(dict, name='nodeTransaction')
node_contact = pyqtSignal(dict, name='nodeContact')
node_member = pyqtSignal(dict, name='nodeMember')
def __init__(self, parent=None):
"""
Create scene of the graph
:param parent: [Optional, default=None] Parent view
"""
super(Scene, self).__init__(parent)
self.lastDragPos = QPoint()
self.setItemIndexMethod(QGraphicsScene.NoIndex)
# list of nodes in scene
self.nodes = dict()
# axis of the scene for debug purpose
# self.addLine(-100, 0, 100, 0)
# self.addLine(0, -100, 0, 100)
def add_node(self, metadata, pos):
"""
Add a node item in the graph
:param dict metadata: Node metadata
:param tuple pos: Position (x,y) of the node
:return: Node
"""
node = Node(metadata, pos)
self.addItem(node)
self.nodes[node.id] = node
return node
def add_arc(self, source_node, destination_node, metadata):
"""
Add an arc between two nodes
:param Node source_node: Source node of the arc
:param Node destination_node: Destination node of the arc
:param dict arc: Arc data
:return: Arc
"""
arc = Arc(source_node, destination_node, metadata)
self.addItem(arc)
return arc
def update_wot(self, graph):
"""
draw community graph
:param dict graph: graph to draw
"""
# clear scene
self.clear()
# capture selected node (to draw it in the center)
for _id, node in graph.items():
if node['status'] & NODE_STATUS_SELECTED:
selected_id = _id
selected_node = node
root_node = self.add_node(selected_node, (0, 0))
# add certified by selected node
y = 0
x = 200
# capture nodes for sorting by text
nodes = list()
for arc in selected_node['arcs']:
nodes.append({'node': graph[arc['id']], 'arc': arc})
# sort by text
nodes = sorted(nodes, key=lambda _node: _node['node']['text'].lower())
# add nodes and arcs
for _node in nodes:
node = self.add_node(_node['node'], (x, y))
self.add_arc(root_node, node, _node['arc'])
y += 50
# add certifiers of selected node
y = 0
x = -200
# sort by text
nodes = ((k, v) for (k, v) in sorted(graph.items(), key=lambda kv: kv[1]['text'].lower()) if
selected_id in (arc['id'] for arc in v['arcs']))
# add nodes and arcs
for _id, certifier_node in nodes:
node = self.add_node(certifier_node, (x, y))
for arc in certifier_node['arcs']:
if arc['id'] == selected_id:
self.add_arc(node, root_node, arc)
y += 50
self.update()
def update_path(self, path):
x = 0
y = 0
for json_node in path:
if json_node['status'] & NODE_STATUS_SELECTED:
previous_node = json_node
y -= 100
continue
node = self.add_node(json_node, (x, y))
skip_reverse_arc = False
for arc in json_node['arcs']:
if arc['id'] == previous_node['id']:
#print("arc from %s to %s" % (node.id, previous_node['id']))
self.add_arc(node, self.nodes[previous_node['id']], arc)
skip_reverse_arc = True
break
if not skip_reverse_arc:
for arc in previous_node['arcs']:
if arc['id'] == json_node['id']:
#print("arc from %s to %s" % (previous_node['id'], node.id))
self.add_arc(self.nodes[previous_node['id']], node, arc)
previous_node = json_node
y -= 100
class Node(QGraphicsEllipseItem):
def __init__(self, metadata, x_y):
"""
Create node in the graph scene
:param dict metadata: Node metadata
:param x_y: Position of the node
"""
# unpack tuple
x, y = x_y
super(Node, self).__init__()
self.metadata = metadata
self.id = metadata['id']
self.status_wallet = self.metadata['status'] & NODE_STATUS_HIGHLIGHTED
self.status_member = not self.metadata['status'] & NODE_STATUS_OUT
self.text = self.metadata['text']
self.setToolTip(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
# color around ellipse
outline_color = QColor('grey')
outline_style = Qt.SolidLine
outline_width = 1
if self.status_wallet:
outline_color = QColor('black')
outline_width = 2
if not self.status_member:
outline_color = QColor('red')
outline_style = Qt.SolidLine
self.setPen(QPen(outline_color, outline_width, outline_style))
# text inside ellipse
self.text_item = QGraphicsSimpleTextItem(self)
self.text_item.setText(self.text)
text_color = QColor('grey')
if self.status_wallet == NODE_STATUS_HIGHLIGHTED:
text_color = QColor('black')
self.text_item.setBrush(QBrush(text_color))
# center ellipse around text
self.setRect(
0,
0,
self.text_item.boundingRect().width() * 2,
self.text_item.boundingRect().height() * 2
)
# set anchor to the center
self.setTransform(
QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0))
self.setPos(x, y)
# center text in ellipse
self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0)
# create gradient inside the ellipse
gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width())
gradient.setColorAt(0, QColor('white'))
gradient.setColorAt(1, QColor('darkgrey'))
self.setBrush(QBrush(gradient))
# cursor change on hover
self.setAcceptHoverEvents(True)
self.setZValue(1)
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.metadata)
def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
"""
Mouse enter on node zone
:param event: scene hover event
"""
self.setCursor(Qt.ArrowCursor)
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent):
"""
Right click on node to show node menu
Except on wallet node
:param event: scene context menu event
"""
# no menu on the wallet node
if self.status_wallet:
return None
# create node context menus
self.menu = QMenu()
# action show member
self.action_show_member = QAction('Informations', self.scene())
self.menu.addAction(self.action_show_member)
self.action_show_member.triggered.connect(self.member_action)
# action add identity as contact
self.action_contact = QAction('Add as contact', self.scene())
self.menu.addAction(self.action_contact)
self.action_contact.triggered.connect(self.contact_action)
# action transaction toward identity
self.action_transaction = QAction('Send money', self.scene())
self.menu.addAction(self.action_transaction)
self.action_transaction.triggered.connect(self.transaction_action)
# action sign identity
self.action_sign = QAction('Certify identity', self.scene())
self.menu.addAction(self.action_sign)
self.action_sign.triggered.connect(self.sign_action)
# run menu
self.menu.exec(event.screenPos())
def add_arc(self, arc):
"""
Add arc to the arc list
:param arc: Arc
"""
self.arcs.append(arc)
def member_action(self):
"""
Transaction action to identity node
"""
# trigger scene signal
self.scene().node_member.emit(self.metadata)
def contact_action(self):
"""
Transaction action to identity node
"""
# trigger scene signal
self.scene().node_contact.emit(self.metadata)
def sign_action(self):
"""
Sign identity node
"""
# trigger scene signal
self.scene().node_signed.emit(self.metadata)
def transaction_action(self):
"""
Transaction action to identity node
"""
# trigger scene signal
self.scene().node_transaction.emit(self.metadata)
class Arc(QGraphicsLineItem):
def __init__(self, source_node, destination_node, metadata):
"""
Create an arc between two nodes
:param Node source_node: Source node of the arc
:param Node destination_node: Destination node of the arc
:param dict metadata: Arc metadata
"""
super(Arc, self).__init__()
self.metadata = metadata
self.source = source_node
self.destination = destination_node
self.setToolTip(self.metadata['tooltip'])
self.source.add_arc(self)
self.status = self.metadata['status']
self.source_point = None
self.destination_point = None
self.arrow_size = 5.0
self.setAcceptedMouseButtons(Qt.NoButton)
# cursor change on hover
self.setAcceptHoverEvents(True)
self.adjust()
self.setZValue(0)
def adjust(self):
"""
Draw the arc line
"""
if not self.source or not self.destination:
return
line = QLineF(
self.mapFromItem(
self.source,
self.source.boundingRect().width() - (self.source.boundingRect().width() / 2.0),
self.source.boundingRect().height() / 2.0
),
self.mapFromItem(
self.destination,
self.destination.boundingRect().width() / 2.0,
self.destination.boundingRect().height() / 2.0
)
)
self.prepareGeometryChange()
self.source_point = line.p1()
self.destination_point = line.p2()
# mouse over on line only
self.setLine(line)
# virtual function require subclassing
def boundingRect(self):
"""
Return the bounding rectangle size
:return: QRectF
"""
if not self.source or not self.destination:
return QRectF()
pen_width = 1.0
extra = (pen_width + self.arrow_size) / 2.0
return QRectF(
self.source_point, QSizeF(
self.destination_point.x() - self.source_point.x(),
self.destination_point.y() - self.source_point.y()
)
).normalized().adjusted(
-extra,
-extra,
extra,
extra
)
def paint(self, painter, option, widget):
"""
Customize line adding an arrow head
:param QPainter painter: Painter instance of the item
:param option: Painter option of the item
:param widget: Widget instance
"""
if not self.source or not self.destination:
return
line = QLineF(self.source_point, self.destination_point)
if qFuzzyCompare(line.length(), 0):
return
# Draw the line itself
color = QColor()
style = Qt.SolidLine
if self.status == ARC_STATUS_STRONG:
color.setNamedColor('blue')
if self.status == ARC_STATUS_WEAK:
color.setNamedColor('salmon')
style = Qt.DashLine
painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(line)
painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
# Draw the arrows
angle = math.acos(line.dx() / line.length())
if line.dy() >= 0:
angle = (2.0 * math.pi) - angle
# arrow in the middle of the arc
hpx = line.p1().x() + (line.dx() / 2.0)
hpy = line.p1().y() + (line.dy() / 2.0)
head_point = QPointF(hpx, hpy)
painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
destination_arrow_p1 = head_point + QPointF(
math.sin(angle - math.pi / 3) * self.arrow_size,
math.cos(angle - math.pi / 3) * self.arrow_size)
destination_arrow_p2 = head_point + QPointF(
math.sin(angle - math.pi + math.pi / 3) * self.arrow_size,
math.cos(angle - math.pi + math.pi / 3) * self.arrow_size)
painter.setBrush(color)
painter.drawPolygon(QPolygonF([head_point, destination_arrow_p1, destination_arrow_p2]))
def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
"""
Mouse enter on arc zone
:param event: scene hover event
"""
self.setCursor(Qt.ArrowCursor)
def shape(self):
"""
Return real shape of the item to detect collision or hover accurately
:return: QPainterPath
"""
# detection mouse hover on arc path
path = QPainterPath()
path.addPolygon(QPolygonF([self.line().p1(), self.line().p2()]))
# add handles at the start and end of arc
path.addRect(QRectF(
self.line().p1().x() - 5,
self.line().p1().y() - 5,
self.line().p1().x() + 5,
self.line().p1().y() + 5
))
path.addRect(QRectF(
self.line().p2().x() - 5,
self.line().p2().y() - 5,
self.line().p2().x() + 5,
self.line().p2().y() + 5
))
return path
'''
Created on 15 févr. 2015
@author: inso
'''
import logging
from PyQt5.QtWidgets import QWidget, QMenu, QAction, QApplication
from PyQt5.QtCore import QDateTime, QModelIndex, Qt
from PyQt5.QtGui import QCursor
from ..core.person import Person
from ..core.wallet import Wallet
from ..models.wallets import WalletsTableModel, WalletsFilterProxyModel
from ..tools.exceptions import MembershipNotFoundError
from ..gen_resources.wallets_tab_uic import Ui_WalletsTab
class WalletsTabWidget(QWidget, Ui_WalletsTab):
'''
classdocs
'''
def __init__(self, app, account, community):
'''
Constructor
'''
super().__init__()
self.setupUi(self)
self.app = app
self.account = account
self.community = community
self.refresh()
def refresh(self):
parameters = self.community.parameters
last_renewal = ""
expiration = ""
certifiers = 0
certified = 0
try:
person = Person.lookup(self.account.pubkey, self.community)
membership = person.membership(self.community)
certified = person.certified_by(self.community)
certifiers = person.certifiers_of(self.community)
renew_block = membership['blockNumber']
last_renewal = self.community.get_block(renew_block).mediantime
expiration = last_renewal + parameters['sigValidity']
except MembershipNotFoundError:
pass
date_renewal = QDateTime.fromTime_t(last_renewal).date().toString()
date_expiration = QDateTime.fromTime_t(expiration).date().toString()
# set infos in label
self.label_general.setText(
"""
<table cellpadding="5">
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
<tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
</table>
""".format(
self.account.name, self.account.pubkey,
"Membership",
"Last renewal on {:}, expiration on {:}".format(date_renewal, date_expiration),
"Your web of trust :",
"Certified by : {0} members; Certifier of : {1} members".format(len(certified),
len(certifiers))
)
)
amount = self.account.amount(self.community)
maximum = self.community.monetary_mass
# set infos in label
self.label_balance.setText(
"""
<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("Your money share : ", "{:.2f}%".format(amount/maximum*100),
"Your part : ", "{:.2f} {:} in [{:.2f} - {:.2f}] {:}".format(self.get_referential_value(amount),
self.get_referential_name(),
self.get_referential_value(0),
self.get_referential_value(maximum),
self.get_referential_name())
)
)
wallets_model = WalletsTableModel(self.account, self.community)
proxy_model = WalletsFilterProxyModel()
proxy_model.setSourceModel(wallets_model)
wallets_model.dataChanged.connect(self.wallet_changed)
self.table_wallets.setModel(proxy_model)
def get_referential_value(self, value):
return self.account.units_to_ref(value, self.community)
def get_referential_diff_value(self, value):
return self.account.units_to_diff_ref(value, self.community)
def get_referential_name(self):
return self.account.ref_name(self.community.short_currency)
def wallet_context_menu(self, point):
index = self.table_wallets.indexAt(point)
model = self.table_wallets.model()
if index.row() < model.rowCount(QModelIndex()):
source_index = model.mapToSource(index)
name_col = model.sourceModel().columns_types.index('name')
name_index = model.index(index.row(),
name_col)
pubkey_col = model.sourceModel().columns_types.index('pubkey')
pubkey_index = model.sourceModel().index(source_index.row(),
pubkey_col)
pubkey = model.sourceModel().data(pubkey_index, Qt.DisplayRole)
menu = QMenu(model.data(index, Qt.DisplayRole), self)
rename = QAction("Rename", self)
rename.triggered.connect(self.rename_wallet)
rename.setData(name_index)
copy_pubkey = QAction("Copy pubkey to clipboard", self)
copy_pubkey.triggered.connect(self.copy_pubkey_to_clipboard)
copy_pubkey.setData(pubkey)
menu.addAction(rename)
menu.addAction(copy_pubkey)
# Show the context menu.
menu.exec_(QCursor.pos())
def rename_wallet(self):
index = self.sender().data()
self.table_wallets.edit(index)
def wallet_changed(self):
self.app.save(self.app.current_account)
def copy_pubkey_to_clipboard(self):
data = self.sender().data()
clipboard = QApplication.clipboard()
if data.__class__ is Wallet:
clipboard.setText(data.pubkey)
elif data.__class__ is Person:
clipboard.setText(data.pubkey)
elif data.__class__ is str:
clipboard.setText(data)
# -*- coding: utf-8 -*-
import logging
from cutecoin.core.graph import Graph
from PyQt5.QtWidgets import QWidget, QComboBox
from ..gen_resources.wot_tab_uic import Ui_WotTabWidget
from cutecoin.gui.views.wot import NODE_STATUS_HIGHLIGHTED, NODE_STATUS_SELECTED, NODE_STATUS_OUT, ARC_STATUS_STRONG, ARC_STATUS_WEAK
from ucoinpy.api import bma
from cutecoin.core.person import Person
class WotTabWidget(QWidget, Ui_WotTabWidget):
def __init__(self, account, community, password_asker, parent=None):
"""
:param cutecoin.core.account.Account account:
:param cutecoin.core.community.Community community:
:param parent:
:return:
"""
super().__init__(parent)
self.parent = parent
# construct from qtDesigner
self.setupUi(self)
# add combobox events
self.comboBoxSearch.lineEdit().returnPressed.connect(self.search)
# To fix a recall of the same item with different case,
# the edited text is not added in the item list
self.comboBoxSearch.setInsertPolicy(QComboBox.NoInsert)
# add scene events
self.graphicsView.scene().node_clicked.connect(self.draw_graph)
self.graphicsView.scene().node_signed.connect(self.sign_node)
self.graphicsView.scene().node_transaction.connect(self.send_money_to_node)
self.graphicsView.scene().node_contact.connect(self.add_node_as_contact)
self.graphicsView.scene().node_member.connect(self.member_informations)
self.account = account
self.community = community
self.password_asker = password_asker
# nodes list for menu from search
self.nodes = list()
# create node metadata from account
metadata = {'text': self.account.name, 'id': self.account.pubkey}
self.draw_graph(metadata)
def draw_graph(self, metadata):
"""
Draw community graph centered on the identity
:param dict metadata: Graph node metadata of the identity
"""
logging.debug("Draw graph - " + metadata['text'])
# create Person from node metadata
person = Person.from_metadata(metadata)
person_account = Person.from_metadata({'text':self.account.name,
'id':self.account.pubkey})
certifier_list = person.certifiers_of(self.community)
certified_list = person.certified_by(self.community)
# create empty graph instance
graph = Graph(self.community)
# add wallet node
node_status = 0
if person.pubkey == person_account.pubkey:
node_status += NODE_STATUS_HIGHLIGHTED
if person.is_member(self.community) is False:
node_status += NODE_STATUS_OUT
node_status += NODE_STATUS_SELECTED
graph.add_person(person, node_status)
# populate graph with certifiers-of
graph.add_certifier_list(certifier_list, person, person_account)
# populate graph with certified-by
graph.add_certified_list(certified_list, person, person_account)
# draw graph in qt scene
self.graphicsView.scene().update_wot(graph.get())
# if selected member is not the account member...
if person.pubkey != person_account.pubkey:
# add path from selected member to account member
path = graph.get_shortest_path_between_members(person, person_account)
if path:
self.graphicsView.scene().update_path(path)
def reset(self):
"""
Reset graph scene to wallet identity
"""
metadata = {'text': self.account.name, 'id': self.account.pubkey}
self.draw_graph(
metadata
)
def search(self):
"""
Search nodes when return is pressed in combobox lineEdit
"""
text = self.comboBoxSearch.lineEdit().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
nodes = {}
for identity in response['results']:
nodes[identity['pubkey']] = identity['uids'][0]['uid']
if nodes:
self.nodes = list()
self.comboBoxSearch.clear()
self.comboBoxSearch.lineEdit().setText(text)
for pubkey, uid in nodes.items():
self.nodes.append({'pubkey': pubkey, 'uid': uid})
self.comboBoxSearch.addItem(uid)
self.comboBoxSearch.showPopup()
def select_node(self, index):
"""
Select node in graph when item is selected in combobox
"""
if index < 0 or index >= len(self.nodes):
return False
node = self.nodes[index]
metadata = {'id': node['pubkey'], 'text': node['uid']}
self.draw_graph(
metadata
)
def member_informations(self, metadata):
person = Person.from_metadata(metadata)
self.parent.member_informations(person)
def sign_node(self, metadata):
person = Person.from_metadata(metadata)
self.parent.certify_member(person)
def send_money_to_node(self, metadata):
person = Person.from_metadata(metadata)
self.parent.send_money_to_member(person)
def add_node_as_contact(self, metadata):
# check if contact already exists...
if metadata['id'] == self.account.pubkey or metadata['id'] in [contact.pubkey for contact in self.account.contacts]:
return False
person = Person.from_metadata(metadata)
self.parent.add_member_as_contact(person)
def get_block_mediantime(self, number):
try:
block = self.community.get_block(number)
except Exception as e:
logging.debug('community.get_block request error : ' + str(e))
return False
return block.mediantime
"""
Created on 1 févr. 2014
@author: inso
"""
import signal
import sys
import os
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QLocale
from cutecoin.gui.mainwindow import MainWindow
from cutecoin.core.app import Application
if __name__ == '__main__':
# activate ctrl-c interrupt
signal.signal(signal.SIGINT, signal.SIG_DFL)
cutecoin = QApplication(sys.argv)
app = Application(sys.argv)
QLocale.setDefault(QLocale("en_GB"))
window = MainWindow(app)
window.show()
sys.exit(cutecoin.exec_())
pass
'''
Created on 5 févr. 2014
@author: inso
'''
from PyQt5.QtCore import QAbstractListModel, Qt
class CommunitiesListModel(QAbstractListModel):
'''
A Qt abstract item model to display communities in a tree
'''
def __init__(self, account, parent=None):
'''
Constructor
'''
super(CommunitiesListModel, self).__init__(parent)
self.communities = account.communities
def rowCount(self, parent):
return len(self.communities)
def data(self, index, role):
if role == Qt.DisplayRole:
row = index.row()
value = self.communities[row].name
return value
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
'''
Created on 5 févr. 2014
@author: inso
'''
class CommunityItemModel(object):
def __init__(self, community, communities_item=None):
self.communities_item = communities_item
self.community_text = community.name
self.main_node_items = []
def appendChild(self, item):
self.main_node_items.append(item)
def child(self, row):
return self.main_node_items[row]
def childCount(self):
return len(self.main_node_items)
def columnCount(self):
return 1
def data(self, column):
try:
return self.community_text
except IndexError:
return None
def parent(self):
return self.communities_item
def row(self):
if self.communities_item:
return self.communities_item.index(self)
return 0
'''
Created on 5 févr. 2014
@author: inso
'''
from ucoinpy.api import bma
from ..core.person import Person
from PyQt5.QtCore import QAbstractTableModel, QSortFilterProxyModel, Qt, \
QDateTime, QModelIndex
from PyQt5.QtGui import QColor
import logging
class MembersFilterProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self.community = None
def setSourceModel(self, sourceModel):
self.community = sourceModel.community
super().setSourceModel(sourceModel)
def lessThan(self, left, right):
"""
Sort table by given column number.
"""
left_data = self.sourceModel().data(left, Qt.DisplayRole)
right_data = self.sourceModel().data(right, Qt.DisplayRole)
return (left_data < right_data)
def data(self, index, role):
source_index = self.mapToSource(index)
source_data = self.sourceModel().data(source_index, role)
expiration_col = self.sourceModel().columns_ids.index('expiration')
expiration_index = self.sourceModel().index(source_index.row(), expiration_col)
expiration_data = self.sourceModel().data(expiration_index, Qt.DisplayRole)
current_time = QDateTime().currentDateTime().toMSecsSinceEpoch()
sig_validity = self.community.parameters['sigValidity']
warning_expiration_time = int(sig_validity / 3)
#logging.debug("{0} > {1}".format(current_time, expiration_data))
will_expire_soon = (current_time > expiration_data*1000 - warning_expiration_time*1000)
if role == Qt.DisplayRole:
if source_index.column() == self.sourceModel().columns_ids.index('renew'):
date = QDateTime.fromTime_t(source_data)
return date.date()
if source_index.column() == self.sourceModel().columns_ids.index('expiration'):
date = QDateTime.fromTime_t(source_data)
return date.date()
if source_index.column() == self.sourceModel().columns_ids.index('pubkey'):
return "pub:{0}".format(source_data[:5])
if role == Qt.ForegroundRole:
if will_expire_soon:
return QColor(Qt.red)
return source_data
class MembersTableModel(QAbstractTableModel):
'''
A Qt abstract item model to display communities in a tree
'''
def __init__(self, community, parent=None):
'''
Constructor
'''
super().__init__(parent)
self.community = community
self.columns_titles = {
'uid': 'UID',
'pubkey': 'Pubkey',
'renew': 'Last renew date',
'expiration': 'Expiration'}
self.columns_ids = ('uid', 'pubkey', 'renew', 'expiration')
@property
def pubkeys(self):
return self.community.members_pubkeys()
def rowCount(self, parent):
return len(self.pubkeys)
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.name, pubkey, join_date, expiration_date)
def data(self, index, role):
if role == Qt.DisplayRole:
row = index.row()
col = index.column()
return self.member_data(self.pubkeys[row])[col]
def person_index(self, pubkey):
try:
row = self.pubkeys.index(pubkey)
index_start = self.index(row, 0)
index_end = self.index(row, len(self.columns_ids))
return (index_start, index_end)
except ValueError:
return (QModelIndex(), QModelIndex())
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
'''
Created on 5 févr. 2014
@author: inso
'''
import logging
from ..core.person import Person
from ..tools.exceptions import PersonNotFoundError
from ..core.net.node import Node
from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel
from PyQt5.QtGui import QColor
class NetworkFilterProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self.community = None
def columnCount(self, parent):
return self.sourceModel().columnCount(None)
def setSourceModel(self, sourceModel):
self.community = sourceModel.community
super().setSourceModel(sourceModel)
def lessThan(self, left, right):
"""
Sort table by given column number.
"""
left_data = self.sourceModel().data(left, Qt.DisplayRole)
right_data = self.sourceModel().data(right, Qt.DisplayRole)
return (left_data < right_data)
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole:
return QVariant()
header_names = {'pubkey': 'Pubkey',
'is_member': 'Membre',
'uid': 'UID',
'address': 'Address',
'port': 'Port',
'current_block': 'Block'}
type = self.sourceModel().headerData(section, orientation, role)
return header_names[type]
def data(self, index, role):
source_index = self.mapToSource(index)
if not source_index.isValid():
return QVariant()
source_data = self.sourceModel().data(source_index, role)
if index.column() == self.sourceModel().column_types.index('is_member') \
and role == Qt.DisplayRole:
value = {True: 'oui', False: 'non'}
return value[source_data]
return source_data
class NetworkTableModel(QAbstractTableModel):
'''
A Qt abstract item model to display
'''
def __init__(self, community, parent=None):
'''
Constructor
'''
super().__init__(parent)
self.community = community
self.column_types = (
'pubkey',
'is_member',
'uid',
'address',
'port',
'current_block'
)
@property
def nodes(self):
return self.community.nodes
def rowCount(self, parent):
return len(self.nodes)
def columnCount(self, parent):
return len(self.column_types)
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole:
return QVariant()
return self.column_types[section]
def data_node(self, node):
try:
person = Person.lookup(node.pubkey, self.community)
uid = person.name
except PersonNotFoundError:
uid = ""
is_member = node.pubkey in self.community.members_pubkeys()
address = ""
if node.endpoint.server:
address = node.endpoint.server
elif node.endpoint.ipv4:
address = node.endpoint.ipv4
elif node.endpoint.ipv6:
address = node.endpoint.ipv6
port = node.endpoint.port
return (node.pubkey, is_member, uid, address, port, node.block)
def data(self, index, role):
row = index.row()
col = index.column()
if not index.isValid():
return QVariant()
node = self.nodes[row]
if role == Qt.DisplayRole:
return self.data_node(node)[col]
if role == Qt.BackgroundColorRole:
colors = {Node.ONLINE: QVariant(),
Node.OFFLINE: QColor(Qt.darkRed),
Node.DESYNCED: QColor(Qt.gray),
Node.CORRUPTED: QColor(Qt.darkRed)
}
return colors[node.state]
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled