+ address: ''
+ memo: ''
+ amount: ''
+ status: ''
+ date: ''
+ icon: 'atlas://gui/kivy/theming/light/important'
+ Image:
+ id: icon
+ source: root.icon
+ size_hint: None, 1
+ width: self.height *.54
+ mipmap: True
+ BoxLayout:
+ spacing: '8dp'
+ height: '32dp'
+ orientation: 'vertical'
+ Widget
+ RequestLabel:
+ text: root.address
+ shorten: True
+ RequestLabel:
+ text: root.memo
+ color: .699, .699, .699, 1
+ font_size: '13sp'
+ shorten: True
+ Widget
+ RequestLabel:
+ halign: 'right'
+ font_size: '15sp'
+ size_hint: None, 1
+ width: '110sp'
+ text: root.amount
+
+
+RequestsScreen:
+ name: 'requests'
+ BoxLayout:
+ orientation: 'vertical'
+ spacing: '1dp'
+ ScrollView:
+ canvas.before:
+ Color:
+ rgba: .8901, .8901, .8901, 0
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ GridLayout:
+ cols: 1
+ id: requests_container
+ size_hint_y: None
+ height: self.minimum_height
+ spacing: '2dp'
+ padding: '12dp'
diff --git a/gui/kivy/uix/ui_screens/send.kv b/gui/kivy/uix/ui_screens/send.kv
index 7a92fd172..2784c4ab8 100644
--- a/gui/kivy/uix/ui_screens/send.kv
+++ b/gui/kivy/uix/ui_screens/send.kv
@@ -23,6 +23,7 @@ SendScreen:
id: blue_bottom
size_hint: 1, None
height: self.minimum_height
+ spacing: '5dp'
BoxLayout:
size_hint: 1, None
height: blue_bottom.item_height
@@ -32,32 +33,32 @@ SendScreen:
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
- TextInputBlue:
+ BlueButton:
id: payto_e
- text: s.address
- hint_text: "Recipient"
+ text: s.address if s.address else _('Recipient')
+ on_release: app.address_dialog(s)
CardSeparator:
opacity: message_selection.opacity
color: blue_bottom.foreground_color
BoxLayout:
size_hint: 1, None
height: blue_bottom.item_height
+ spacing: '5dp'
Image:
source: 'atlas://gui/kivy/theming/light/globe'
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
- AmountButton:
+ BlueButton:
id: amount_e
- text: s.amount if s.amount else 'Amount'
- on_release: app.amount_dialog(s, True)
-
+ default_text: _('Amount')
+ text: s.amount if s.amount else _('Amount')
+ on_release: s.amount_dialog()
CardSeparator:
opacity: message_selection.opacity
color: blue_bottom.foreground_color
BoxLayout:
id: message_selection
- opacity: 1
size_hint: 1, None
height: blue_bottom.item_height
spacing: '5dp'
@@ -66,11 +67,10 @@ SendScreen:
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
- TextInputBlue:
- id: message_e
- hint_text: 'Description'
- text: s.message
- on_text_validate: s.message = self.text
+ BlueButton:
+ id: description
+ text: s.message if s.message else _('Description')
+ on_release: app.description_dialog(s)
BoxLayout:
size_hint: 1, None
height: '48dp'
@@ -78,7 +78,7 @@ SendScreen:
id: qr
text: _('QR Code')
on_release:
- app.scan_qr(on_complete=s.parent.set_URI)
+ app.scan_qr(on_complete=app.set_URI)
Button:
id: paste_button
text: _('Paste')
diff --git a/gui/kivy/uix/ui_screens/settings.kv b/gui/kivy/uix/ui_screens/settings.kv
deleted file mode 100644
index 0f211f9e3..000000000
--- a/gui/kivy/uix/ui_screens/settings.kv
+++ /dev/null
@@ -1,37 +0,0 @@
-Popup:
- id: settings
- title: _('Settings')
-
- BoxLayout:
- orientation: 'vertical'
- SettingsItem:
- title: _('PIN Code') + ' (%s)'%('ON' if app.wallet.use_encryption else 'OFF')
- description: _("Your PIN code will be required in order to spend bitcoins.")
- on_release:
- app.change_password()
- self.title = _('PIN Code') + ' (%s)'%('ON' if app.wallet.use_encryption else 'OFF')
- CardSeparator
- SettingsItem:
- title: _('Denomination') + ' (' + app.base_unit + ')'
- description: _("Base unit for Bitcoin amounts.")
- on_release:
- app._rotate_bu()
- self.title = _('Denomination') + ' (' + app.base_unit + ')'
- CardSeparator
- SettingsItem:
- title: _('OpenAlias')
- description: "Email-like address."
-
- Widget:
- size_hint: 1, 1
-
- BoxLayout:
- Widget:
- size_hint: 0.5, None
- Button:
- size_hint: 0.5, None
- height: '48dp'
- text: _('OK')
- on_release:
- settings.dismiss()
-
diff --git a/gui/kivy/uix/ui_screens/status.kv b/gui/kivy/uix/ui_screens/status.kv
new file mode 100644
index 000000000..59e23d93e
--- /dev/null
+++ b/gui/kivy/uix/ui_screens/status.kv
@@ -0,0 +1,31 @@
+#:import os os
+
+Popup:
+ title: "Balance"
+ confirmed: 0
+ unconfirmed: 0
+ unmatured: 0
+ on_parent:
+ self.confirmed, self.unconfirmed, self.x = app.wallet.get_balance()
+ BoxLayout:
+ orientation: 'vertical'
+ spacing: '1dp'
+ GridLayout:
+ cols:2
+ Label:
+ text: _("Wallet:")
+ Label:
+ text: os.path.basename(app.wallet.storage.path)
+ Label:
+ text: _("Confirmed:")
+ Label:
+ text: app.format_amount_and_units(root.confirmed)
+ Label:
+ text: _("Unconfirmed:")
+ Label:
+ text: app.format_amount_and_units(root.unconfirmed)
+ Label:
+ text: "Unmatured:"
+ Label:
+ text: app.format_amount_and_units(root.unmatured)
+ Widget
diff --git a/gui/kivy/uix/ui_screens/wallets.kv b/gui/kivy/uix/ui_screens/wallets.kv
deleted file mode 100644
index aae9fdd67..000000000
--- a/gui/kivy/uix/ui_screens/wallets.kv
+++ /dev/null
@@ -1,35 +0,0 @@
-#:import os os
-
-Popup:
- title: _('Wallets')
- id: popup
- BoxLayout:
- orientation: 'vertical'
- GridLayout:
- size_hint_y: None
- cols: 2
- Label:
- height: '32dp'
- size_hint_y: None
- text: _('Wallet file') + ':'
- TextInput:
- id: text_input
- height: '32dp'
- size_hint_y: None
- text: os.path.basename(app.wallet.storage.path)
-
- FileChooserIconView:
- id: wallet_selector
- path: os.path.dirname(app.wallet.storage.path)
- on_selection: text_input.text = os.path.basename(self.selection[0]) if self.selection else ''
- size_hint: 1, 1
-
- BoxLayout:
- Widget:
- size_hint: 0.5, None
- Button:
- size_hint: 0.5, None
- height: '48dp'
- text: _('OK')
- on_release:
- popup.dismiss()
diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py
index 14fb0100a..c9d52a1e2 100644
--- a/gui/qt/__init__.py
+++ b/gui/qt/__init__.py
@@ -64,7 +64,7 @@ class OpenFileEventFilter(QObject):
-class ElectrumGui:
+class ElectrumGui(MessageBoxMixin):
def __init__(self, config, network, plugins):
set_language(config.get('language'))
@@ -134,7 +134,7 @@ class ElectrumGui:
try:
storage = WalletStorage(filename)
except Exception as e:
- QMessageBox.information(None, _('Error'), str(e), _('OK'))
+ self.show_error(str(e))
return
if not storage.file_exists:
recent = self.config.get('recently_open', [])
@@ -147,7 +147,7 @@ class ElectrumGui:
wallet = Wallet(storage)
except BaseException as e:
traceback.print_exc(file=sys.stdout)
- QMessageBox.warning(None, _('Warning'), str(e), _('OK'))
+ self.show_warning(str(e))
return
action = wallet.get_action()
# run wizard
@@ -162,32 +162,6 @@ class ElectrumGui:
return wallet
- def get_wallet_folder(self):
- #return os.path.dirname(os.path.abspath(self.wallet.storage.path if self.wallet else self.wallet.storage.path))
- return os.path.dirname(os.path.abspath(self.config.get_wallet_path()))
-
- def new_wallet(self):
- wallet_folder = self.get_wallet_folder()
- i = 1
- while True:
- filename = "wallet_%d"%i
- if filename in os.listdir(wallet_folder):
- i += 1
- else:
- break
- filename = line_dialog(None, _('New Wallet'), _('Enter file name') + ':', _('OK'), filename)
- if not filename:
- return
- full_path = os.path.join(wallet_folder, filename)
- storage = WalletStorage(full_path)
- if storage.file_exists:
- QMessageBox.critical(None, "Error", _("File exists"))
- return
- wizard = InstallWizard(self.app, self.config, self.network, storage)
- wallet = wizard.run('new')
- if wallet:
- self.new_window(full_path)
-
def new_window(self, path, uri=None):
# Use a signal as can be called from daemon thread
self.app.emit(SIGNAL('new_window'), path, uri)
@@ -245,6 +219,9 @@ class ElectrumGui:
# main loop
self.app.exec_()
+ # Shut down the timer cleanly
+ self.timer.stop()
+
# clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html
event = QtCore.QEvent(QtCore.QEvent.Clipboard)
self.app.sendEvent(self.app.clipboard(), event)
diff --git a/gui/qt/address_dialog.py b/gui/qt/address_dialog.py
index d4812a1aa..68ca823c7 100644
--- a/gui/qt/address_dialog.py
+++ b/gui/qt/address_dialog.py
@@ -25,9 +25,10 @@ from PyQt4.QtCore import *
from util import *
from history_widget import HistoryWidget
-class AddressDialog(QDialog):
+class AddressDialog(WindowModalDialog):
- def __init__(self, address, parent):
+ def __init__(self, parent, address):
+ WindowModalDialog.__init__(self, parent, _("Address"))
self.address = address
self.parent = parent
self.config = parent.config
@@ -35,10 +36,7 @@ class AddressDialog(QDialog):
self.app = parent.app
self.saved = True
- QDialog.__init__(self)
self.setMinimumWidth(700)
- self.setWindowTitle(_("Address"))
- self.setModal(1)
vbox = QVBoxLayout()
self.setLayout(vbox)
diff --git a/gui/qt/history_widget.py b/gui/qt/history_widget.py
index dc34ff2b8..260802ad2 100644
--- a/gui/qt/history_widget.py
+++ b/gui/qt/history_widget.py
@@ -74,7 +74,7 @@ class HistoryWidget(MyTreeWidget):
icon, time_str = self.get_icon(conf, timestamp)
v_str = self.parent.format_amount(value, True, whitespaces=True)
balance_str = self.parent.format_amount(balance, whitespaces=True)
- label, is_default_label = self.wallet.get_label(tx_hash)
+ label = self.wallet.get_label(tx_hash)
entry = ['', tx_hash, time_str, label, v_str, balance_str]
run_hook('history_tab_update', tx, entry)
item = QTreeWidgetItem(entry)
@@ -88,8 +88,6 @@ class HistoryWidget(MyTreeWidget):
item.setForeground(4, QBrush(QColor("#BC1E1E")))
if tx_hash:
item.setData(0, Qt.UserRole, tx_hash)
- if is_default_label:
- item.setForeground(3, QBrush(QColor('grey')))
self.insertTopLevelItem(0, item)
if current_tx == tx_hash:
self.setCurrentItem(item)
diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
index 0c4d2119f..de9468283 100644
--- a/gui/qt/installwizard.py
+++ b/gui/qt/installwizard.py
@@ -62,17 +62,17 @@ class CosignWidget(QWidget):
-class InstallWizard(QDialog):
+class InstallWizard(WindowModalDialog, MessageBoxMixin):
def __init__(self, app, config, network, storage):
- QDialog.__init__(self)
+ title = 'Electrum' + ' - ' + _('Install Wizard')
+ WindowModalDialog.__init__(self, None, title=title)
self.app = app
self.config = config
self.network = network
self.storage = storage
self.setMinimumSize(575, 400)
self.setMaximumSize(575, 400)
- self.setWindowTitle('Electrum' + ' - ' + _('Install Wizard'))
self.connect(self, QtCore.SIGNAL('accept'), self.accept)
self.stack = QStackedLayout()
self.setLayout(self.stack)
@@ -139,8 +139,8 @@ class InstallWizard(QDialog):
button.setChecked(True)
vbox.addStretch(1)
- self.set_layout(vbox)
vbox.addLayout(Buttons(CancelButton(self), OkButton(self, _('Next'))))
+ self.set_layout(vbox)
self.show()
self.raise_()
@@ -157,7 +157,7 @@ class InstallWizard(QDialog):
if not r:
return
if prepare_seed(r) != prepare_seed(seed):
- QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
+ self.show_error(_('Incorrect seed'))
return False
else:
return True
@@ -384,22 +384,6 @@ class InstallWizard(QDialog):
wallet_type = '%dof%d'%(m,n)
return wallet_type
- def question(self, msg, yes_label=_('OK'), no_label=_('Cancel'), icon=None):
- vbox = QVBoxLayout()
- self.set_layout(vbox)
- if icon:
- logo = QLabel()
- logo.setPixmap(icon)
- vbox.addWidget(logo)
- label = QLabel(msg)
- label.setWordWrap(True)
- vbox.addWidget(label)
- vbox.addStretch(1)
- vbox.addLayout(Buttons(CancelButton(self, no_label), OkButton(self, yes_label)))
- if not self.exec_():
- return None
- return True
-
def show_seed(self, seed, sid):
vbox = seed_dialog.show_seed_box_msg(seed, sid)
vbox.addLayout(Buttons(CancelButton(self), OkButton(self, _("Next"))))
@@ -407,22 +391,21 @@ class InstallWizard(QDialog):
return self.exec_()
def password_dialog(self):
- msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
- +_("Leave these fields empty if you want to disable encryption.")
- from password_dialog import make_password_dialog, run_password_dialog
- self.set_layout( make_password_dialog(self, None, msg) )
- return run_password_dialog(self, None, self)[2]
+ from password_dialog import PasswordDialog
+ msg = _("Please choose a password to encrypt your wallet keys.\n"
+ "Leave these fields empty if you want to disable encryption.")
+ dialog = PasswordDialog(self, None, _("Choose a password"), msg, True)
+ return dialog.run()[2]
def run(self, action):
if self.storage.file_exists and action != 'new':
path = self.storage.path
msg = _("The file '%s' contains an incompletely created wallet.\n"
"Do you want to complete its creation now?") % path
- if not question(msg):
- if question(_("Do you want to delete '%s'?") % path):
+ if not self.question(msg):
+ if self.question(_("Do you want to delete '%s'?") % path):
os.remove(path)
- QMessageBox.information(self, _('Warning'),
- _('The file was removed'), _('OK'))
+ self.show_warning(_('The file was removed'))
return
return
self.show()
@@ -434,7 +417,7 @@ class InstallWizard(QDialog):
wallet = self.run_wallet_type(action, wallet_type)
except BaseException as e:
traceback.print_exc(file=sys.stdout)
- QMessageBox.information(None, _('Error'), str(e), _('OK'))
+ self.show_error(str(e))
return
return wallet
@@ -463,7 +446,7 @@ class InstallWizard(QDialog):
elif wallet_type == 'twofactor':
wallet_type = '2fa'
if action == 'create':
- self.storage.put('wallet_type', wallet_type, False)
+ self.storage.put('wallet_type', wallet_type)
if action is None:
return
@@ -527,7 +510,7 @@ class InstallWizard(QDialog):
if self.config.get('server') is None:
self.network_dialog()
else:
- QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
+ self.show_warning(_('You are offline'))
# start wallet threads
@@ -539,7 +522,7 @@ class InstallWizard(QDialog):
msg = _("Recovery successful") if wallet.is_found() else _("No transactions found for this seed")
else:
msg = _("This wallet was restored offline. It may contain more addresses than displayed.")
- QMessageBox.information(None, _('Information'), msg, _('OK'))
+ self.show_message(msg)
return wallet
@@ -560,7 +543,7 @@ class InstallWizard(QDialog):
password = self.password_dialog() if any(map(lambda x: Wallet.is_seed(x) or Wallet.is_xprv(x), key_list)) else None
wallet = Wallet.from_multisig(key_list, password, self.storage, t)
else:
- self.storage.put('wallet_type', t, False)
+ self.storage.put('wallet_type', t)
# call the constructor to load the plugin (side effect)
Wallet(self.storage)
wallet = always_hook('installwizard_restore', self, self.storage)
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
index bda09737a..fb9aa9fee 100644
--- a/gui/qt/main_window.py
+++ b/gui/qt/main_window.py
@@ -17,7 +17,7 @@
# along with this program. If not, see .
import sys, time, threading
-import os.path, json, traceback
+import os, json, traceback
import shutil
import socket
import weakref
@@ -40,20 +40,17 @@ from electrum.i18n import _
from electrum.util import block_explorer, block_explorer_info, block_explorer_URL
from electrum.util import format_satoshis, format_satoshis_plain, format_time
from electrum.util import PrintError, NotEnoughFunds, StoreDict
-from electrum import Transaction
-from electrum import mnemonic
+from electrum import Transaction, mnemonic
from electrum import util, bitcoin, commands, Wallet
from electrum import SimpleConfig, COIN_CHOOSERS, WalletStorage
-from electrum import Imported_Wallet
-from electrum import paymentrequest
+from electrum import Imported_Wallet, paymentrequest
from amountedit import BTCAmountEdit, MyLineEdit, BTCkBEdit
from network_dialog import NetworkDialog
from qrcodewidget import QRCodeWidget, QRDialog
-from qrtextedit import ScanQRTextEdit, ShowQRTextEdit
+from qrtextedit import ShowQRTextEdit
from transaction_dialog import show_transaction
-
-
+from installwizard import InstallWizard
@@ -83,7 +80,6 @@ class StatusBarButton(QPushButton):
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
-from electrum.paymentrequest import PaymentRequest, get_payment_request
pr_icons = {
PR_UNPAID:":icons/unpaid.png",
@@ -106,7 +102,7 @@ expiration_values = [
-class ElectrumWindow(QMainWindow, PrintError):
+class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def __init__(self, gui_object, wallet):
QMainWindow.__init__(self)
@@ -270,7 +266,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.update_account_selector()
# update menus
self.new_account_menu.setVisible(self.wallet.can_create_accounts())
- self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
+ self.export_menu.setEnabled(not self.wallet.is_watching_only())
self.password_menu.setEnabled(self.wallet.can_change_password())
self.seed_menu.setEnabled(self.wallet.has_seed())
self.mpk_menu.setEnabled(self.wallet.is_deterministic())
@@ -288,14 +284,17 @@ class ElectrumWindow(QMainWindow, PrintError):
except:
self.setGeometry(100, 100, 840, 400)
self.show()
+ self.warn_if_watching_only()
+ run_hook('load_wallet', wallet, self)
+
+ def warn_if_watching_only(self):
if self.wallet.is_watching_only():
msg = ' '.join([
_("This wallet is watching-only."),
_("This means you will not be able to spend Bitcoins with it."),
_("Make sure you own the seed phrase or the private keys, before you request Bitcoins to be sent to this wallet.")
])
- QMessageBox.warning(self, _('Information'), msg, _('OK'))
- run_hook('load_wallet', wallet, self)
+ self.show_warning(msg, title=_('Information'))
def import_old_contacts(self):
# backward compatibility: import contacts
@@ -321,7 +320,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.wallet.synchronize()
def open_wallet(self):
- wallet_folder = self.gui_object.get_wallet_folder()
+ wallet_folder = self.get_wallet_folder()
filename = unicode(QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder))
if not filename:
return
@@ -339,11 +338,9 @@ class ElectrumWindow(QMainWindow, PrintError):
if new_path != path:
try:
shutil.copy2(path, new_path)
- QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
+ self.show_message(_("A copy of your wallet file was created in")+" '%s'" % str(new_path), title=_("Wallet backup created"))
except (IOError, os.error), reason:
- QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
-
-
+ self.show_critical(_("Electrum was unable to copy your wallet file to the specified location.") + "\n" + str(reason), title=_("Unable to create backup"))
def update_recently_visited(self, filename=None):
recent = self.config.get('recently_open', [])
@@ -361,13 +358,39 @@ class ElectrumWindow(QMainWindow, PrintError):
self.recently_visited_menu.addAction(b, loader(k)).setShortcut(QKeySequence("Ctrl+%d"%(i+1)))
self.recently_visited_menu.setEnabled(len(recent))
+ def get_wallet_folder(self):
+ return os.path.dirname(os.path.abspath(self.config.get_wallet_path()))
+
+ def new_wallet(self):
+ wallet_folder = self.get_wallet_folder()
+ i = 1
+ while True:
+ filename = "wallet_%d" % i
+ if filename in os.listdir(wallet_folder):
+ i += 1
+ else:
+ break
+ filename = line_dialog(self, _('New Wallet'), _('Enter file name')
+ + ':', _('OK'), filename)
+ if not filename:
+ return
+ full_path = os.path.join(wallet_folder, filename)
+ storage = WalletStorage(full_path)
+ if storage.file_exists:
+ self.show_critical(_("File exists"))
+ return
+ wizard = InstallWizard(self.app, self.config, self.network, storage)
+ wallet = wizard.run('new')
+ if wallet:
+ self.new_window(full_path)
+
def init_menubar(self):
menubar = QMenuBar()
file_menu = menubar.addMenu(_("&File"))
self.recently_visited_menu = file_menu.addMenu(_("&Recently open"))
file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
- file_menu.addAction(_("&New/Restore"), self.gui_object.new_wallet).setShortcut(QKeySequence.New)
+ file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
file_menu.addSeparator()
file_menu.addAction(_("&Quit"), self.close)
@@ -435,7 +458,7 @@ class ElectrumWindow(QMainWindow, PrintError):
_("Before reporting a bug, upgrade to the most recent version of Electrum (latest release or git HEAD), and include the version number in your report."),
_("Try to explain not only what the bug is, but how it occurs.")
])
- QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"), msg)
+ self.show_message(msg, title="Electrum - " + _("Reporting Bugs"))
def notify_transactions(self):
if not self.network or not self.network.is_connected():
@@ -582,7 +605,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def show_address(self, addr):
import address_dialog
- d = address_dialog.AddressDialog(addr, self)
+ d = address_dialog.AddressDialog(self, addr)
d.exec_()
def show_transaction(self, tx, tx_desc = None):
@@ -744,7 +767,7 @@ class ElectrumWindow(QMainWindow, PrintError):
try:
self.wallet.sign_payment_request(addr, alias, alias_addr, password)
except Exception as e:
- QMessageBox.warning(self, _('Error'), str(e), _('OK'))
+ self.show_error(str(e))
return
else:
return
@@ -757,7 +780,7 @@ class ElectrumWindow(QMainWindow, PrintError):
amount = self.receive_amount_e.get_amount()
message = unicode(self.receive_message_e.text())
if not message and not amount:
- QMessageBox.warning(self, _('Error'), _('No message or amount'), _('OK'))
+ self.show_error(_('No message or amount'))
return False
i = self.expires_combo.currentIndex()
expiration = map(lambda x: x[1], expiration_values)[i]
@@ -769,8 +792,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.save_request_button.setEnabled(False)
def view_and_paste(self, title, msg, data):
- dialog = QDialog(self)
- dialog.setWindowTitle(title)
+ dialog = WindowModalDialog(self, title)
vbox = QVBoxLayout()
label = QLabel(msg)
label.setWordWrap(True)
@@ -1129,7 +1151,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.wallet.check_password(password)
break
except Exception as e:
- QMessageBox.warning(parent, _('Error'), str(e), _('OK'))
+ self.show_error(str(e), parent=parent)
continue
else:
password = None
@@ -1140,7 +1162,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def read_send_tab(self):
if self.payment_request and self.payment_request.has_expired():
- QMessageBox.warning(self, _('Error'), _('Payment request has expired'), _('OK'))
+ self.show_error(_('Payment request has expired'))
return
label = unicode( self.message_e.text() )
@@ -1161,23 +1183,23 @@ class ElectrumWindow(QMainWindow, PrintError):
return
if not outputs:
- QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
+ self.show_error(_('No outputs'))
return
for _type, addr, amount in outputs:
if addr is None:
- QMessageBox.warning(self, _('Error'), _('Bitcoin Address is None'), _('OK'))
+ self.show_error(_('Bitcoin Address is None'))
return
if _type == 'address' and not bitcoin.is_address(addr):
- QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
+ self.show_error(_('Invalid Bitcoin Address'))
return
if amount is None:
- QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
+ self.show_error(_('Invalid Amount'))
return
fee = self.fee_e.get_amount()
if fee is None:
- QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
+ self.show_error(_('Invalid Fee'))
return
coins = self.get_coins()
@@ -1203,7 +1225,7 @@ class ElectrumWindow(QMainWindow, PrintError):
return
if tx.get_fee() < MIN_RELAY_TX_FEE and tx.requires_fee(self.wallet):
- QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
+ self.show_error(_("This transaction requires a higher fee, or it will not be propagated by the network"))
return
if self.show_before_broadcast():
@@ -1258,16 +1280,14 @@ class ElectrumWindow(QMainWindow, PrintError):
def sign_thread():
if not self.wallet.is_watching_only():
self.wallet.sign_transaction(tx, password)
- def on_sign_successful(ret):
+ def on_signed(ret):
success[0] = True
- def on_dialog_close():
+ def on_finished():
self.send_button.setDisabled(False)
callback(success[0])
- # keep a reference to WaitingDialog or the gui might crash
- self.waiting_dialog = WaitingDialog(parent, 'Signing transaction...', sign_thread, on_sign_successful, on_dialog_close)
- self.waiting_dialog.start()
-
+ WaitingDialog(parent, _('Signing transaction...'), sign_thread,
+ on_success=on_signed, on_finished=on_finished)
def broadcast_transaction(self, tx, tx_desc, parent=None):
@@ -1296,19 +1316,16 @@ class ElectrumWindow(QMainWindow, PrintError):
if status:
if tx_desc is not None and tx.is_complete():
self.wallet.set_label(tx.hash(), tx_desc)
- QMessageBox.information(parent, '', _('Payment sent.') + '\n' + msg, _('OK'))
+ self.show_message(_('Payment sent.') + '\n' + msg, parent=parent)
self.invoices_list.update()
self.do_clear()
else:
- QMessageBox.warning(parent, _('Error'), msg, _('OK'))
+ self.show_error(msg, parent=parent)
self.send_button.setDisabled(False)
- if parent == None:
- parent = self
- self.waiting_dialog = WaitingDialog(parent, 'Broadcasting transaction...', broadcast_thread, broadcast_done)
- self.waiting_dialog.start()
-
-
+ parent = parent or self
+ WaitingDialog(parent, _('Broadcasting transaction...'),
+ broadcast_thread, broadcast_done)
def prepare_for_payment_request(self):
self.tabs.setCurrentIndex(1)
@@ -1346,37 +1363,28 @@ class ElectrumWindow(QMainWindow, PrintError):
self.payment_request = None
self.do_clear()
+ def on_pr(self, request):
+ self.payment_request = request
+ if self.payment_request.verify(self.contacts):
+ self.emit(SIGNAL('payment_request_ok'))
+ else:
+ self.emit(SIGNAL('payment_request_error'))
+
def pay_to_URI(self, URI):
if not URI:
return
try:
- out = util.parse_URI(unicode(URI))
- except Exception as e:
- QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
+ out = util.parse_URI(unicode(URI), self.on_pr)
+ except BaseException as e:
+ self.show_error(_('Invalid bitcoin URI:') + '\n' + str(e))
return
self.tabs.setCurrentIndex(1)
-
r = out.get('r')
sig = out.get('sig')
name = out.get('name')
if r or (name and sig):
- def get_payment_request_thread():
- if name and sig:
- from electrum import paymentrequest
- pr = paymentrequest.serialize_request(out).SerializeToString()
- self.payment_request = paymentrequest.PaymentRequest(pr)
- else:
- self.payment_request = get_payment_request(r)
- if self.payment_request.verify(self.contacts):
- self.emit(SIGNAL('payment_request_ok'))
- else:
- self.emit(SIGNAL('payment_request_error'))
- t = threading.Thread(target=get_payment_request_thread)
- t.setDaemon(True)
- t.start()
self.prepare_for_payment_request()
return
-
address = out.get('address')
amount = out.get('amount')
label = out.get('label')
@@ -1555,6 +1563,13 @@ class ElectrumWindow(QMainWindow, PrintError):
def paytomany(self):
self.tabs.setCurrentIndex(1)
self.payto_e.paytomany()
+ msg = '\n'.join([
+ _('Enter a list of outputs in the \'Pay to\' field.'),
+ _('One output per line.'),
+ _('Format: address, amount'),
+ _('You may load a CSV file using the file icon.')
+ ])
+ self.show_warning(msg, title=_('Pay to many'))
def payto_contacts(self, labels):
paytos = [self.get_contact_payto(label) for label in labels]
@@ -1578,7 +1593,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def set_contact(self, label, address):
if not is_valid(address):
- QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
+ self.show_error(_('Invalid Address'))
self.contacts_list.update() # Displays original unchanged value
return False
self.contacts[label] = ('address', address)
@@ -1628,8 +1643,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.show_pr_details(pr)
def show_pr_details(self, pr):
- d = QDialog(self)
- d.setWindowTitle(_("Invoice"))
+ d = WindowModalDialog(self, _("Invoice"))
vbox = QVBoxLayout(d)
grid = QGridLayout()
grid.addWidget(QLabel(_("Requestor") + ':'), 0, 0)
@@ -1840,8 +1854,39 @@ class ElectrumWindow(QMainWindow, PrintError):
def change_password_dialog(self):
from password_dialog import PasswordDialog
- d = PasswordDialog(self.wallet, self)
- d.run()
+
+ if self.wallet and self.wallet.is_watching_only():
+ self.show_error(_('This is a watching-only wallet'))
+ return
+
+ msg = (_('Your wallet is encrypted. Use this dialog to change your '
+ 'password. To disable wallet encryption, enter an empty new '
+ 'password.') if self.wallet.use_encryption
+ else _('Your wallet keys are not encrypted'))
+ d = PasswordDialog(self, self.wallet, _("Set Password"), msg, True)
+ ok, password, new_password = d.run()
+ if not ok:
+ return
+
+ try:
+ self.wallet.check_password(password)
+ except BaseException as e:
+ self.show_error(str(e))
+ return
+
+ try:
+ self.wallet.update_password(password, new_password)
+ except:
+ traceback.print_exc(file=sys.stdout)
+ self.show_error(_('Failed to update password'))
+ return
+
+ if new_password:
+ msg = _('Password was updated successfully')
+ else:
+ msg = _('This wallet is not encrypted')
+ self.show_message(msg, title=_("Success"))
+
self.update_lock_icon()
def toggle_search(self):
@@ -1866,8 +1911,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def new_contact_dialog(self):
- d = QDialog(self)
- d.setWindowTitle(_("New Contact"))
+ d = WindowModalDialog(self, _("New Contact"))
vbox = QVBoxLayout(d)
vbox.addWidget(QLabel(_('New Contact') + ':'))
grid = QGridLayout()
@@ -1892,9 +1936,7 @@ class ElectrumWindow(QMainWindow, PrintError):
@protected
def new_account_dialog(self, password):
- dialog = QDialog(self)
- dialog.setModal(1)
- dialog.setWindowTitle(_("New Account"))
+ dialog = WindowModalDialog(self, _("New Account"))
vbox = QVBoxLayout()
vbox.addWidget(QLabel(_('Account name')+':'))
e = QLineEdit()
@@ -1917,11 +1959,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def show_master_public_keys(self):
-
- dialog = QDialog(self)
- dialog.setModal(1)
- dialog.setWindowTitle(_("Master Public Keys"))
-
+ dialog = WindowModalDialog(self, "Master Public Keys")
mpk_dict = self.wallet.get_master_public_keys()
vbox = QVBoxLayout()
# only show the combobox in case multiple accounts are available
@@ -1966,13 +2004,13 @@ class ElectrumWindow(QMainWindow, PrintError):
@protected
def show_seed_dialog(self, password):
if not self.wallet.has_seed():
- QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
+ self.show_message(_('This wallet has no seed'))
return
try:
mnemonic = self.wallet.get_mnemonic(password)
except BaseException as e:
- QMessageBox.warning(self, _('Error'), str(e), _('OK'))
+ self.show_error(str(e))
return
from seed_dialog import SeedDialog
d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
@@ -1980,10 +2018,10 @@ class ElectrumWindow(QMainWindow, PrintError):
- def show_qrcode(self, data, title = _("QR code")):
+ def show_qrcode(self, data, title = _("QR code"), parent=None):
if not data:
return
- d = QRDialog(data, self, title)
+ d = QRDialog(data, parent or self, title)
d.exec_()
def show_public_keys(self, address):
@@ -1995,10 +2033,8 @@ class ElectrumWindow(QMainWindow, PrintError):
self.show_message(str(e))
return
- d = QDialog(self)
+ d = WindowModalDialog(self, _("Public key"))
d.setMinimumSize(600, 200)
- d.setModal(1)
- d.setWindowTitle(_("Public key"))
vbox = QVBoxLayout()
vbox.addWidget( QLabel(_("Address") + ': ' + address))
vbox.addWidget( QLabel(_("Public key") + ':'))
@@ -2019,10 +2055,8 @@ class ElectrumWindow(QMainWindow, PrintError):
self.show_message(str(e))
return
- d = QDialog(self)
+ d = WindowModalDialog(self, _("Private key"))
d.setMinimumSize(600, 200)
- d.setModal(1)
- d.setWindowTitle(_("Private key"))
vbox = QVBoxLayout()
vbox.addWidget( QLabel(_("Address") + ': ' + address))
vbox.addWidget( QLabel(_("Private key") + ':'))
@@ -2056,9 +2090,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def sign_verify_message(self, address=''):
- d = QDialog(self)
- d.setModal(1)
- d.setWindowTitle(_('Sign/verify Message'))
+ d = WindowModalDialog(self, _('Sign/verify Message'))
d.setMinimumSize(410, 290)
layout = QGridLayout(d)
@@ -2117,9 +2149,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def encrypt_message(self, address = ''):
- d = QDialog(self)
- d.setModal(1)
- d.setWindowTitle(_('Encrypt/decrypt Message'))
+ d = WindowModalDialog(self, _('Encrypt/decrypt Message'))
d.setMinimumSize(610, 490)
layout = QGridLayout(d)
@@ -2157,22 +2187,9 @@ class ElectrumWindow(QMainWindow, PrintError):
layout.addLayout(hbox, 4, 1)
d.exec_()
-
- def question(self, msg):
- return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
-
- def show_message(self, msg):
- QMessageBox.information(self, _('Message'), msg, _('OK'))
-
- def show_warning(self, msg):
- QMessageBox.warning(self, _('Warning'), msg, _('OK'))
-
def password_dialog(self, msg=None, parent=None):
- if parent == None:
- parent = self
- d = QDialog(parent)
- d.setModal(1)
- d.setWindowTitle(_("Enter Password"))
+ parent = parent or self
+ d = WindowModalDialog(parent, _("Enter Password"))
pw = QLineEdit()
pw.setEchoMode(2)
vbox = QVBoxLayout()
@@ -2200,33 +2217,24 @@ class ElectrumWindow(QMainWindow, PrintError):
except:
is_hex = False
- if is_hex:
- try:
- return Transaction(txt)
- except:
- traceback.print_exc(file=sys.stdout)
- QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
- return
-
try:
+ if is_hex:
+ return Transaction(txt)
tx_dict = json.loads(str(txt))
assert "hex" in tx_dict.keys()
tx = Transaction(tx_dict["hex"])
- #if tx_dict.has_key("input_info"):
- # input_info = json.loads(tx_dict['input_info'])
- # tx.add_input_info(input_info)
return tx
- except Exception:
+ except:
traceback.print_exc(file=sys.stdout)
- QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
-
+ self.show_critical(_("Electrum was unable to parse your transaction"))
+ return
def read_tx_from_qrcode(self):
from electrum import qrscanner
try:
data = qrscanner.scan_qr(self.config)
- except BaseException, e:
- QMessageBox.warning(self, _('Error'), _(e), _('OK'))
+ except BaseException as e:
+ self.show_error(str(e))
return
if not data:
return
@@ -2252,8 +2260,8 @@ class ElectrumWindow(QMainWindow, PrintError):
try:
with open(fileName, "r") as f:
file_content = f.read()
- except (ValueError, IOError, os.error), reason:
- QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
+ except (ValueError, IOError, os.error) as reason:
+ self.show_critical(_("Electrum was unable to open your transaction file") + "\n" + str(reason), title=_("Unable to read file or no transaction found"))
return self.tx_from_text(file_content)
def do_process_from_text(self):
@@ -2292,11 +2300,10 @@ class ElectrumWindow(QMainWindow, PrintError):
try:
self.wallet.check_password(password)
except Exception as e:
- QMessageBox.warning(self, _('Error'), str(e), _('OK'))
+ self.show_error(str(e))
return
- d = QDialog(self)
- d.setWindowTitle(_('Private keys'))
+ d = WindowModalDialog(self, _('Private keys'))
d.setMinimumSize(850, 300)
vbox = QVBoxLayout(d)
@@ -2349,9 +2356,12 @@ class ElectrumWindow(QMainWindow, PrintError):
try:
self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
- except (IOError, os.error), reason:
- export_error_label = _("Electrum was unable to produce a private key-export.")
- QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
+ except (IOError, os.error) as reason:
+ txt = "\n".join([
+ _("Electrum was unable to produce a private key-export."),
+ str(reason)
+ ])
+ self.show_critical(txt, title=_("Unable to create csv"))
except Exception as e:
self.show_message(str(e))
@@ -2381,9 +2391,9 @@ class ElectrumWindow(QMainWindow, PrintError):
f.close()
for key, value in json.loads(data).items():
self.wallet.set_label(key, value)
- QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
- except (IOError, os.error), reason:
- QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
+ self.show_message(_("Your labels were imported from") + " '%s'" % str(labelsFile))
+ except (IOError, os.error) as reason:
+ self.show_critical(_("Electrum was unable to import your labels.") + "\n" + str(reason))
def do_export_labels(self):
@@ -2393,14 +2403,13 @@ class ElectrumWindow(QMainWindow, PrintError):
if fileName:
with open(fileName, 'w+') as f:
json.dump(labels, f)
- QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
+ self.show_message(_("Your labels where exported to") + " '%s'" % str(fileName))
except (IOError, os.error), reason:
- QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
+ self.show_critical(_("Electrum was unable to export your labels.") + "\n" + str(reason))
def export_history_dialog(self):
- d = QDialog(self)
- d.setWindowTitle(_('Export History'))
+ d = WindowModalDialog(self, _('Export History'))
d.setMinimumSize(400, 200)
vbox = QVBoxLayout(d)
defaultname = os.path.expanduser('~/electrum-history.csv')
@@ -2421,9 +2430,9 @@ class ElectrumWindow(QMainWindow, PrintError):
self.do_export_history(self.wallet, filename, csv_button.isChecked())
except (IOError, os.error), reason:
export_error_label = _("Electrum was unable to produce a transaction export.")
- QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
+ self.show_critical(export_error_label + "\n" + str(reason), title=_("Unable to export history"))
return
- QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
+ self.show_message(_("Your wallet history has been successfully exported."))
def do_export_history(self, wallet, fileName, is_csv):
@@ -2445,7 +2454,7 @@ class ElectrumWindow(QMainWindow, PrintError):
value_string = '--'
if tx_hash:
- label, is_default_label = wallet.get_label(tx_hash)
+ label = wallet.get_label(tx_hash)
label = label.encode('utf-8')
else:
label = ""
@@ -2467,12 +2476,11 @@ class ElectrumWindow(QMainWindow, PrintError):
def sweep_key_dialog(self):
- d = QDialog(self)
- d.setWindowTitle(_('Sweep private keys'))
+ d = WindowModalDialog(self, title=_('Sweep private keys'))
d.setMinimumSize(600, 300)
vbox = QVBoxLayout(d)
- vbox.addWidget(QLabel(_("Enter private keys")))
+ vbox.addWidget(QLabel(_("Enter private keys:")))
keys_e = QTextEdit()
keys_e.setTabChangesFocus(True)
@@ -2508,16 +2516,17 @@ class ElectrumWindow(QMainWindow, PrintError):
if not tx:
self.show_message(_('No inputs found. (Note that inputs need to be confirmed)'))
return
+ self.warn_if_watching_only()
self.show_transaction(tx)
@protected
def do_import_privkey(self, password):
if not self.wallet.has_imported_keys():
- r = QMessageBox.question(None, _('Warning'), ''+_('Warning') +':\n
'+ _('Imported keys are not recoverable from seed.') + ' ' \
- + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '' \
- + _('Are you sure you understand what you are doing?'), 3, 4)
- if r == 4: return
+ if not self.question(''+_('Warning') +':\n
'+ _('Imported keys are not recoverable from seed.') + ' ' \
+ + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '
' \
+ + _('Are you sure you understand what you are doing?'), title=_('Warning')):
+ return
text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
if not text: return
@@ -2536,18 +2545,16 @@ class ElectrumWindow(QMainWindow, PrintError):
else:
addrlist.append(addr)
if addrlist:
- QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
+ self.show_message(_("The following addresses were added") + ':\n' + '\n'.join(addrlist))
if badkeys:
- QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
+ self.show_critical(_("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
self.address_list.update()
self.history_list.update()
def settings_dialog(self):
self.need_restart = False
- d = QDialog(self)
- d.setWindowTitle(_('Preferences'))
- d.setModal(1)
+ d = WindowModalDialog(self, _('Preferences'))
vbox = QVBoxLayout()
tabs = QTabWidget()
gui_widgets = []
@@ -2593,21 +2600,6 @@ class ElectrumWindow(QMainWindow, PrintError):
nz.valueChanged.connect(on_nz)
gui_widgets.append((nz_label, nz))
- choosers = sorted(COIN_CHOOSERS.keys())
- chooser_name = self.wallet.coin_chooser_name(self.config)
- msg = _('Choose coin (UTXO) selection method. The following are available:\n\n')
- msg += '\n\n'.join(key + ": " + klass.__doc__
- for key, klass in COIN_CHOOSERS.items())
- chooser_label = HelpLabel(_('Coin selection') + ':', msg)
- chooser_combo = QComboBox()
- chooser_combo.addItems(choosers)
- chooser_combo.setCurrentIndex(choosers.index(chooser_name))
- def on_chooser(x):
- chooser_name = choosers[chooser_combo.currentIndex()]
- self.config.set_key('coin_chooser', chooser_name)
- chooser_combo.currentIndexChanged.connect(on_chooser)
- tx_widgets.append((chooser_label, chooser_combo))
-
msg = _('Fee per kilobyte of transaction.') + '\n' \
+ _('If you enable dynamic fees, this parameter will be used as upper bound.')
fee_label = HelpLabel(_('Transaction fee per kb') + ':', msg)
@@ -2784,6 +2776,24 @@ class ElectrumWindow(QMainWindow, PrintError):
can_edit_fees_cb.setToolTip(_('This option lets you edit fees in the send tab.'))
tx_widgets.append((can_edit_fees_cb, None))
+ def fmt_docs(key, klass):
+ lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")]
+ return '\n'.join([key, "", " ".join(lines)])
+
+ choosers = sorted(COIN_CHOOSERS.keys())
+ chooser_name = self.wallet.coin_chooser_name(self.config)
+ msg = _('Choose coin (UTXO) selection method. The following are available:\n\n')
+ msg += '\n\n'.join(fmt_docs(*item) for item in COIN_CHOOSERS.items())
+ chooser_label = HelpLabel(_('Coin selection') + ':', msg)
+ chooser_combo = QComboBox()
+ chooser_combo.addItems(choosers)
+ chooser_combo.setCurrentIndex(choosers.index(chooser_name))
+ def on_chooser(x):
+ chooser_name = choosers[chooser_combo.currentIndex()]
+ self.config.set_key('coin_chooser', chooser_name)
+ chooser_combo.currentIndexChanged.connect(on_chooser)
+ tx_widgets.append((chooser_label, chooser_combo))
+
tabs_info = [
(tx_widgets, _('Transactions')),
(gui_widgets, _('Appearance')),
@@ -2813,13 +2823,11 @@ class ElectrumWindow(QMainWindow, PrintError):
run_hook('close_settings_dialog')
if self.need_restart:
- QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
-
-
+ self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success'))
def run_network_dialog(self):
if not self.network:
- QMessageBox.warning(self, _('Offline'), _('You are using Electrum in offline mode.\nRestart Electrum if you want to get connected.'), _('OK'))
+ self.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
return
NetworkDialog(self.wallet.network, self.config, self).do_exec()
@@ -2839,9 +2847,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def plugins_dialog(self):
- self.pluginsdialog = d = QDialog(self)
- d.setWindowTitle(_('Electrum Plugins'))
- d.setModal(1)
+ self.pluginsdialog = d = WindowModalDialog(self, _('Electrum Plugins'))
plugins = self.gui_object.plugins
@@ -2867,7 +2873,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def enable_settings_widget(p, name, i):
widget = settings_widgets.get(name)
if not widget and p and p.requires_settings():
- widget = settings_widgets[name] = p.settings_widget(self)
+ widget = settings_widgets[name] = p.settings_widget(d)
grid.addWidget(widget, i, 1)
if widget:
widget.setEnabled(bool(p and p.is_enabled()))
@@ -2904,9 +2910,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def show_account_details(self, k):
account = self.wallet.accounts[k]
- d = QDialog(self)
- d.setWindowTitle(_('Account Details'))
- d.setModal(1)
+ d = WindowModalDialog(self, _('Account Details'))
vbox = QVBoxLayout(d)
name = self.wallet.get_account_name(k)
diff --git a/gui/qt/network_dialog.py b/gui/qt/network_dialog.py
index 0eb670216..3ac3464e7 100644
--- a/gui/qt/network_dialog.py
+++ b/gui/qt/network_dialog.py
@@ -16,30 +16,21 @@
# 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
-import os.path, json, ast, traceback
-
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from electrum.i18n import _
-from electrum.util import print_error, print_msg
from electrum import DEFAULT_PORTS
from electrum.network import serialize_server, deserialize_server
from util import *
-#protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
-#protocol_letters = 'thsg'
protocol_names = ['TCP', 'SSL']
protocol_letters = 'ts'
-class NetworkDialog(QDialog):
+class NetworkDialog(WindowModalDialog):
def __init__(self, network, config, parent):
-
- QDialog.__init__(self,parent)
- self.setModal(1)
- self.setWindowTitle(_('Network'))
+ WindowModalDialog.__init__(self, parent, _('Network'))
self.setMinimumSize(375, 20)
self.network = network
diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
index e287607f7..6f0b975bc 100644
--- a/gui/qt/password_dialog.py
+++ b/gui/qt/password_dialog.py
@@ -23,84 +23,6 @@ from util import *
import re
import math
-
-
-def make_password_dialog(self, wallet, msg, new_pass=True):
-
- self.pw = QLineEdit()
- self.pw.setEchoMode(2)
- self.new_pw = QLineEdit()
- self.new_pw.setEchoMode(2)
- self.conf_pw = QLineEdit()
- self.conf_pw.setEchoMode(2)
-
- vbox = QVBoxLayout()
- label = QLabel(msg)
- label.setWordWrap(True)
-
- grid = QGridLayout()
- grid.setSpacing(8)
- grid.setColumnMinimumWidth(0, 70)
- grid.setColumnStretch(1,1)
-
- logo = QLabel()
- lockfile = ":icons/lock.png" if wallet and wallet.use_encryption else ":icons/unlock.png"
- logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))
- logo.setAlignment(Qt.AlignCenter)
-
- grid.addWidget(logo, 0, 0)
- grid.addWidget(label, 0, 1, 1, 2)
- vbox.addLayout(grid)
-
- grid = QGridLayout()
- grid.setSpacing(8)
- grid.setColumnMinimumWidth(0, 250)
- grid.setColumnStretch(1,1)
-
- if wallet and wallet.use_encryption:
- grid.addWidget(QLabel(_('Password')), 0, 0)
- grid.addWidget(self.pw, 0, 1)
-
- grid.addWidget(QLabel(_('New Password') if new_pass else _('Password')), 1, 0)
- grid.addWidget(self.new_pw, 1, 1)
-
- grid.addWidget(QLabel(_('Confirm Password')), 2, 0)
- grid.addWidget(self.conf_pw, 2, 1)
- vbox.addLayout(grid)
-
- #Password Strength Label
- self.pw_strength = QLabel()
- grid.addWidget(self.pw_strength, 3, 0, 1, 2)
- self.new_pw.textChanged.connect(lambda: update_password_strength(self.pw_strength, self.new_pw.text()))
-
- vbox.addStretch(1)
- vbox.addLayout(Buttons(CancelButton(self), OkButton(self)))
- return vbox
-
-
-def run_password_dialog(self, wallet, parent):
-
- if wallet and wallet.is_watching_only():
- QMessageBox.information(parent, _('Error'), _('This is a watching-only wallet'), _('OK'))
- return False, None, None
-
- if not self.exec_():
- return False, None, None
-
- password = unicode(self.pw.text()) if wallet and wallet.use_encryption else None
- new_password = unicode(self.new_pw.text())
- new_password2 = unicode(self.conf_pw.text())
-
- if new_password != new_password2:
- QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
- # Retry
- return run_password_dialog(self, wallet, parent)
-
- if not new_password:
- new_password = None
-
- return True, password, new_password
-
def check_password_strength(password):
'''
@@ -117,59 +39,89 @@ def check_password_strength(password):
password_strength = {0:"Weak",1:"Medium",2:"Strong",3:"Very Strong"}
return password_strength[min(3, int(score))]
+class PasswordDialog(WindowModalDialog):
-def update_password_strength(pw_strength_label,password):
-
- '''
- call the function check_password_strength and update the label pw_strength interactively as the user is typing the password
- :param pw_strength_label: the label pw_strength
- :param password: password entered in New Password text box
- :return: None
- '''
- if password:
- colors = {"Weak":"Red","Medium":"Blue","Strong":"Green", "Very Strong":"Green"}
- strength = check_password_strength(password)
- label = _("Password Strength")+ ": "+"" + strength + ""
- else:
- label = ""
- pw_strength_label.setText(label)
-
-
-
-class PasswordDialog(QDialog):
-
- def __init__(self, wallet, parent):
- QDialog.__init__(self, parent)
- self.setModal(1)
+ def __init__(self, parent, wallet, title, msg, new_pass):
+ WindowModalDialog.__init__(self, parent, title)
self.wallet = wallet
- self.parent = parent
- self.setWindowTitle(_("Set Password"))
- msg = (_('Your wallet is encrypted. Use this dialog to change your password.') + ' '\
- +_('To disable wallet encryption, enter an empty new password.')) \
- if wallet.use_encryption else _('Your wallet keys are not encrypted')
- self.setLayout(make_password_dialog(self, wallet, msg))
+ self.pw = QLineEdit()
+ self.pw.setEchoMode(2)
+ self.new_pw = QLineEdit()
+ self.new_pw.setEchoMode(2)
+ self.conf_pw = QLineEdit()
+ self.conf_pw.setEchoMode(2)
+
+ vbox = QVBoxLayout()
+ label = QLabel(msg)
+ label.setWordWrap(True)
+
+ grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.setColumnMinimumWidth(0, 70)
+ grid.setColumnStretch(1,1)
+
+ logo = QLabel()
+ logo.setAlignment(Qt.AlignCenter)
+
+ grid.addWidget(logo, 0, 0)
+ grid.addWidget(label, 0, 1, 1, 2)
+ vbox.addLayout(grid)
+
+ grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.setColumnMinimumWidth(0, 250)
+ grid.setColumnStretch(1,1)
+
+ if wallet and wallet.use_encryption:
+ grid.addWidget(QLabel(_('Password')), 0, 0)
+ grid.addWidget(self.pw, 0, 1)
+ lockfile = ":icons/lock.png"
+ else:
+ self.pw = None
+ lockfile = ":icons/unlock.png"
+ logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))
+
+ grid.addWidget(QLabel(_('New Password') if new_pass else _('Password')), 1, 0)
+ grid.addWidget(self.new_pw, 1, 1)
+
+ grid.addWidget(QLabel(_('Confirm Password')), 2, 0)
+ grid.addWidget(self.conf_pw, 2, 1)
+ vbox.addLayout(grid)
+
+ # Password Strength Label
+ self.pw_strength = QLabel()
+ grid.addWidget(self.pw_strength, 3, 0, 1, 2)
+ self.new_pw.textChanged.connect(self.pw_changed)
+ self.conf_pw.textChanged.connect(self.check_OKButton)
+
+ self.OKButton = OkButton(self)
+ vbox.addStretch(1)
+ vbox.addLayout(Buttons(CancelButton(self), self.OKButton))
+ self.setLayout(vbox)
+
+ def pw_changed(self):
+ password = self.new_pw.text()
+ if password:
+ colors = {"Weak":"Red", "Medium":"Blue", "Strong":"Green",
+ "Very Strong":"Green"}
+ strength = check_password_strength(password)
+ label = (_("Password Strength") + ": " + "" + strength + "")
+ else:
+ label = ""
+ self.pw_strength.setText(label)
+ self.check_OKButton()
+
+ def check_OKButton(self):
+ self.OKButton.setEnabled(self.new_pw.text() == self.conf_pw.text())
def run(self):
- ok, password, new_password = run_password_dialog(self, self.wallet, self.parent)
- if not ok:
- return
-
- try:
- self.wallet.check_password(password)
- except BaseException as e:
- QMessageBox.warning(self.parent, _('Error'), str(e), _('OK'))
+ if not self.exec_():
return False, None, None
- try:
- self.wallet.update_password(password, new_password)
- except:
- import traceback, sys
- traceback.print_exc(file=sys.stdout)
- QMessageBox.warning(self.parent, _('Error'), _('Failed to update password'), _('OK'))
- return
+ password = unicode(self.pw.text()) if self.pw else None
+ new_password = unicode(self.new_pw.text())
+ new_password2 = unicode(self.conf_pw.text())
- if new_password:
- QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK'))
- else:
- QMessageBox.information(self.parent, _('Success'), _('This wallet is not encrypted'), _('OK'))
+ return True, password or None, new_password or None
diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py
index c1da6e0c1..0c654ea38 100644
--- a/gui/qt/paytoedit.py
+++ b/gui/qt/paytoedit.py
@@ -160,16 +160,8 @@ class PayToEdit(ScanQRTextEdit):
return len(self.lines()) > 1
def paytomany(self):
- from electrum.i18n import _
self.setText("\n\n\n")
self.update_size()
- msg = '\n'.join([
- _('Enter a list of outputs in the \'Pay to\' field.'),
- _('One output per line.'),
- _('Format: address, amount.'),
- _('You may load a CSV file using the file icon.')
- ])
- QMessageBox.warning(self, _('Pay to many'), msg, _('OK'))
def update_size(self):
docHeight = self.document().size().height()
diff --git a/gui/qt/qrcodewidget.py b/gui/qt/qrcodewidget.py
index ab1008de4..51a028470 100644
--- a/gui/qt/qrcodewidget.py
+++ b/gui/qt/qrcodewidget.py
@@ -1,6 +1,5 @@
from PyQt4.QtGui import *
from PyQt4.QtCore import *
-import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
import os
@@ -9,6 +8,7 @@ import qrcode
import electrum
from electrum import bmp
from electrum.i18n import _
+from util import WindowModalDialog, MessageBoxMixin
class QRCodeWidget(QWidget):
@@ -83,13 +83,11 @@ class QRCodeWidget(QWidget):
-class QRDialog(QDialog):
+class QRDialog(WindowModalDialog, MessageBoxMixin):
def __init__(self, data, parent=None, title = "", show_text=False):
- QDialog.__init__(self, parent)
+ WindowModalDialog.__init__(self, parent, title)
- d = self
- d.setWindowTitle(title)
vbox = QVBoxLayout()
qrw = QRCodeWidget(data)
vbox.addWidget(qrw, 1)
@@ -107,12 +105,12 @@ class QRDialog(QDialog):
def print_qr():
bmp.save_qrcode(qrw.qr, filename)
- QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
+ self.show_message(_("QR code saved to file") + " " + filename)
def copy_to_clipboard():
bmp.save_qrcode(qrw.qr, filename)
QApplication.clipboard().setImage(QImage(filename))
- QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
+ self.show_message(_("QR code copied to clipboard"))
b = QPushButton(_("Copy"))
hbox.addWidget(b)
@@ -124,8 +122,8 @@ class QRDialog(QDialog):
b = QPushButton(_("Close"))
hbox.addWidget(b)
- b.clicked.connect(d.accept)
+ b.clicked.connect(self.accept)
b.setDefault(True)
vbox.addLayout(hbox)
- d.setLayout(vbox)
+ self.setLayout(vbox)
diff --git a/gui/qt/qrtextedit.py b/gui/qt/qrtextedit.py
index 0d23e27a5..c46191926 100644
--- a/gui/qt/qrtextedit.py
+++ b/gui/qt/qrtextedit.py
@@ -3,7 +3,7 @@ from electrum.plugins import run_hook
from PyQt4.QtGui import *
from PyQt4.QtCore import *
-from util import ButtonsTextEdit
+from util import ButtonsTextEdit, MessageBoxMixin
class ShowQRTextEdit(ButtonsTextEdit):
@@ -29,7 +29,7 @@ class ShowQRTextEdit(ButtonsTextEdit):
m.exec_(e.globalPos())
-class ScanQRTextEdit(ButtonsTextEdit):
+class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
def __init__(self, text=""):
ButtonsTextEdit.__init__(self, text)
@@ -50,8 +50,8 @@ class ScanQRTextEdit(ButtonsTextEdit):
from electrum import qrscanner, get_config
try:
data = qrscanner.scan_qr(get_config())
- except BaseException, e:
- QMessageBox.warning(self, _('Error'), _(e), _('OK'))
+ except BaseException as e:
+ self.show_error(str(e))
return ""
if type(data) != str:
return
diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py
index 9b788884d..688361135 100644
--- a/gui/qt/seed_dialog.py
+++ b/gui/qt/seed_dialog.py
@@ -20,17 +20,14 @@ from PyQt4.QtGui import *
from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
from electrum.i18n import _
-from electrum import mnemonic
from util import *
from qrtextedit import ShowQRTextEdit, ScanQRTextEdit
-class SeedDialog(QDialog):
+class SeedDialog(WindowModalDialog):
def __init__(self, parent, seed, imported_keys):
- QDialog.__init__(self, parent)
- self.setModal(1)
+ WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
self.setMinimumWidth(400)
- self.setWindowTitle('Electrum' + ' - ' + _('Seed'))
vbox = show_seed_box_msg(seed)
if imported_keys:
vbox.addWidget(QLabel(""+_("WARNING")+": " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "
"))
diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py
index a69ecfac6..a49dc1e8f 100644
--- a/gui/qt/transaction_dialog.py
+++ b/gui/qt/transaction_dialog.py
@@ -38,7 +38,7 @@ def show_transaction(tx, parent, desc=None, prompt_if_unsaved=False):
dialogs.append(d)
d.show()
-class TxDialog(QDialog):
+class TxDialog(QDialog, MessageBoxMixin):
def __init__(self, tx, parent, desc, prompt_if_unsaved):
'''Transactions in the wallet will show their description.
@@ -54,7 +54,7 @@ class TxDialog(QDialog):
self.desc = desc
QDialog.__init__(self)
- self.setMinimumWidth(600)
+ self.setMinimumWidth(660)
self.setWindowTitle(_("Transaction"))
vbox = QVBoxLayout()
@@ -62,7 +62,7 @@ class TxDialog(QDialog):
vbox.addWidget(QLabel(_("Transaction ID:")))
self.tx_hash_e = ButtonsLineEdit()
- qr_show = lambda: self.parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID')
+ qr_show = lambda: self.parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)
self.tx_hash_e.addButton(":icons/qrcode.png", qr_show, _("Show as QR code"))
self.tx_hash_e.setReadOnly(True)
vbox.addWidget(self.tx_hash_e)
@@ -122,10 +122,7 @@ class TxDialog(QDialog):
def closeEvent(self, event):
if (self.prompt_if_unsaved and not self.saved and not self.broadcast
- and QMessageBox.question(
- self, _('Warning'),
- _('This transaction is not saved. Close anyway?'),
- QMessageBox.Yes | QMessageBox.No) == QMessageBox.No):
+ and not self.question(_('This transaction is not saved. Close anyway?'), title=_("Warning"))):
event.ignore()
else:
event.accept()
@@ -135,7 +132,7 @@ class TxDialog(QDialog):
text = self.tx.raw.decode('hex')
text = base_encode(text, base=43)
try:
- self.parent.show_qrcode(text, 'Transaction')
+ self.parent.show_qrcode(text, 'Transaction', parent=self)
except Exception as e:
self.show_message(str(e))
@@ -173,7 +170,7 @@ class TxDialog(QDialog):
status = _("Signed")
if tx_hash in self.wallet.transactions.keys():
- desc, is_default = self.wallet.get_label(tx_hash)
+ desc = self.wallet.get_label(tx_hash)
conf, timestamp = self.wallet.get_confirmations(tx_hash)
if timestamp:
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
@@ -249,6 +246,9 @@ class TxDialog(QDialog):
return chg if self.wallet.is_change(addr) else rec
return ext
+ def format_amount(amt):
+ return self.parent.format_amount(amt, whitespaces = True)
+
i_text = QTextEdit()
i_text.setFont(QFont(MONOSPACE_FONT))
i_text.setReadOnly(True)
@@ -270,6 +270,8 @@ class TxDialog(QDialog):
if addr is None:
addr = _('unknown')
cursor.insertText(addr, text_format(addr))
+ if x.get('value'):
+ cursor.insertText(format_amount(x['value']), ext)
cursor.insertBlock()
vbox.addWidget(i_text)
@@ -283,11 +285,6 @@ class TxDialog(QDialog):
cursor.insertText(addr, text_format(addr))
if v is not None:
cursor.insertText('\t', ext)
- cursor.insertText(self.parent.format_amount(v, whitespaces = True), ext)
+ cursor.insertText(format_amount(v), ext)
cursor.insertBlock()
vbox.addWidget(o_text)
-
-
-
- def show_message(self, msg):
- QMessageBox.information(self, _('Message'), msg, _('OK'))
diff --git a/gui/qt/util.py b/gui/qt/util.py
index 2217a7922..702e69f1c 100644
--- a/gui/qt/util.py
+++ b/gui/qt/util.py
@@ -21,51 +21,19 @@ RED_FG = "QWidget {color:red;}"
BLUE_FG = "QWidget {color:blue;}"
BLACK_FG = "QWidget {color:black;}"
-
-class WaitingDialog(QThread):
- def __init__(self, parent, message, run_task, on_success=None, on_complete=None):
- QThread.__init__(self)
- self.parent = parent
- self.d = QDialog(parent)
- self.d.setWindowTitle('Please wait')
- l = QLabel(message)
- vbox = QVBoxLayout(self.d)
- vbox.addWidget(l)
- self.run_task = run_task
- self.on_success = on_success
- self.on_complete = on_complete
- self.d.connect(self.d, SIGNAL('done'), self.close)
- self.d.show()
-
- def run(self):
- self.error = None
- try:
- self.result = self.run_task()
- except BaseException as e:
- traceback.print_exc(file=sys.stdout)
- self.error = str(e)
- self.d.emit(SIGNAL('done'))
-
- def close(self):
- self.d.accept()
- if self.error:
- QMessageBox.warning(self.parent, _('Error'), self.error, _('OK'))
- else:
- if self.on_success:
- if type(self.result) is not tuple:
- self.result = (self.result,)
- self.on_success(*self.result)
-
- if self.on_complete:
- self.on_complete()
-
+dialogs = []
class Timer(QThread):
+ stopped = False
+
def run(self):
- while True:
+ while not self.stopped:
self.emit(SIGNAL('timersignal'))
time.sleep(0.5)
+ def stop(self):
+ self.stopped = True
+ self.wait()
class EnterButton(QPushButton):
def __init__(self, text, func):
@@ -187,12 +155,91 @@ class CancelButton(QPushButton):
QPushButton.__init__(self, label or _("Cancel"))
self.clicked.connect(dialog.reject)
+class MessageBoxMixin:
+ def question(self, msg, parent=None, title=None):
+ Yes, No = QMessageBox.Yes, QMessageBox.No
+ return self.msg_box(QMessageBox.Question, parent or self, title or '',
+ msg, buttons=Yes|No, defaultButton=No) == Yes
+
+ def show_warning(self, msg, parent=None, title=None):
+ return self.msg_box(QMessageBox.Warning, parent or self,
+ title or _('Warning'), msg)
+
+ def show_error(self, msg, parent=None):
+ return self.msg_box(QMessageBox.Warning, parent or self,
+ _('Error'), msg)
+
+ def show_critical(self, msg, parent=None, title=None):
+ return self.msg_box(QMessageBox.Critical, parent or self,
+ title or _('Critical Error'), msg)
+
+ def show_message(self, msg, parent=None, title=None):
+ return self.msg_box(QMessageBox.Information, parent or self,
+ title or _('Information'), msg)
+
+ @staticmethod
+ def msg_box(icon, parent, title, text, buttons=QMessageBox.Ok,
+ defaultButton=QMessageBox.NoButton):
+ # handle e.g. ElectrumGui
+ if not isinstance(parent, QWidget):
+ parent = None
+ d = QMessageBox(icon, title, text, buttons, parent)
+ d.setWindowModality(Qt.WindowModal)
+ d.setDefaultButton(defaultButton)
+ return d.exec_()
+
+class WindowModalDialog(QDialog):
+ '''Handy wrapper; window modal dialogs are better for our multi-window
+ daemon model as other wallet windows can still be accessed.'''
+ def __init__(self, parent, title=None):
+ QDialog.__init__(self, parent)
+ self.setWindowModality(Qt.WindowModal)
+ if title:
+ self.setWindowTitle(title)
+
+class WaitingDialog(QThread, MessageBoxMixin):
+ '''Shows a please wait dialog whilst runnning a task. It is not
+ necessary to maintain a reference to this dialog.'''
+ def __init__(self, parent, message, task, on_success=None,
+ on_finished=None):
+ global dialogs
+ dialogs.append(self) # Prevent GC
+ QThread.__init__(self)
+ self.task = task
+ self.on_success = on_success
+ self.on_finished = on_finished
+ self.dialog = WindowModalDialog(parent, _("Please wait"))
+ vbox = QVBoxLayout(self.dialog)
+ vbox.addWidget(QLabel(message))
+ self.dialog.show()
+ self.dialog.connect(self, SIGNAL("finished()"), self.finished)
+ self.start()
+
+ def run(self):
+ self.error = None
+ try:
+ self.result = self.task()
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ self.error = str(e)
+
+ def finished(self):
+ global dialogs
+ dialogs.remove(self)
+ if self.error:
+ self.show_error(self.error, parent=self.dialog.parent())
+ elif self.on_success:
+ result = self.result
+ if type(result) is not tuple:
+ result = (result,)
+ self.on_success(*result)
+ if self.on_finished:
+ self.on_finished()
+ self.dialog.accept()
def line_dialog(parent, title, label, ok_label, default=None):
- dialog = QDialog(parent)
+ dialog = WindowModalDialog(parent, title)
dialog.setMinimumWidth(500)
- dialog.setWindowTitle(title)
- dialog.setModal(1)
l = QVBoxLayout()
dialog.setLayout(l)
l.addWidget(QLabel(label))
@@ -206,10 +253,8 @@ def line_dialog(parent, title, label, ok_label, default=None):
def text_dialog(parent, title, label, ok_label, default=None):
from qrtextedit import ScanQRTextEdit
- dialog = QDialog(parent)
+ dialog = WindowModalDialog(parent, title)
dialog.setMinimumWidth(500)
- dialog.setWindowTitle(title)
- dialog.setModal(1)
l = QVBoxLayout()
dialog.setLayout(l)
l.addWidget(QLabel(label))
@@ -221,9 +266,6 @@ def text_dialog(parent, title, label, ok_label, default=None):
if dialog.exec_():
return unicode(txt.toPlainText())
-def question(msg):
- return QMessageBox.question(None, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
-
def address_field(addresses):
hbox = QHBoxLayout()
address_e = QLineEdit()
@@ -383,12 +425,6 @@ class MyTreeWidget(QTreeWidget):
key = str(item.data(0, Qt.UserRole).toString())
text = unicode(item.text(column))
self.parent.wallet.set_label(key, text)
- if text:
- item.setForeground(column, QBrush(QColor('black')))
- else:
- text = self.parent.wallet.get_default_label(key)
- item.setText(column, text)
- item.setForeground(column, QBrush(QColor('gray')))
self.parent.history_list.update()
self.parent.update_completions()
diff --git a/gui/stdio.py b/gui/stdio.py
index f6b9daa29..52ad67989 100644
--- a/gui/stdio.py
+++ b/gui/stdio.py
@@ -96,7 +96,7 @@ class ElectrumGui:
else:
time_str = 'pending'
- label, is_default_label = self.wallet.get_label(tx_hash)
+ label = self.wallet.get_label(tx_hash)
messages.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance")))
diff --git a/gui/text.py b/gui/text.py
index 1ddc35bcc..250fea16f 100644
--- a/gui/text.py
+++ b/gui/text.py
@@ -116,7 +116,7 @@ class ElectrumGui:
else:
time_str = 'pending'
- label, is_default_label = self.wallet.get_label(tx_hash)
+ label = self.wallet.get_label(tx_hash)
if len(label) > 40:
label = label[0:37] + '...'
self.history.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
diff --git a/lib/blockchain.py b/lib/blockchain.py
index 89fda7ed5..2f6a090e8 100644
--- a/lib/blockchain.py
+++ b/lib/blockchain.py
@@ -21,13 +21,14 @@ import os
import util
from bitcoin import *
+MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
class Blockchain(util.PrintError):
'''Manages blockchain headers and their verification'''
def __init__(self, config, network):
self.config = config
self.network = network
- self.headers_url = 'https://headers.electrum.org/blockchain_headers'
+ self.headers_url = "https://headers.electrum.org/blockchain_headers"
self.local_height = 0
self.set_local_height()
@@ -39,76 +40,44 @@ class Blockchain(util.PrintError):
self.set_local_height()
self.print_error("%d blocks" % self.local_height)
+ def verify_header(self, header, prev_header, bits, target):
+ prev_hash = self.hash_header(prev_header)
+ assert prev_hash == header.get('prev_block_hash'), "prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash'))
+ assert bits == header.get('bits'), "bits mismatch: %s vs %s" % (bits, header.get('bits'))
+ _hash = self.hash_header(header)
+ assert int('0x' + _hash, 16) <= target, "insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target)
+
def verify_chain(self, chain):
first_header = chain[0]
- prev_header = self.read_header(first_header.get('block_height') -1)
-
+ prev_header = self.read_header(first_header.get('block_height') - 1)
for header in chain:
height = header.get('block_height')
- prev_hash = self.hash_header(prev_header)
- if prev_hash != header.get('prev_block_hash'):
- self.print_error("prev hash mismatch: %s vs %s"
- % (prev_hash, header.get('prev_block_hash')))
- return False
- bits, target = self.get_target(height/2016, chain)
- if bits != header.get('bits'):
- self.print_error("bits mismatch: %s vs %s"
- % (bits, header.get('bits')))
- return False
- _hash = self.hash_header(header)
- if int('0x'+_hash, 16) > target:
- self.print_error("insufficient proof of work: %s vs target %s"
- % (int('0x'+_hash, 16), target))
- return False
-
+ bits, target = self.get_target(height / 2016, chain)
+ self.verify_header(header, prev_header, bits, target)
prev_header = header
- return True
-
-
-
- def verify_chunk(self, index, hexdata):
- data = hexdata.decode('hex')
- height = index*2016
- num = len(data)/80
-
- if index == 0:
- previous_hash = ("0"*64)
- else:
- prev_header = self.read_header(index*2016-1)
- if prev_header is None: raise
- previous_hash = self.hash_header(prev_header)
-
+ def verify_chunk(self, index, data):
+ num = len(data) / 80
+ prev_header = None
+ if index != 0:
+ prev_header = self.read_header(index*2016 - 1)
bits, target = self.get_target(index)
-
for i in range(num):
- height = index*2016 + i
- raw_header = data[i*80:(i+1)*80]
- header = self.header_from_string(raw_header)
- _hash = self.hash_header(header)
- assert previous_hash == header.get('prev_block_hash')
- assert bits == header.get('bits')
- assert int('0x'+_hash,16) < target
-
- previous_header = header
- previous_hash = _hash
-
- self.save_chunk(index, data)
- self.print_error("validated chunk %d to height %d" % (index, height))
-
-
+ raw_header = data[i*80:(i+1) * 80]
+ header = self.deserialize_header(raw_header)
+ self.verify_header(header, prev_header, bits, target)
+ prev_header = header
- def header_to_string(self, res):
- s = int_to_hex(res.get('version'),4) \
+ def serialize_header(self, res):
+ s = int_to_hex(res.get('version'), 4) \
+ rev_hex(res.get('prev_block_hash')) \
+ rev_hex(res.get('merkle_root')) \
- + int_to_hex(int(res.get('timestamp')),4) \
- + int_to_hex(int(res.get('bits')),4) \
- + int_to_hex(int(res.get('nonce')),4)
+ + int_to_hex(int(res.get('timestamp')), 4) \
+ + int_to_hex(int(res.get('bits')), 4) \
+ + int_to_hex(int(res.get('nonce')), 4)
return s
-
- def header_from_string(self, s):
+ def deserialize_header(self, s):
hex_to_int = lambda s: int('0x' + s[::-1].encode('hex'), 16)
h = {}
h['version'] = hex_to_int(s[0:4])
@@ -120,7 +89,9 @@ class Blockchain(util.PrintError):
return h
def hash_header(self, header):
- return rev_hex(Hash(self.header_to_string(header).decode('hex')).encode('hex'))
+ if header is None:
+ return '0' * 64
+ return hash_encode(Hash(self.serialize_header(header).decode('hex')))
def path(self):
return os.path.join(self.config.path, 'blockchain_headers')
@@ -132,28 +103,28 @@ class Blockchain(util.PrintError):
try:
import urllib, socket
socket.setdefaulttimeout(30)
- self.print_error("downloading ", self.headers_url )
+ self.print_error("downloading ", self.headers_url)
urllib.urlretrieve(self.headers_url, filename)
self.print_error("done.")
except Exception:
- self.print_error( "download failed. creating file", filename )
- open(filename,'wb+').close()
+ self.print_error("download failed. creating file", filename)
+ open(filename, 'wb+').close()
def save_chunk(self, index, chunk):
filename = self.path()
- f = open(filename,'rb+')
- f.seek(index*2016*80)
+ f = open(filename, 'rb+')
+ f.seek(index * 2016 * 80)
h = f.write(chunk)
f.close()
self.set_local_height()
def save_header(self, header):
- data = self.header_to_string(header).decode('hex')
+ data = self.serialize_header(header).decode('hex')
assert len(data) == 80
height = header.get('block_height')
filename = self.path()
- f = open(filename,'rb+')
- f.seek(height*80)
+ f = open(filename, 'rb+')
+ f.seek(height * 80)
h = f.write(data)
f.close()
self.set_local_height()
@@ -168,58 +139,47 @@ class Blockchain(util.PrintError):
def read_header(self, block_height):
name = self.path()
if os.path.exists(name):
- f = open(name,'rb')
- f.seek(block_height*80)
+ f = open(name, 'rb')
+ f.seek(block_height * 80)
h = f.read(80)
f.close()
if len(h) == 80:
- h = self.header_from_string(h)
+ h = self.deserialize_header(h)
return h
def get_target(self, index, chain=None):
- if chain is None:
- chain = [] # Do not use mutables as default values!
-
- max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
- if index == 0: return 0x1d00ffff, max_target
-
- first = self.read_header((index-1)*2016)
- last = self.read_header(index*2016-1)
+ if index == 0:
+ return 0x1d00ffff, MAX_TARGET
+ first = self.read_header((index-1) * 2016)
+ last = self.read_header(index*2016 - 1)
if last is None:
for h in chain:
- if h.get('block_height') == index*2016-1:
+ if h.get('block_height') == index*2016 - 1:
last = h
-
- nActualTimespan = last.get('timestamp') - first.get('timestamp')
- nTargetTimespan = 14*24*60*60
- nActualTimespan = max(nActualTimespan, nTargetTimespan/4)
- nActualTimespan = min(nActualTimespan, nTargetTimespan*4)
-
+ assert last is not None
+ # bits to target
bits = last.get('bits')
- # convert to bignum
- MM = 256*256*256
- a = bits%MM
- if a < 0x8000:
- a *= 256
- target = (a) * pow(2, 8 * (bits/MM - 3))
-
+ bitsN = (bits >> 24) & 0xff
+ assert bitsN >= 0x03 and bitsN <= 0x1d, "First part of bits should be in [0x03, 0x1d]"
+ bitsBase = bits & 0xffffff
+ assert bitsBase >= 0x8000 and bitsBase <= 0x7fffff, "Second part of bits should be in [0x8000, 0x7fffff]"
+ target = bitsBase << (8 * (bitsN-3))
# new target
- new_target = min( max_target, (target * nActualTimespan)/nTargetTimespan )
-
- # convert it to bits
- c = ("%064X"%new_target)[2:]
- i = 31
- while c[0:2]=="00":
+ nActualTimespan = last.get('timestamp') - first.get('timestamp')
+ nTargetTimespan = 14 * 24 * 60 * 60
+ nActualTimespan = max(nActualTimespan, nTargetTimespan / 4)
+ nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
+ new_target = min(MAX_TARGET, (target*nActualTimespan) / nTargetTimespan)
+ # convert new target to bits
+ c = ("%064x" % new_target)[2:]
+ while c[:2] == '00' and len(c) > 6:
c = c[2:]
- i -= 1
-
- c = int('0x'+c[0:6],16)
- if c >= 0x800000:
- c /= 256
- i += 1
-
- new_bits = c + MM * i
- return new_bits, new_target
+ bitsN, bitsBase = len(c) / 2, int('0x' + c[:6], 16)
+ if bitsBase >= 0x800000:
+ bitsN += 1
+ bitsBase >>= 8
+ new_bits = bitsN << 24 | bitsBase
+ return new_bits, bitsBase << (8 * (bitsN-3))
def connect_header(self, chain, header):
'''Builds a header chain until it connects. Returns True if it has
@@ -241,18 +201,23 @@ class Blockchain(util.PrintError):
# The chain is complete. Reverse to order by increasing height
chain.reverse()
- if self.verify_chain(chain):
+ try:
+ self.verify_chain(chain)
self.print_error("connected at height:", previous_height)
for header in chain:
self.save_header(header)
return True
+ except BaseException as e:
+ self.print_error(str(e))
+ return False
- return False
-
- def connect_chunk(self, idx, chunk):
+ def connect_chunk(self, idx, hexdata):
try:
- self.verify_chunk(idx, chunk)
+ data = hexdata.decode('hex')
+ self.verify_chunk(idx, data)
+ self.print_error("validated chunk %d" % idx)
+ self.save_chunk(idx, data)
return idx + 1
- except Exception:
- self.print_error('verify_chunk failed')
+ except BaseException as e:
+ self.print_error('verify_chunk failed', str(e))
return idx - 1
diff --git a/lib/coinchooser.py b/lib/coinchooser.py
index 32a5ab44a..4df77a80d 100644
--- a/lib/coinchooser.py
+++ b/lib/coinchooser.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
-# Copyright (C) 2011 thomasv@gitorious
+# Copyright (C) 2015 kyuupichan@gmail
#
# 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
@@ -17,7 +17,8 @@
# along with this program. If not, see .
from collections import defaultdict, namedtuple
-from random import shuffle
+from random import choice, randint, shuffle
+from math import floor, log10
from bitcoin import COIN
from transaction import Transaction
@@ -25,6 +26,15 @@ from util import NotEnoughFunds, PrintError, profiler
Bucket = namedtuple('Bucket', ['desc', 'size', 'value', 'coins'])
+def strip_unneeded(bkts, sufficient_funds):
+ '''Remove buckets that are unnecessary in achieving the spend amount'''
+ bkts = sorted(bkts, key = lambda bkt: bkt.value)
+ for i in range(len(bkts)):
+ if not sufficient_funds(bkts[i + 1:]):
+ return bkts[i:]
+ # Shouldn't get here
+ return bkts
+
class CoinChooserBase(PrintError):
def keys(self, coins):
@@ -49,17 +59,25 @@ class CoinChooserBase(PrintError):
return 0
return penalty
- def add_change(self, tx, change_addrs, fee_estimator, dust_threshold):
- # How much is left if we add 1 change output?
- change_amount = tx.get_fee() - fee_estimator(1)
+ def change_amounts(self, tx, count, fee_estimator, dust_threshold):
+ # The amount left after adding 1 change output
+ return [max(0, tx.get_fee() - fee_estimator(1))]
+ def change_outputs(self, tx, change_addrs, fee_estimator, dust_threshold):
+ amounts = self.change_amounts(tx, len(change_addrs), fee_estimator,
+ dust_threshold)
+ assert min(amounts) >= 0
+ assert len(change_addrs) >= len(amounts)
# If change is above dust threshold after accounting for the
# size of the change output, add it to the transaction.
- if change_amount > dust_threshold:
- tx.outputs.append(('address', change_addrs[0], change_amount))
- self.print_error('change', change_amount)
- elif change_amount:
- self.print_error('not keeping dust', change_amount)
+ dust = sum(amount for amount in amounts if amount < dust_threshold)
+ amounts = [amount for amount in amounts if amount >= dust_threshold]
+ change = [('address', addr, amount)
+ for addr, amount in zip(change_addrs, amounts)]
+ self.print_error('change:', change)
+ if dust:
+ self.print_error('not keeping dust', dust)
+ return change
def make_tx(self, coins, outputs, change_addrs, fee_estimator,
dust_threshold):
@@ -72,12 +90,18 @@ class CoinChooserBase(PrintError):
tx = Transaction.from_io([], outputs[:])
# Size of the transaction with no inputs and no change
base_size = tx.estimated_size()
- # Returns fee given input size
- fee = lambda input_size: fee_estimator(base_size + input_size)
+ spent_amount = tx.output_value()
+
+ def sufficient_funds(buckets):
+ '''Given a list of buckets, return True if it has enough
+ value to pay for the transaction'''
+ total_input = sum(bucket.value for bucket in buckets)
+ total_size = sum(bucket.size for bucket in buckets) + base_size
+ return total_input >= spent_amount + fee_estimator(total_size)
# Collect the coins into buckets, choose a subset of the buckets
buckets = self.bucketize_coins(coins)
- buckets = self.choose_buckets(buckets, tx.output_value(), fee,
+ buckets = self.choose_buckets(buckets, sufficient_funds,
self.penalty_func(tx))
tx.inputs = [coin for b in buckets for coin in b.coins]
@@ -86,50 +110,37 @@ class CoinChooserBase(PrintError):
# This takes a count of change outputs and returns a tx fee;
# each pay-to-bitcoin-address output serializes as 34 bytes
fee = lambda count: fee_estimator(tx_size + count * 34)
- self.add_change(tx, change_addrs, fee, dust_threshold)
+ change = self.change_outputs(tx, change_addrs, fee, dust_threshold)
+ tx.outputs.extend(change)
self.print_error("using %d inputs" % len(tx.inputs))
self.print_error("using buckets:", [bucket.desc for bucket in buckets])
return tx
-class CoinChooserClassic(CoinChooserBase):
- '''
- The classic electrum algorithm. Chooses coins starting with
- the oldest that are sufficient to cover the spent amount, and
- then removes any unneeded starting with the smallest in value.'''
+class CoinChooserOldestFirst(CoinChooserBase):
+ '''The classic electrum algorithm. Chooses coins starting with the
+ oldest that are sufficient to cover the spent amount, and then
+ removes any unneeded starting with the smallest in value.'''
def keys(self, coins):
return [coin['prevout_hash'] + ':' + str(coin['prevout_n'])
for coin in coins]
- def choose_buckets(self, buckets, spent_amount, fee, penalty_func):
+ def choose_buckets(self, buckets, sufficient_funds, penalty_func):
'''Spend the oldest buckets first.'''
# Unconfirmed coins are young, not old
adj_height = lambda height: 99999999 if height == 0 else height
buckets.sort(key = lambda b: max(adj_height(coin['height'])
for coin in b.coins))
- selected, value, size = [], 0, 0
+ selected = []
for bucket in buckets:
selected.append(bucket)
- value += bucket.value
- size += bucket.size
- if value >= spent_amount + fee(size):
- break
+ if sufficient_funds(selected):
+ return strip_unneeded(selected, sufficient_funds)
else:
raise NotEnoughFunds()
- # Remove unneeded inputs starting with the smallest.
- selected.sort(key = lambda b: b.value)
- dropped = []
- for bucket in selected:
- if value - bucket.value >= spent_amount + fee(size - bucket.size):
- value -= bucket.value
- size -= bucket.size
- dropped.append(bucket)
-
- return [bucket for bucket in selected if bucket not in dropped]
-
class CoinChooserRandom(CoinChooserBase):
def bucket_candidates(self, buckets, sufficient_funds):
@@ -157,18 +168,11 @@ class CoinChooserRandom(CoinChooserBase):
else:
raise NotEnoughFunds()
- return [[buckets[n] for n in candidate] for candidate in candidates]
-
- def choose_buckets(self, buckets, spent_amount, fee, penalty_func):
+ candidates = [[buckets[n] for n in c] for c in candidates]
+ return [strip_unneeded(c, sufficient_funds) for c in candidates]
- def sufficient(buckets):
- '''Given a set of buckets, return True if it has enough
- value to pay for the transaction'''
- total_input = sum(bucket.value for bucket in buckets)
- total_size = sum(bucket.size for bucket in buckets)
- return total_input >= spent_amount + fee(total_size)
-
- candidates = self.bucket_candidates(buckets, sufficient)
+ def choose_buckets(self, buckets, sufficient_funds, penalty_func):
+ candidates = self.bucket_candidates(buckets, sufficient_funds)
penalties = [penalty_func(cand) for cand in candidates]
winner = candidates[penalties.index(min(penalties))]
self.print_error("Bucket sets:", len(buckets))
@@ -176,15 +180,19 @@ class CoinChooserRandom(CoinChooserBase):
return winner
class CoinChooserPrivacy(CoinChooserRandom):
- '''
- Attempts to better preserve user privacy. First, if any coin is
+ '''Attempts to better preserve user privacy. First, if any coin is
spent from a user address, all coins are. Compared to spending
from other addresses to make up an amount, this reduces
information leakage about sender holdings. It also helps to
- reduce blockchain UTXO bloat, and reduce future privacy loss
- that would come from reusing that address' remaining UTXOs.
- Second, it penalizes change that is quite different to the sent
- amount. Third, it penalizes change that is too big.'''
+ reduce blockchain UTXO bloat, and reduce future privacy loss that
+ would come from reusing that address' remaining UTXOs. Second, it
+ penalizes change that is quite different to the sent amount.
+ Third, it penalizes change that is too big. Fourth, it breaks
+ large change up into amounts comparable to the spent amount.
+ Finally, change is rounded to similar precision to sent amounts.
+ Extra change outputs and rounding might raise the transaction fee
+ slightly. Transaction priority might be less than if older coins
+ were chosen.'''
def keys(self, coins):
return [coin['address'] for coin in coins]
@@ -213,5 +221,52 @@ class CoinChooserPrivacy(CoinChooserRandom):
return penalty
-COIN_CHOOSERS = {'Classic': CoinChooserClassic,
+ def change_amounts(self, tx, count, fee_estimator, dust_threshold):
+
+ # Break change up if bigger than max_change
+ output_amounts = [o[2] for o in tx.outputs]
+ max_change = max(max(output_amounts) * 1.25, dust_threshold * 10)
+
+ # Use N change outputs
+ for n in range(1, count + 1):
+ # How much is left if we add this many change outputs?
+ change_amount = max(0, tx.get_fee() - fee_estimator(n))
+ if change_amount // n <= max_change:
+ break
+
+ # Get a handle on the precision of the output amounts; round our
+ # change to look similar
+ def trailing_zeroes(val):
+ s = str(val)
+ return len(s) - len(s.rstrip('0'))
+
+ zeroes = map(trailing_zeroes, output_amounts)
+ min_zeroes = min(zeroes)
+ max_zeroes = max(zeroes)
+ zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1)
+
+ # Calculate change; randomize it a bit if using more than 1 output
+ remaining = change_amount
+ amounts = []
+ while n > 1:
+ average = remaining // n
+ amount = randint(int(average * 0.7), int(average * 1.3))
+ precision = min(choice(zeroes), int(floor(log10(amount))))
+ amount = int(round(amount, -precision))
+ amounts.append(amount)
+ remaining -= amount
+ n -= 1
+
+ # Last change output. Round down to maximum precision but lose
+ # no more than 100 satoshis to fees (2dp)
+ N = pow(10, min(2, zeroes[0]))
+ amount = (remaining // N) * N
+ amounts.append(amount)
+
+ assert sum(amounts) <= change_amount
+
+ return amounts
+
+
+COIN_CHOOSERS = {'Oldest First': CoinChooserOldestFirst,
'Privacy': CoinChooserPrivacy}
diff --git a/lib/commands.py b/lib/commands.py
index 47fe03b12..135890930 100644
--- a/lib/commands.py
+++ b/lib/commands.py
@@ -74,21 +74,22 @@ def command(s):
class Commands:
- def __init__(self, config, wallet, network, callback = None):
+ def __init__(self, config, wallet, network, callback = None, password=None, new_password=None):
self.config = config
self.wallet = wallet
self.network = network
self._callback = callback
- self.password = None
+ self._password = password
+ self.new_password = new_password
self.contacts = contacts.Contacts(self.config)
def _run(self, method, args, password_getter):
cmd = known_commands[method]
if cmd.requires_password and self.wallet.use_encryption:
- self.password = apply(password_getter,())
+ self._password = apply(password_getter,())
f = getattr(self, method)
result = f(*args)
- self.password = None
+ self._password = None
if self._callback:
apply(self._callback, ())
return result
@@ -120,7 +121,9 @@ class Commands:
@command('wp')
def password(self):
"""Change wallet password. """
- raise BaseException('Not a JSON-RPC command')
+ self.wallet.update_password(self._password, self.new_password)
+ self.wallet.storage.write()
+ return {'password':self.wallet.use_encryption}
@command('')
def getconfig(self, key):
@@ -157,7 +160,7 @@ class Commands:
"""
return self.network.synchronous_get(('blockchain.address.get_history', [address]))
- @command('nw')
+ @command('w')
def listunspent(self):
"""List unspent outputs. Returns the list of unspent transaction
outputs in your wallet."""
@@ -200,7 +203,7 @@ class Commands:
outputs = map(lambda x: ('address', x[0], int(COIN*x[1])), outputs.items())
tx = Transaction.from_io(tx_inputs, outputs)
if not unsigned:
- self.wallet.sign_transaction(tx, self.password)
+ self.wallet.sign_transaction(tx, self._password)
return tx.as_dict()
@command('wp')
@@ -212,7 +215,7 @@ class Commands:
pubkey = bitcoin.public_key_from_private_key(privkey)
t.sign({pubkey:privkey})
else:
- self.wallet.sign_transaction(t, self.password)
+ self.wallet.sign_transaction(t, self._password)
return t.as_dict()
@command('')
@@ -250,7 +253,7 @@ class Commands:
"""Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses."""
is_list = type(address) is list
domain = address if is_list else [address]
- out = [self.wallet.get_private_key(address, self.password) for address in domain]
+ out = [self.wallet.get_private_key(address, self._password) for address in domain]
return out if is_list else out[0]
@command('w')
@@ -273,10 +276,9 @@ class Commands:
"""Return the public keys for a wallet address. """
return self.wallet.get_public_keys(address)
- @command('nw')
+ @command('w')
def getbalance(self, account=None):
- """Return the balance of your wallet. If run with the --offline flag,
- returns the last known balance."""
+ """Return the balance of your wallet. """
if account is None:
c, u, x = self.wallet.get_balance()
else:
@@ -334,19 +336,19 @@ class Commands:
@command('wp')
def getmasterprivate(self):
"""Get master private key. Return your wallet\'s master private key"""
- return str(self.wallet.get_master_private_key(self.wallet.root_name, self.password))
+ return str(self.wallet.get_master_private_key(self.wallet.root_name, self._password))
@command('wp')
def getseed(self):
"""Get seed phrase. Print the generation seed of your wallet."""
- s = self.wallet.get_mnemonic(self.password)
+ s = self.wallet.get_mnemonic(self._password)
return s.encode('utf8')
@command('wp')
def importprivkey(self, privkey):
"""Import a private key. """
try:
- addr = self.wallet.import_key(privkey, self.password)
+ addr = self.wallet.import_key(privkey, self._password)
out = "Keypair imported: " + addr
except Exception as e:
out = "Error: " + str(e)
@@ -377,7 +379,7 @@ class Commands:
def signmessage(self, address, message):
"""Sign a message with a key. Use quotes if your message contains
whitespaces"""
- sig = self.wallet.sign_message(address, message, self.password)
+ sig = self.wallet.sign_message(address, message, self._password)
return base64.b64encode(sig)
@command('')
@@ -415,24 +417,24 @@ class Commands:
tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
str(tx) #this serializes
if not unsigned:
- self.wallet.sign_transaction(tx, self.password)
+ self.wallet.sign_transaction(tx, self._password)
return tx
- @command('wpn')
+ @command('wp')
def payto(self, destination, amount, tx_fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False):
"""Create a transaction. """
domain = [from_addr] if from_addr else None
tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck, unsigned)
return tx.as_dict()
- @command('wpn')
+ @command('wp')
def paytomany(self, outputs, tx_fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False):
"""Create a multi-output transaction. """
domain = [from_addr] if from_addr else None
tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck, unsigned)
return tx.as_dict()
- @command('wn')
+ @command('w')
def history(self):
"""Wallet history. Returns the transaction history of your wallet."""
balance = 0
@@ -443,7 +445,7 @@ class Commands:
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
except Exception:
time_str = "----"
- label, is_default_label = self.wallet.get_label(tx_hash)
+ label = self.wallet.get_label(tx_hash)
out.append({
'txid':tx_hash,
'timestamp':timestamp,
@@ -502,7 +504,7 @@ class Commands:
out.append(item)
return out
- @command('nw')
+ @command('w')
def gettransaction(self, txid):
"""Retrieve a transaction. """
tx = self.wallet.transactions.get(txid) if self.wallet else None
@@ -522,7 +524,7 @@ class Commands:
@command('wp')
def decrypt(self, pubkey, encrypted):
"""Decrypt a message encrypted with a public key."""
- return self.wallet.decrypt_message(pubkey, encrypted, self.password)
+ return self.wallet.decrypt_message(pubkey, encrypted, self._password)
def _format_request(self, out):
pr_str = {
@@ -535,7 +537,7 @@ class Commands:
out['status'] = pr_str[out.get('status', PR_UNKNOWN)]
return out
- @command('wn')
+ @command('w')
def getrequest(self, key):
"""Return a payment request"""
r = self.wallet.get_payment_request(key, self.config)
@@ -548,7 +550,7 @@ class Commands:
# """"""
# pass
- @command('wn')
+ @command('w')
def listrequests(self, pending=False, expired=False, paid=False):
"""List the payment requests you made."""
out = self.wallet.get_sorted_requests(self.config)
@@ -565,7 +567,7 @@ class Commands:
return map(self._format_request, out)
@command('w')
- def addrequest(self, requested_amount, memo='', expiration=60*60, force=False):
+ def addrequest(self, amount, memo='', expiration=60*60, force=False):
"""Create a payment request."""
addr = self.wallet.get_unused_address(None)
if addr is None:
@@ -573,7 +575,7 @@ class Commands:
addr = self.wallet.create_new_address(None, False)
else:
return False
- amount = int(Decimal(requested_amount)*COIN)
+ amount = int(COIN*Decimal(amount))
expiration = int(expiration)
req = self.wallet.make_payment_request(addr, amount, memo, expiration)
self.wallet.add_payment_request(req, self.config)
@@ -587,7 +589,7 @@ class Commands:
if not alias:
raise BaseException('No alias in your configuration')
alias_addr = self.contacts.resolve(alias)['address']
- self.wallet.sign_payment_request(address, alias, alias_addr, self.password)
+ self.wallet.sign_payment_request(address, alias, alias_addr, self._password)
@command('w')
def rmrequest(self, address):
@@ -664,15 +666,18 @@ command_options = {
}
+# don't use floats because of rounding errors
+json_loads = lambda x: json.loads(x, parse_float=lambda x: str(Decimal(x)))
arg_types = {
- 'num':int,
- 'nbits':int,
- 'entropy':long,
- 'pubkeys': json.loads,
- 'inputs': json.loads,
- 'outputs': json.loads,
- 'tx_fee': lambda x: float(x) if x is not None else None,
- 'amount': lambda x: float(x) if x!='!' else '!',
+ 'num': int,
+ 'nbits': int,
+ 'entropy': long,
+ 'tx': json_loads,
+ 'pubkeys': json_loads,
+ 'inputs': json_loads,
+ 'outputs': json_loads,
+ 'tx_fee': lambda x: str(Decimal(x)) if x is not None else None,
+ 'amount': lambda x: str(Decimal(x)) if x!='!' else '!',
}
config_variables = {
@@ -728,7 +733,6 @@ def get_parser():
group.add_argument("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Show debugging information")
group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory")
group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
- group.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
# create main parser
parser = argparse.ArgumentParser(
parents=[parent_parser],
@@ -739,6 +743,7 @@ def get_parser():
parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)")
#parser_gui.set_defaults(func=run_gui)
parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'lite', 'gtk', 'kivy', 'text', 'stdio', 'jsonrpc'])
+ parser_gui.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI")
add_network_options(parser_gui)
diff --git a/lib/daemon.py b/lib/daemon.py
index 5bae88bea..de9354dc7 100644
--- a/lib/daemon.py
+++ b/lib/daemon.py
@@ -103,7 +103,7 @@ class Daemon(DaemonThread):
'nodes': self.network.get_interfaces(),
'connected': self.network.is_connected(),
'auto_connect': p[4],
- 'wallets': self.wallets.keys(),
+ 'wallets': dict([ (k, w.is_up_to_date()) for k, w in self.wallets.items()]),
}
elif sub == 'stop':
self.stop()
@@ -135,21 +135,19 @@ class Daemon(DaemonThread):
return wallet
def run_cmdline(self, config_options):
- password = config_options.get('password')
config = SimpleConfig(config_options)
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
wallet = self.load_wallet(config) if cmd.requires_wallet else None
- if wallet:
- wallet.wait_until_synchronized()
# arguments passed to function
args = map(lambda x: config.get(x), cmd.params)
# decode json arguments
args = map(json_decode, args)
# options
args += map(lambda x: config.get(x), cmd.options)
- cmd_runner = Commands(config, wallet, self.network)
- cmd_runner.password = password
+ cmd_runner = Commands(config, wallet, self.network,
+ password=config_options.get('password'),
+ new_password=config_options.get('new_password'))
func = getattr(cmd_runner, cmd.name)
result = func(*args)
return result
diff --git a/lib/qrscanner.py b/lib/qrscanner.py
index 419635c91..d38f7db77 100644
--- a/lib/qrscanner.py
+++ b/lib/qrscanner.py
@@ -12,7 +12,7 @@ proc = None
def scan_qr(config):
global proc
if not zbar:
- raise BaseException("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo pip install zbar'")]))
+ raise RuntimeError("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo pip install zbar'")]))
if proc is None:
device = config.get("video_device", "default")
if device == 'default':
diff --git a/lib/synchronizer.py b/lib/synchronizer.py
index 8762ea0d4..0b870d85f 100644
--- a/lib/synchronizer.py
+++ b/lib/synchronizer.py
@@ -179,5 +179,5 @@ class Synchronizer(ThreadJob):
if up_to_date != self.wallet.is_up_to_date():
self.wallet.set_up_to_date(up_to_date)
if up_to_date:
- self.wallet.save_transactions()
+ self.wallet.save_transactions(write=True)
self.network.trigger_callback('updated')
diff --git a/lib/tests/test_wallet.py b/lib/tests/test_wallet.py
index 62831609e..b884f680a 100644
--- a/lib/tests/test_wallet.py
+++ b/lib/tests/test_wallet.py
@@ -57,7 +57,7 @@ class TestWalletStorage(WalletTestCase):
some_dict = {"a":"b", "c":"d"}
for key, value in some_dict.items():
- storage.put(key, value, False)
+ storage.put(key, value)
storage.write()
contents = ""
diff --git a/lib/util.py b/lib/util.py
index 375c21dcf..4bf3e36c5 100644
--- a/lib/util.py
+++ b/lib/util.py
@@ -10,6 +10,8 @@ import urllib
import threading
from i18n import _
+base_units = {'BTC':8, 'mBTC':5, 'uBTC':2}
+
def normalize_version(v):
return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
@@ -147,20 +149,20 @@ def json_encode(obj):
def json_decode(x):
try:
- return json.loads(x)
+ return json.loads(x, parse_float=decimal.Decimal)
except:
return x
# decorator that prints execution time
def profiler(func):
- def do_profile(func, args):
+ def do_profile(func, args, kw_args):
n = func.func_name
t0 = time.time()
- o = apply(func, args)
+ o = func(*args, **kw_args)
t = time.time() - t0
print_error("[profiler]", n, "%.4f"%t)
return o
- return lambda *args: do_profile(func, args)
+ return lambda *args, **kw_args: do_profile(func, args, kw_args)
@@ -317,7 +319,7 @@ def block_explorer_URL(config, kind, item):
#_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
#urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
-def parse_URI(uri):
+def parse_URI(uri, on_pr=None):
import bitcoin
from bitcoin import COIN
@@ -364,6 +366,22 @@ def parse_URI(uri):
if 'sig' in out:
out['sig'] = bitcoin.base_decode(out['sig'], None, base=58).encode('hex')
+ r = out.get('r')
+ sig = out.get('sig')
+ name = out.get('name')
+ if r or (name and sig):
+ def get_payment_request_thread():
+ import paymentrequest as pr
+ if name and sig:
+ s = pr.serialize_request(out).SerializeToString()
+ request = pr.PaymentRequest(s)
+ else:
+ request = pr.get_payment_request(r)
+ on_pr(request)
+ t = threading.Thread(target=get_payment_request_thread)
+ t.setDaemon(True)
+ t.start()
+
return out
@@ -561,13 +579,14 @@ class StoreDict(dict):
def check_www_dir(rdir):
- # rewrite index.html every time
import urllib, urlparse, shutil, os
if not os.path.exists(rdir):
os.mkdir(rdir)
index = os.path.join(rdir, 'index.html')
- src = os.path.join(os.path.dirname(__file__), 'www', 'index.html')
- shutil.copy(src, index)
+ if not os.path.exists(index):
+ print_error("copying index.html")
+ src = os.path.join(os.path.dirname(__file__), 'www', 'index.html')
+ shutil.copy(src, index)
files = [
"https://code.jquery.com/jquery-1.9.1.min.js",
"https://raw.githubusercontent.com/davidshimjs/qrcodejs/master/qrcode.js",
diff --git a/lib/wallet.py b/lib/wallet.py
index 4bda5fabc..6e0a950d4 100644
--- a/lib/wallet.py
+++ b/lib/wallet.py
@@ -26,7 +26,7 @@ import json
import copy
from functools import partial
-from util import PrintError, profiler
+from util import NotEnoughFunds, PrintError, profiler
from bitcoin import *
from account import *
@@ -69,11 +69,11 @@ class WalletStorage(PrintError):
except:
try:
d = ast.literal_eval(data) #parse raw data from reading wallet file
+ labels = d.get('labels', {})
except Exception as e:
raise IOError("Cannot read wallet file '%s'" % self.path)
self.data = {}
# In old versions of Electrum labels were latin1 encoded, this fixes breakage.
- labels = d.get('labels', {})
for i, label in labels.items():
try:
unicode(label)
@@ -98,7 +98,7 @@ class WalletStorage(PrintError):
v = copy.deepcopy(v)
return v
- def put(self, key, value, save = True):
+ def put(self, key, value):
try:
json.dumps(key)
json.dumps(value)
@@ -113,8 +113,6 @@ class WalletStorage(PrintError):
elif key in self.data:
self.modified = True
self.data.pop(key)
- if save:
- self.write()
def write(self):
if threading.currentThread().isDaemon():
@@ -142,7 +140,7 @@ class WalletStorage(PrintError):
import stat
os.chmod(self.path, mode)
self.print_error("saved", self.path)
-
+ self.modified = False
class Abstract_Wallet(PrintError):
@@ -199,7 +197,7 @@ class Abstract_Wallet(PrintError):
# save wallet type the first time
if self.storage.get('wallet_type') is None:
- self.storage.put('wallet_type', self.wallet_type, True)
+ self.storage.put('wallet_type', self.wallet_type)
def diagnostic_name(self):
return self.basename()
@@ -220,17 +218,18 @@ class Abstract_Wallet(PrintError):
self.transactions.pop(tx_hash)
@profiler
- def save_transactions(self):
+ def save_transactions(self, write=False):
with self.transaction_lock:
tx = {}
for k,v in self.transactions.items():
tx[k] = str(v)
- # Flush storage only with the last put
- self.storage.put('transactions', tx, False)
- self.storage.put('txi', self.txi, False)
- self.storage.put('txo', self.txo, False)
- self.storage.put('pruned_txo', self.pruned_txo, False)
- self.storage.put('addr_history', self.history, True)
+ self.storage.put('transactions', tx)
+ self.storage.put('txi', self.txi)
+ self.storage.put('txo', self.txo)
+ self.storage.put('pruned_txo', self.pruned_txo)
+ self.storage.put('addr_history', self.history)
+ if write:
+ self.storage.write()
def clear_history(self):
with self.transaction_lock:
@@ -376,7 +375,7 @@ class Abstract_Wallet(PrintError):
if changed:
run_hook('set_label', self, name, text)
- self.storage.put('labels', self.labels, True)
+ self.storage.put('labels', self.labels)
return changed
@@ -436,7 +435,7 @@ class Abstract_Wallet(PrintError):
self.unverified_tx.pop(tx_hash, None)
with self.lock:
self.verified_tx[tx_hash] = info # (tx_height, timestamp, pos)
- self.storage.put('verified_tx3', self.verified_tx, True)
+ self.storage.put('verified_tx3', self.verified_tx)
conf, timestamp = self.get_confirmations(tx_hash)
self.network.trigger_callback('verified', tx_hash, conf, timestamp)
@@ -867,24 +866,9 @@ class Abstract_Wallet(PrintError):
return h2
-
def get_label(self, tx_hash):
- label = self.labels.get(tx_hash)
- is_default = (label == '') or (label is None)
- if is_default:
- label = self.get_default_label(tx_hash)
- return label, is_default
-
- def get_default_label(self, tx_hash):
- if self.txi.get(tx_hash) == {}:
- d = self.txo.get(tx_hash, {})
- labels = []
- for addr in d.keys():
- label = self.labels.get(addr)
- if label:
- labels.append(label)
- return ', '.join(labels)
- return ''
+ label = self.labels.get(tx_hash, '')
+ return label
def fee_per_kb(self, config):
b = config.get('dynamic_fees')
@@ -899,7 +883,7 @@ class Abstract_Wallet(PrintError):
def coin_chooser_name(self, config):
kind = config.get('coin_chooser')
if not kind in COIN_CHOOSERS:
- kind = 'Classic'
+ kind = 'Oldest First'
return kind
def coin_chooser(self, config):
@@ -912,6 +896,10 @@ class Abstract_Wallet(PrintError):
if type == 'address':
assert is_address(data), "Address " + data + " is invalid!"
+ # Avoid index-out-of-range with coins[0] below
+ if not coins:
+ raise NotEnoughFunds()
+
for item in coins:
self.add_input_info(item)
@@ -1044,7 +1032,7 @@ class Abstract_Wallet(PrintError):
if self.has_seed():
decoded = self.get_seed(old_password)
self.seed = pw_encode( decoded, new_password)
- self.storage.put('seed', self.seed, True)
+ self.storage.put('seed', self.seed)
imported_account = self.accounts.get(IMPORTED_ACCOUNT)
if imported_account:
@@ -1056,10 +1044,10 @@ class Abstract_Wallet(PrintError):
b = pw_decode(v, old_password)
c = pw_encode(b, new_password)
self.master_private_keys[k] = c
- self.storage.put('master_private_keys', self.master_private_keys, True)
+ self.storage.put('master_private_keys', self.master_private_keys)
self.use_encryption = (new_password != None)
- self.storage.put('use_encryption', self.use_encryption,True)
+ self.storage.put('use_encryption', self.use_encryption)
def is_frozen(self, addr):
return addr in self.frozen_addresses
@@ -1071,7 +1059,7 @@ class Abstract_Wallet(PrintError):
self.frozen_addresses |= set(addrs)
else:
self.frozen_addresses -= set(addrs)
- self.storage.put('frozen_addresses', list(self.frozen_addresses), True)
+ self.storage.put('frozen_addresses', list(self.frozen_addresses))
return True
return False
@@ -1109,7 +1097,8 @@ class Abstract_Wallet(PrintError):
self.verifier = None
# Now no references to the syncronizer or verifier
# remain so they will be GC-ed
- self.storage.put('stored_height', self.get_local_height(), True)
+ self.storage.put('stored_height', self.get_local_height())
+ self.storage.write()
def wait_until_synchronized(self, callback=None):
from i18n import _
@@ -1147,7 +1136,7 @@ class Abstract_Wallet(PrintError):
d = {}
for k, v in self.accounts.items():
d[k] = v.dump()
- self.storage.put('accounts', d, True)
+ self.storage.put('accounts', d)
def can_import(self):
return not self.is_watching_only()
@@ -1431,9 +1420,9 @@ class Deterministic_Wallet(Abstract_Wallet):
else:
self.use_encryption = False
- self.storage.put('seed', self.seed, False)
- self.storage.put('seed_version', self.seed_version, False)
- self.storage.put('use_encryption', self.use_encryption,True)
+ self.storage.put('seed', self.seed)
+ self.storage.put('seed_version', self.seed_version)
+ self.storage.put('use_encryption', self.use_encryption)
def get_seed(self, password):
return pw_decode(self.seed, password)
@@ -1445,7 +1434,7 @@ class Deterministic_Wallet(Abstract_Wallet):
assert isinstance(value, int), 'gap limit must be of type int, not of %s'%type(value)
if value >= self.gap_limit:
self.gap_limit = value
- self.storage.put('gap_limit', self.gap_limit, True)
+ self.storage.put('gap_limit', self.gap_limit)
return True
elif value >= self.min_acceptable_gap():
@@ -1456,7 +1445,7 @@ class Deterministic_Wallet(Abstract_Wallet):
account.receiving_pubkeys = account.receiving_pubkeys[0:n]
account.receiving_addresses = account.receiving_addresses[0:n]
self.gap_limit = value
- self.storage.put('gap_limit', self.gap_limit, True)
+ self.storage.put('gap_limit', self.gap_limit)
self.save_accounts()
return True
else:
@@ -1579,11 +1568,11 @@ class BIP32_Wallet(Deterministic_Wallet):
if xpub in self.master_public_keys.values():
raise BaseException('Duplicate master public key')
self.master_public_keys[name] = xpub
- self.storage.put('master_public_keys', self.master_public_keys, True)
+ self.storage.put('master_public_keys', self.master_public_keys)
def add_master_private_key(self, name, xpriv, password):
self.master_private_keys[name] = pw_encode(xpriv, password)
- self.storage.put('master_private_keys', self.master_private_keys, True)
+ self.storage.put('master_private_keys', self.master_private_keys)
def derive_xkeys(self, root, derivation, password):
x = self.master_private_keys[root]
@@ -1626,16 +1615,16 @@ class BIP32_Simple_Wallet(BIP32_Wallet):
def create_xprv_wallet(self, xprv, password):
xpub = bitcoin.xpub_from_xprv(xprv)
account = BIP32_Account({'xpub':xpub})
- self.storage.put('seed_version', self.seed_version, True)
+ self.storage.put('seed_version', self.seed_version)
self.add_master_private_key(self.root_name, xprv, password)
self.add_master_public_key(self.root_name, xpub)
self.add_account('0', account)
self.use_encryption = (password != None)
- self.storage.put('use_encryption', self.use_encryption,True)
+ self.storage.put('use_encryption', self.use_encryption)
def create_xpub_wallet(self, xpub):
account = BIP32_Account({'xpub':xpub})
- self.storage.put('seed_version', self.seed_version, True)
+ self.storage.put('seed_version', self.seed_version)
self.add_master_public_key(self.root_name, xpub)
self.add_account('0', account)
@@ -1834,7 +1823,7 @@ class OldWallet(Deterministic_Wallet):
def create_master_keys(self, password):
seed = self.get_seed(password)
mpk = OldAccount.mpk_from_seed(seed)
- self.storage.put('master_public_key', mpk, True)
+ self.storage.put('master_public_key', mpk)
def get_master_public_key(self):
return self.storage.get("master_public_key")
@@ -1852,8 +1841,8 @@ class OldWallet(Deterministic_Wallet):
def create_watching_only_wallet(self, mpk):
self.seed_version = OLD_SEED_VERSION
- self.storage.put('seed_version', self.seed_version, False)
- self.storage.put('master_public_key', mpk, True)
+ self.storage.put('seed_version', self.seed_version)
+ self.storage.put('master_public_key', mpk)
self.create_account(mpk)
def get_seed(self, password):
@@ -2037,7 +2026,7 @@ class Wallet(object):
@classmethod
def from_multisig(klass, key_list, password, storage, wallet_type):
- storage.put('wallet_type', wallet_type, True)
+ storage.put('wallet_type', wallet_type)
self = Multisig_Wallet(storage)
key_list = sorted(key_list, key = lambda x: klass.is_xpub(x))
for i, text in enumerate(key_list):
@@ -2056,7 +2045,7 @@ class Wallet(object):
else:
self.add_cosigner_seed(text, name, password)
self.use_encryption = (password != None)
- self.storage.put('use_encryption', self.use_encryption, True)
+ self.storage.put('use_encryption', self.use_encryption)
self.create_main_account(password)
return self
diff --git a/plugins/audio_modem/qt.py b/plugins/audio_modem/qt.py
index e797f12c8..09a1e5b4b 100644
--- a/plugins/audio_modem/qt.py
+++ b/plugins/audio_modem/qt.py
@@ -1,18 +1,18 @@
+from functools import partial
+import zlib
+import json
+from io import BytesIO
+import sys
+import platform
+
from electrum.plugins import BasePlugin, hook
-from electrum_gui.qt.util import WaitingDialog, EnterButton
+from electrum_gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog
from electrum.util import print_msg, print_error
from electrum.i18n import _
from PyQt4.QtGui import *
from PyQt4.QtCore import *
-import traceback
-import zlib
-import json
-from io import BytesIO
-import sys
-import platform
-
try:
import amodem.audio
import amodem.main
@@ -42,11 +42,10 @@ class Plugin(BasePlugin):
return True
def settings_widget(self, window):
- return EnterButton(_('Settings'), self.settings_dialog)
+ return EnterButton(_('Settings'), partial(self.settings_dialog, window))
- def settings_dialog(self):
- d = QDialog()
- d.setWindowTitle("Settings")
+ def settings_dialog(self, window):
+ d = WindowModalDialog(window, _("Audio Modem Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel(_('Bit rate [kbps]: ')), 0, 0)
@@ -75,24 +74,20 @@ class Plugin(BasePlugin):
def handler():
blob = json.dumps(dialog.tx.as_dict())
- self.sender = self._send(parent=dialog, blob=blob)
- self.sender.start()
+ self._send(parent=dialog, blob=blob)
b.clicked.connect(handler)
dialog.sharing_buttons.insert(-1, b)
@hook
def scan_text_edit(self, parent):
- def handler():
- self.receiver = self._recv(parent=parent)
- self.receiver.start()
- parent.addButton(':icons/microphone.png', handler, _("Read from microphone"))
+ parent.addButton(':icons/microphone.png', partial(self._recv, parent),
+ _("Read from microphone"))
@hook
def show_text_edit(self, parent):
def handler():
blob = str(parent.toPlainText())
- self.sender = self._send(parent=parent, blob=blob)
- self.sender.start()
+ self._send(parent=parent, blob=blob)
parent.addButton(':icons/speaker.png', handler, _("Send to speaker"))
def _audio_interface(self):
@@ -101,31 +96,25 @@ class Plugin(BasePlugin):
def _send(self, parent, blob):
def sender_thread():
- try:
- with self._audio_interface() as interface:
- src = BytesIO(blob)
- dst = interface.player()
- amodem.main.send(config=self.modem_config, src=src, dst=dst)
- except Exception:
- traceback.print_exc()
+ with self._audio_interface() as interface:
+ src = BytesIO(blob)
+ dst = interface.player()
+ amodem.main.send(config=self.modem_config, src=src, dst=dst)
print_msg('Sending:', repr(blob))
blob = zlib.compress(blob)
kbps = self.modem_config.modem_bps / 1e3
msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps)
- return WaitingDialog(parent=parent, message=msg, run_task=sender_thread)
+ WaitingDialog(parent, msg, sender_thread)
def _recv(self, parent):
def receiver_thread():
- try:
- with self._audio_interface() as interface:
- src = interface.recorder()
- dst = BytesIO()
- amodem.main.recv(config=self.modem_config, src=src, dst=dst)
- return dst.getvalue()
- except Exception:
- traceback.print_exc()
+ with self._audio_interface() as interface:
+ src = interface.recorder()
+ dst = BytesIO()
+ amodem.main.recv(config=self.modem_config, src=src, dst=dst)
+ return dst.getvalue()
def on_success(blob):
if blob:
@@ -135,5 +124,4 @@ class Plugin(BasePlugin):
kbps = self.modem_config.modem_bps / 1e3
msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps)
- return WaitingDialog(parent=parent, message=msg,
- run_task=receiver_thread, on_success=on_success)
+ WaitingDialog(parent, msg, receiver_thread, on_success=on_success)
diff --git a/plugins/email_requests/qt.py b/plugins/email_requests/qt.py
index f76099e4b..0aae26e7e 100644
--- a/plugins/email_requests/qt.py
+++ b/plugins/email_requests/qt.py
@@ -18,12 +18,10 @@
from __future__ import absolute_import
-import socket
import time
import threading
import base64
-from decimal import Decimal
-from Queue import Queue
+from functools import partial
import smtplib
import imaplib
@@ -37,12 +35,11 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
-from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED
from electrum.plugins import BasePlugin, hook
-from electrum import util
from electrum.paymentrequest import PaymentRequest
from electrum.i18n import _
-from electrum_gui.qt.util import text_dialog, EnterButton
+from electrum_gui.qt.util import EnterButton, Buttons, CloseButton
+from electrum_gui.qt.util import OkButton, WindowModalDialog
@@ -166,14 +163,10 @@ class Plugin(BasePlugin):
return True
def settings_widget(self, window):
- self.settings_window = window
- return EnterButton(_('Settings'), self.settings_dialog)
+ return EnterButton(_('Settings'), partial(self.settings_dialog, window))
- def settings_dialog(self, x):
- from electrum_gui.qt.util import Buttons, CloseButton, OkButton
-
- d = QDialog(self.settings_window)
- d.setWindowTitle("Email settings")
+ def settings_dialog(self, window):
+ d = WindowModalDialog(window, _("Email settings"))
d.setMinimumSize(500, 200)
vbox = QVBoxLayout(d)
diff --git a/plugins/exchange_rate/exchange_rate.py b/plugins/exchange_rate/exchange_rate.py
index e931588e4..7430e8645 100644
--- a/plugins/exchange_rate/exchange_rate.py
+++ b/plugins/exchange_rate/exchange_rate.py
@@ -346,13 +346,18 @@ class FxPlugin(BasePlugin, ThreadJob):
return _("No data")
@hook
- def historical_value_str(self, satoshis, d_t):
+ def history_rate(self, d_t):
rate = self.exchange.historical_rate(self.ccy, d_t)
# Frequently there is no rate for today, until tomorrow :)
# Use spot quotes in that case
if rate is None and (datetime.today().date() - d_t.date()).days <= 2:
rate = self.exchange.quotes.get(self.ccy)
self.history_used_spot = True
+ return rate
+
+ @hook
+ def historical_value_str(self, satoshis, d_t):
+ rate = self.history_rate(d_t)
return self.value_str(satoshis, rate)
@hook
diff --git a/plugins/exchange_rate/qt.py b/plugins/exchange_rate/qt.py
index a06e4009e..4322f352d 100644
--- a/plugins/exchange_rate/qt.py
+++ b/plugins/exchange_rate/qt.py
@@ -128,11 +128,10 @@ class Plugin(FxPlugin):
window.connect(window.app, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
def settings_widget(self, window):
- return EnterButton(_('Settings'), self.settings_dialog)
+ return EnterButton(_('Settings'), partial(self.settings_dialog, window))
- def settings_dialog(self):
- d = QDialog()
- d.setWindowTitle("Settings")
+ def settings_dialog(self, window):
+ d = WindowModalDialog(window, _("Exchange Rate Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0)
layout.addWidget(QLabel(_('Currency: ')), 1, 0)
diff --git a/plugins/greenaddress_instant/qt.py b/plugins/greenaddress_instant/qt.py
index 0dddf23e9..604f90446 100644
--- a/plugins/greenaddress_instant/qt.py
+++ b/plugins/greenaddress_instant/qt.py
@@ -21,7 +21,7 @@ import urllib
import sys
import requests
-from PyQt4.QtGui import QMessageBox, QApplication, QPushButton
+from PyQt4.QtGui import QApplication, QPushButton
from electrum.plugins import BasePlugin, hook
from electrum.i18n import _
@@ -65,7 +65,7 @@ class Plugin(BasePlugin):
'to verify that transaction is instant.\n'
'Please enter your password to sign a\n'
'verification request.')
- password = window.password_dialog(msg)
+ password = window.password_dialog(msg, parent=d)
if not password:
return
try:
@@ -84,14 +84,12 @@ class Plugin(BasePlugin):
# 3. display the result
if response.get('verified'):
- QMessageBox.information(None, _('Verification successful!'),
- _('%s is covered by GreenAddress instant confirmation') % (tx.hash()), _('OK'))
+ d.show_message(_('%s is covered by GreenAddress instant confirmation') % (tx.hash()), title=_('Verification successful!'))
else:
- QMessageBox.critical(None, _('Verification failed!'),
- _('%s is not covered by GreenAddress instant confirmation') % (tx.hash()), _('OK'))
+ d.show_critical(_('%s is not covered by GreenAddress instant confirmation') % (tx.hash()), title=_('Verification failed!'))
except BaseException as e:
import traceback
traceback.print_exc(file=sys.stdout)
- QMessageBox.information(None, _('Error'), str(e), _('OK'))
+ d.show_error(str(e))
finally:
d.verify_button.setText(self.button_label)
diff --git a/plugins/keepkey/qt.py b/plugins/keepkey/qt.py
index ab6ce807a..07309fa7a 100644
--- a/plugins/keepkey/qt.py
+++ b/plugins/keepkey/qt.py
@@ -1,4 +1,4 @@
-from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
+from PyQt4.Qt import QVBoxLayout, QLabel, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
@@ -26,11 +26,11 @@ class Plugin(KeepKeyPlugin):
try:
self.get_client().ping('t')
except BaseException as e:
- QMessageBox.information(window, _('Error'), _("KeepKey device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
+ window.show_error(_('KeepKey device not detected.\nContinuing in watching-only mode.\nReason:\n' + str(e)))
self.wallet.force_watching_only = True
return
if self.wallet.addresses() and not self.wallet.check_proper_device():
- QMessageBox.information(window, _('Error'), _("This wallet does not match your KeepKey device"), _('OK'))
+ window.show_error(_("This wallet does not match your KeepKey device"))
self.wallet.force_watching_only = True
@hook
@@ -73,7 +73,7 @@ class Plugin(KeepKeyPlugin):
return
get_label = lambda: self.get_client().features.label
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
- d = QDialog()
+ d = WindowModalDialog(window, _("KeepKey Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel("KeepKey Options"),0,0)
layout.addWidget(QLabel("ID:"),1,0)
@@ -132,10 +132,7 @@ class KeepKeyQtHandler:
return self.passphrase
def pin_dialog(self):
- d = QDialog(None)
- d.setModal(1)
- d.setWindowTitle(_("Enter PIN"))
- d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
+ d = WindowModalDialog(self.win, _("Enter PIN"))
matrix = PinMatrixWidget()
vbox = QVBoxLayout()
vbox.addWidget(QLabel(self.message))
@@ -153,23 +150,18 @@ class KeepKeyQtHandler:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
else:
assert type(self.win) is InstallWizard
- from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
- d = QDialog()
- d.setModal(1)
- d.setLayout(make_password_dialog(d, None, self.message, False))
- confirmed, p, passphrase = run_password_dialog(d, None, None)
+ from electrum_gui.qt.password_dialog import PasswordDialog
+ d = PasswordDialog(self.win, None, None, self.message, False)
+ confirmed, p, passphrase = d.run()
if not confirmed:
- QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
+ self.win.show_critical(_("Password request canceled"))
self.passphrase = None
else:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
self.done.set()
def message_dialog(self):
- self.d = QDialog()
- self.d.setModal(1)
- self.d.setWindowTitle('Please Check KeepKey Device')
- self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
+ self.d = WindowModalDialog(self.win, _('Please Check KeepKey Device'))
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
@@ -182,5 +174,3 @@ class KeepKeyQtHandler:
def dialog_stop(self):
self.d.hide()
-
-
diff --git a/plugins/labels/labels.py b/plugins/labels/labels.py
index 1cd099126..c36066daf 100644
--- a/plugins/labels/labels.py
+++ b/plugins/labels/labels.py
@@ -42,9 +42,9 @@ class LabelsPlugin(BasePlugin):
self.set_nonce(wallet, nonce)
return nonce
- def set_nonce(self, wallet, nonce, force_write=True):
+ def set_nonce(self, wallet, nonce):
self.print_error("set", wallet.basename(), "nonce to", nonce)
- wallet.storage.put("wallet_nonce", nonce, force_write)
+ wallet.storage.put("wallet_nonce", nonce)
@hook
def set_label(self, wallet, item, label):
@@ -61,7 +61,7 @@ class LabelsPlugin(BasePlugin):
t.setDaemon(True)
t.start()
# Caller will write the wallet
- self.set_nonce(wallet, nonce + 1, force_write=False)
+ self.set_nonce(wallet, nonce + 1)
def do_request(self, method, url = "/labels", is_batch=False, data=None):
url = 'https://' + self.target_host + url
@@ -125,8 +125,8 @@ class LabelsPlugin(BasePlugin):
self.print_error("received %d labels" % len(response))
# do not write to disk because we're in a daemon thread
- wallet.storage.put('labels', wallet.labels, False)
- self.set_nonce(wallet, response["nonce"] + 1, False)
+ wallet.storage.put('labels', wallet.labels)
+ self.set_nonce(wallet, response["nonce"] + 1)
self.on_pulled(wallet)
except Exception as e:
diff --git a/plugins/labels/qt.py b/plugins/labels/qt.py
index b84dd43c1..b3659c19f 100644
--- a/plugins/labels/qt.py
+++ b/plugins/labels/qt.py
@@ -6,7 +6,8 @@ from PyQt4.QtCore import *
from electrum.plugins import hook
from electrum.i18n import _
from electrum_gui.qt import EnterButton
-from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
+from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton
+from electrum_gui.qt.util import WindowModalDialog, OkButton
from labels import LabelsPlugin
@@ -25,25 +26,23 @@ class Plugin(LabelsPlugin):
partial(self.settings_dialog, window))
def settings_dialog(self, window):
- d = QDialog(window)
+ wallet = window.parent().wallet
+ d = WindowModalDialog(window, _("Label Settings"))
vbox = QVBoxLayout(d)
layout = QGridLayout()
vbox.addLayout(layout)
layout.addWidget(QLabel("Label sync options: "), 2, 0)
self.upload = ThreadedButton("Force upload",
- partial(self.push_thread, window.wallet),
+ partial(self.push_thread, wallet),
self.done_processing)
layout.addWidget(self.upload, 2, 1)
self.download = ThreadedButton("Force download",
- partial(self.pull_thread, window.wallet, True),
+ partial(self.pull_thread, wallet, True),
self.done_processing)
layout.addWidget(self.download, 2, 2)
self.accept = OkButton(d, _("Done"))
vbox.addLayout(Buttons(CancelButton(d), self.accept))
- if d.exec_():
- return True
- else:
- return False
+ return bool(d.exec_())
def on_pulled(self, wallet):
self.obj.emit(SIGNAL('labels_changed'), wallet)
diff --git a/plugins/ledger/qt.py b/plugins/ledger/qt.py
index aa83df244..7ebd0f106 100644
--- a/plugins/ledger/qt.py
+++ b/plugins/ledger/qt.py
@@ -1,7 +1,7 @@
-from PyQt4.Qt import QApplication, QMessageBox, QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, QThread, SIGNAL
+from PyQt4.Qt import QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, SIGNAL
import PyQt4.QtCore as QtCore
+import threading
-from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
from electrum.plugins import BasePlugin, hook
from ledger import LedgerPlugin
@@ -16,10 +16,10 @@ class Plugin(LedgerPlugin):
self.handler = BTChipQTHandler(window)
if self.btchip_is_connected():
if not self.wallet.check_proper_device():
- QMessageBox.information(window, _('Error'), _("This wallet does not match your Ledger device"), _('OK'))
+ window.show_error(_("This wallet does not match your Ledger device"))
self.wallet.force_watching_only = True
else:
- QMessageBox.information(window, _('Error'), _("Ledger device not detected.\nContinuing in watching-only mode."), _('OK'))
+ window.show_error(_("Ledger device not detected.\nContinuing in watching-only mode."))
self.wallet.force_watching_only = True
diff --git a/plugins/trezor/qt.py b/plugins/trezor/qt.py
index 2d62a4c7e..bd9217e5a 100644
--- a/plugins/trezor/qt.py
+++ b/plugins/trezor/qt.py
@@ -1,4 +1,4 @@
-from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
+from PyQt4.Qt import QVBoxLayout, QLabel, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
@@ -46,10 +46,7 @@ class TrezorQtHandler:
return self.passphrase
def pin_dialog(self):
- d = QDialog(None)
- d.setModal(1)
- d.setWindowTitle(_("Enter PIN"))
- d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
+ d = WindowModalDialog(self.win, _("Enter PIN"))
matrix = PinMatrixWidget()
vbox = QVBoxLayout()
vbox.addWidget(QLabel(self.message))
@@ -67,23 +64,18 @@ class TrezorQtHandler:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
else:
assert type(self.win) is InstallWizard
- from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
- d = QDialog()
- d.setModal(1)
- d.setLayout(make_password_dialog(d, None, self.message, False))
- confirmed, p, passphrase = run_password_dialog(d, None, None)
+ from electrum_gui.qt.password_dialog import PasswordDialog
+ d = PasswordDialog(self.win, None, None, self.message, False)
+ confirmed, p, passphrase = d.run()
if not confirmed:
- QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
+ self.win.show_critical(_("Password request canceled"))
self.passphrase = None
else:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
self.done.set()
def message_dialog(self):
- self.d = QDialog()
- self.d.setModal(1)
- self.d.setWindowTitle('Please Check Trezor Device')
- self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
+ self.d = WindowModalDialog(self.win, _('Please Check Trezor Device'))
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
@@ -108,11 +100,11 @@ class Plugin(TrezorPlugin):
try:
self.get_client().ping('t')
except BaseException as e:
- QMessageBox.information(window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
+ window.show_error(_('Trezor device not detected.\nContinuing in watching-only mode.\nReason:\n' + str(e)))
self.wallet.force_watching_only = True
return
if self.wallet.addresses() and not self.wallet.check_proper_device():
- QMessageBox.information(window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
+ window.show_error(_("This wallet does not match your Trezor device"))
self.wallet.force_watching_only = True
@hook
@@ -171,7 +163,7 @@ class Plugin(TrezorPlugin):
return
get_label = lambda: self.get_client().features.label
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
- d = QDialog()
+ d = WindowModalDialog(window, _("Trezor Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel("Trezor Options"),0,0)
layout.addWidget(QLabel("ID:"),1,0)
@@ -194,7 +186,3 @@ class Plugin(TrezorPlugin):
layout.addWidget(current_label_label,3,0)
layout.addWidget(change_label_button,3,1)
d.exec_()
-
-
-
-
diff --git a/plugins/trustedcoin/qt.py b/plugins/trustedcoin/qt.py
index b1dd00e8a..3ae7e8ca0 100644
--- a/plugins/trustedcoin/qt.py
+++ b/plugins/trustedcoin/qt.py
@@ -1,4 +1,23 @@
+#!/usr/bin/env python
+#
+# Electrum - Lightweight Bitcoin Client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# 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 .
+
from functools import partial
+from threading import Thread
from PyQt4.QtGui import *
from PyQt4.QtCore import *
@@ -10,7 +29,19 @@ from electrum_gui.qt.main_window import StatusBarButton
from electrum.i18n import _
from electrum.plugins import hook
-from trustedcoin import TrustedCoinPlugin
+from trustedcoin import TrustedCoinPlugin, Wallet_2fa
+
+def need_server(wallet, tx):
+ from electrum.account import BIP32_Account
+ # Detect if the server is needed
+ long_id, short_id = wallet.get_user_id()
+ xpub3 = wallet.master_public_keys['x3/']
+ for x in tx.inputs_to_sign():
+ if x[0:2] == 'ff':
+ xpub, sequence = BIP32_Account.parse_xpubkey(x)
+ if xpub == xpub3:
+ return True
+ return False
class Plugin(TrustedCoinPlugin):
@@ -27,8 +58,7 @@ class Plugin(TrustedCoinPlugin):
t.start()
def auth_dialog(self, window):
- d = QDialog(window)
- d.setModal(1)
+ d = WindowModalDialog(window, _("Authorization"))
vbox = QVBoxLayout(d)
pw = AmountEdit(None, is_int = True)
msg = _('Please enter your Google Authenticator code')
@@ -55,16 +85,18 @@ class Plugin(TrustedCoinPlugin):
self.print_error("twofactor: xpub3 not needed")
window.wallet.auth_code = auth_code
+ def waiting_dialog(self, window, on_success=None):
+ task = partial(self.request_billing_info, window.wallet)
+ return WaitingDialog(window, 'Getting billing information...', task,
+ on_success=on_success)
+
@hook
def abort_send(self, window):
wallet = window.wallet
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
if wallet.billing_info is None:
# request billing info before forming the transaction
- task = partial(self.request_billing_info, wallet)
- waiting_dialog = WaitingDialog(window, 'please wait...', task)
- waiting_dialog.start()
- waiting_dialog.wait()
+ waiting_dialog(self, window).wait()
if wallet.billing_info is None:
window.show_message('Could not contact server')
return True
@@ -72,9 +104,8 @@ class Plugin(TrustedCoinPlugin):
def settings_dialog(self, window):
- task = partial(self.request_billing_info, window.wallet)
- self.waiting_dialog = WaitingDialog(window, 'please wait...', task, partial(self.show_settings_dialog, window))
- self.waiting_dialog.start()
+ on_success = partial(self.show_settings_dialog, window)
+ self.waiting_dialog(window, on_success)
def show_settings_dialog(self, window, success):
if not success:
@@ -82,8 +113,7 @@ class Plugin(TrustedCoinPlugin):
return
wallet = window.wallet
- d = QDialog(window)
- d.setWindowTitle("TrustedCoin Information")
+ d = WindowModalDialog(window, _("TrustedCoin Information"))
d.setMinimumSize(500, 200)
vbox = QVBoxLayout(d)
hbox = QHBoxLayout()
@@ -238,7 +268,5 @@ class Plugin(TrustedCoinPlugin):
server.auth(_id, otp)
return True
except:
- QMessageBox.information(window, _('Message'), _('Incorrect password'), _('OK'))
+ window.show_message(_('Incorrect password'))
pw.setText('')
-
-
diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py
index 25bcf4dce..00668f61d 100644
--- a/plugins/trustedcoin/trustedcoin.py
+++ b/plugins/trustedcoin/trustedcoin.py
@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from threading import Thread
import socket
import os
import re
@@ -270,18 +269,6 @@ def make_billing_address(wallet, num):
address = public_key_to_bc_address( cK )
return address
-def need_server(wallet, tx):
- from electrum.account import BIP32_Account
- # Detect if the server is needed
- long_id, short_id = wallet.get_user_id()
- xpub3 = wallet.master_public_keys['x3/']
- for x in tx.inputs_to_sign():
- if x[0:2] == 'ff':
- xpub, sequence = BIP32_Account.parse_xpubkey(x)
- if xpub == xpub3:
- return True
- return False
-
class TrustedCoinPlugin(BasePlugin):
@@ -318,8 +305,8 @@ class TrustedCoinPlugin(BasePlugin):
return
password = window.password_dialog()
- wallet.storage.put('seed_version', wallet.seed_version, True)
- wallet.storage.put('use_encryption', password is not None, True)
+ wallet.storage.put('seed_version', wallet.seed_version)
+ wallet.storage.put('use_encryption', password is not None)
words = seed.split()
n = len(words)/2