diff --git a/gui/gui_classic/main_window.py b/gui/gui_classic/main_window.py index 662b75193..f6ac8648e 100644 --- a/gui/gui_classic/main_window.py +++ b/gui/gui_classic/main_window.py @@ -612,59 +612,16 @@ class ElectrumWindow(QMainWindow): tx_hash = str(item.data(0, Qt.UserRole).toString()) if not tx_hash: return menu = QMenu() - #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash)) - menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash))) + menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash)) + menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash))) menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2)) menu.exec_(self.contacts_list.viewport().mapToGlobal(position)) - def show_tx_details(self, tx): - dialog = QDialog(self) - dialog.setModal(1) - dialog.setWindowTitle(_("Transaction Details")) - vbox = QVBoxLayout() - dialog.setLayout(vbox) - dialog.setMinimumSize(600,300) - - tx_hash = tx.hash() - if tx_hash in self.wallet.transactions.keys(): - is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx) - conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash) - if timestamp: - time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] - else: - time_str = 'pending' - else: - is_mine = False - - vbox.addWidget(QLabel("Transaction ID:")) - e = QLineEdit(tx_hash) - e.setReadOnly(True) - vbox.addWidget(e) - - vbox.addWidget(QLabel("Date: %s"%time_str)) - vbox.addWidget(QLabel("Status: %d confirmations"%conf)) - if is_mine: - if fee is not None: - vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee))) - vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee))) - else: - vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v))) - vbox.addWidget(QLabel("Transaction fee: unknown")) - else: - vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v))) - - vbox.addWidget( self.generate_transaction_information_widget(tx) ) - - ok_button = QPushButton(_("Close")) - ok_button.setDefault(True) - ok_button.clicked.connect(dialog.accept) - - hbox = QHBoxLayout() - hbox.addStretch(1) - hbox.addWidget(ok_button) - vbox.addLayout(hbox) - dialog.exec_() + def show_transaction(self, tx): + import transaction_dialog + d = transaction_dialog.TxDialog(tx, self) + d.exec_() def tx_label_clicked(self, item, column): if column==2 and item.isSelected(): @@ -1678,45 +1635,6 @@ class ElectrumWindow(QMainWindow): - def generate_transaction_information_widget(self, tx): - tabs = QTabWidget(self) - - tab1 = QWidget() - grid_ui = QGridLayout(tab1) - grid_ui.setColumnStretch(0,1) - tabs.addTab(tab1, _('Outputs') ) - - tree_widget = MyTreeWidget(self) - tree_widget.setColumnCount(2) - tree_widget.setHeaderLabels( [_('Address'), _('Amount')] ) - tree_widget.setColumnWidth(0, 300) - tree_widget.setColumnWidth(1, 50) - - for address, value in tx.outputs: - item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] ) - tree_widget.addTopLevelItem(item) - - tree_widget.setMaximumHeight(100) - - grid_ui.addWidget(tree_widget) - - tab2 = QWidget() - grid_ui = QGridLayout(tab2) - grid_ui.setColumnStretch(0,1) - tabs.addTab(tab2, _('Inputs') ) - - tree_widget = MyTreeWidget(self) - tree_widget.setColumnCount(2) - tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] ) - - for input_line in tx.inputs: - item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] ) - tree_widget.addTopLevelItem(item) - - tree_widget.setMaximumHeight(100) - - grid_ui.addWidget(tree_widget) - return tabs def tx_dict_from_text(self, txt): @@ -1747,28 +1665,9 @@ class ElectrumWindow(QMainWindow): @protected def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""): - try: - self.wallet.signrawtransaction(tx, input_info, [], password) - - fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn") - if fileName: - with open(fileName, "w+") as f: - f.write(json.dumps(tx.as_dict(),indent=4) + '\n') - self.show_message(_("Transaction saved successfully")) - if dialog: - dialog.done(0) - except BaseException, e: - self.show_message(str(e)) + self.wallet.signrawtransaction(tx, input_info, [], password) - def send_raw_transaction(self, raw_tx, dialog = ""): - result, result_message = self.wallet.sendtx( raw_tx ) - if result: - self.show_message("Transaction successfully sent: %s" % (result_message)) - if dialog: - dialog.done(0) - else: - self.show_message("There was a problem sending your transaction:\n %s" % (result_message)) def do_process_from_text(self): text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction")) @@ -1801,8 +1700,9 @@ class ElectrumWindow(QMainWindow): self.show_message(str(e)) return - tx_dict = tx.as_dict() - self.create_process_transaction_window(tx_dict) + self.show_transaction(tx) + #tx_dict = tx.as_dict() + #self.create_process_transaction_window(tx_dict) def do_process_from_csv_file(self): fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv") @@ -1824,41 +1724,10 @@ class ElectrumWindow(QMainWindow): csvReader = csv.reader(f) self.do_process_from_csvReader(csvReader) + def create_process_transaction_window(self, tx_dict): tx = Transaction(tx_dict["hex"]) - - dialog = QDialog(self) - dialog.setMinimumWidth(500) - dialog.setWindowTitle(_('Process raw transaction')) - dialog.setModal(1) - - l = QGridLayout() - dialog.setLayout(l) - - l.addWidget(QLabel(_("Transaction status:")), 3,0) - l.addWidget(QLabel(_("Actions")), 4,0) - - if tx_dict["complete"] == False: - l.addWidget(QLabel(_("Unsigned")), 3,1) - if self.wallet.seed : - b = QPushButton("Sign transaction") - input_info = json.loads(tx_dict["input_info"]) - b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog)) - l.addWidget(b, 4, 1) - else: - l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1) - else: - l.addWidget(QLabel(_("Signed")), 3,1) - b = QPushButton("Broadcast transaction") - b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog)) - l.addWidget(b,4,1) - - l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3) - cancelButton = QPushButton(_("Cancel")) - cancelButton.clicked.connect(lambda: dialog.done(0)) - l.addWidget(cancelButton, 4,2) - - dialog.exec_() + self.show_transaction(tx) @protected diff --git a/gui/gui_classic/transaction_dialog.py b/gui/gui_classic/transaction_dialog.py new file mode 100644 index 000000000..ae182421b --- /dev/null +++ b/gui/gui_classic/transaction_dialog.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2012 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys, time, datetime, re, threading +from electrum.i18n import _, set_language +from electrum.util import print_error, print_msg +import os.path, json, ast, traceback +import shutil +import StringIO + + +try: + import PyQt4 +except: + sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'") + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import PyQt4.QtCore as QtCore + +from electrum import transaction + + +class TxDialog(QDialog): + + def __init__(self, tx, parent): + self.tx = tx + tx_dict = tx.as_dict() + self.parent = parent + self.wallet = parent.wallet + + QDialog.__init__(self) + self.setMinimumWidth(600) + self.setWindowTitle(_('Transaction')) + self.setModal(1) + + vbox = QVBoxLayout() + self.setLayout(vbox) + + vbox.addWidget(QLabel("Transaction ID:")) + self.tx_hash_e = QLineEdit() + self.tx_hash_e.setReadOnly(True) + vbox.addWidget(self.tx_hash_e) + self.status_label = QLabel() + vbox.addWidget(self.status_label) + + self.date_label = QLabel() + vbox.addWidget(self.date_label) + self.amount_label = QLabel() + vbox.addWidget(self.amount_label) + self.fee_label = QLabel() + vbox.addWidget(self.fee_label) + + self.io = self.io_widget(tx) + vbox.addWidget( self.io ) + + buttons = QHBoxLayout() + vbox.addLayout( buttons ) + + buttons.addStretch(1) + + self.sign_button = b = QPushButton(_("Sign")) + b.clicked.connect(self.sign) + buttons.addWidget(b) + + self.broadcast_button = b = QPushButton(_("Broadcast")) + b.clicked.connect(self.broadcast) + b.hide() + buttons.addWidget(b) + + self.save_button = b = QPushButton(_("Save")) + b.clicked.connect(self.save) + buttons.addWidget(b) + + cancelButton = QPushButton(_("Close")) + cancelButton.clicked.connect(lambda: self.done(0)) + buttons.addWidget(cancelButton) + + self.update() + + + + + def sign(self): + tx_dict = self.tx.as_dict() + input_info = json.loads(tx_dict["input_info"]) + self.parent.sign_raw_transaction(self.tx, input_info, self) + self.update() + + + def save(self): + fileName = self.parent.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (self.tx.hash()[0:8]), "*.txn") + if fileName: + with open(fileName, "w+") as f: + f.write(json.dumps(self.tx.as_dict(),indent=4) + '\n') + self.show_message(_("Transaction saved successfully")) + + + def update(self): + tx_hash = self.tx.hash() + + is_relevant, is_mine, v, fee = self.wallet.get_tx_value(self.tx) + + if self.tx.is_complete: + status = "Status: Signed" + self.sign_button.hide() + + if tx_hash in self.wallet.transactions.keys(): + conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash) + if timestamp: + time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] + else: + time_str = 'pending' + status = "Status: %d confirmations"%conf + self.broadcast_button.hide() + else: + time_str = None + conf = 0 + self.broadcast_button.show() + else: + status = "Status: Unsigned" + time_str = None + self.sign_button.show() + self.broadcast_button.hide() + + self.tx_hash_e.setText(tx_hash) + self.status_label.setText(status) + + if time_str is not None: + self.date_label.setText("Date: %s"%time_str) + self.date_label.show() + else: + self.date_label.hide() + + if is_relevant: + if is_mine: + if fee is not None: + self.amount_label.setText("Amount sent: %s"% self.parent.format_amount(v-fee)) + self.fee_label.setText("Transaction fee: %s"% self.parent.format_amount(fee)) + else: + self.amount_label.setText("Amount sent: %s"% self.parent.format_amount(v)) + self.fee_label.setText("Transaction fee: unknown") + else: + self.amount_label.setText("Amount received: %s"% self.parent.format_amount(v)) + else: + self.amount_label.setText("Transaction unrelated to your wallet") + + + + def io_widget(self, tx): + tabs = QTabWidget(self) + + tab1 = QWidget() + grid_ui = QGridLayout(tab1) + grid_ui.setColumnStretch(0,1) + tabs.addTab(tab1, _('Outputs') ) + + tree_widget = QTreeWidget(self) + tree_widget.setColumnCount(2) + tree_widget.setHeaderLabels( [_('Address'), _('Amount')] ) + tree_widget.setColumnWidth(0, 300) + tree_widget.setColumnWidth(1, 50) + + for address, value in tx.outputs: + item = QTreeWidgetItem( [address, "%s" % ( self.parent.format_amount(value))] ) + tree_widget.addTopLevelItem(item) + + tree_widget.setMaximumHeight(100) + + grid_ui.addWidget(tree_widget) + + tab2 = QWidget() + grid_ui = QGridLayout(tab2) + grid_ui.setColumnStretch(0,1) + tabs.addTab(tab2, _('Inputs') ) + + tree_widget = QTreeWidget(self) + tree_widget.setColumnCount(2) + tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] ) + + for input_line in tx.inputs: + item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line.get("prevout_hash"))] ) + tree_widget.addTopLevelItem(item) + + tree_widget.setMaximumHeight(100) + + grid_ui.addWidget(tree_widget) + return tabs + + + def broadcast(self): + result, result_message = self.wallet.sendtx( self.tx ) + if result: + self.show_message("Transaction successfully sent: %s" % (result_message)) + if dialog: + dialog.done(0) + else: + self.show_message("There was a problem sending your transaction:\n %s" % (result_message)) + + def show_message(self, msg): + QMessageBox.information(self, _('Message'), msg, _('OK')) + + + + diff --git a/lib/__init__.py b/lib/__init__.py index 5182f424f..fe4c7b350 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -8,6 +8,7 @@ from interface import Interface from simple_config import SimpleConfig import bitcoin import account +import transaction from transaction import Transaction from plugins import BasePlugin from mnemonic import mn_encode as mnemonic_encode diff --git a/lib/commands.py b/lib/commands.py index 5e105d9d6..53a829d01 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -247,7 +247,7 @@ class Commands: def mktx(self, to_address, amount, fee = None, change_addr = None, domain = None): tx = self._mktx([(to_address, amount)], fee, change_addr, domain) - return tx.as_dict() + return tx def mksendmanytx(self, outputs, fee = None, change_addr = None, domain = None): tx = self._mktx(outputs, fee, change_addr, domain) diff --git a/lib/interface.py b/lib/interface.py index cf87442fa..97d1ac2cb 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -449,7 +449,7 @@ class Interface(threading.Thread): def synchronous_get(self, requests, timeout=100000000): # todo: use generators, unanswered_requests should be a list of arrays... - q = Queue.Queue() + queue = Queue.Queue() ids = self.send(requests, lambda i,r: queue.put(r)) id2 = ids[:] res = {} diff --git a/lib/network.py b/lib/network.py index 9663da495..fba293712 100644 --- a/lib/network.py +++ b/lib/network.py @@ -174,6 +174,12 @@ class Network(threading.Thread): def is_running(self): with self.lock: return self.running + + def retrieve_transaction(self, tx_hash, tx_height=0): + import transaction + r = self.interface.synchronous_get([ ('blockchain.transaction.get',[tx_hash, tx_height]) ])[0] + return transaction.Transaction(r) + def parse_servers(self, result): """ parse servers list into dict format""" diff --git a/lib/transaction.py b/lib/transaction.py index 3136bfa3b..892ec5a8a 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -379,6 +379,13 @@ class Transaction: self.input_info = None self.is_complete = True + + def __repr__(self): + return "Transaction('"+self.raw+"')" + + def __str__(self): + return self.raw + @classmethod def from_io(klass, inputs, outputs): raw = klass.serialize(inputs, outputs, for_sig = -1) # for_sig=-1 means do not sign @@ -390,12 +397,13 @@ class Transaction: for i in self.inputs: e = { 'txid':i['tx_hash'], 'vout':i['index'], 'scriptPubKey':i.get('raw_output_script') } extras.append(e) + # fixme: simplify this + i['prevout_hash'] = i['tx_hash'] + i['prevout_n'] = i['index'] + self.input_info = extras return self - def __str__(self): - return self.raw - @classmethod def multisig_script(klass, public_keys, num=None): n = len(public_keys) diff --git a/lib/util.py b/lib/util.py index 915732712..9503b88cf 100644 --- a/lib/util.py +++ b/lib/util.py @@ -24,7 +24,10 @@ def print_msg(*args): def print_json(obj): import json - s = json.dumps(obj,sort_keys = True, indent = 4) + try: + s = json.dumps(obj,sort_keys = True, indent = 4) + except TypeError: + s = repr(obj) sys.stdout.write(s + "\n") sys.stdout.flush() diff --git a/setup.py b/setup.py index 79215c023..f4c13f49b 100644 --- a/setup.py +++ b/setup.py @@ -90,6 +90,7 @@ setup(name = "Electrum", 'electrum_gui.gui_classic.network_dialog', 'electrum_gui.gui_classic.password_dialog', 'electrum_gui.gui_classic.seed_dialog', + 'electrum_gui.gui_classic.transaction dialog', 'electrum_gui.gui_classic.version_getter', 'electrum_gui.gui_classic.amountedit', 'electrum_plugins.pointofsale',