Skip to content
Snippets Groups Projects
Commit 687e2cb7 authored by inso's avatar inso
Browse files

Transfer component

parent 1da703df
No related branches found
No related tags found
No related merge requests found
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CommunityWidget</class>
<widget class="QWidget" name="CommunityWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>624</width>
<height>429</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabs"/>
</item>
</layout>
</widget>
<resources>
<include location="../icons/icons.qrc"/>
</resources>
<connections/>
</ui>
......@@ -128,11 +128,12 @@ class CertificationController(ComponentController):
"""
Validate the dialog
"""
self.view.button_box.setDisabled(True)
pubkey = self.selected_pubkey()
if pubkey:
password = await self.password_asker.async_exec()
if password == "":
self.view.enable_button_box()
self.view.button_box.setEnabled(True)
return
QApplication.setOverrideCursor(Qt.WaitCursor)
result = await self.account.certify(password, self.community, pubkey)
......@@ -143,7 +144,7 @@ class CertificationController(ComponentController):
else:
await self.view.show_error(result[1])
QApplication.restoreOverrideCursor()
self.view.enable_button_box()
self.view.button_box.setEnabled(True)
def reject(self):
self.view.reject()
......@@ -200,7 +201,7 @@ class CertificationController(ComponentController):
def change_current_community(self, index):
self.model.change_community(index)
self.search_user.set_community(self.community)
self.member_widget.change_community(self.community)
self.user_information.change_community(self.community)
self.refresh()
def async_exec(self):
......
......@@ -64,7 +64,7 @@ class MainWindowController(ComponentController):
main_window.navigation = main_window.attach(NavigationController.create(main_window, app))
view.bottom_layout.insertWidget(0, main_window.navigation._view)
main_window.navigation.community_changed.connect(main_window.handle_community_change)
main_window.navigation.account_changed.connect(main_window.handle_community_change)
main_window.navigation.account_changed.connect(main_window.handle_account_change)
main_window.toolbar = main_window.attach(ToolbarController.create(main_window, app,
app.current_account, None,
......@@ -116,14 +116,15 @@ class MainWindowController(ComponentController):
Set current account
:param sakia.core.Account account:
"""
self.toolbar.set_account(account)
self.toolbar.account = account
self.password_asker.change_account(account)
def handle_community_change(self, community):
"""
Set current community
:param sakia.core.Community community:
"""
self.toolbar.set_community(community)
self.toolbar.community = community
def refresh(self):
"""
......
......@@ -28,6 +28,18 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>true</bool>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
</widget>
</item>
<item>
......
......@@ -21,6 +21,7 @@ class NavigationView(QFrame, Ui_Navigation):
:param sakia.gui.navigation.model.NavigationModel
"""
self.tree_view.setModel(model.generic_tree())
self.tree_view.expandAll()
def add_widget(self, widget):
"""
......
......@@ -7,6 +7,7 @@ from ...tools.decorators import asyncify, once_at_a_time, cancel_once_task
from ..widgets.dialogs import QAsyncMessageBox, QAsyncFileDialog, dialog_async_exec
from ..widgets import toast
from ..certification.controller import CertificationController
from ..transfer.controller import TransferController
import logging
......@@ -199,13 +200,10 @@ The process to join back the community later will have to be done again.""")
self.account)
def open_transfer_money_dialog(self):
dialog = TransferMoneyDialog(self.app,
self.account,
self.password_asker,
self.community_view.community,
None)
if dialog.exec() == QDialog.Accepted:
self.community_view.tab_history.table_history.model().sourceModel().refresh_transfers()
dialog = TransferController.open_dialog(self, self.model.app,
account=self.model.account,
password_asker=self.password_asker,
community=self.model.community)
def retranslateUi(self, widget):
"""
......
from ..component.controller import ComponentController
from ..search_user.controller import SearchUserController
from ..user_information.controller import UserInformationController
from .view import TransferView
from .model import TransferModel
from sakia.tools.decorators import asyncify
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
import logging
import asyncio
class TransferController(ComponentController):
"""
The transfer component controller
"""
def __init__(self, parent, view, model, search_user, user_information, password_asker):
"""
Constructor of the transfer component
:param sakia.gui.transfer.view.TransferView: the view
:param sakia.gui.transfer.model.TransferModel model: the model
"""
super().__init__(parent, view, model)
self.password_asker = password_asker
self.search_user = search_user
self.user_information = user_information
self.view.button_box.accepted.connect(self.accept)
self.view.button_box.rejected.connect(self.reject)
self.view.combo_community.currentIndexChanged.connect(self.change_current_community)
self.view.combo_wallets.currentIndexChanged.connect(self.change_current_wallet)
self.view.spinbox_amount.valueChanged.connect(self.handle_amount_change)
self.view.spinbox_relative.valueChanged.connect(self.handle_relative_change)
@classmethod
def create(cls, parent, app, **kwargs):
"""
Instanciate a transfer component
:param sakia.gui.component.controller.ComponentController parent:
:param sakia.core.Application app:
:return: a new Transfer controller
:rtype: TransferController
"""
account = kwargs['account']
community = kwargs['community']
transfer = kwargs['transfer']
password_asker = kwargs['password_asker']
communities_names = [c.name for c in account.communities]
wallets_names = [w.name for w in account.wallets]
contacts_names = [c['name'] for c in account.contacts]
view = TransferView(parent.view, None, None, communities_names, contacts_names, wallets_names)
model = TransferModel(None, app, account=account, community=community, resent_transfer=transfer)
transfer = cls(parent, view, model, None, None, password_asker)
search_user = SearchUserController.create(transfer, app,
account=model.account,
community=model.community)
transfer.set_search_user(search_user)
user_information = UserInformationController.create(transfer, app,
account=model.account,
community=model.community,
identity=None)
transfer.set_user_information(user_information)
model.setParent(transfer)
return transfer
@classmethod
def open_dialog(cls, parent, app, account, password_asker, community):
dialog = cls.create(parent, app,
account=account,
password_asker=password_asker,
community=community,
transfer=None)
return dialog.exec()
@classmethod
async def send_money_to_identity(cls, parent, app, account, password_asker, community, identity):
dialog = cls.create(parent, app,
account=account,
password_asker=password_asker,
community=community,
transfer=None)
dialog.view.edit_pubkey.setText(identity.pubkey)
dialog.view.radio_pubkey.setChecked(True)
return await dialog.async_exec()
@classmethod
async def send_transfer_again(cls, parent, app, account, password_asker, community, resent_transfer):
dialog = cls.create(parent, app,
account=account,
password_asker=password_asker,
community=community,
resent_transfer=resent_transfer)
relative = await dialog.model.quant_to_rel(resent_transfer.metadata['amount'])
dialog.view.set_spinboxes_parameters(1, resent_transfer.metadata['amount'], relative)
dialog.view.change_relative_amount(relative)
dialog.view.change_quantitative_amount(resent_transfer.metadata['amount'])
account = resent_transfer.metadata['issuer']
wallet_index = [w.pubkey for w in app.current_account.wallets].index(account)
dialog.view.combo_wallets.setCurrentIndex(wallet_index)
dialog.view.edit_pubkey.setText(resent_transfer.metadata['receiver'])
dialog.view.radio_pubkey.setChecked(True)
dialog.view.edit_message.setText(resent_transfer.metadata['comment'])
return await dialog.async_exec()
@property
def view(self) -> TransferView:
return self._view
@property
def model(self) -> TransferModel:
return self._model
def set_search_user(self, search_user):
"""
:param search_user:
:return:
"""
self.search_user = search_user
self.view.set_search_user(search_user.view)
search_user.identity_selected.connect(self.refresh_user_information)
def set_user_information(self, user_information):
"""
:param user_information:
:return:
"""
self.user_information = user_information
self.view.set_user_information(user_information.view)
def refresh_user_information(self):
"""
Refresh user information
"""
pubkey = self.selected_pubkey()
self.user_information.search_identity(pubkey)
def selected_pubkey(self):
"""
Get selected pubkey in the widgets of the window
:return: the current pubkey
:rtype: str
"""
pubkey = None
if self.view.recipient_mode() == TransferView.RecipientMode.CONTACT:
contact_name = self.view.selected_contact()
pubkey = self.model.contact_name_pubkey(contact_name)
elif self.view.recipient_mode() == TransferView.RecipientMode.SEARCH:
if self.search_user.current_identity():
pubkey = self.search_user.current_identity().pubkey
else:
pubkey = self.view.pubkey_value()
return pubkey
@asyncify
async def accept(self):
logging.debug("Accept transfer action...")
self.view.button_box.setEnabled(False)
comment = self.view.edit_message.text()
logging.debug("checking recipient mode...")
recipient = self.selected_pubkey()
amount = self.view.spinbox_amount.value()
logging.debug("Showing password dialog...")
password = await self.password_asker.async_exec()
if password == "":
self.view.button_box.setEnabled(True)
return
logging.debug("Setting cursor...")
QApplication.setOverrideCursor(Qt.WaitCursor)
logging.debug("Send money...")
result = await self.model.send_money(recipient, amount, comment, password)
if result[0]:
await self.view.show_success(self.model.app.preferences['notifications'], recipient)
logging.debug("Restore cursor...")
QApplication.restoreOverrideCursor()
# If we sent back a transaction we cancel the first one
self.model.cancel_previous()
self.model.app.refresh_transfers.emit()
self.view.accept()
else:
await self.view.show_error(self.model.app.preferences['notifications'], result[1])
QApplication.restoreOverrideCursor()
self.view.button_box.setEnabled(True)
def reject(self):
self.view.reject()
@asyncify
async def refresh(self):
amount = await self.model.wallet_value()
total_text = await self.model.localized_amount(amount)
self.view.refresh_labels(total_text, self.model.community.currency)
if amount == 0:
self.view.set_button_box(TransferView.ButtonBoxState.NO_AMOUNT)
else:
self.view.set_button_box(TransferView.ButtonBoxState.OK)
max_relative = await self.model.quant_to_rel(amount)
current_base = await self.model.current_base()
self.view.set_spinboxes_parameters(pow(10, current_base), amount, max_relative)
@asyncify
async def handle_amount_change(self, value):
relative = await self.model.quant_to_rel(value)
self.view.change_relative_amount(relative)
@asyncify
async def handle_relative_change(self, value):
amount = await self.model.rel_to_quant(value)
self.view.change_quantitative_amount(amount)
def change_current_community(self, index):
self.model.change_community(index)
self.search_user.set_community(self.community)
self.user_information.change_community(self.community)
self.refresh()
def change_current_wallet(self, index):
self.model.change_wallet(index)
self.refresh()
def async_exec(self):
future = asyncio.Future()
self.view.finished.connect(lambda r: future.set_result(r))
self.view.open()
self.refresh()
return future
def exec(self):
self.refresh()
self.view.exec()
\ No newline at end of file
from sakia.gui.component.model import ComponentModel
class TransferModel(ComponentModel):
"""
The model of transfer component
"""
def __init__(self, parent, app, account, community, resent_transfer):
super().__init__(parent)
self.app = app
self.account = account
self.resent_transfer = resent_transfer
self.community = community if community else self.account.communities[0]
self.wallet = self.account.wallets[0]
def contact_name_pubkey(self, name):
"""
Get the pubkey of a contact from its name
:param str name:
:return:
:rtype: str
"""
for contact in self.account.contacts:
if contact['name'] == name:
return contact['pubkey']
async def rel_to_quant(self, rel_value):
"""
Get the quantitative value of a relative amount
:param float rel_value:
:rtype: int
"""
ud_block = await self.community.get_ud_block()
if ud_block:
dividend = ud_block['dividend']
base = ud_block['unitbase']
else:
dividend = 1
base = 0
amount = rel_value * dividend * pow(10, base)
# amount is rounded to the nearest power of 10 depending of last ud base
rounded = int(pow(10, base) * round(float(amount) / pow(10, base)))
return rounded
async def quant_to_rel(self, amount):
"""
Get the relative value of a given amount
:param int amount:
:rtype: float
"""
ud_block = await self.community.get_ud_block()
if ud_block:
dividend = ud_block['dividend']
base = ud_block['unitbase']
else:
dividend = 1
base = 0
relative = amount / (dividend * pow(10, base))
return relative
async def wallet_value(self):
"""
Get the value of the current wallet in the current community
"""
return await self.wallet.value(self.community)
async def current_base(self):
"""
Get the current base of the network
"""
ud_block = await self.community.get_ud_block()
if ud_block:
base = ud_block['unitbase']
else:
base = 0
return base
async def localized_amount(self, amount):
"""
Get the value of the current referential
"""
localized = await self.account.current_ref.instance(amount, self.community, self.app) \
.diff_localized(units=True,
international_system=self.app.preferences['international_system_of_units'])
return localized
def change_community(self, index):
"""
Change the current community
:param int index: index in the list of communities
"""
self.community = self.account.communities[index]
def change_wallet(self, index):
"""
Change the current wallet
:param int index: index in the list of wallets
"""
self.wallet = self.account.wallets[index]
def cancel_previous(self):
if self.resent_transfer:
self.resent_transfer.cancel()
async def send_money(self, recipient, amount, comment, password):
"""
Send money to given recipient using the account
:param str recipient:
:param int amount:
:param str comment:
:param str password:
:return: the result of the send
"""
return await self.wallet.send_money(self.account.salt, password, self.community,
recipient, amount, comment)
\ No newline at end of file
......@@ -195,15 +195,6 @@ class TransferMoneyDialog(QObject):
@asyncify
async def relative_amount_changed(self, value):
ud_block = await self.community.get_ud_block()
if ud_block:
dividend = ud_block['dividend']
base = ud_block['unitbase']
else:
dividend = 1
base = 0
amount = value * dividend * pow(10, base)
amount = int(pow(10, base) * round(float(amount) / pow(10, base)))
self.ui.spinbox_amount.blockSignals(True)
self.ui.spinbox_amount.setValue(amount)
self.ui.spinbox_amount.blockSignals(False)
......
......@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>496</width>
<height>485</height>
<width>800</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
......@@ -27,11 +27,19 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="group_box_recipient">
<property name="title">
<string>Transfer money to</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="topMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
......@@ -140,14 +148,14 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<layout class="QHBoxLayout" name="layout_search_user">
<property name="topMargin">
<number>6</number>
</property>
<item>
<widget class="QRadioButton" name="radio_search">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
......@@ -176,6 +184,8 @@
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
......
from PyQt5.QtWidgets import QDialog, QDialogButtonBox
from PyQt5.QtGui import QRegExpValidator
from PyQt5.QtCore import QT_TRANSLATE_NOOP, QRegExp
from .transfer_uic import Ui_TransferMoneyDialog
from enum import Enum
from ..widgets import toast
from ..widgets.dialogs import QAsyncMessageBox
class TransferView(QDialog, Ui_TransferMoneyDialog):
"""
Transfer component view
"""
class ButtonBoxState(Enum):
NO_AMOUNT = 0
OK = 1
class RecipientMode(Enum):
CONTACT = 0
PUBKEY = 1
SEARCH = 2
_button_box_values = {
ButtonBoxState.NO_AMOUNT: (False,
QT_TRANSLATE_NOOP("TransferView", "No amount. Please give the transfer amount")),
ButtonBoxState.OK: (True, QT_TRANSLATE_NOOP("CertificationView", "&Ok"))
}
def __init__(self, parent, search_user_view, user_information_view,
communities_names, contacts_names, wallets_names):
"""
:param parent:
:param sakia.gui.search_user.view.SearchUserView search_user_view:
:param sakia.gui.user_information.view.UserInformationView user_information_view:
:param list[str] communities_names:
:param list[str] contacts_names:
:param list[str] wallets_names:
"""
super().__init__(parent)
self.setupUi(self)
self.radio_contact.toggled.connect(lambda c, radio=TransferView.RecipientMode.CONTACT: self.recipient_mode_changed(radio))
self.radio_pubkey.toggled.connect(lambda c, radio=TransferView.RecipientMode.PUBKEY: self.recipient_mode_changed(radio))
self.radio_search.toggled.connect(lambda c, radio=TransferView.RecipientMode.SEARCH: self.recipient_mode_changed(radio))
regexp = QRegExp('^([ a-zA-Z0-9-_:/;*?\[\]\(\)\\\?!^+=@&~#{}|<>%.]{0,255})$')
validator = QRegExpValidator(regexp)
self.edit_message.setValidator(validator)
for name in communities_names:
self.combo_community.addItem(name)
for name in sorted(contacts_names):
self.combo_contact.addItem(name)
for name in wallets_names:
self.combo_wallets.addItem(name)
if len(contacts_names) == 0:
self.combo_contact.setEnabled(False)
self.radio_pubkey.setChecked(True)
self.radio_contact.setEnabled(False)
self.search_user = search_user_view
self.user_information_view = user_information_view
def recipient_mode(self):
if self.radio_contact.isChecked():
return TransferView.RecipientMode.CONTACT
elif self.radio_search.isChecked():
return TransferView.RecipientMode.SEARCH
else:
return TransferView.RecipientMode.PUBKEY
def selected_contact(self):
return self.combo_contact.currentText()
def set_search_user(self, search_user_view):
"""
:param sakia.gui.search_user.view.SearchUserView search_user_view:
:return:
"""
self.search_user = search_user_view
self.layout_search_user.addWidget(search_user_view)
self.search_user.button_reset.hide()
def set_user_information(self, user_information_view):
self.user_information_view = user_information_view
self.group_box_recipient.layout().addWidget(user_information_view)
def recipient_mode_changed(self, radio):
"""
:param str radio:
"""
self.edit_pubkey.setEnabled(radio == TransferView.RecipientMode.PUBKEY)
self.combo_contact.setEnabled(radio == TransferView.RecipientMode.CONTACT)
self.search_user.setEnabled(radio == TransferView.RecipientMode.SEARCH)
def change_quantitative_amount(self, amount):
"""
Change relative amount with signals blocked
:param amount:
"""
self.spinbox_amount.blockSignals(True)
self.spinbox_amount.setValue(amount)
self.spinbox_amount.blockSignals(False)
def change_relative_amount(self, relative):
"""
Change the quantitative amount with signals blocks
:param relative:
"""
self.spinbox_relative.blockSignals(True)
self.spinbox_relative.setValue(relative)
self.spinbox_relative.blockSignals(False)
def set_spinboxes_parameters(self, tick_quant, max_quant, max_rel):
"""
Configure the spinboxes
It should depend on what the last UD base is
:param int tick_quant:
:param int max_quant:
:param float max_rel:
"""
self.spinbox_amount.setMaximum(max_quant)
self.spinbox_relative.setMaximum(max_rel)
self.spinbox_amount.setSingleStep(tick_quant)
def refresh_labels(self, total_text, currency):
"""
Refresh displayed texts
:param str total_text:
:param str currency:
"""
self.label_total.setText("{0}".format(total_text))
self.spinbox_amount.setSuffix(" " + currency)
def set_button_box(self, state, **kwargs):
"""
Set button box state
:param sakia.gui.transfer.view.TransferView.ButtonBoxState state: the state of te button box
:param dict kwargs: the values to replace from the text in the state
:return:
"""
button_box_state = TransferView._button_box_values[state]
self.button_box.button(QDialogButtonBox.Ok).setEnabled(button_box_state[0])
self.button_box.button(QDialogButtonBox.Ok).setText(button_box_state[1].format(**kwargs))
async def show_success(self, notification, recipient):
if notification:
toast.display(self.tr("Transfer"),
self.tr("Success sending money to {0}").format(recipient))
else:
await QAsyncMessageBox.information(self.widget, self.tr("Transfer"),
self.tr("Success sending money to {0}").format(recipient))
async def show_error(self, notification, error_txt):
if notification:
toast.display(self.tr("Transfer"), "Error : {0}".format(error_txt))
else:
await QAsyncMessageBox.critical(self.widget, self.tr("Transfer"), error_txt)
......@@ -5,7 +5,7 @@ import logging
from ..user_information.controller import UserInformationController
from ..contact import ConfigureContactDialog
from ..transfer import TransferMoneyDialog
from ..transfer.controller import TransferController
from ..certification.controller import CertificationController
from ...tools.decorators import asyncify
from ...core.transfer import Transfer, TransferState
......@@ -143,7 +143,7 @@ class ContextMenu(QObject):
@asyncify
async def send_money(self, identity):
await TransferMoneyDialog.send_money_to_identity(self._app, self._account, self._password_asker,
await TransferController.send_money_to_identity(None, self._app, self._account, self._password_asker,
self._community, identity)
self._app.refresh_transfers.emit()
......@@ -157,7 +157,7 @@ class ContextMenu(QObject):
@asyncify
async def send_again(self, transfer):
await TransferMoneyDialog.send_transfer_again(self._app, self._app.current_account,
await TransferController.send_transfer_again(None, self._app, self._app.current_account,
self._password_asker, self._community, transfer)
self._app.refresh_transfers.emit()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment