Commit 590c56ec authored by Vincent Texier's avatar Vincent Texier

[enh] #798 add context_menu "Send as source" and handle source in transfer GUI

parent 2877a799
......@@ -35,6 +35,9 @@ class SourcesProcessor:
except sqlite3.IntegrityError:
self._logger.debug("Source already known: {0}".format(source.identifier))
def get_one(self, **search):
return self._repo.get_one(**search)
def amount(self, currency, pubkey):
"""
Get the amount value of the sources for a given pubkey
......
......@@ -55,8 +55,8 @@ class TxHistoryController(QObject):
sources_service,
):
transfer = TransferController.integrate_to_main_view(None, app, connection)
view = TxHistoryView(parent.view, transfer.view)
controller = TransferController.integrate_to_main_view(None, app, connection)
view = TxHistoryView(parent.view, controller.view)
model = TxHistoryModel(
None,
app,
......@@ -66,14 +66,14 @@ class TxHistoryController(QObject):
transactions_service,
sources_service,
)
txhistory = cls(view, model, transfer)
txhistory = cls(view, model, controller)
model.setParent(txhistory)
app.referential_changed.connect(txhistory.refresh_balance)
app.sources_refreshed.connect(txhistory.refresh_balance)
txhistory.view_in_wot.connect(app.view_in_wot)
txhistory.view.spin_page.valueChanged.connect(model.change_page)
transfer.accepted.connect(view.clear)
transfer.rejected.connect(view.clear)
controller.accepted.connect(view.clear)
controller.rejected.connect(view.clear)
return txhistory
def refresh_minimum_maximum(self):
......
......@@ -20,6 +20,10 @@ class PasswordInputView(QWidget, Ui_PasswordInputWidget):
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
self.layout().addWidget(self.button_box)
self.button_box.hide()
font = self.edit_secret_key.font()
font.setBold(False)
self.edit_secret_key.setFont(font)
self.edit_password.setFont(font)
def error(self, text):
self.label_info.setText(text)
......
......@@ -70,51 +70,69 @@ class TransferController(QObject):
password_input.view,
)
model = TransferModel(app)
transfer = cls(view, model, search_user, user_information, password_input)
controller = cls(view, model, search_user, user_information, password_input)
search_user.identity_selected.connect(user_information.search_identity)
view.set_keys(transfer.model.available_connections())
view.set_contacts(transfer.model.contacts())
view.set_keys(controller.model.available_connections())
view.set_contacts(controller.model.contacts())
app.new_connection.connect(view.add_key)
app.connection_removed.connect(view.remove_key)
return transfer
return controller
@classmethod
def integrate_to_main_view(cls, parent, app, connection):
transfer = cls.create(parent, app)
transfer.view.combo_connections.setCurrentText(connection.title())
transfer.view.radio_pubkey.toggle()
transfer.view.groupbox_connection.hide()
transfer.view.label_total.hide()
return transfer
controller = cls.create(parent, app)
controller.view.combo_connections.setCurrentText(connection.title())
controller.view.radio_pubkey.toggle()
controller.view.label_connections.hide()
controller.view.combo_connections.hide()
controller.view.label_total.hide()
return controller
@classmethod
def open_transfer_with_pubkey(cls, parent, app, connection, pubkey):
transfer = cls.create(parent, app)
transfer.view.groupbox_connection.show()
def open_transfer_with_pubkey(cls, parent, app, connection, pubkey, source):
controller = cls.create(parent, app)
controller.view.label_connections.show()
controller.view.combo_connections.show()
if connection:
transfer.view.combo_connections.setCurrentText(connection.title())
transfer.view.edit_pubkey.setText(pubkey)
transfer.view.radio_pubkey.setChecked(True)
transfer.view.radio_pubkey.toggle()
transfer.refresh()
return transfer
controller.view.combo_connections.setCurrentText(connection.title())
if pubkey:
controller.view.edit_pubkey.setText(pubkey)
controller.view.radio_pubkey.setChecked(True)
controller.view.radio_pubkey.toggle()
else:
controller.view.radio_local_key.setChecked(True)
controller.view.radio_local_key.toggle()
if source:
controller.model.current_source = source
controller.view.label_source_identifier.setText(
"{}:{}".format(source.identifier, source.noffset)
)
amount = source.amount * (10 ** source.base) / 100
controller.view.spinbox_amount.setValue(amount)
controller.view.spinbox_amount.setDisabled(True)
controller.view.spinbox_relative.setDisabled(True)
controller.view.button_source_check.setEnabled(True)
controller.refresh()
return controller
@classmethod
def send_money_to_pubkey(cls, parent, app, connection, pubkey):
def send_money_to_pubkey(cls, parent, app, connection, pubkey, source):
dialog = QDialog(parent)
dialog.setWindowTitle(
QCoreApplication.translate("TransferController", "Transfer")
)
dialog.setLayout(QVBoxLayout(dialog))
transfer = cls.open_transfer_with_pubkey(parent, app, connection, pubkey)
controller = cls.open_transfer_with_pubkey(
parent, app, connection, pubkey, source
)
dialog.layout().addWidget(transfer.view)
transfer.accepted.connect(dialog.accept)
transfer.rejected.connect(dialog.reject)
dialog.layout().addWidget(controller.view)
controller.accepted.connect(dialog.accept)
controller.rejected.connect(dialog.reject)
return dialog.exec()
@classmethod
......@@ -124,14 +142,14 @@ class TransferController(QObject):
QCoreApplication.translate("TransferController", "Transfer")
)
dialog.setLayout(QVBoxLayout(dialog))
transfer = cls.open_transfer_with_pubkey(
parent, app, connection, identity.pubkey
controller = cls.open_transfer_with_pubkey(
parent, app, connection, identity.pubkey, None
)
transfer.user_information.change_identity(identity)
dialog.layout().addWidget(transfer.view)
transfer.accepted.connect(dialog.accept)
transfer.rejected.connect(dialog.reject)
controller.user_information.change_identity(identity)
dialog.layout().addWidget(controller.view)
controller.accepted.connect(dialog.accept)
controller.rejected.connect(dialog.reject)
return dialog.exec()
@classmethod
......@@ -141,34 +159,35 @@ class TransferController(QObject):
QCoreApplication.translate("TransferController", "Transfer")
)
dialog.setLayout(QVBoxLayout(dialog))
transfer = cls.create(parent, app)
transfer.view.groupbox_connection.show()
transfer.view.label_total.show()
transfer.view.combo_connections.setCurrentText(connection.title())
transfer.view.edit_pubkey.setText(resent_transfer.receivers[0])
transfer.view.radio_pubkey.setChecked(True)
controller = cls.create(parent, app)
controller.view.label_connections.show()
controller.view.combo_connections.show()
controller.view.label_total.show()
controller.view.combo_connections.setCurrentText(connection.title())
controller.view.edit_pubkey.setText(resent_transfer.receivers[0])
controller.view.radio_pubkey.setChecked(True)
transfer.refresh()
controller.refresh()
current_base = transfer.model.current_base()
current_base = controller.model.current_base()
current_base_amount = resent_transfer.amount / pow(
10, resent_transfer.amount_base - current_base
)
relative = transfer.model.quant_to_rel(current_base_amount / 100)
transfer.view.set_spinboxes_parameters(current_base_amount / 100, relative)
transfer.view.change_relative_amount(relative)
transfer.view.change_quantitative_amount(current_base_amount / 100)
relative = controller.model.quant_to_rel(current_base_amount / 100)
controller.view.set_spinboxes_parameters(current_base_amount / 100, relative)
controller.view.change_relative_amount(relative)
controller.view.change_quantitative_amount(current_base_amount / 100)
connections_processor = ConnectionsProcessor.instanciate(app)
wallet_index = connections_processor.connections().index(connection)
transfer.view.combo_connections.setCurrentIndex(wallet_index)
transfer.view.edit_pubkey.setText(resent_transfer.receivers[0])
transfer.view.radio_pubkey.toggle()
transfer.view.edit_message.setText(resent_transfer.comment)
dialog.layout().addWidget(transfer.view)
transfer.accepted.connect(dialog.accept)
transfer.rejected.connect(dialog.reject)
controller.view.combo_connections.setCurrentIndex(wallet_index)
controller.view.edit_pubkey.setText(resent_transfer.receivers[0])
controller.view.radio_pubkey.toggle()
controller.view.edit_message.setText(resent_transfer.comment)
dialog.layout().addWidget(controller.view)
controller.accepted.connect(dialog.accept)
controller.rejected.connect(dialog.reject)
return dialog.exec()
def valid_crc_pubkey(self):
......@@ -215,7 +234,8 @@ class TransferController(QObject):
async def accept(self):
logging.debug("Accept transfer action...")
self.view.button_box.setEnabled(False)
comment = self.view.edit_message.text()
source = self.model.current_source
logging.debug("checking recipient mode...")
recipient = self.selected_pubkey()
......@@ -228,11 +248,19 @@ class TransferController(QObject):
logging.debug("Setting cursor...")
QApplication.setOverrideCursor(Qt.WaitCursor)
comment = self.view.edit_message.text()
lock_mode = self.view.combo_locks.currentIndex()
logging.debug("Send money...")
result, transactions = await self.model.send_money(
recipient, secret_key, password, amount, amount_base, comment, lock_mode
recipient,
secret_key,
password,
amount,
amount_base,
comment,
lock_mode,
source,
)
if result[0]:
await self.view.show_success(self.model.notifications(), recipient)
......
......@@ -21,6 +21,7 @@ class TransferModel(QObject):
app = attr.ib()
connection = attr.ib(default=None)
resent_transfer = attr.ib(default=None)
current_source = attr.ib(default=None)
_blockchain_processor = attr.ib(default=None)
_sources_processor = attr.ib(default=None)
_connections_processor = attr.ib(default=None)
......@@ -90,7 +91,15 @@ class TransferModel(QObject):
self.connection = connections[index]
async def send_money(
self, recipient, secret_key, password, amount, amount_base, comment, lock_mode
self,
recipient,
secret_key,
password,
amount,
amount_base,
comment,
lock_mode,
source,
):
"""
Send money to given recipient using the account
......@@ -102,6 +111,7 @@ class TransferModel(QObject):
:param int amount_base:
:param str comment:
:param int lock_mode:
:param Source source:
:return: the result of the send
"""
......@@ -114,6 +124,7 @@ class TransferModel(QObject):
amount_base,
comment,
lock_mode,
source,
)
for transaction in transactions:
self.app.sources_service.parse_transaction_outputs(
......
......@@ -15,22 +15,98 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupbox_connection">
<property name="title">
<string>Select account</string>
<layout class="QHBoxLayout" name="layout_connections">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QComboBox" name="combo_connections"/>
</item>
</layout>
</widget>
<item>
<widget class="QLabel" name="label_connections">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Select account</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_connections">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="label_source">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Source</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_source_identifier">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Automatic</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_source_check">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Check</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="group_box_recipient">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="title">
<string>Transfer money to</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
......@@ -50,6 +126,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>&amp;Recipient public key</string>
</property>
......@@ -85,6 +167,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="inputMask">
<string/>
</property>
......@@ -147,7 +235,14 @@
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_local_keys"/>
<widget class="QComboBox" name="combo_local_keys">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
</widget>
</item>
</layout>
</item>
......@@ -164,7 +259,14 @@
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_contacts"/>
<widget class="QComboBox" name="combo_contacts">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
</widget>
</item>
</layout>
</item>
......@@ -184,6 +286,12 @@
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QLabel" name="label_total">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Available money: </string>
</property>
......@@ -193,6 +301,12 @@
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Amount</string>
</property>
......@@ -251,6 +365,12 @@
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="title">
<string>Message</string>
</property>
......@@ -273,12 +393,24 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="title">
<string>Spend condition</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QComboBox" name="combo_locks">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<item>
<property name="text">
<string>Receiver signature</string>
......@@ -298,6 +430,18 @@
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="title">
<string>Secret Key / Password</string>
</property>
......
......@@ -5,7 +5,9 @@ from PyQt5.QtWidgets import QMenu, QAction, QApplication, QMessageBox
from duniterpy.constants import PUBKEY_REGEX
from duniterpy.documents.crc_pubkey import CRCPubkey
from sakia.data.entities import Identity, Transaction, Dividend
from sakia.app import Application
from sakia.data.entities import Identity, Transaction, Dividend, Connection
from sakia.data.processors import BlockchainProcessor, TransactionsProcessor
from sakia.decorators import asyncify
from sakia.gui.sub.certification.controller import CertificationController
......@@ -17,7 +19,7 @@ class ContextMenu(QObject):
view_identity_in_wot = pyqtSignal(object)
identity_information_loaded = pyqtSignal(Identity)
def __init__(self, qmenu, app, connection):
def __init__(self, qmenu: QMenu, app: Application, connection: Connection):
"""
:param PyQt5.QtWidgets.QMenu: the qmenu widget
:param sakia.app.Application app: Application instance
......@@ -25,7 +27,7 @@ class ContextMenu(QObject):
"""
super().__init__()
self.qmenu = qmenu
self._app = app
self._app = app # type: Application
self._connection = connection
@staticmethod
......@@ -130,6 +132,16 @@ class ContextMenu(QObject):
)
menu.qmenu.addAction(cancel)
# if special lock condition on transaction...
if transfer.conditions is not None:
send_as_source = QAction(
QCoreApplication.translate("ContextMenu", "Send as source"),
menu.qmenu.parent(),
)
send_as_source.triggered.connect(
lambda checked, tr=transfer: menu.send_as_source(tr)
)
menu.qmenu.addAction(send_as_source)
if menu._app.parameters.expert_mode:
copy_doc = QAction(
QCoreApplication.translate(
......@@ -250,7 +262,7 @@ class ContextMenu(QObject):
)
else:
TransferController.send_money_to_pubkey(
None, self._app, self._connection, identity_or_pubkey
None, self._app, self._connection, identity_or_pubkey, None
)
def view_wot(self, identity):
......@@ -286,6 +298,15 @@ This money transfer will be removed and not sent.""",
self._app.db.commit()
self._app.transaction_state_changed.emit(transfer)
def send_as_source(self, transfer: Transaction):
# get source from conditions and transaction hash
source = self._app.sources_service.get_one(
identifier=transfer.sha_hash, conditions=transfer.conditions
)
TransferController.send_money_to_pubkey(
None, self._app, self._connection, None, source
)
def copy_transaction_to_clipboard(self, tx):
clipboard = QApplication.clipboard()
clipboard.setText(tx.raw)
......
......@@ -494,10 +494,10 @@ class DocumentsService:
message,
currency,
lock_mode=0,
source=None,
):
"""
Prepare a simple Transaction document
:param int lock_mode: Lock condition mode selected in combo box
:param SigningKey key: the issuer of the transaction
:param str receiver: the target of the transaction
:param duniterpy.documents.BlockUID blockstamp: the blockstamp
......@@ -505,32 +505,39 @@ class DocumentsService:
:param int amount_base: the amount base of the currency
:param str message: the comment of the tx
:param str currency: the target community
:param int lock_mode: Lock condition mode selected in combo box
:param Source source: Source instance or None
:return: the transaction document
:rtype: List[sakia.data.entities.Transaction]
"""
forged_tx = []
sources = [None] * 41
while len(sources) > 40:
result = self.tx_sources(int(amount), amount_base, currency, key)
sources = result[0]
computed_outputs = result[1]
overheads = result[2]
# Fix issue #594
if len(sources) > 40:
sources_value = 0
for s in sources[:39]:
sources_value += s.amount * (10 ** s.base)
sources_value, sources_base = reduce_base(sources_value, 0)
chained_tx = self.prepare_tx(
key,
key.pubkey,
blockstamp,
sources_value,
sources_base,
"[CHAINED]",
currency,
)
forged_tx += chained_tx
if source is None:
# automatic selection of sources
sources = [None] * 41
while len(sources) > 40:
result = self.tx_sources(int(amount), amount_base, currency, key)
sources = result[0]
computed_outputs = result[1]
overheads = result[2]
# Fix issue #594
if len(sources) > 40:
sources_value = 0
for s in sources[:39]:
sources_value += s.amount * (10 ** s.base)
sources_value, sources_base = reduce_base(sources_value, 0)
chained_tx = self.prepare_tx(
key,
key.pubkey,
blockstamp,
sources_value,
sources_base,
"[CHAINED]",