diff --git a/res/ui/transfer.ui b/res/ui/transfer.ui index c3230ec3129a9164c67ce4ea4c89314ba5693358..d258290a8cd86addfc12749be43ec20cf359f106 100644 --- a/res/ui/transfer.ui +++ b/res/ui/transfer.ui @@ -37,7 +37,7 @@ <item> <widget class="QRadioButton" name="radio_contact"> <property name="text"> - <string>Contact</string> + <string>Con&tact</string> </property> <property name="checked"> <bool>true</bool> @@ -286,6 +286,22 @@ </hint> </hints> </connection> + <connection> + <sender>spinbox_amount</sender> + <signal>valueChanged(double)</signal> + <receiver>TransferMoneyDialog</receiver> + <slot>amount_changed()</slot> + <hints> + <hint type="sourcelabel"> + <x>209</x> + <y>292</y> + </hint> + <hint type="destinationlabel"> + <x>247</x> + <y>219</y> + </hint> + </hints> + </connection> </connections> <slots> <slot>open_manage_wallet_coins()</slot> diff --git a/src/cutecoin/core/transfer.py b/src/cutecoin/core/transfer.py index bb396aab8afa887daa3c97261e46b9d1dec87561..6444fe6221fdb5c16a96df741513d67f2278bf91 100644 --- a/src/cutecoin/core/transfer.py +++ b/src/cutecoin/core/transfer.py @@ -71,29 +71,32 @@ class Transfer(QObject): self._table_states = { (TransferState.TO_SEND, (list, Block)): - (self._broadcast_success, self._wait, TransferState.AWAITING), + ( + (self._broadcast_success, self._wait, TransferState.AWAITING), + (self._broadcast_failure, None, TransferState.REFUSED), + ), + (TransferState.TO_SEND, ()): + ((self._is_locally_created, self._drop, TransferState.DROPPED),), (TransferState.AWAITING, (bool, Block)): - (self._found_in_block, self._be_validating, TransferState.VALIDATING), + ((self._found_in_block, self._be_validating, TransferState.VALIDATING),), (TransferState.AWAITING, (bool, Block, int, int)): - (self._not_found_in_blockchain, None, TransferState.REFUSED), + ((self._not_found_in_blockchain, None, TransferState.REFUSED),), (TransferState.VALIDATING, (bool, Block, int)): - (self._reached_enough_validation, None, TransferState.VALIDATED), + ((self._reached_enough_validation, None, TransferState.VALIDATED),), (TransferState.VALIDATING, (bool, Block)): - (self._rollback_and_removed, self._drop, TransferState.DROPPED), + ((self._rollback_and_removed, self._drop, TransferState.DROPPED),), (TransferState.VALIDATED, (bool, Block)): - (self._rollback_still_present, self._be_validating, TransferState.VALIDATING), - (TransferState.VALIDATED, (bool, Block)): - (self._rollback_and_removed, self._drop, TransferState.DROPPED), - (TransferState.VALIDATED, (bool, Block)): - (self._rollback_and_local, self._wait, TransferState.AWAITING), + ( + (self._rollback_still_present, self._be_validating, TransferState.VALIDATING), + (self._rollback_and_removed, self._drop, TransferState.DROPPED), + (self._rollback_and_local, self._wait, TransferState.AWAITING), + ), - (TransferState.DROPPED, ()): - (self._is_locally_created, None, TransferState.TO_SEND), (TransferState.REFUSED, ()): - (self._is_locally_created, self._drop, TransferState.DROPPED), + ((self._is_locally_created, self._drop, TransferState.DROPPED),) } @classmethod @@ -180,16 +183,25 @@ class Transfer(QObject): return True return False - def _broadcast_success(self, ret_codes, time): + def _broadcast_success(self, ret_codes, block): """ Check if the retcode is 200 after a POST :param list ret_codes: The POST return codes of the broadcast - :param int time: The mediantime of the blockchain. Used for transition. + :param ucoinpy.documents.Block block: The current block used for transition. :return: True if the post was successful :rtype: bool """ return 200 in ret_codes + def _broadcast_failure(self, ret_codes): + """ + Check if no retcode is 200 after a POST + :param list ret_codes: The POST return codes of the broadcast + :return: True if the post was failed + :rtype: bool + """ + return 200 not in ret_codes + def _reached_enough_validation(self, rollback, current_block, fork_window): """ Check if the transfer reached enough validation in the blockchain @@ -250,7 +262,7 @@ class Transfer(QObject): :param list ret_codes: The responses return codes :param ucoinpy.documents.Block current_block: Current block of the main blockchain """ - self.blockid = current_block + self.blockid = current_block.blockid self._metadata['time'] = current_block.mediantime def _be_validating(self, rollback, block): @@ -283,14 +295,15 @@ class Transfer(QObject): for i, input in enumerate(inputs): if type(input) is not transition_key[1][i]: return False - if self._table_states[transition_key][0](*inputs): - next_state = self._table_states[transition_key] - logging.debug("{0} : {1} --> {2}".format(self.sha_hash[:5], self.state.name, next_state[2].name)) - # If the transition changes data, apply changes - if next_state[1]: - next_state[1](*inputs) - self.state = next_state[2] - return True + for transition in self._table_states[transition_key]: + if transition[0](*inputs): + logging.debug("{0} : {1} --> {2}".format(self.sha_hash[:5], self.state.name, + transition[2].name)) + # If the transition changes data, apply changes + if transition[1]: + transition[1](*inputs) + self.state = transition[2] + return True return False def run_state_transitions(self, inputs): @@ -306,6 +319,12 @@ class Transfer(QObject): return True return False + def cancel(self): + """ + Cancel a local transaction + """ + self.run_state_transitions(()) + @asyncio.coroutine def send(self, txdoc, community): """ @@ -318,10 +337,12 @@ class Transfer(QObject): """ responses = yield from community.bma_access.broadcast(bma.tx.Process, post_args={'transaction': txdoc.signed_raw()}) + self.sha_hash = txdoc.sha_hash blockid = yield from community.blockid() block = yield from community.bma_access.future_request(bma.blockchain.Block, req_args={'number': blockid.number}) - time = block['medianTime'] + signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) + block_doc = Block.from_signed_raw(signed_raw) result = (False, "") for r in responses: if r.status == 200: @@ -330,5 +351,6 @@ class Transfer(QObject): result = (False, (yield from r.text())) else: yield from r.text() - self.run_state_transitions(([r.status for r in responses], time)) + self.run_state_transitions(([r.status for r in responses], block_doc)) + self.run_state_transitions(([r.status for r in responses])) return result diff --git a/src/cutecoin/gui/transactions_tab.py b/src/cutecoin/gui/transactions_tab.py index 9dc95e951fa9cfbbde66ab29c3e53b82e7197ee1..749faacf0ade6293e4916918a37d5176a47dea15 100644 --- a/src/cutecoin/gui/transactions_tab.py +++ b/src/cutecoin/gui/transactions_tab.py @@ -76,7 +76,7 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): self.password_asker = password_asker self.table_history.model().sourceModel().change_account(account) if account: - self.connect_progress() + self.connect_progress() def change_community(self, community): self.cancel_once_tasks() @@ -184,7 +184,7 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): transfer = model.sourceModel().transfers()[source_index.row()] if state_data == TransferState.REFUSED or state_data == TransferState.TO_SEND: send_back = QAction(self.tr("Send again"), self) - send_back.triggered.connect(self.send_again) + send_back.triggered.connect(lambda checked, tr=transfer: self.send_again(checked, tr)) send_back.setData(transfer) menu.addAction(send_back) @@ -259,14 +259,9 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): @asyncify @asyncio.coroutine def send_money_to_identity(self, identity): - if isinstance(identity, str): - pubkey = identity - else: - pubkey = identity.pubkey - result = yield from TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker, + yield from TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker, self.community, identity) - if result == QDialog.Accepted: - self.table_history.model().sourceModel().refresh_transfers() + self.table_history.model().sourceModel().refresh_transfers() @asyncify @asyncio.coroutine @@ -278,22 +273,12 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): identity = self.sender().data() self.view_in_wot.emit(identity) - def send_again(self): - transfer = self.sender().data() - dialog = TransferMoneyDialog(self.app, self.app.current_account, - self.password_asker) - 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().sourceModel().refresh_transfers() + @asyncify + @asyncio.coroutine + def send_again(self, checked=False, transfer=None): + result = yield from TransferMoneyDialog.send_transfer_again(self.app, self.app.current_account, + self.password_asker, self.community, transfer) + self.table_history.model().sourceModel().refresh_transfers() def cancel_transfer(self): reply = QMessageBox.warning(self, self.tr("Warning"), @@ -302,7 +287,7 @@ This money transfer will be removed and not sent."""), QMessageBox.Ok | QMessageBox.Cancel) if reply == QMessageBox.Ok: transfer = self.sender().data() - transfer.drop() + transfer.cancel() self.table_history.model().sourceModel().refresh_transfers() def dates_changed(self): diff --git a/src/cutecoin/gui/transfer.py b/src/cutecoin/gui/transfer.py index e6819758fd9e3436ee4e34823ec0a28b9be7a98c..edc2bc82fb9ffaf90c94cafe0c2ca2b3757cc80a 100644 --- a/src/cutecoin/gui/transfer.py +++ b/src/cutecoin/gui/transfer.py @@ -22,7 +22,7 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): classdocs """ - def __init__(self, app, sender, password_asker): + def __init__(self, app, sender, password_asker, community, transfer): """ Constructor :param cutecoin.core.Application app: The application @@ -36,8 +36,9 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): self.account = sender self.password_asker = password_asker self.recipient_trusts = [] + self.transfer = transfer self.wallet = None - self.community = self.account.communities[0] + self.community = community if community else self.account.communities[0] self.wallet = self.account.wallets[0] regexp = QRegExp('^([ a-zA-Z0-9-_:/;*?\[\]\(\)\\\?!^+=@&~#{}|<>%.]{0,255})$') @@ -58,15 +59,37 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): self.radio_contact.setEnabled(False) self.radio_pubkey.setChecked(True) + self.combo_community.setCurrentText(self.community.name) + + if self.transfer: + sender = self.transfer.metadata['issuer'] + wallet_index = [w.pubkey for w in app.current_account.wallets].index(sender) + self.combo_wallets.setCurrentIndex(wallet_index) + self.edit_pubkey.setText(transfer.metadata['receiver']) + self.radio_pubkey.setChecked(True) + self.edit_message.setText(transfer.metadata['comment']) + + @classmethod @asyncio.coroutine def send_money_to_identity(cls, app, account, password_asker, community, identity): - dialog = cls(app, account, password_asker) + dialog = cls(app, account, password_asker, community, None) dialog.edit_pubkey.setText(identity.pubkey) - dialog.combo_community.setCurrentText(community.name) dialog.radio_pubkey.setChecked(True) return (yield from dialog.async_exec()) + @classmethod + @asyncio.coroutine + def send_transfer_again(cls, app, account, password_asker, community, transfer): + dialog = cls(app, account, password_asker, community, transfer) + dividend = yield from community.dividend() + relative = transfer.metadata['amount'] / dividend + dialog.spinbox_amount.setMaximum(transfer.metadata['amount']) + dialog.spinbox_relative.setMaximum(relative) + dialog.spinbox_amount.setValue(transfer.metadata['amount']) + + return (yield from dialog.async_exec()) + @asyncify @asyncio.coroutine def accept(self): @@ -101,6 +124,11 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): yield from QAsyncMessageBox.information(self, self.tr("Transfer"), self.tr("Success sending money to {0}").format(recipient)) QApplication.restoreOverrideCursor() + + # If we sent back a transaction we cancel the first one + if self.transfer: + self.transfer.cancel() + super().accept() else: if self.app.preferences['notifications']: @@ -139,7 +167,6 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): international_system=self.app.preferences['international_system_of_units']) self.label_total.setText("{0}".format(ref_text)) self.spinbox_amount.setSuffix(" " + self.community.currency) - self.spinbox_amount.setValue(0) amount = yield from self.wallet.value(self.community) dividend = yield from self.community.dividend() relative = amount / dividend @@ -155,7 +182,6 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): .diff_localized(units=True, international_system=self.app.preferences['international_system_of_units']) self.label_total.setText("{0}".format(ref_text)) - self.spinbox_amount.setValue(0) amount = yield from self.wallet.value(self.community) dividend = yield from self.community.dividend() relative = amount / dividend diff --git a/src/cutecoin/models/txhistory.py b/src/cutecoin/models/txhistory.py index c3dbc4ca62c3856ceedff78d8542338bf893a20d..58097206e2e80d06da5efbb48a25e4d5931baab7 100644 --- a/src/cutecoin/models/txhistory.py +++ b/src/cutecoin/models/txhistory.py @@ -153,9 +153,12 @@ class TxFilterProxyModel(QSortFilterProxyModel): current_validations = 0 if state_data == TransferState.VALIDATING: - current_blockid.number = self.community.network.current_blockid.number - if current_blockid.number: - current_validations = current_blockid.number - block_data + current_blockid_number = self.community.network.current_blockid.number + if current_blockid_number: + current_validations = current_blockid_number - block_data + elif state_data == TransferState.AWAITING: + current_validations = 0 + max_validations = self.sourceModel().max_validations() if self.app.preferences['expert_mode']: