174fbdfa3cb1e683459dfed84ce573a05feb1839
[electrum-nvc.git] / gui / qt / main_window.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 import webbrowser
24 import shutil
25 import StringIO
26
27
28 import PyQt4
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
32
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
35
36 import icons_rc
37
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
43
44 from electrum.paymentrequest import PR_UNPAID, PR_PAID
45
46
47 from electrum import bmp, pyqrnative
48
49 from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
50 from network_dialog import NetworkDialog
51 from qrcodewidget import QRCodeWidget
52
53 from decimal import Decimal
54
55 import platform
56 import httplib
57 import socket
58 import webbrowser
59 import csv
60
61 if platform.system() == 'Windows':
62     MONOSPACE_FONT = 'Lucida Console'
63 elif platform.system() == 'Darwin':
64     MONOSPACE_FONT = 'Monaco'
65 else:
66     MONOSPACE_FONT = 'monospace'
67
68 from electrum import ELECTRUM_VERSION
69 import re
70
71 from util import *
72
73
74 def format_status(x):
75     if x == PR_UNPAID:
76         return _('Unpaid')
77     elif x == PR_PAID:
78         return _('Paid')
79
80
81 class StatusBarButton(QPushButton):
82     def __init__(self, icon, tooltip, func):
83         QPushButton.__init__(self, icon, '')
84         self.setToolTip(tooltip)
85         self.setFlat(True)
86         self.setMaximumWidth(25)
87         self.clicked.connect(func)
88         self.func = func
89         self.setIconSize(QSize(25,25))
90
91     def keyPressEvent(self, e):
92         if e.key() == QtCore.Qt.Key_Return:
93             apply(self.func,())
94
95
96
97
98
99
100
101
102
103
104 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
105
106 class ElectrumWindow(QMainWindow):
107
108
109
110     def __init__(self, config, network, gui_object):
111         QMainWindow.__init__(self)
112
113         self.config = config
114         self.network = network
115         self.gui_object = gui_object
116         self.tray = gui_object.tray
117         self.go_lite = gui_object.go_lite
118         self.lite = None
119
120         self.create_status_bar()
121         self.need_update = threading.Event()
122
123         self.decimal_point = config.get('decimal_point', 5)
124         self.num_zeros     = int(config.get('num_zeros',0))
125         self.invoices      = {}
126
127         set_language(config.get('language'))
128
129         self.funds_error = False
130         self.completions = QStringListModel()
131
132         self.tabs = tabs = QTabWidget(self)
133         self.column_widths = self.config.get("column_widths_2", default_column_widths )
134         tabs.addTab(self.create_history_tab(), _('History') )
135         tabs.addTab(self.create_send_tab(), _('Send') )
136         tabs.addTab(self.create_receive_tab(), _('Receive') )
137         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
138         tabs.addTab(self.create_invoices_tab(), _('Invoices') )
139         tabs.addTab(self.create_console_tab(), _('Console') )
140         tabs.setMinimumSize(600, 400)
141         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
142         self.setCentralWidget(tabs)
143
144         g = self.config.get("winpos-qt",[100, 100, 840, 400])
145         self.setGeometry(g[0], g[1], g[2], g[3])
146         if self.config.get("is_maximized"):
147             self.showMaximized()
148
149         self.setWindowIcon(QIcon(":icons/electrum.png"))
150         self.init_menubar()
151
152         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
153         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
154         QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
155         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
156         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
157
158         for i in range(tabs.count()):
159             QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
160
161         self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
162         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
163         self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
164         self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
165         self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
166
167         self.history_list.setFocus(True)
168
169         # network callbacks
170         if self.network:
171             self.network.register_callback('updated', lambda: self.need_update.set())
172             self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
173             self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
174             self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
175             self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
176
177             # set initial message
178             self.console.showMessage(self.network.banner)
179
180         self.wallet = None
181
182
183     def update_account_selector(self):
184         # account selector
185         accounts = self.wallet.get_account_names()
186         self.account_selector.clear()
187         if len(accounts) > 1:
188             self.account_selector.addItems([_("All accounts")] + accounts.values())
189             self.account_selector.setCurrentIndex(0)
190             self.account_selector.show()
191         else:
192             self.account_selector.hide()
193
194
195     def load_wallet(self, wallet):
196         import electrum
197
198         self.wallet = wallet
199         self.update_wallet_format()
200
201         self.invoices = self.wallet.storage.get('invoices', {})
202         self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
203         self.current_account = self.wallet.storage.get("current_account", None)
204         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.storage.path
205         if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
206         self.setWindowTitle( title )
207         self.update_wallet()
208         # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
209         self.notify_transactions()
210         self.update_account_selector()
211         # update menus
212         self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
213         self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
214         self.password_menu.setEnabled(not self.wallet.is_watching_only())
215         self.seed_menu.setEnabled(self.wallet.has_seed())
216         self.mpk_menu.setEnabled(self.wallet.is_deterministic())
217         self.import_menu.setEnabled(self.wallet.can_import())
218
219         self.update_lock_icon()
220         self.update_buttons_on_seed()
221         self.update_console()
222
223         run_hook('load_wallet', wallet)
224
225
226     def update_wallet_format(self):
227         # convert old-format imported keys
228         if self.wallet.imported_keys:
229             password = self.password_dialog(_("Please enter your password in order to update imported keys"))
230             try:
231                 self.wallet.convert_imported_keys(password)
232             except:
233                 self.show_message("error")
234
235
236     def open_wallet(self):
237         wallet_folder = self.wallet.storage.path
238         filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
239         if not filename:
240             return
241
242         storage = WalletStorage({'wallet_path': filename})
243         if not storage.file_exists:
244             self.show_message("file not found "+ filename)
245             return
246
247         self.wallet.stop_threads()
248
249         # create new wallet
250         wallet = Wallet(storage)
251         wallet.start_threads(self.network)
252
253         self.load_wallet(wallet)
254
255
256
257     def backup_wallet(self):
258         import shutil
259         path = self.wallet.storage.path
260         wallet_folder = os.path.dirname(path)
261         filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
262         if not filename:
263             return
264
265         new_path = os.path.join(wallet_folder, filename)
266         if new_path != path:
267             try:
268                 shutil.copy2(path, new_path)
269                 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
270             except (IOError, os.error), reason:
271                 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
272
273
274     def new_wallet(self):
275         import installwizard
276
277         wallet_folder = os.path.dirname(self.wallet.storage.path)
278         filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
279         if not filename:
280             return
281         filename = os.path.join(wallet_folder, filename)
282
283         storage = WalletStorage({'wallet_path': filename})
284         if storage.file_exists:
285             QMessageBox.critical(None, "Error", _("File exists"))
286             return
287
288         wizard = installwizard.InstallWizard(self.config, self.network, storage)
289         wallet = wizard.run('new')
290         if wallet:
291             self.load_wallet(wallet)
292
293
294
295     def init_menubar(self):
296         menubar = QMenuBar()
297
298         file_menu = menubar.addMenu(_("&File"))
299         file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
300         file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
301         file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
302         file_menu.addAction(_("&Quit"), self.close)
303
304         wallet_menu = menubar.addMenu(_("&Wallet"))
305         wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
306         self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
307
308         wallet_menu.addSeparator()
309
310         self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
311         self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
312         self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
313
314         wallet_menu.addSeparator()
315         labels_menu = wallet_menu.addMenu(_("&Labels"))
316         labels_menu.addAction(_("&Import"), self.do_import_labels)
317         labels_menu.addAction(_("&Export"), self.do_export_labels)
318
319         self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
320         self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
321         self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
322         self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
323         wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
324
325         tools_menu = menubar.addMenu(_("&Tools"))
326
327         # Settings / Preferences are all reserved keywords in OSX using this as work around
328         tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
329         tools_menu.addAction(_("&Network"), self.run_network_dialog)
330         tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
331         tools_menu.addSeparator()
332         tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
333         tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
334         tools_menu.addSeparator()
335
336         csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
337         csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
338         csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
339
340         raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
341         raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
342         raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
343         raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
344         self.raw_transaction_menu = raw_transaction_menu
345
346         help_menu = menubar.addMenu(_("&Help"))
347         help_menu.addAction(_("&About"), self.show_about)
348         help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
349         help_menu.addSeparator()
350         help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
351         help_menu.addAction(_("&Report Bug"), self.show_report_bug)
352
353         self.setMenuBar(menubar)
354
355     def show_about(self):
356         QMessageBox.about(self, "Electrum",
357             _("Version")+" %s" % (self.wallet.electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
358
359     def show_report_bug(self):
360         QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
361             _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
362
363
364     def notify_transactions(self):
365         if not self.network or not self.network.is_connected():
366             return
367
368         print_error("Notifying GUI")
369         if len(self.network.pending_transactions_for_notifications) > 0:
370             # Combine the transactions if there are more then three
371             tx_amount = len(self.network.pending_transactions_for_notifications)
372             if(tx_amount >= 3):
373                 total_amount = 0
374                 for tx in self.network.pending_transactions_for_notifications:
375                     is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
376                     if(v > 0):
377                         total_amount += v
378
379                 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
380                                 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
381
382                 self.network.pending_transactions_for_notifications = []
383             else:
384               for tx in self.network.pending_transactions_for_notifications:
385                   if tx:
386                       self.network.pending_transactions_for_notifications.remove(tx)
387                       is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
388                       if(v > 0):
389                           self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
390
391     def notify(self, message):
392         self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
393
394
395
396     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
397     def getOpenFileName(self, title, filter = ""):
398         directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
399         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
400         if fileName and directory != os.path.dirname(fileName):
401             self.config.set_key('io_dir', os.path.dirname(fileName), True)
402         return fileName
403
404     def getSaveFileName(self, title, filename, filter = ""):
405         directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
406         path = os.path.join( directory, filename )
407         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
408         if fileName and directory != os.path.dirname(fileName):
409             self.config.set_key('io_dir', os.path.dirname(fileName), True)
410         return fileName
411
412     def close(self):
413         QMainWindow.close(self)
414         run_hook('close_main_window')
415
416     def connect_slots(self, sender):
417         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
418         self.previous_payto_e=''
419
420     def timer_actions(self):
421         if self.need_update.is_set():
422             self.update_wallet()
423             self.need_update.clear()
424         run_hook('timer_actions')
425
426     def format_amount(self, x, is_diff=False, whitespaces=False):
427         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
428
429
430     def get_decimal_point(self):
431         return self.decimal_point
432
433
434     def base_unit(self):
435         assert self.decimal_point in [5,8]
436         return "BTC" if self.decimal_point == 8 else "mBTC"
437
438
439     def update_status(self):
440         if self.network is None or not self.network.is_running():
441             text = _("Offline")
442             icon = QIcon(":icons/status_disconnected.png")
443
444         elif self.network.is_connected():
445             if not self.wallet.up_to_date:
446                 text = _("Synchronizing...")
447                 icon = QIcon(":icons/status_waiting.png")
448             elif self.network.server_lag > 1:
449                 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
450                 icon = QIcon(":icons/status_lagging.png")
451             else:
452                 c, u = self.wallet.get_account_balance(self.current_account)
453                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
454                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
455
456                 # append fiat balance and price from exchange rate plugin
457                 r = {}
458                 run_hook('get_fiat_status_text', c+u, r)
459                 quote = r.get(0)
460                 if quote:
461                     text += "%s"%quote
462
463                 self.tray.setToolTip(text)
464                 icon = QIcon(":icons/status_connected.png")
465         else:
466             text = _("Not connected")
467             icon = QIcon(":icons/status_disconnected.png")
468
469         self.balance_label.setText(text)
470         self.status_button.setIcon( icon )
471
472
473     def update_wallet(self):
474         self.update_status()
475         if self.wallet.up_to_date or not self.network or not self.network.is_connected():
476             self.update_history_tab()
477             self.update_receive_tab()
478             self.update_contacts_tab()
479             self.update_completions()
480             self.update_invoices_tab()
481
482
483     def create_history_tab(self):
484         self.history_list = l = MyTreeWidget(self)
485         l.setColumnCount(5)
486         for i,width in enumerate(self.column_widths['history']):
487             l.setColumnWidth(i, width)
488         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
489         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
490         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
491
492         l.customContextMenuRequested.connect(self.create_history_menu)
493         return l
494
495
496     def create_history_menu(self, position):
497         self.history_list.selectedIndexes()
498         item = self.history_list.currentItem()
499         be = self.config.get('block_explorer', 'Blockchain.info')
500         if be == 'Blockchain.info':
501             block_explorer = 'https://blockchain.info/tx/'
502         elif be == 'Blockr.io':
503             block_explorer = 'https://blockr.io/tx/info/'
504         elif be == 'Insight.is':
505             block_explorer = 'http://live.insight.is/tx/'
506         if not item: return
507         tx_hash = str(item.data(0, Qt.UserRole).toString())
508         if not tx_hash: return
509         menu = QMenu()
510         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
511         menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
512         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
513         menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
514         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
515
516
517     def show_transaction(self, tx):
518         import transaction_dialog
519         d = transaction_dialog.TxDialog(tx, self)
520         d.exec_()
521
522     def tx_label_clicked(self, item, column):
523         if column==2 and item.isSelected():
524             self.is_edit=True
525             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
526             self.history_list.editItem( item, column )
527             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
528             self.is_edit=False
529
530     def tx_label_changed(self, item, column):
531         if self.is_edit:
532             return
533         self.is_edit=True
534         tx_hash = str(item.data(0, Qt.UserRole).toString())
535         tx = self.wallet.transactions.get(tx_hash)
536         text = unicode( item.text(2) )
537         self.wallet.set_label(tx_hash, text)
538         if text:
539             item.setForeground(2, QBrush(QColor('black')))
540         else:
541             text = self.wallet.get_default_label(tx_hash)
542             item.setText(2, text)
543             item.setForeground(2, QBrush(QColor('gray')))
544         self.is_edit=False
545
546
547     def edit_label(self, is_recv):
548         l = self.receive_list if is_recv else self.contacts_list
549         item = l.currentItem()
550         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
551         l.editItem( item, 1 )
552         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
553
554
555
556     def address_label_clicked(self, item, column, l, column_addr, column_label):
557         if column == column_label and item.isSelected():
558             is_editable = item.data(0, 32).toBool()
559             if not is_editable:
560                 return
561             addr = unicode( item.text(column_addr) )
562             label = unicode( item.text(column_label) )
563             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
564             l.editItem( item, column )
565             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
566
567
568     def address_label_changed(self, item, column, l, column_addr, column_label):
569         if column == column_label:
570             addr = unicode( item.text(column_addr) )
571             text = unicode( item.text(column_label) )
572             is_editable = item.data(0, 32).toBool()
573             if not is_editable:
574                 return
575
576             changed = self.wallet.set_label(addr, text)
577             if changed:
578                 self.update_history_tab()
579                 self.update_completions()
580
581             self.current_item_changed(item)
582
583         run_hook('item_changed', item, column)
584
585
586     def current_item_changed(self, a):
587         run_hook('current_item_changed', a)
588
589
590
591     def update_history_tab(self):
592
593         self.history_list.clear()
594         for item in self.wallet.get_tx_history(self.current_account):
595             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
596             time_str = _("unknown")
597             if conf > 0:
598                 try:
599                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
600                 except Exception:
601                     time_str = _("error")
602
603             if conf == -1:
604                 time_str = 'unverified'
605                 icon = QIcon(":icons/unconfirmed.png")
606             elif conf == 0:
607                 time_str = 'pending'
608                 icon = QIcon(":icons/unconfirmed.png")
609             elif conf < 6:
610                 icon = QIcon(":icons/clock%d.png"%conf)
611             else:
612                 icon = QIcon(":icons/confirmed.png")
613
614             if value is not None:
615                 v_str = self.format_amount(value, True, whitespaces=True)
616             else:
617                 v_str = '--'
618
619             balance_str = self.format_amount(balance, whitespaces=True)
620
621             if tx_hash:
622                 label, is_default_label = self.wallet.get_label(tx_hash)
623             else:
624                 label = _('Pruned transaction outputs')
625                 is_default_label = False
626
627             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
628             item.setFont(2, QFont(MONOSPACE_FONT))
629             item.setFont(3, QFont(MONOSPACE_FONT))
630             item.setFont(4, QFont(MONOSPACE_FONT))
631             if value < 0:
632                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
633             if tx_hash:
634                 item.setData(0, Qt.UserRole, tx_hash)
635                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
636             if is_default_label:
637                 item.setForeground(2, QBrush(QColor('grey')))
638
639             item.setIcon(0, icon)
640             self.history_list.insertTopLevelItem(0,item)
641
642
643         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
644         run_hook('history_tab_update')
645
646
647     def create_send_tab(self):
648         w = QWidget()
649
650         self.send_grid = grid = QGridLayout(w)
651         grid.setSpacing(8)
652         grid.setColumnMinimumWidth(3,300)
653         grid.setColumnStretch(5,1)
654         grid.setRowStretch(8, 1)
655
656         from paytoedit import PayToEdit
657         self.amount_e = BTCAmountEdit(self.get_decimal_point)
658         self.payto_e = PayToEdit(self.amount_e)
659         self.payto_help = HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)'))
660         grid.addWidget(QLabel(_('Pay to')), 1, 0)
661         grid.addWidget(self.payto_e, 1, 1, 1, 3)
662         grid.addWidget(self.payto_help, 1, 4)
663
664         completer = QCompleter()
665         completer.setCaseSensitivity(False)
666         self.payto_e.setCompleter(completer)
667         completer.setModel(self.completions)
668
669         self.message_e = MyLineEdit()
670         self.message_help = HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.'))
671         grid.addWidget(QLabel(_('Description')), 2, 0)
672         grid.addWidget(self.message_e, 2, 1, 1, 3)
673         grid.addWidget(self.message_help, 2, 4)
674
675         self.from_label = QLabel(_('From'))
676         grid.addWidget(self.from_label, 3, 0)
677         self.from_list = MyTreeWidget(self)
678         self.from_list.setColumnCount(2)
679         self.from_list.setColumnWidth(0, 350)
680         self.from_list.setColumnWidth(1, 50)
681         self.from_list.setHeaderHidden(True)
682         self.from_list.setMaximumHeight(80)
683         self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
684         self.from_list.customContextMenuRequested.connect(self.from_list_menu)
685         grid.addWidget(self.from_list, 3, 1, 1, 3)
686         self.set_pay_from([])
687
688         self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
689                                       + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') \
690                                       + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
691         grid.addWidget(QLabel(_('Amount')), 4, 0)
692         grid.addWidget(self.amount_e, 4, 1, 1, 2)
693         grid.addWidget(self.amount_help, 4, 3)
694
695         self.fee_e = BTCAmountEdit(self.get_decimal_point)
696         grid.addWidget(QLabel(_('Fee')), 5, 0)
697         grid.addWidget(self.fee_e, 5, 1, 1, 2)
698         grid.addWidget(HelpButton(
699                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
700                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
701                     + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 5, 3)
702
703         self.send_button = EnterButton(_("Send"), self.do_send)
704         grid.addWidget(self.send_button, 6, 1)
705
706         b = EnterButton(_("Clear"), self.do_clear)
707         grid.addWidget(b, 6, 2)
708
709         self.payto_sig = QLabel('')
710         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
711
712         #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
713         #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
714         w.setLayout(grid)
715
716         def entry_changed( is_fee ):
717             self.funds_error = False
718
719             if self.amount_e.is_shortcut:
720                 self.amount_e.is_shortcut = False
721                 sendable = self.get_sendable_balance()
722                 # there is only one output because we are completely spending inputs
723                 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
724                 fee = self.wallet.estimated_fee(inputs, 1)
725                 amount = total - fee
726                 self.amount_e.setAmount(amount)
727                 self.fee_e.setAmount(fee)
728                 return
729
730             amount = self.amount_e.get_amount()
731             fee = self.fee_e.get_amount()
732
733             if not is_fee: fee = None
734             if amount is None:
735                 return
736             # assume that there will be 2 outputs (one for change)
737             inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
738             if not is_fee:
739                 self.fee_e.setAmount(fee)
740             if inputs:
741                 palette = QPalette()
742                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
743                 text = ""
744             else:
745                 palette = QPalette()
746                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
747                 self.funds_error = True
748                 text = _( "Not enough funds" )
749                 c, u = self.wallet.get_frozen_balance()
750                 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
751
752             self.statusBar().showMessage(text)
753             self.amount_e.setPalette(palette)
754             self.fee_e.setPalette(palette)
755
756         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
757         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
758
759         run_hook('create_send_tab', grid)
760         return w
761
762     def from_list_delete(self, item):
763         i = self.from_list.indexOfTopLevelItem(item)
764         self.pay_from.pop(i)
765         self.redraw_from_list()
766
767     def from_list_menu(self, position):
768         item = self.from_list.itemAt(position)
769         menu = QMenu()
770         menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
771         menu.exec_(self.from_list.viewport().mapToGlobal(position))
772
773     def set_pay_from(self, domain = None):
774         self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
775         self.redraw_from_list()
776
777     def redraw_from_list(self):
778         self.from_list.clear()
779         self.from_label.setHidden(len(self.pay_from) == 0)
780         self.from_list.setHidden(len(self.pay_from) == 0)
781
782         def format(x):
783             h = x.get('prevout_hash')
784             return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
785
786         for item in self.pay_from:
787             self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
788
789     def update_completions(self):
790         l = []
791         for addr,label in self.wallet.labels.items():
792             if addr in self.wallet.addressbook:
793                 l.append( label + '  <' + addr + '>')
794
795         run_hook('update_completions', l)
796         self.completions.setStringList(l)
797
798
799     def protected(func):
800         return lambda s, *args: s.do_protect(func, args)
801
802
803     def read_send_tab(self):
804         label = unicode( self.message_e.text() )
805
806         if self.gui_object.payment_request:
807             outputs = self.gui_object.payment_request.get_outputs()
808         else:
809             outputs = self.payto_e.get_outputs()
810
811         if not outputs:
812             QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
813             return
814
815         for addr, x in outputs:
816             if addr is None or not bitcoin.is_address(addr):
817                 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
818                 return
819             if x is None:
820                 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
821                 return
822
823         amount = sum(map(lambda x:x[1], outputs))
824
825         fee = self.fee_e.get_amount()
826         if fee is None:
827             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
828             return
829
830         confirm_amount = self.config.get('confirm_amount', 100000000)
831         if amount >= confirm_amount:
832             o = '\n'.join(map(lambda x:x[0], outputs))
833             if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
834                 return
835             
836         confirm_fee = self.config.get('confirm_fee', 100000)
837         if fee >= confirm_fee:
838             if not self.question(_("The fee for this transaction seems unusually high.\nAre you really sure you want to pay %(fee)s in fees?")%{ 'fee' : self.format_amount(fee) + ' '+ self.base_unit()}):
839                 return
840
841         coins = self.get_coins()
842         return outputs, fee, label, coins
843
844
845     def do_send(self):
846         r = self.read_send_tab()
847         if not r:
848             return
849         outputs, fee, label, coins = r
850         self.send_tx(outputs, fee, label, coins)
851
852
853     @protected
854     def send_tx(self, outputs, fee, label, coins, password):
855         self.send_button.setDisabled(True)
856
857         # first, create an unsigned tx 
858         try:
859             tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
860             tx.error = None
861         except Exception as e:
862             traceback.print_exc(file=sys.stdout)
863             self.show_message(str(e))
864             self.send_button.setDisabled(False)
865             return
866
867         # call hook to see if plugin needs gui interaction
868         run_hook('send_tx', tx)
869
870         # sign the tx
871         def sign_thread():
872             time.sleep(0.1)
873             keypairs = {}
874             self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
875             self.wallet.sign_transaction(tx, keypairs, password)
876             return tx, fee, label
877
878         def sign_done(tx, fee, label):
879             if tx.error:
880                 self.show_message(tx.error)
881                 self.send_button.setDisabled(False)
882                 return
883             if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
884                 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
885                 self.send_button.setDisabled(False)
886                 return
887             if label:
888                 self.wallet.set_label(tx.hash(), label)
889
890             if not tx.is_complete() or self.config.get('show_before_broadcast'):
891                 self.show_transaction(tx)
892                 self.do_clear()
893                 self.send_button.setDisabled(False)
894                 return
895
896             self.broadcast_transaction(tx)
897
898         self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
899         self.waiting_dialog.start()
900
901
902
903     def broadcast_transaction(self, tx):
904
905         def broadcast_thread():
906             pr = self.gui_object.payment_request
907             if pr is None:
908                 return self.wallet.sendtx(tx)
909
910             if pr.has_expired():
911                 self.gui_object.payment_request = None
912                 return False, _("Payment request has expired")
913
914             status, msg =  self.wallet.sendtx(tx)
915             if not status:
916                 return False, msg
917
918             self.invoices[pr.get_id()] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), PR_PAID, tx.hash())
919             self.wallet.storage.put('invoices', self.invoices)
920             self.update_invoices_tab()
921             self.gui_object.payment_request = None
922             refund_address = self.wallet.addresses()[0]
923             ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
924             if ack_status:
925                 msg = ack_msg
926
927             return status, msg
928
929         def broadcast_done(status, msg):
930             if status:
931                 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
932                 self.do_clear()
933             else:
934                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
935             self.send_button.setDisabled(False)
936
937         self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
938         self.waiting_dialog.start()
939
940
941
942     def prepare_for_payment_request(self):
943         self.tabs.setCurrentIndex(1)
944         self.payto_e.is_pr = True
945         for e in [self.payto_e, self.amount_e, self.message_e]:
946             e.setFrozen(True)
947         for h in [self.payto_help, self.amount_help, self.message_help]:
948             h.hide()
949         self.payto_e.setText(_("please wait..."))
950         return True
951
952     def payment_request_ok(self):
953         pr = self.gui_object.payment_request
954         pr_id = pr.get_id()
955         if pr_id not in self.invoices:
956             self.invoices[pr_id] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), PR_UNPAID, None)
957             self.wallet.storage.put('invoices', self.invoices)
958             self.update_invoices_tab()
959         else:
960             print_error('invoice already in list')
961
962         status = self.invoices[pr_id][3]
963         if status == PR_PAID:
964             self.do_clear()
965             self.show_message("invoice already paid")
966             self.gui_object.payment_request = None
967             return
968
969         self.payto_help.show()
970         self.payto_help.set_alt(lambda: self.show_pr_details(pr))
971
972         self.payto_e.setGreen()
973         self.payto_e.setText(pr.domain)
974         self.amount_e.setText(self.format_amount(pr.get_amount()))
975         self.message_e.setText(pr.get_memo())
976
977     def payment_request_error(self):
978         self.do_clear()
979         self.show_message(self.gui_object.payment_request.error)
980         self.gui_object.payment_request = None
981
982     def set_send(self, address, amount, label, message):
983
984         if label and self.wallet.labels.get(address) != label:
985             if self.question('Give label "%s" to address %s ?'%(label,address)):
986                 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
987                     self.wallet.addressbook.append(address)
988                 self.wallet.set_label(address, label)
989
990         self.tabs.setCurrentIndex(1)
991         label = self.wallet.labels.get(address)
992         m_addr = label + '  <'+ address +'>' if label else address
993         self.payto_e.setText(m_addr)
994
995         self.message_e.setText(message)
996         if amount:
997             self.amount_e.setText(amount)
998
999
1000     def do_clear(self):
1001         self.payto_e.is_pr = False
1002         self.payto_sig.setVisible(False)
1003         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1004             e.setText('')
1005             e.setFrozen(False)
1006
1007         for h in [self.payto_help, self.amount_help, self.message_help]:
1008             h.show()
1009
1010         self.payto_help.set_alt(None)
1011         self.set_pay_from([])
1012         self.update_status()
1013
1014
1015
1016     def set_addrs_frozen(self,addrs,freeze):
1017         for addr in addrs:
1018             if not addr: continue
1019             if addr in self.wallet.frozen_addresses and not freeze:
1020                 self.wallet.unfreeze(addr)
1021             elif addr not in self.wallet.frozen_addresses and freeze:
1022                 self.wallet.freeze(addr)
1023         self.update_receive_tab()
1024
1025
1026
1027     def create_list_tab(self, headers):
1028         "generic tab creation method"
1029         l = MyTreeWidget(self)
1030         l.setColumnCount( len(headers) )
1031         l.setHeaderLabels( headers )
1032
1033         w = QWidget()
1034         vbox = QVBoxLayout()
1035         w.setLayout(vbox)
1036
1037         vbox.setMargin(0)
1038         vbox.setSpacing(0)
1039         vbox.addWidget(l)
1040         buttons = QWidget()
1041         vbox.addWidget(buttons)
1042
1043         return l, w
1044
1045
1046     def create_receive_tab(self):
1047         l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1048         for i,width in enumerate(self.column_widths['receive']):
1049             l.setColumnWidth(i, width)
1050         l.setContextMenuPolicy(Qt.CustomContextMenu)
1051         l.customContextMenuRequested.connect(self.create_receive_menu)
1052         l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1053         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1054         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1055         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1056         self.receive_list = l
1057         return w
1058
1059
1060
1061
1062     def save_column_widths(self):
1063         self.column_widths["receive"] = []
1064         for i in range(self.receive_list.columnCount() -1):
1065             self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1066
1067         self.column_widths["history"] = []
1068         for i in range(self.history_list.columnCount() - 1):
1069             self.column_widths["history"].append(self.history_list.columnWidth(i))
1070
1071         self.column_widths["contacts"] = []
1072         for i in range(self.contacts_list.columnCount() - 1):
1073             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1074
1075         self.config.set_key("column_widths_2", self.column_widths, True)
1076
1077
1078     def create_contacts_tab(self):
1079         l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1080         l.setContextMenuPolicy(Qt.CustomContextMenu)
1081         l.customContextMenuRequested.connect(self.create_contact_menu)
1082         for i,width in enumerate(self.column_widths['contacts']):
1083             l.setColumnWidth(i, width)
1084         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1085         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1086         self.contacts_list = l
1087         return w
1088
1089
1090     def create_invoices_tab(self):
1091         l, w = self.create_list_tab([_('Requestor'), _('Memo'),_('Amount'), _('Status')])
1092         h = l.header()
1093         h.setStretchLastSection(False)
1094         h.setResizeMode(1, QHeaderView.Stretch)
1095         l.setContextMenuPolicy(Qt.CustomContextMenu)
1096         l.customContextMenuRequested.connect(self.create_invoice_menu)
1097         self.invoices_list = l
1098         return w
1099
1100     def update_invoices_tab(self):
1101         invoices = self.wallet.storage.get('invoices', {})
1102         l = self.invoices_list
1103         l.clear()
1104         for key, value in invoices.items():
1105             try:
1106                 domain, memo, amount, status, tx_hash = value
1107             except:
1108                 invoices.pop(key)
1109                 continue
1110             item = QTreeWidgetItem( [ domain, memo, self.format_amount(amount), format_status(status)] )
1111             l.addTopLevelItem(item)
1112
1113         l.setCurrentItem(l.topLevelItem(0))
1114
1115
1116
1117     def delete_imported_key(self, addr):
1118         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1119             self.wallet.delete_imported_key(addr)
1120             self.update_receive_tab()
1121             self.update_history_tab()
1122
1123     def edit_account_label(self, k):
1124         text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1125         if ok:
1126             label = unicode(text)
1127             self.wallet.set_label(k,label)
1128             self.update_receive_tab()
1129
1130     def account_set_expanded(self, item, k, b):
1131         item.setExpanded(b)
1132         self.accounts_expanded[k] = b
1133
1134     def create_account_menu(self, position, k, item):
1135         menu = QMenu()
1136         if item.isExpanded():
1137             menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1138         else:
1139             menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1140         menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1141         if self.wallet.seed_version > 4:
1142             menu.addAction(_("View details"), lambda: self.show_account_details(k))
1143         if self.wallet.account_is_pending(k):
1144             menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1145         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1146
1147     def delete_pending_account(self, k):
1148         self.wallet.delete_pending_account(k)
1149         self.update_receive_tab()
1150
1151     def create_receive_menu(self, position):
1152         # fixme: this function apparently has a side effect.
1153         # if it is not called the menu pops up several times
1154         #self.receive_list.selectedIndexes()
1155
1156         selected = self.receive_list.selectedItems()
1157         multi_select = len(selected) > 1
1158         addrs = [unicode(item.text(0)) for item in selected]
1159         if not multi_select:
1160             item = self.receive_list.itemAt(position)
1161             if not item: return
1162
1163             addr = addrs[0]
1164             if not is_valid(addr):
1165                 k = str(item.data(0,32).toString())
1166                 if k:
1167                     self.create_account_menu(position, k, item)
1168                 else:
1169                     item.setExpanded(not item.isExpanded())
1170                 return
1171
1172         menu = QMenu()
1173         if not multi_select:
1174             menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1175             menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1176             menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1177             menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1178             if not self.wallet.is_watching_only():
1179                 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1180                 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1181                 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1182             if self.wallet.is_imported(addr):
1183                 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1184
1185         if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1186             menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1187         if any(addr in self.wallet.frozen_addresses for addr in addrs):
1188             menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1189
1190         if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1191             menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1192
1193         run_hook('receive_menu', menu, addrs)
1194         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1195
1196
1197     def get_sendable_balance(self):
1198         return sum(map(lambda x:x['value'], self.get_coins()))
1199
1200
1201     def get_coins(self):
1202         if self.pay_from:
1203             return self.pay_from
1204         else:
1205             domain = self.wallet.get_account_addresses(self.current_account)
1206             for i in self.wallet.frozen_addresses:
1207                 if i in domain: domain.remove(i)
1208             return self.wallet.get_unspent_coins(domain)
1209
1210
1211     def send_from_addresses(self, addrs):
1212         self.set_pay_from( addrs )
1213         self.tabs.setCurrentIndex(1)
1214
1215
1216     def payto(self, addr):
1217         if not addr: return
1218         label = self.wallet.labels.get(addr)
1219         m_addr = label + '  <' + addr + '>' if label else addr
1220         self.tabs.setCurrentIndex(1)
1221         self.payto_e.setText(m_addr)
1222         self.amount_e.setFocus()
1223
1224
1225     def delete_contact(self, x):
1226         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1227             self.wallet.delete_contact(x)
1228             self.wallet.set_label(x, None)
1229             self.update_history_tab()
1230             self.update_contacts_tab()
1231             self.update_completions()
1232
1233
1234     def create_contact_menu(self, position):
1235         item = self.contacts_list.itemAt(position)
1236         menu = QMenu()
1237         if not item:
1238             menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1239         else:
1240             addr = unicode(item.text(0))
1241             label = unicode(item.text(1))
1242             is_editable = item.data(0,32).toBool()
1243             payto_addr = item.data(0,33).toString()
1244             menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1245             menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1246             menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1247             if is_editable:
1248                 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1249                 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1250
1251         run_hook('create_contact_menu', menu, item)
1252         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1253
1254     def delete_invoice(self, key):
1255         self.invoices.pop(key)
1256         self.wallet.storage.put('invoices', self.invoices)
1257         self.update_invoices_tab()
1258
1259     def show_invoice(self, key):
1260         from electrum.paymentrequest import PaymentRequest
1261         domain, memo, value, status, tx_hash = self.invoices[key]
1262         pr = PaymentRequest(self.config)
1263         pr.read_file(key)
1264         pr.domain = domain
1265         pr.verify()
1266         self.show_pr_details(pr)
1267
1268     def show_pr_details(self, pr):
1269         msg = 'Domain: ' + pr.domain
1270         msg += '\nStatus: ' + pr.get_status()
1271         msg += '\nMemo: ' + pr.get_memo()
1272         msg += '\nPayment URL: ' + pr.payment_url
1273         msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1274         QMessageBox.information(self, 'Invoice', msg , 'OK')
1275
1276     def do_pay_invoice(self, key):
1277         from electrum.paymentrequest import PaymentRequest
1278         domain, memo, value, status, tx_hash = self.invoices[key]
1279         pr = PaymentRequest(self.config)
1280         pr.read_file(key)
1281         pr.domain = domain
1282         self.gui_object.payment_request = pr
1283         self.prepare_for_payment_request()
1284         if pr.verify():
1285             self.payment_request_ok()
1286         else:
1287             self.payment_request_error()
1288             
1289
1290     def create_invoice_menu(self, position):
1291         item = self.invoices_list.itemAt(position)
1292         if not item:
1293             return
1294         k = self.invoices_list.indexOfTopLevelItem(item)
1295         key = self.invoices.keys()[k]
1296         domain, memo, value, status, tx_hash = self.invoices[key]
1297         menu = QMenu()
1298         menu.addAction(_("Details"), lambda: self.show_invoice(key))
1299         if status == PR_UNPAID:
1300             menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
1301         menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1302         menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1303
1304
1305     def update_receive_item(self, item):
1306         item.setFont(0, QFont(MONOSPACE_FONT))
1307         address = str(item.data(0,0).toString())
1308         label = self.wallet.labels.get(address,'')
1309         item.setData(1,0,label)
1310         item.setData(0,32, True) # is editable
1311
1312         run_hook('update_receive_item', address, item)
1313
1314         if not self.wallet.is_mine(address): return
1315
1316         c, u = self.wallet.get_addr_balance(address)
1317         balance = self.format_amount(c + u)
1318         item.setData(2,0,balance)
1319
1320         if address in self.wallet.frozen_addresses:
1321             item.setBackgroundColor(0, QColor('lightblue'))
1322
1323
1324     def update_receive_tab(self):
1325         l = self.receive_list
1326         # extend the syntax for consistency
1327         l.addChild = l.addTopLevelItem
1328         l.insertChild = l.insertTopLevelItem
1329
1330         l.clear()
1331
1332         accounts = self.wallet.get_accounts()
1333         if self.current_account is None:
1334             account_items = sorted(accounts.items())
1335         else:
1336             account_items = [(self.current_account, accounts.get(self.current_account))]
1337
1338
1339         for k, account in account_items:
1340
1341             if len(accounts) > 1:
1342                 name = self.wallet.get_account_name(k)
1343                 c,u = self.wallet.get_account_balance(k)
1344                 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1345                 l.addTopLevelItem(account_item)
1346                 account_item.setExpanded(self.accounts_expanded.get(k, True))
1347                 account_item.setData(0, 32, k)
1348             else:
1349                 account_item = l
1350
1351             sequences = [0,1] if account.has_change() else [0]
1352             for is_change in sequences:
1353                 if len(sequences) > 1:
1354                     name = _("Receiving") if not is_change else _("Change")
1355                     seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1356                     account_item.addChild(seq_item)
1357                     if not is_change: 
1358                         seq_item.setExpanded(True)
1359                 else:
1360                     seq_item = account_item
1361                     
1362                 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1363                 used_flag = False
1364
1365                 is_red = False
1366                 gap = 0
1367
1368                 for address in account.get_addresses(is_change):
1369
1370                     num, is_used = self.wallet.is_used(address)
1371                     if num == 0:
1372                         gap += 1
1373                         if gap > self.wallet.gap_limit:
1374                             is_red = True
1375                     else:
1376                         gap = 0
1377
1378                     item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1379                     self.update_receive_item(item)
1380                     if is_red:
1381                         item.setBackgroundColor(1, QColor('red'))
1382
1383                     if is_used:
1384                         if not used_flag:
1385                             seq_item.insertChild(0,used_item)
1386                             used_flag = True
1387                         used_item.addChild(item)
1388                     else:
1389                         seq_item.addChild(item)
1390
1391         # we use column 1 because column 0 may be hidden
1392         l.setCurrentItem(l.topLevelItem(0),1)
1393
1394
1395     def update_contacts_tab(self):
1396         l = self.contacts_list
1397         l.clear()
1398
1399         for address in self.wallet.addressbook:
1400             label = self.wallet.labels.get(address,'')
1401             n = self.wallet.get_num_tx(address)
1402             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1403             item.setFont(0, QFont(MONOSPACE_FONT))
1404             # 32 = label can be edited (bool)
1405             item.setData(0,32, True)
1406             # 33 = payto string
1407             item.setData(0,33, address)
1408             l.addTopLevelItem(item)
1409
1410         run_hook('update_contacts_tab', l)
1411         l.setCurrentItem(l.topLevelItem(0))
1412
1413
1414
1415     def create_console_tab(self):
1416         from console import Console
1417         self.console = console = Console()
1418         return console
1419
1420
1421     def update_console(self):
1422         console = self.console
1423         console.history = self.config.get("console-history",[])
1424         console.history_index = len(console.history)
1425
1426         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1427         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1428
1429         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1430         methods = {}
1431         def mkfunc(f, method):
1432             return lambda *args: apply( f, (method, args, self.password_dialog ))
1433         for m in dir(c):
1434             if m[0]=='_' or m in ['network','wallet']: continue
1435             methods[m] = mkfunc(c._run, m)
1436
1437         console.updateNamespace(methods)
1438
1439
1440     def change_account(self,s):
1441         if s == _("All accounts"):
1442             self.current_account = None
1443         else:
1444             accounts = self.wallet.get_account_names()
1445             for k, v in accounts.items():
1446                 if v == s:
1447                     self.current_account = k
1448         self.update_history_tab()
1449         self.update_status()
1450         self.update_receive_tab()
1451
1452     def create_status_bar(self):
1453
1454         sb = QStatusBar()
1455         sb.setFixedHeight(35)
1456         qtVersion = qVersion()
1457
1458         self.balance_label = QLabel("")
1459         sb.addWidget(self.balance_label)
1460
1461         from version_getter import UpdateLabel
1462         self.updatelabel = UpdateLabel(self.config, sb)
1463
1464         self.account_selector = QComboBox()
1465         self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1466         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1467         sb.addPermanentWidget(self.account_selector)
1468
1469         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1470             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1471
1472         self.lock_icon = QIcon()
1473         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1474         sb.addPermanentWidget( self.password_button )
1475
1476         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1477         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1478         sb.addPermanentWidget( self.seed_button )
1479         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1480         sb.addPermanentWidget( self.status_button )
1481
1482         run_hook('create_status_bar', (sb,))
1483
1484         self.setStatusBar(sb)
1485
1486
1487     def update_lock_icon(self):
1488         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1489         self.password_button.setIcon( icon )
1490
1491
1492     def update_buttons_on_seed(self):
1493         if self.wallet.has_seed():
1494            self.seed_button.show()
1495         else:
1496            self.seed_button.hide()
1497
1498         if not self.wallet.is_watching_only():
1499            self.password_button.show()
1500            self.send_button.setText(_("Send"))
1501         else:
1502            self.password_button.hide()
1503            self.send_button.setText(_("Create unsigned transaction"))
1504
1505
1506     def change_password_dialog(self):
1507         from password_dialog import PasswordDialog
1508         d = PasswordDialog(self.wallet, self)
1509         d.run()
1510         self.update_lock_icon()
1511
1512
1513     def new_contact_dialog(self):
1514
1515         d = QDialog(self)
1516         d.setWindowTitle(_("New Contact"))
1517         vbox = QVBoxLayout(d)
1518         vbox.addWidget(QLabel(_('New Contact')+':'))
1519
1520         grid = QGridLayout()
1521         line1 = QLineEdit()
1522         line2 = QLineEdit()
1523         grid.addWidget(QLabel(_("Address")), 1, 0)
1524         grid.addWidget(line1, 1, 1)
1525         grid.addWidget(QLabel(_("Name")), 2, 0)
1526         grid.addWidget(line2, 2, 1)
1527
1528         vbox.addLayout(grid)
1529         vbox.addLayout(ok_cancel_buttons(d))
1530
1531         if not d.exec_():
1532             return
1533
1534         address = str(line1.text())
1535         label = unicode(line2.text())
1536
1537         if not is_valid(address):
1538             QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1539             return
1540
1541         self.wallet.add_contact(address)
1542         if label:
1543             self.wallet.set_label(address, label)
1544
1545         self.update_contacts_tab()
1546         self.update_history_tab()
1547         self.update_completions()
1548         self.tabs.setCurrentIndex(3)
1549
1550
1551     @protected
1552     def new_account_dialog(self, password):
1553
1554         dialog = QDialog(self)
1555         dialog.setModal(1)
1556         dialog.setWindowTitle(_("New Account"))
1557
1558         vbox = QVBoxLayout()
1559         vbox.addWidget(QLabel(_('Account name')+':'))
1560         e = QLineEdit()
1561         vbox.addWidget(e)
1562         msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1563             + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1564         l = QLabel(msg)
1565         l.setWordWrap(True)
1566         vbox.addWidget(l)
1567
1568         vbox.addLayout(ok_cancel_buttons(dialog))
1569         dialog.setLayout(vbox)
1570         r = dialog.exec_()
1571         if not r: return
1572
1573         name = str(e.text())
1574         if not name: return
1575
1576         self.wallet.create_pending_account(name, password)
1577         self.update_receive_tab()
1578         self.tabs.setCurrentIndex(2)
1579
1580
1581
1582
1583     def show_master_public_keys(self):
1584
1585         dialog = QDialog(self)
1586         dialog.setModal(1)
1587         dialog.setWindowTitle(_("Master Public Keys"))
1588
1589         main_layout = QGridLayout()
1590         mpk_dict = self.wallet.get_master_public_keys()
1591         i = 0
1592         for key, value in mpk_dict.items():
1593             main_layout.addWidget(QLabel(key), i, 0)
1594             mpk_text = QTextEdit()
1595             mpk_text.setReadOnly(True)
1596             mpk_text.setMaximumHeight(170)
1597             mpk_text.setText(value)
1598             main_layout.addWidget(mpk_text, i + 1, 0)
1599             i += 2
1600
1601         vbox = QVBoxLayout()
1602         vbox.addLayout(main_layout)
1603         vbox.addLayout(close_button(dialog))
1604
1605         dialog.setLayout(vbox)
1606         dialog.exec_()
1607
1608
1609     @protected
1610     def show_seed_dialog(self, password):
1611         if not self.wallet.has_seed():
1612             QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1613             return
1614
1615         try:
1616             mnemonic = self.wallet.get_mnemonic(password)
1617         except Exception:
1618             QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1619             return
1620         from seed_dialog import SeedDialog
1621         d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1622         d.exec_()
1623
1624
1625
1626     def show_qrcode(self, data, title = _("QR code")):
1627         if not data: return
1628         d = QDialog(self)
1629         d.setModal(1)
1630         d.setWindowTitle(title)
1631         d.setMinimumSize(270, 300)
1632         vbox = QVBoxLayout()
1633         qrw = QRCodeWidget(data)
1634         vbox.addWidget(qrw, 1)
1635         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1636         hbox = QHBoxLayout()
1637         hbox.addStretch(1)
1638
1639         filename = os.path.join(self.config.path, "qrcode.bmp")
1640
1641         def print_qr():
1642             bmp.save_qrcode(qrw.qr, filename)
1643             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1644
1645         def copy_to_clipboard():
1646             bmp.save_qrcode(qrw.qr, filename)
1647             self.app.clipboard().setImage(QImage(filename))
1648             QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1649
1650         b = QPushButton(_("Copy"))
1651         hbox.addWidget(b)
1652         b.clicked.connect(copy_to_clipboard)
1653
1654         b = QPushButton(_("Save"))
1655         hbox.addWidget(b)
1656         b.clicked.connect(print_qr)
1657
1658         b = QPushButton(_("Close"))
1659         hbox.addWidget(b)
1660         b.clicked.connect(d.accept)
1661         b.setDefault(True)
1662
1663         vbox.addLayout(hbox)
1664         d.setLayout(vbox)
1665         d.exec_()
1666
1667
1668     def do_protect(self, func, args):
1669         if self.wallet.use_encryption:
1670             password = self.password_dialog()
1671             if not password:
1672                 return
1673         else:
1674             password = None
1675
1676         if args != (False,):
1677             args = (self,) + args + (password,)
1678         else:
1679             args = (self,password)
1680         apply( func, args)
1681
1682
1683     def show_public_keys(self, address):
1684         if not address: return
1685         try:
1686             pubkey_list = self.wallet.get_public_keys(address)
1687         except Exception as e:
1688             traceback.print_exc(file=sys.stdout)
1689             self.show_message(str(e))
1690             return
1691
1692         d = QDialog(self)
1693         d.setMinimumSize(600, 200)
1694         d.setModal(1)
1695         vbox = QVBoxLayout()
1696         vbox.addWidget( QLabel(_("Address") + ': ' + address))
1697         vbox.addWidget( QLabel(_("Public key") + ':'))
1698         keys = QTextEdit()
1699         keys.setReadOnly(True)
1700         keys.setText('\n'.join(pubkey_list))
1701         vbox.addWidget(keys)
1702         #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1703         vbox.addLayout(close_button(d))
1704         d.setLayout(vbox)
1705         d.exec_()
1706
1707     @protected
1708     def show_private_key(self, address, password):
1709         if not address: return
1710         try:
1711             pk_list = self.wallet.get_private_key(address, password)
1712         except Exception as e:
1713             traceback.print_exc(file=sys.stdout)
1714             self.show_message(str(e))
1715             return
1716
1717         d = QDialog(self)
1718         d.setMinimumSize(600, 200)
1719         d.setModal(1)
1720         vbox = QVBoxLayout()
1721         vbox.addWidget( QLabel(_("Address") + ': ' + address))
1722         vbox.addWidget( QLabel(_("Private key") + ':'))
1723         keys = QTextEdit()
1724         keys.setReadOnly(True)
1725         keys.setText('\n'.join(pk_list))
1726         vbox.addWidget(keys)
1727         vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1728         vbox.addLayout(close_button(d))
1729         d.setLayout(vbox)
1730         d.exec_()
1731
1732
1733     @protected
1734     def do_sign(self, address, message, signature, password):
1735         message = unicode(message.toPlainText())
1736         message = message.encode('utf-8')
1737         try:
1738             sig = self.wallet.sign_message(str(address.text()), message, password)
1739             signature.setText(sig)
1740         except Exception as e:
1741             self.show_message(str(e))
1742
1743     def do_verify(self, address, message, signature):
1744         message = unicode(message.toPlainText())
1745         message = message.encode('utf-8')
1746         if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1747             self.show_message(_("Signature verified"))
1748         else:
1749             self.show_message(_("Error: wrong signature"))
1750
1751
1752     def sign_verify_message(self, address=''):
1753         d = QDialog(self)
1754         d.setModal(1)
1755         d.setWindowTitle(_('Sign/verify Message'))
1756         d.setMinimumSize(410, 290)
1757
1758         layout = QGridLayout(d)
1759
1760         message_e = QTextEdit()
1761         layout.addWidget(QLabel(_('Message')), 1, 0)
1762         layout.addWidget(message_e, 1, 1)
1763         layout.setRowStretch(2,3)
1764
1765         address_e = QLineEdit()
1766         address_e.setText(address)
1767         layout.addWidget(QLabel(_('Address')), 2, 0)
1768         layout.addWidget(address_e, 2, 1)
1769
1770         signature_e = QTextEdit()
1771         layout.addWidget(QLabel(_('Signature')), 3, 0)
1772         layout.addWidget(signature_e, 3, 1)
1773         layout.setRowStretch(3,1)
1774
1775         hbox = QHBoxLayout()
1776
1777         b = QPushButton(_("Sign"))
1778         b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1779         hbox.addWidget(b)
1780
1781         b = QPushButton(_("Verify"))
1782         b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1783         hbox.addWidget(b)
1784
1785         b = QPushButton(_("Close"))
1786         b.clicked.connect(d.accept)
1787         hbox.addWidget(b)
1788         layout.addLayout(hbox, 4, 1)
1789         d.exec_()
1790
1791
1792     @protected
1793     def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1794         try:
1795             decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1796             message_e.setText(decrypted)
1797         except Exception as e:
1798             self.show_message(str(e))
1799
1800
1801     def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1802         message = unicode(message_e.toPlainText())
1803         message = message.encode('utf-8')
1804         try:
1805             encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1806             encrypted_e.setText(encrypted)
1807         except Exception as e:
1808             self.show_message(str(e))
1809
1810
1811
1812     def encrypt_message(self, address = ''):
1813         d = QDialog(self)
1814         d.setModal(1)
1815         d.setWindowTitle(_('Encrypt/decrypt Message'))
1816         d.setMinimumSize(610, 490)
1817
1818         layout = QGridLayout(d)
1819
1820         message_e = QTextEdit()
1821         layout.addWidget(QLabel(_('Message')), 1, 0)
1822         layout.addWidget(message_e, 1, 1)
1823         layout.setRowStretch(2,3)
1824
1825         pubkey_e = QLineEdit()
1826         if address:
1827             pubkey = self.wallet.getpubkeys(address)[0]
1828             pubkey_e.setText(pubkey)
1829         layout.addWidget(QLabel(_('Public key')), 2, 0)
1830         layout.addWidget(pubkey_e, 2, 1)
1831
1832         encrypted_e = QTextEdit()
1833         layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1834         layout.addWidget(encrypted_e, 3, 1)
1835         layout.setRowStretch(3,1)
1836
1837         hbox = QHBoxLayout()
1838         b = QPushButton(_("Encrypt"))
1839         b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1840         hbox.addWidget(b)
1841
1842         b = QPushButton(_("Decrypt"))
1843         b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1844         hbox.addWidget(b)
1845
1846         b = QPushButton(_("Close"))
1847         b.clicked.connect(d.accept)
1848         hbox.addWidget(b)
1849
1850         layout.addLayout(hbox, 4, 1)
1851         d.exec_()
1852
1853
1854     def question(self, msg):
1855         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1856
1857     def show_message(self, msg):
1858         QMessageBox.information(self, _('Message'), msg, _('OK'))
1859
1860     def password_dialog(self, msg=None):
1861         d = QDialog(self)
1862         d.setModal(1)
1863         d.setWindowTitle(_("Enter Password"))
1864
1865         pw = QLineEdit()
1866         pw.setEchoMode(2)
1867
1868         vbox = QVBoxLayout()
1869         if not msg:
1870             msg = _('Please enter your password')
1871         vbox.addWidget(QLabel(msg))
1872
1873         grid = QGridLayout()
1874         grid.setSpacing(8)
1875         grid.addWidget(QLabel(_('Password')), 1, 0)
1876         grid.addWidget(pw, 1, 1)
1877         vbox.addLayout(grid)
1878
1879         vbox.addLayout(ok_cancel_buttons(d))
1880         d.setLayout(vbox)
1881
1882         run_hook('password_dialog', pw, grid, 1)
1883         if not d.exec_(): return
1884         return unicode(pw.text())
1885
1886
1887
1888
1889
1890
1891
1892
1893     def tx_from_text(self, txt):
1894         "json or raw hexadecimal"
1895         try:
1896             txt.decode('hex')
1897             tx = Transaction(txt)
1898             return tx
1899         except Exception:
1900             pass
1901
1902         try:
1903             tx_dict = json.loads(str(txt))
1904             assert "hex" in tx_dict.keys()
1905             tx = Transaction(tx_dict["hex"])
1906             if tx_dict.has_key("input_info"):
1907                 input_info = json.loads(tx_dict['input_info'])
1908                 tx.add_input_info(input_info)
1909             return tx
1910         except Exception:
1911             traceback.print_exc(file=sys.stdout)
1912             pass
1913
1914         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1915
1916
1917
1918     def read_tx_from_file(self):
1919         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1920         if not fileName:
1921             return
1922         try:
1923             with open(fileName, "r") as f:
1924                 file_content = f.read()
1925         except (ValueError, IOError, os.error), reason:
1926             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1927
1928         return self.tx_from_text(file_content)
1929
1930
1931     @protected
1932     def sign_raw_transaction(self, tx, input_info, password):
1933         self.wallet.signrawtransaction(tx, input_info, [], password)
1934
1935     def do_process_from_text(self):
1936         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1937         if not text:
1938             return
1939         tx = self.tx_from_text(text)
1940         if tx:
1941             self.show_transaction(tx)
1942
1943     def do_process_from_file(self):
1944         tx = self.read_tx_from_file()
1945         if tx:
1946             self.show_transaction(tx)
1947
1948     def do_process_from_txid(self):
1949         from electrum import transaction
1950         txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1951         if ok and txid:
1952             r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1953             if r:
1954                 tx = transaction.Transaction(r)
1955                 if tx:
1956                     self.show_transaction(tx)
1957                 else:
1958                     self.show_message("unknown transaction")
1959
1960     def do_process_from_csvReader(self, csvReader):
1961         outputs = []
1962         errors = []
1963         errtext = ""
1964         try:
1965             for position, row in enumerate(csvReader):
1966                 address = row[0]
1967                 if not is_valid(address):
1968                     errors.append((position, address))
1969                     continue
1970                 amount = Decimal(row[1])
1971                 amount = int(100000000*amount)
1972                 outputs.append((address, amount))
1973         except (ValueError, IOError, os.error), reason:
1974             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1975             return
1976         if errors != []:
1977             for x in errors:
1978                 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1979             QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1980             return
1981
1982         try:
1983             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1984         except Exception as e:
1985             self.show_message(str(e))
1986             return
1987
1988         self.show_transaction(tx)
1989
1990     def do_process_from_csv_file(self):
1991         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1992         if not fileName:
1993             return
1994         try:
1995             with open(fileName, "r") as f:
1996                 csvReader = csv.reader(f)
1997                 self.do_process_from_csvReader(csvReader)
1998         except (ValueError, IOError, os.error), reason:
1999             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2000             return
2001
2002     def do_process_from_csv_text(self):
2003         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2004                                + _("Format: address, amount. One output per line"), _("Load CSV"))
2005         if not text:
2006             return
2007         f = StringIO.StringIO(text)
2008         csvReader = csv.reader(f)
2009         self.do_process_from_csvReader(csvReader)
2010
2011
2012
2013     @protected
2014     def export_privkeys_dialog(self, password):
2015         if self.wallet.is_watching_only():
2016             self.show_message(_("This is a watching-only wallet"))
2017             return
2018
2019         d = QDialog(self)
2020         d.setWindowTitle(_('Private keys'))
2021         d.setMinimumSize(850, 300)
2022         vbox = QVBoxLayout(d)
2023
2024         msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), 
2025                               _("Exposing a single private key can compromise your entire wallet!"), 
2026                               _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
2027         vbox.addWidget(QLabel(msg))
2028
2029         e = QTextEdit()
2030         e.setReadOnly(True)
2031         vbox.addWidget(e)
2032
2033         defaultname = 'electrum-private-keys.csv'
2034         select_msg = _('Select file to export your private keys to')
2035         hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2036         vbox.addLayout(hbox)
2037
2038         h, b = ok_cancel_buttons2(d, _('Export'))
2039         b.setEnabled(False)
2040         vbox.addLayout(h)
2041
2042         private_keys = {}
2043         addresses = self.wallet.addresses(True)
2044         done = False
2045         def privkeys_thread():
2046             for addr in addresses:
2047                 time.sleep(0.1)
2048                 if done: 
2049                     break
2050                 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
2051                 d.emit(SIGNAL('computing_privkeys'))
2052             d.emit(SIGNAL('show_privkeys'))
2053
2054         def show_privkeys():
2055             s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2056             e.setText(s)
2057             b.setEnabled(True)
2058
2059         d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2060         d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2061         threading.Thread(target=privkeys_thread).start()
2062
2063         if not d.exec_():
2064             done = True
2065             return
2066
2067         filename = filename_e.text()
2068         if not filename:
2069             return
2070
2071         try:
2072             self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2073         except (IOError, os.error), reason:
2074             export_error_label = _("Electrum was unable to produce a private key-export.")
2075             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2076
2077         except Exception as e:
2078             self.show_message(str(e))
2079             return
2080
2081         self.show_message(_("Private keys exported."))
2082
2083
2084     def do_export_privkeys(self, fileName, pklist, is_csv):
2085         with open(fileName, "w+") as f:
2086             if is_csv:
2087                 transaction = csv.writer(f)
2088                 transaction.writerow(["address", "private_key"])
2089                 for addr, pk in pklist.items():
2090                     transaction.writerow(["%34s"%addr,pk])
2091             else:
2092                 import json
2093                 f.write(json.dumps(pklist, indent = 4))
2094
2095
2096     def do_import_labels(self):
2097         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2098         if not labelsFile: return
2099         try:
2100             f = open(labelsFile, 'r')
2101             data = f.read()
2102             f.close()
2103             for key, value in json.loads(data).items():
2104                 self.wallet.set_label(key, value)
2105             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2106         except (IOError, os.error), reason:
2107             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2108
2109
2110     def do_export_labels(self):
2111         labels = self.wallet.labels
2112         try:
2113             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2114             if fileName:
2115                 with open(fileName, 'w+') as f:
2116                     json.dump(labels, f)
2117                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2118         except (IOError, os.error), reason:
2119             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2120
2121
2122     def export_history_dialog(self):
2123
2124         d = QDialog(self)
2125         d.setWindowTitle(_('Export History'))
2126         d.setMinimumSize(400, 200)
2127         vbox = QVBoxLayout(d)
2128
2129         defaultname = os.path.expanduser('~/electrum-history.csv')
2130         select_msg = _('Select file to export your wallet transactions to')
2131
2132         hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2133         vbox.addLayout(hbox)
2134
2135         vbox.addStretch(1)
2136
2137         h, b = ok_cancel_buttons2(d, _('Export'))
2138         vbox.addLayout(h)
2139         if not d.exec_():
2140             return
2141
2142         filename = filename_e.text()
2143         if not filename:
2144             return
2145
2146         try:
2147             self.do_export_history(self.wallet, filename, csv_button.isChecked())
2148         except (IOError, os.error), reason:
2149             export_error_label = _("Electrum was unable to produce a transaction export.")
2150             QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2151             return
2152
2153         QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2154
2155
2156     def do_export_history(self, wallet, fileName, is_csv):
2157         history = wallet.get_tx_history()
2158         lines = []
2159         for item in history:
2160             tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2161             if confirmations:
2162                 if timestamp is not None:
2163                     try:
2164                         time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2165                     except [RuntimeError, TypeError, NameError] as reason:
2166                         time_string = "unknown"
2167                         pass
2168                 else:
2169                     time_string = "unknown"
2170             else:
2171                 time_string = "pending"
2172
2173             if value is not None:
2174                 value_string = format_satoshis(value, True)
2175             else:
2176                 value_string = '--'
2177
2178             if fee is not None:
2179                 fee_string = format_satoshis(fee, True)
2180             else:
2181                 fee_string = '0'
2182
2183             if tx_hash:
2184                 label, is_default_label = wallet.get_label(tx_hash)
2185                 label = label.encode('utf-8')
2186             else:
2187                 label = ""
2188
2189             balance_string = format_satoshis(balance, False)
2190             if is_csv:
2191                 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2192             else:
2193                 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2194
2195         with open(fileName, "w+") as f:
2196             if is_csv:
2197                 transaction = csv.writer(f)
2198                 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2199                 for line in lines:
2200                     transaction.writerow(line)
2201             else:
2202                 import json
2203                 f.write(json.dumps(lines, indent = 4))
2204
2205
2206     def sweep_key_dialog(self):
2207         d = QDialog(self)
2208         d.setWindowTitle(_('Sweep private keys'))
2209         d.setMinimumSize(600, 300)
2210
2211         vbox = QVBoxLayout(d)
2212         vbox.addWidget(QLabel(_("Enter private keys")))
2213
2214         keys_e = QTextEdit()
2215         keys_e.setTabChangesFocus(True)
2216         vbox.addWidget(keys_e)
2217
2218         h, address_e = address_field(self.wallet.addresses())
2219         vbox.addLayout(h)
2220
2221         vbox.addStretch(1)
2222         hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2223         vbox.addLayout(hbox)
2224         button.setEnabled(False)
2225
2226         def get_address():
2227             addr = str(address_e.text())
2228             if bitcoin.is_address(addr):
2229                 return addr
2230
2231         def get_pk():
2232             pk = str(keys_e.toPlainText()).strip()
2233             if Wallet.is_private_key(pk):
2234                 return pk.split()
2235
2236         f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2237         keys_e.textChanged.connect(f)
2238         address_e.textChanged.connect(f)
2239         if not d.exec_():
2240             return
2241
2242         fee = self.wallet.fee
2243         tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2244         self.show_transaction(tx)
2245
2246
2247     @protected
2248     def do_import_privkey(self, password):
2249         if not self.wallet.has_imported_keys():
2250             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2251                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2252                                          + _('Are you sure you understand what you are doing?'), 3, 4)
2253             if r == 4: return
2254
2255         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2256         if not text: return
2257
2258         text = str(text).split()
2259         badkeys = []
2260         addrlist = []
2261         for key in text:
2262             try:
2263                 addr = self.wallet.import_key(key, password)
2264             except Exception as e:
2265                 badkeys.append(key)
2266                 continue
2267             if not addr:
2268                 badkeys.append(key)
2269             else:
2270                 addrlist.append(addr)
2271         if addrlist:
2272             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2273         if badkeys:
2274             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2275         self.update_receive_tab()
2276         self.update_history_tab()
2277
2278
2279     def settings_dialog(self):
2280         d = QDialog(self)
2281         d.setWindowTitle(_('Electrum Settings'))
2282         d.setModal(1)
2283         vbox = QVBoxLayout()
2284         grid = QGridLayout()
2285         grid.setColumnStretch(0,1)
2286
2287         nz_label = QLabel(_('Display zeros') + ':')
2288         grid.addWidget(nz_label, 0, 0)
2289         nz_e = AmountEdit(None,True)
2290         nz_e.setText("%d"% self.num_zeros)
2291         grid.addWidget(nz_e, 0, 1)
2292         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2293         grid.addWidget(HelpButton(msg), 0, 2)
2294         if not self.config.is_modifiable('num_zeros'):
2295             for w in [nz_e, nz_label]: w.setEnabled(False)
2296
2297         lang_label=QLabel(_('Language') + ':')
2298         grid.addWidget(lang_label, 1, 0)
2299         lang_combo = QComboBox()
2300         from electrum.i18n import languages
2301         lang_combo.addItems(languages.values())
2302         try:
2303             index = languages.keys().index(self.config.get("language",''))
2304         except Exception:
2305             index = 0
2306         lang_combo.setCurrentIndex(index)
2307         grid.addWidget(lang_combo, 1, 1)
2308         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2309         if not self.config.is_modifiable('language'):
2310             for w in [lang_combo, lang_label]: w.setEnabled(False)
2311
2312
2313         fee_label = QLabel(_('Transaction fee') + ':')
2314         grid.addWidget(fee_label, 2, 0)
2315         fee_e = BTCAmountEdit(self.get_decimal_point)
2316         fee_e.setAmount(self.wallet.fee)
2317         grid.addWidget(fee_e, 2, 1)
2318         msg = _('Fee per kilobyte of transaction.') + '\n' \
2319             + _('Recommended value') + ': ' + self.format_amount(10000) + ' ' + self.base_unit()
2320         grid.addWidget(HelpButton(msg), 2, 2)
2321         if not self.config.is_modifiable('fee_per_kb'):
2322             for w in [fee_e, fee_label]: w.setEnabled(False)
2323
2324         units = ['BTC', 'mBTC']
2325         unit_label = QLabel(_('Base unit') + ':')
2326         grid.addWidget(unit_label, 3, 0)
2327         unit_combo = QComboBox()
2328         unit_combo.addItems(units)
2329         unit_combo.setCurrentIndex(units.index(self.base_unit()))
2330         grid.addWidget(unit_combo, 3, 1)
2331         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2332                                              + '\n1BTC=1000mBTC.\n' \
2333                                              + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2334
2335         usechange_cb = QCheckBox(_('Use change addresses'))
2336         usechange_cb.setChecked(self.wallet.use_change)
2337         grid.addWidget(usechange_cb, 4, 0)
2338         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2339         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2340
2341         block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2342         block_ex_label = QLabel(_('Online Block Explorer') + ':')
2343         grid.addWidget(block_ex_label, 5, 0)
2344         block_ex_combo = QComboBox()
2345         block_ex_combo.addItems(block_explorers)
2346         block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2347         grid.addWidget(block_ex_combo, 5, 1)
2348         grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2349
2350         show_tx = self.config.get('show_before_broadcast', False)
2351         showtx_cb = QCheckBox(_('Show before broadcast'))
2352         showtx_cb.setChecked(show_tx)
2353         grid.addWidget(showtx_cb, 6, 0)
2354         grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2355
2356         vbox.addLayout(grid)
2357         vbox.addStretch(1)
2358         vbox.addLayout(ok_cancel_buttons(d))
2359         d.setLayout(vbox)
2360
2361         # run the dialog
2362         if not d.exec_(): return
2363
2364         fee = fee_e.get_amount()
2365         if fee is None:
2366             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2367             return
2368
2369         self.wallet.set_fee(fee)
2370
2371         nz = unicode(nz_e.text())
2372         try:
2373             nz = int( nz )
2374             if nz>8: nz=8
2375         except Exception:
2376             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2377             return
2378
2379         if self.num_zeros != nz:
2380             self.num_zeros = nz
2381             self.config.set_key('num_zeros', nz, True)
2382             self.update_history_tab()
2383             self.update_receive_tab()
2384
2385         usechange_result = usechange_cb.isChecked()
2386         if self.wallet.use_change != usechange_result:
2387             self.wallet.use_change = usechange_result
2388             self.wallet.storage.put('use_change', self.wallet.use_change)
2389
2390         if showtx_cb.isChecked() != show_tx:
2391             self.config.set_key('show_before_broadcast', not show_tx)
2392
2393         unit_result = units[unit_combo.currentIndex()]
2394         if self.base_unit() != unit_result:
2395             self.decimal_point = 8 if unit_result == 'BTC' else 5
2396             self.config.set_key('decimal_point', self.decimal_point, True)
2397             self.update_history_tab()
2398             self.update_status()
2399
2400         need_restart = False
2401
2402         lang_request = languages.keys()[lang_combo.currentIndex()]
2403         if lang_request != self.config.get('language'):
2404             self.config.set_key("language", lang_request, True)
2405             need_restart = True
2406
2407         be_result = block_explorers[block_ex_combo.currentIndex()]
2408         self.config.set_key('block_explorer', be_result, True)
2409
2410         run_hook('close_settings_dialog')
2411
2412         if need_restart:
2413             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2414
2415
2416     def run_network_dialog(self):
2417         if not self.network:
2418             return
2419         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2420
2421     def closeEvent(self, event):
2422         self.tray.hide()
2423         self.config.set_key("is_maximized", self.isMaximized())
2424         if not self.isMaximized():
2425             g = self.geometry()
2426             self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2427         self.save_column_widths()
2428         self.config.set_key("console-history", self.console.history[-50:], True)
2429         self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2430         event.accept()
2431
2432
2433     def plugins_dialog(self):
2434         from electrum.plugins import plugins
2435
2436         d = QDialog(self)
2437         d.setWindowTitle(_('Electrum Plugins'))
2438         d.setModal(1)
2439
2440         vbox = QVBoxLayout(d)
2441
2442         # plugins
2443         scroll = QScrollArea()
2444         scroll.setEnabled(True)
2445         scroll.setWidgetResizable(True)
2446         scroll.setMinimumSize(400,250)
2447         vbox.addWidget(scroll)
2448
2449         w = QWidget()
2450         scroll.setWidget(w)
2451         w.setMinimumHeight(len(plugins)*35)
2452
2453         grid = QGridLayout()
2454         grid.setColumnStretch(0,1)
2455         w.setLayout(grid)
2456
2457         def do_toggle(cb, p, w):
2458             r = p.toggle()
2459             cb.setChecked(r)
2460             if w: w.setEnabled(r)
2461
2462         def mk_toggle(cb, p, w):
2463             return lambda: do_toggle(cb,p,w)
2464
2465         for i, p in enumerate(plugins):
2466             try:
2467                 cb = QCheckBox(p.fullname())
2468                 cb.setDisabled(not p.is_available())
2469                 cb.setChecked(p.is_enabled())
2470                 grid.addWidget(cb, i, 0)
2471                 if p.requires_settings():
2472                     w = p.settings_widget(self)
2473                     w.setEnabled( p.is_enabled() )
2474                     grid.addWidget(w, i, 1)
2475                 else:
2476                     w = None
2477                 cb.clicked.connect(mk_toggle(cb,p,w))
2478                 grid.addWidget(HelpButton(p.description()), i, 2)
2479             except Exception:
2480                 print_msg(_("Error: cannot display plugin"), p)
2481                 traceback.print_exc(file=sys.stdout)
2482         grid.setRowStretch(i+1,1)
2483
2484         vbox.addLayout(close_button(d))
2485
2486         d.exec_()
2487
2488
2489     def show_account_details(self, k):
2490         account = self.wallet.accounts[k]
2491
2492         d = QDialog(self)
2493         d.setWindowTitle(_('Account Details'))
2494         d.setModal(1)
2495
2496         vbox = QVBoxLayout(d)
2497         name = self.wallet.get_account_name(k)
2498         label = QLabel('Name: ' + name)
2499         vbox.addWidget(label)
2500
2501         vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2502
2503         vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2504
2505         vbox.addWidget(QLabel(_('Master Public Key:')))
2506
2507         text = QTextEdit()
2508         text.setReadOnly(True)
2509         text.setMaximumHeight(170)
2510         vbox.addWidget(text)
2511
2512         mpk_text = '\n'.join( account.get_master_pubkeys() )
2513         text.setText(mpk_text)
2514
2515         vbox.addLayout(close_button(d))
2516         d.exec_()