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