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