master public key dialog for bip32
[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 shutil
24 import StringIO
25
26
27 import PyQt4
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31
32 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
33 from electrum.plugins import run_hook
34
35 import icons_rc
36
37 from electrum.wallet import format_satoshis
38 from electrum import Transaction
39 from electrum import mnemonic
40 from electrum import util, bitcoin, commands, Interface, Wallet
41 from electrum import SimpleConfig, Wallet, WalletStorage
42
43
44 from electrum import bmp, pyqrnative
45
46 from amountedit import AmountEdit
47 from network_dialog import NetworkDialog
48 from qrcodewidget import QRCodeWidget
49
50 from decimal import Decimal
51
52 import platform
53 import httplib
54 import socket
55 import webbrowser
56 import csv
57
58 if platform.system() == 'Windows':
59     MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61     MONOSPACE_FONT = 'Monaco'
62 else:
63     MONOSPACE_FONT = 'monospace'
64
65 from electrum import ELECTRUM_VERSION
66 import re
67
68 from util import *
69
70
71         
72
73
74
75 class StatusBarButton(QPushButton):
76     def __init__(self, icon, tooltip, func):
77         QPushButton.__init__(self, icon, '')
78         self.setToolTip(tooltip)
79         self.setFlat(True)
80         self.setMaximumWidth(25)
81         self.clicked.connect(func)
82         self.func = func
83         self.setIconSize(QSize(25,25))
84
85     def keyPressEvent(self, e):
86         if e.key() == QtCore.Qt.Key_Return:
87             apply(self.func,())
88
89
90
91
92
93
94
95
96
97
98 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
99
100 class ElectrumWindow(QMainWindow):
101     def changeEvent(self, event):
102         flags = self.windowFlags();
103         if event and event.type() == QtCore.QEvent.WindowStateChange:
104             if self.windowState() & QtCore.Qt.WindowMinimized:
105                 self.build_menu(True)
106                 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
107                 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
108                 # Electrum from closing.
109                 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
110                 # self.setWindowFlags(flags & ~Qt.ToolTip)
111             elif event.oldState() & QtCore.Qt.WindowMinimized:
112                 self.build_menu(False)
113                 #self.setWindowFlags(flags | Qt.ToolTip)
114
115     def build_menu(self, is_hidden = False):
116         m = QMenu()
117         if self.isMinimized():
118             m.addAction(_("Show"), self.showNormal)
119         else:
120             m.addAction(_("Hide"), self.showMinimized)
121
122         m.addSeparator()
123         m.addAction(_("Exit Electrum"), self.close)
124         self.tray.setContextMenu(m)
125
126     def tray_activated(self, reason):
127         if reason == QSystemTrayIcon.DoubleClick:
128             self.showNormal()
129
130
131     def __init__(self, config, network):
132         QMainWindow.__init__(self)
133
134         self.config = config
135         self.network = network
136
137         self._close_electrum = False
138         self.lite = None
139         self.current_account = self.config.get("current_account", None)
140
141         self.icon = QIcon(':icons/electrum.png')
142         self.tray = QSystemTrayIcon(self.icon, self)
143         self.tray.setToolTip('Electrum')
144         self.tray.activated.connect(self.tray_activated)
145
146         self.build_menu()
147         self.tray.show()
148         self.create_status_bar()
149
150         self.need_update = threading.Event()
151
152         self.expert_mode   = config.get('classic_expert_mode', False)
153         self.decimal_point = config.get('decimal_point', 8)
154         self.num_zeros     = int(config.get('num_zeros',0))
155
156         set_language(config.get('language'))
157
158         self.funds_error = False
159         self.completions = QStringListModel()
160
161         self.tabs = tabs = QTabWidget(self)
162         self.column_widths = self.config.get("column_widths", default_column_widths )
163         tabs.addTab(self.create_history_tab(), _('History') )
164         tabs.addTab(self.create_send_tab(), _('Send') )
165         tabs.addTab(self.create_receive_tab(), _('Receive') )
166         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
167         tabs.addTab(self.create_console_tab(), _('Console') )
168         tabs.setMinimumSize(600, 400)
169         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
170         self.setCentralWidget(tabs)
171
172         g = self.config.get("winpos-qt",[100, 100, 840, 400])
173         self.setGeometry(g[0], g[1], g[2], g[3])
174
175         self.init_menubar()
176
177         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
178         QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
179         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
180         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
181         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
182         
183         self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
184         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
185         self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
186
187         self.history_list.setFocus(True)
188
189         # network callbacks
190         self.network.register_callback('updated', lambda: self.need_update.set())
191         self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
192         self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
193         self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
194         self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
195         # set initial message
196         self.console.showMessage(self.network.banner)
197
198         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
199         if platform.system() == 'Windows':
200             n = 3 if self.wallet.seed else 2
201             tabs.setCurrentIndex (n)
202             tabs.setCurrentIndex (0)
203
204         self.wallet = None
205         self.init_lite()
206
207
208     def go_full(self):
209         self.config.set_key('lite_mode', False, True)
210         self.mini.hide()
211         self.show()
212
213     def go_lite(self):
214         self.config.set_key('lite_mode', True, True)
215         self.hide()
216         self.mini.show()
217
218
219     def init_lite(self):
220         import lite_window
221         if not self.check_qt_version():
222             if self.config.get('lite_mode') is True:
223                 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
224                 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
225                 self.config.set_key('lite_mode', False, True)
226                 sys.exit(0)
227             self.mini = None
228             return
229
230         actuator = lite_window.MiniActuator(self)
231
232         # Should probably not modify the current path but instead
233         # change the behaviour of rsrc(...)
234         old_path = QDir.currentPath()
235         actuator.load_theme()
236
237         self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
238
239         driver = lite_window.MiniDriver(self, self.mini)
240
241         # Reset path back to original value now that loading the GUI
242         # is completed.
243         QDir.setCurrent(old_path)
244
245         if self.config.get('lite_mode') is True:
246             self.go_lite()
247         else:
248             self.go_full()
249
250
251     def check_qt_version(self):
252         qtVersion = qVersion()
253         return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
254     
255
256
257     def load_wallet(self, wallet):
258         import electrum
259         self.wallet = wallet
260
261         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.storage.path
262         if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
263         self.setWindowTitle( title )
264         self.update_wallet()
265         # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
266         self.notify_transactions()
267
268         # account selector
269         accounts = self.wallet.get_account_names()
270         self.account_selector.clear()
271         if len(accounts) > 1:
272             self.account_selector.addItems([_("All accounts")] + accounts.values())
273             self.account_selector.setCurrentIndex(0)
274             self.account_selector.show()
275         else:
276             self.account_selector.hide()
277
278         self.new_account.setEnabled(self.wallet.seed_version>4)
279
280         self.update_lock_icon()
281         self.update_buttons_on_seed()
282         self.update_console()
283
284         run_hook('load_wallet', wallet)
285
286
287     def select_wallet_file(self):
288         wallet_folder = self.wallet.storage.path
289         re.sub("(\/\w*.dat)$", "", wallet_folder)
290         file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
291         return file_name
292
293
294     def open_wallet(self):
295
296         filename = self.select_wallet_file()
297         if not filename:
298             return
299
300         storage = WalletStorage({'wallet_path': filename})
301         if not storage.file_exists:
302             self.show_message("file not found "+ filename)
303             return
304
305         self.wallet.stop_threads()
306         
307         # create new wallet 
308         wallet = Wallet(storage)
309         wallet.start_threads(self.network)
310
311         self.load_wallet(wallet)
312
313
314
315     def backup_wallet(self):
316         import shutil
317         path = self.wallet.storage.path
318         wallet_folder = os.path.dirname(path)
319         new_filename, ok = QInputDialog.getText(self, _('Filename'), _('Current directory') + ': ' + wallet_folder + '\n' + _('Enter a filename for the copy of your wallet') + ':')
320         new_filename = unicode(new_filename)
321         if not ok or not new_filename:
322             return
323
324         new_path = os.path.join(wallet_folder, new_filename)
325         if new_path != path:
326             try:
327                 shutil.copy2(path, new_path)
328                 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
329             except (IOError, os.error), reason:
330                 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
331
332
333     def new_wallet(self):
334         import installwizard
335
336         wallet_folder = os.path.dirname(self.wallet.storage.path)
337         filename, ok = QInputDialog.getText(self, _('Filename'), _('Current directory') + ': ' + wallet_folder + '\n'+_('Enter a new file name') + ':')
338         filename = unicode(filename)
339         if not ok or not filename:
340             return
341         filename = os.path.join(wallet_folder, filename)
342
343         storage = WalletStorage({'wallet_path': filename})
344         assert not storage.file_exists
345
346         wizard = installwizard.InstallWizard(self.config, self.network, storage)
347         wallet = wizard.run()
348         if wallet: 
349             self.load_wallet(wallet)
350         
351
352
353     def init_menubar(self):
354         menubar = QMenuBar()
355
356         file_menu = menubar.addMenu(_("&File"))
357         open_wallet_action = file_menu.addAction(_("&Open"))
358         open_wallet_action.triggered.connect(self.open_wallet)
359
360         new_wallet_action = file_menu.addAction(_("&Create/Restore"))
361         new_wallet_action.triggered.connect(self.new_wallet)
362
363         wallet_backup = file_menu.addAction(_("&Copy"))
364         wallet_backup.triggered.connect(self.backup_wallet)
365
366         quit_item = file_menu.addAction(_("&Close"))
367         quit_item.triggered.connect(self.close)
368
369         wallet_menu = menubar.addMenu(_("&Wallet"))
370
371         new_contact = wallet_menu.addAction(_("&New contact"))
372         new_contact.triggered.connect(self.new_contact_dialog)
373
374         self.new_account = wallet_menu.addAction(_("&New account"))
375         self.new_account.triggered.connect(self.new_account_dialog)
376
377         wallet_menu.addSeparator()
378
379         #if self.wallet.seed:
380         show_seed = wallet_menu.addAction(_("&Seed"))
381         show_seed.triggered.connect(self.show_seed_dialog)
382
383         show_mpk = wallet_menu.addAction(_("&Master Public Key"))
384         show_mpk.triggered.connect(self.show_master_public_key)
385
386         wallet_menu.addSeparator()
387
388         csv_transaction_menu = wallet_menu.addMenu(_("&Create transaction"))
389
390         csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
391         csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
392
393         csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
394         csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
395
396         raw_transaction_menu = wallet_menu.addMenu(_("&Load transaction"))
397
398         raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
399         raw_transaction_file.triggered.connect(self.do_process_from_file)
400
401         raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
402         raw_transaction_text.triggered.connect(self.do_process_from_text)
403
404
405         tools_menu = menubar.addMenu(_("&Tools"))
406
407         # Settings / Preferences are all reserved keywords in OSX using this as work around
408         preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
409         preferences_menu = tools_menu.addAction(preferences_name)
410         preferences_menu.triggered.connect(self.settings_dialog)
411
412         plugins_labels = tools_menu.addAction(_("&Plugins"))
413         plugins_labels.triggered.connect(self.plugins_dialog)
414
415         wallet_menu.addSeparator()
416
417         labels_menu = tools_menu.addMenu(_("&Labels"))
418         import_labels = labels_menu.addAction(_("&Import"))
419         import_labels.triggered.connect(self.do_import_labels)
420         export_labels = labels_menu.addAction(_("&Export"))
421         export_labels.triggered.connect(self.do_export_labels)
422
423         keys_menu = tools_menu.addMenu(_("&Private keys"))
424         import_keys = keys_menu.addAction(_("&Import"))
425         import_keys.triggered.connect(self.do_import_privkey)
426         export_keys = keys_menu.addAction(_("&Export"))
427         export_keys.triggered.connect(self.do_export_privkeys)
428
429         ex_history = tools_menu.addAction(_("&Export History"))
430         ex_history.triggered.connect(self.do_export_history)
431
432
433         help_menu = menubar.addMenu(_("&Help"))
434         show_about = help_menu.addAction(_("&About"))
435         show_about.triggered.connect(self.show_about)
436         web_open = help_menu.addAction(_("&Official website")) 
437         web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
438
439         help_menu.addSeparator()
440         doc_open = help_menu.addAction(_("&Documentation"))
441         doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
442         report_bug = help_menu.addAction(_("&Report Bug"))
443         report_bug.triggered.connect(self.show_report_bug)
444
445         self.setMenuBar(menubar)
446
447     def show_about(self):
448         QMessageBox.about(self, "Electrum",
449             _("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."))
450
451     def show_report_bug(self):
452         QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
453             _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
454
455
456     def notify_transactions(self):
457         print_error("Notifying GUI")
458         if len(self.network.interface.pending_transactions_for_notifications) > 0:
459             # Combine the transactions if there are more then three
460             tx_amount = len(self.network.interface.pending_transactions_for_notifications)
461             if(tx_amount >= 3):
462                 total_amount = 0
463                 for tx in self.network.interface.pending_transactions_for_notifications:
464                     is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
465                     if(v > 0):
466                         total_amount += v
467
468                 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
469                                 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
470
471                 self.network.interface.pending_transactions_for_notifications = []
472             else:
473               for tx in self.network.interface.pending_transactions_for_notifications:
474                   if tx:
475                       self.network.interface.pending_transactions_for_notifications.remove(tx)
476                       is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
477                       if(v > 0):
478                           self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
479
480     def notify(self, message):
481         self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
482
483
484
485     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
486     def getOpenFileName(self, title, filter = ""):
487         directory = self.config.get('io_dir', os.path.expanduser('~'))
488         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
489         if fileName and directory != os.path.dirname(fileName):
490             self.config.set_key('io_dir', os.path.dirname(fileName), True)
491         return fileName
492
493     def getSaveFileName(self, title, filename, filter = ""):
494         directory = self.config.get('io_dir', os.path.expanduser('~'))
495         path = os.path.join( directory, filename )
496         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
497         if fileName and directory != os.path.dirname(fileName):
498             self.config.set_key('io_dir', os.path.dirname(fileName), True)
499         return fileName
500
501     def close(self):
502         QMainWindow.close(self)
503         run_hook('close_main_window')
504
505     def connect_slots(self, sender):
506         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
507         self.previous_payto_e=''
508
509     def timer_actions(self):
510         if self.need_update.is_set():
511             self.update_wallet()
512             self.need_update.clear()
513         run_hook('timer_actions')
514     
515     def format_amount(self, x, is_diff=False, whitespaces=False):
516         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
517
518     def read_amount(self, x):
519         if x in['.', '']: return None
520         p = pow(10, self.decimal_point)
521         return int( p * Decimal(x) )
522
523     def base_unit(self):
524         assert self.decimal_point in [5,8]
525         return "BTC" if self.decimal_point == 8 else "mBTC"
526
527
528     def update_status(self):
529         if self.network.interface and self.network.interface.is_connected:
530             if not self.wallet.up_to_date:
531                 text = _("Synchronizing...")
532                 icon = QIcon(":icons/status_waiting.png")
533             else:
534                 c, u = self.wallet.get_account_balance(self.current_account)
535                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
536                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
537
538                 r = {}
539                 run_hook('set_quote_text', c+u, r)
540                 quote = r.get(0)
541                 if quote:
542                     text += "  (%s)"%quote
543
544                 self.tray.setToolTip(text)
545                 icon = QIcon(":icons/status_connected.png")
546         else:
547             text = _("Not connected")
548             icon = QIcon(":icons/status_disconnected.png")
549
550         self.balance_label.setText(text)
551         self.status_button.setIcon( icon )
552
553
554     def update_wallet(self):
555         self.update_status()
556         if self.wallet.up_to_date or not self.network.interface.is_connected:
557             self.update_history_tab()
558             self.update_receive_tab()
559             self.update_contacts_tab()
560             self.update_completions()
561
562         
563     def create_history_tab(self):
564         self.history_list = l = MyTreeWidget(self)
565         l.setColumnCount(5)
566         for i,width in enumerate(self.column_widths['history']):
567             l.setColumnWidth(i, width)
568         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
569         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
570         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
571
572         l.customContextMenuRequested.connect(self.create_history_menu)
573         return l
574
575
576     def create_history_menu(self, position):
577         self.history_list.selectedIndexes() 
578         item = self.history_list.currentItem()
579         if not item: return
580         tx_hash = str(item.data(0, Qt.UserRole).toString())
581         if not tx_hash: return
582         menu = QMenu()
583         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
584         menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
585         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
586         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
587
588
589     def show_transaction(self, tx):
590         import transaction_dialog
591         d = transaction_dialog.TxDialog(tx, self)
592         d.exec_()
593
594     def tx_label_clicked(self, item, column):
595         if column==2 and item.isSelected():
596             self.is_edit=True
597             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
598             self.history_list.editItem( item, column )
599             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
600             self.is_edit=False
601
602     def tx_label_changed(self, item, column):
603         if self.is_edit: 
604             return
605         self.is_edit=True
606         tx_hash = str(item.data(0, Qt.UserRole).toString())
607         tx = self.wallet.transactions.get(tx_hash)
608         text = unicode( item.text(2) )
609         self.wallet.set_label(tx_hash, text) 
610         if text: 
611             item.setForeground(2, QBrush(QColor('black')))
612         else:
613             text = self.wallet.get_default_label(tx_hash)
614             item.setText(2, text)
615             item.setForeground(2, QBrush(QColor('gray')))
616         self.is_edit=False
617
618
619     def edit_label(self, is_recv):
620         l = self.receive_list if is_recv else self.contacts_list
621         item = l.currentItem()
622         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
623         l.editItem( item, 1 )
624         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
625
626
627
628     def address_label_clicked(self, item, column, l, column_addr, column_label):
629         if column == column_label and item.isSelected():
630             is_editable = item.data(0, 32).toBool()
631             if not is_editable:
632                 return
633             addr = unicode( item.text(column_addr) )
634             label = unicode( item.text(column_label) )
635             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
636             l.editItem( item, column )
637             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
638
639
640     def address_label_changed(self, item, column, l, column_addr, column_label):
641         if column == column_label:
642             addr = unicode( item.text(column_addr) )
643             text = unicode( item.text(column_label) )
644             is_editable = item.data(0, 32).toBool()
645             if not is_editable:
646                 return
647
648             changed = self.wallet.set_label(addr, text)
649             if changed:
650                 self.update_history_tab()
651                 self.update_completions()
652                 
653             self.current_item_changed(item)
654
655         run_hook('item_changed', item, column)
656
657
658     def current_item_changed(self, a):
659         run_hook('current_item_changed', a)
660
661
662
663     def update_history_tab(self):
664
665         self.history_list.clear()
666         for item in self.wallet.get_tx_history(self.current_account):
667             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
668             if conf > 0:
669                 try:
670                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
671                 except:
672                     time_str = _("unknown")
673
674             if conf == -1:
675                 time_str = 'unverified'
676                 icon = QIcon(":icons/unconfirmed.png")
677             elif conf == 0:
678                 time_str = 'pending'
679                 icon = QIcon(":icons/unconfirmed.png")
680             elif conf < 6:
681                 icon = QIcon(":icons/clock%d.png"%conf)
682             else:
683                 icon = QIcon(":icons/confirmed.png")
684
685             if value is not None:
686                 v_str = self.format_amount(value, True, whitespaces=True)
687             else:
688                 v_str = '--'
689
690             balance_str = self.format_amount(balance, whitespaces=True)
691             
692             if tx_hash:
693                 label, is_default_label = self.wallet.get_label(tx_hash)
694             else:
695                 label = _('Pruned transaction outputs')
696                 is_default_label = False
697
698             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
699             item.setFont(2, QFont(MONOSPACE_FONT))
700             item.setFont(3, QFont(MONOSPACE_FONT))
701             item.setFont(4, QFont(MONOSPACE_FONT))
702             if value < 0:
703                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
704             if tx_hash:
705                 item.setData(0, Qt.UserRole, tx_hash)
706                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
707             if is_default_label:
708                 item.setForeground(2, QBrush(QColor('grey')))
709
710             item.setIcon(0, icon)
711             self.history_list.insertTopLevelItem(0,item)
712             
713
714         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
715
716
717     def create_send_tab(self):
718         w = QWidget()
719
720         grid = QGridLayout()
721         grid.setSpacing(8)
722         grid.setColumnMinimumWidth(3,300)
723         grid.setColumnStretch(5,1)
724
725
726         self.payto_e = QLineEdit()
727         grid.addWidget(QLabel(_('Pay to')), 1, 0)
728         grid.addWidget(self.payto_e, 1, 1, 1, 3)
729             
730         grid.addWidget(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)')), 1, 4)
731
732         completer = QCompleter()
733         completer.setCaseSensitivity(False)
734         self.payto_e.setCompleter(completer)
735         completer.setModel(self.completions)
736
737         self.message_e = QLineEdit()
738         grid.addWidget(QLabel(_('Description')), 2, 0)
739         grid.addWidget(self.message_e, 2, 1, 1, 3)
740         grid.addWidget(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.')), 2, 4)
741
742         self.amount_e = AmountEdit(self.base_unit)
743         grid.addWidget(QLabel(_('Amount')), 3, 0)
744         grid.addWidget(self.amount_e, 3, 1, 1, 2)
745         grid.addWidget(HelpButton(
746                 _('Amount to be sent.') + '\n\n' \
747                     + _('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.') \
748                     + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
749         
750         self.fee_e = AmountEdit(self.base_unit)
751         grid.addWidget(QLabel(_('Fee')), 4, 0)
752         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
753         grid.addWidget(HelpButton(
754                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
755                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
756                     + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
757
758
759         self.send_button = EnterButton(_("Send"), self.do_send)
760         grid.addWidget(self.send_button, 6, 1)
761
762         b = EnterButton(_("Clear"),self.do_clear)
763         grid.addWidget(b, 6, 2)
764
765         self.payto_sig = QLabel('')
766         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
767
768         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
769         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
770         w.setLayout(grid) 
771
772         w2 = QWidget()
773         vbox = QVBoxLayout()
774         vbox.addWidget(w)
775         vbox.addStretch(1)
776         w2.setLayout(vbox)
777
778         def entry_changed( is_fee ):
779             self.funds_error = False
780
781             if self.amount_e.is_shortcut:
782                 self.amount_e.is_shortcut = False
783                 c, u = self.wallet.get_account_balance(self.current_account)
784                 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
785                 fee = self.wallet.estimated_fee(inputs)
786                 amount = c + u - fee
787                 self.amount_e.setText( self.format_amount(amount) )
788                 self.fee_e.setText( self.format_amount( fee ) )
789                 return
790                 
791             amount = self.read_amount(str(self.amount_e.text()))
792             fee = self.read_amount(str(self.fee_e.text()))
793
794             if not is_fee: fee = None
795             if amount is None:
796                 return
797             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
798             if not is_fee:
799                 self.fee_e.setText( self.format_amount( fee ) )
800             if inputs:
801                 palette = QPalette()
802                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
803                 text = ""
804             else:
805                 palette = QPalette()
806                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
807                 self.funds_error = True
808                 text = _( "Not enough funds" )
809                 c, u = self.wallet.get_frozen_balance()
810                 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
811
812             self.statusBar().showMessage(text)
813             self.amount_e.setPalette(palette)
814             self.fee_e.setPalette(palette)
815
816         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
817         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
818
819         run_hook('create_send_tab', grid)
820         return w2
821
822
823     def update_completions(self):
824         l = []
825         for addr,label in self.wallet.labels.items():
826             if addr in self.wallet.addressbook:
827                 l.append( label + '  <' + addr + '>')
828
829         run_hook('update_completions', l)
830         self.completions.setStringList(l)
831
832
833     def protected(func):
834         return lambda s, *args: s.do_protect(func, args)
835
836
837     def do_send(self):
838
839         label = unicode( self.message_e.text() )
840         r = unicode( self.payto_e.text() )
841         r = r.strip()
842
843         # label or alias, with address in brackets
844         m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
845         to_address = m.group(2) if m else r
846
847         if not is_valid(to_address):
848             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
849             return
850
851         try:
852             amount = self.read_amount(unicode( self.amount_e.text()))
853         except:
854             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
855             return
856         try:
857             fee = self.read_amount(unicode( self.fee_e.text()))
858         except:
859             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
860             return
861
862         confirm_amount = self.config.get('confirm_amount', 100000000)
863         if amount >= confirm_amount:
864             if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
865                 return
866
867         self.send_tx(to_address, amount, fee, label)
868
869
870     @protected
871     def send_tx(self, to_address, amount, fee, label, password):
872
873         try:
874             tx = self.wallet.mktx_from_account( [(to_address, amount)], password, fee, self.current_account)
875         except BaseException, e:
876             traceback.print_exc(file=sys.stdout)
877             self.show_message(str(e))
878             return
879
880         if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
881             QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
882             return
883
884         if label: 
885             self.wallet.set_label(tx.hash(), label)
886
887         if tx.is_complete:
888             h = self.wallet.send_tx(tx)
889             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
890             status, msg = self.wallet.receive_tx( h )
891             if status:
892                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
893                 self.do_clear()
894                 self.update_contacts_tab()
895             else:
896                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
897         else:
898             filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
899             try:
900                 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
901                 with open(fileName,'w') as f:
902                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
903                 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
904             except:
905                 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
906
907         # add recipient to addressbook
908         if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
909             self.wallet.addressbook.append(to_address)
910
911
912
913
914     def set_url(self, url):
915         address, amount, label, message, signature, identity, url = util.parse_url(url)
916
917         if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
918
919         if self.mini:
920             self.mini.set_payment_fields(address, amount)
921
922         if label and self.wallet.labels.get(address) != label:
923             if self.question('Give label "%s" to address %s ?'%(label,address)):
924                 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
925                     self.wallet.addressbook.append(address)
926                 self.wallet.set_label(address, label)
927
928         run_hook('set_url', url, self.show_message, self.question)
929
930         self.tabs.setCurrentIndex(1)
931         label = self.wallet.labels.get(address)
932         m_addr = label + '  <'+ address +'>' if label else address
933         self.payto_e.setText(m_addr)
934
935         self.message_e.setText(message)
936         if amount:
937             self.amount_e.setText(amount)
938
939         if identity:
940             self.set_frozen(self.payto_e,True)
941             self.set_frozen(self.amount_e,True)
942             self.set_frozen(self.message_e,True)
943             self.payto_sig.setText( '      '+_('The bitcoin URI was signed by')+' ' + identity )
944         else:
945             self.payto_sig.setVisible(False)
946
947     def do_clear(self):
948         self.payto_sig.setVisible(False)
949         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
950             e.setText('')
951             self.set_frozen(e,False)
952         self.update_status()
953
954     def set_frozen(self,entry,frozen):
955         if frozen:
956             entry.setReadOnly(True)
957             entry.setFrame(False)
958             palette = QPalette()
959             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
960             entry.setPalette(palette)
961         else:
962             entry.setReadOnly(False)
963             entry.setFrame(True)
964             palette = QPalette()
965             palette.setColor(entry.backgroundRole(), QColor('white'))
966             entry.setPalette(palette)
967
968
969     def toggle_freeze(self,addr):
970         if not addr: return
971         if addr in self.wallet.frozen_addresses:
972             self.wallet.unfreeze(addr)
973         else:
974             self.wallet.freeze(addr)
975         self.update_receive_tab()
976
977     def toggle_priority(self,addr):
978         if not addr: return
979         if addr in self.wallet.prioritized_addresses:
980             self.wallet.unprioritize(addr)
981         else:
982             self.wallet.prioritize(addr)
983         self.update_receive_tab()
984
985
986     def create_list_tab(self, headers):
987         "generic tab creation method"
988         l = MyTreeWidget(self)
989         l.setColumnCount( len(headers) )
990         l.setHeaderLabels( headers )
991
992         w = QWidget()
993         vbox = QVBoxLayout()
994         w.setLayout(vbox)
995
996         vbox.setMargin(0)
997         vbox.setSpacing(0)
998         vbox.addWidget(l)
999         buttons = QWidget()
1000         vbox.addWidget(buttons)
1001
1002         hbox = QHBoxLayout()
1003         hbox.setMargin(0)
1004         hbox.setSpacing(0)
1005         buttons.setLayout(hbox)
1006
1007         return l,w,hbox
1008
1009
1010     def create_receive_tab(self):
1011         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1012         l.setContextMenuPolicy(Qt.CustomContextMenu)
1013         l.customContextMenuRequested.connect(self.create_receive_menu)
1014         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1015         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1016         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1017         self.receive_list = l
1018         self.receive_buttons_hbox = hbox
1019         hbox.addStretch(1)
1020         return w
1021
1022
1023     def receive_tab_set_mode(self, i):
1024         self.save_column_widths()
1025         self.expert_mode = (i == 1)
1026         self.config.set_key('classic_expert_mode', self.expert_mode, True)
1027         self.update_receive_tab()
1028
1029
1030     def save_column_widths(self):
1031         if not self.expert_mode:
1032             widths = [ self.receive_list.columnWidth(0) ]
1033         else:
1034             widths = []
1035             for i in range(self.receive_list.columnCount() -1):
1036                 widths.append(self.receive_list.columnWidth(i))
1037         self.column_widths["receive"][self.expert_mode] = widths
1038         
1039         self.column_widths["history"] = []
1040         for i in range(self.history_list.columnCount() - 1):
1041             self.column_widths["history"].append(self.history_list.columnWidth(i))
1042
1043         self.column_widths["contacts"] = []
1044         for i in range(self.contacts_list.columnCount() - 1):
1045             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1046
1047         self.config.set_key("column_widths", self.column_widths, True)
1048
1049
1050     def create_contacts_tab(self):
1051         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1052         l.setContextMenuPolicy(Qt.CustomContextMenu)
1053         l.customContextMenuRequested.connect(self.create_contact_menu)
1054         for i,width in enumerate(self.column_widths['contacts']):
1055             l.setColumnWidth(i, width)
1056
1057         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1058         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1059         self.contacts_list = l
1060         self.contacts_buttons_hbox = hbox
1061         hbox.addStretch(1)
1062         return w
1063
1064
1065     def delete_imported_key(self, addr):
1066         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1067             self.wallet.delete_imported_key(addr)
1068             self.update_receive_tab()
1069             self.update_history_tab()
1070
1071     def edit_account_label(self, k):
1072         text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':')
1073         if ok:
1074             label = unicode(text)
1075             self.wallet.set_label(k,label)
1076             self.update_receive_tab()
1077
1078     def create_account_menu(self, position, k, item):
1079         menu = QMenu()
1080         if item.isExpanded():
1081             menu.addAction(_("Minimize"), lambda: item.setExpanded(False))
1082         else:
1083             menu.addAction(_("Maximize"), lambda: item.setExpanded(True))
1084         menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1085         menu.addAction(_("View details"), lambda: self.show_account_details(k))
1086         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1087
1088     def create_receive_menu(self, position):
1089         # fixme: this function apparently has a side effect.
1090         # if it is not called the menu pops up several times
1091         #self.receive_list.selectedIndexes() 
1092
1093         item = self.receive_list.itemAt(position)
1094         if not item: return
1095
1096         addr = unicode(item.text(0))
1097         if not is_valid(addr): 
1098             k = str(item.data(0,32).toString())
1099             if k:
1100                 self.create_account_menu(position, k, item)
1101             else:
1102                 item.setExpanded(not item.isExpanded())
1103             return 
1104
1105         menu = QMenu()
1106         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1107         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1108         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1109         menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1110         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1111         if addr in self.wallet.imported_keys:
1112             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1113
1114         if self.expert_mode:
1115             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1116             menu.addAction(t, lambda: self.toggle_freeze(addr))
1117             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1118             menu.addAction(t, lambda: self.toggle_priority(addr))
1119             
1120         run_hook('receive_menu', menu)
1121         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1122
1123
1124     def payto(self, addr):
1125         if not addr: return
1126         label = self.wallet.labels.get(addr)
1127         m_addr = label + '  <' + addr + '>' if label else addr
1128         self.tabs.setCurrentIndex(1)
1129         self.payto_e.setText(m_addr)
1130         self.amount_e.setFocus()
1131
1132
1133     def delete_contact(self, x):
1134         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1135             self.wallet.delete_contact(x)
1136             self.wallet.set_label(x, None)
1137             self.update_history_tab()
1138             self.update_contacts_tab()
1139             self.update_completions()
1140
1141
1142     def create_contact_menu(self, position):
1143         item = self.contacts_list.itemAt(position)
1144         if not item: return
1145         addr = unicode(item.text(0))
1146         label = unicode(item.text(1))
1147         is_editable = item.data(0,32).toBool()
1148         payto_addr = item.data(0,33).toString()
1149         menu = QMenu()
1150         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1151         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1152         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1153         if is_editable:
1154             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1155             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1156
1157         run_hook('create_contact_menu', menu, item)
1158         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1159
1160
1161     def update_receive_item(self, item):
1162         item.setFont(0, QFont(MONOSPACE_FONT))
1163         address = str(item.data(0,0).toString())
1164         label = self.wallet.labels.get(address,'')
1165         item.setData(1,0,label)
1166         item.setData(0,32, True) # is editable
1167
1168         run_hook('update_receive_item', address, item)
1169                 
1170         c, u = self.wallet.get_addr_balance(address)
1171         balance = self.format_amount(c + u)
1172         item.setData(2,0,balance)
1173
1174         if self.expert_mode:
1175             if address in self.wallet.frozen_addresses: 
1176                 item.setBackgroundColor(0, QColor('lightblue'))
1177             elif address in self.wallet.prioritized_addresses: 
1178                 item.setBackgroundColor(0, QColor('lightgreen'))
1179         
1180
1181     def update_receive_tab(self):
1182         l = self.receive_list
1183         
1184         l.clear()
1185         l.setColumnHidden(2, not self.expert_mode)
1186         l.setColumnHidden(3, not self.expert_mode)
1187         for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1188             l.setColumnWidth(i, width)
1189
1190         if self.current_account is None:
1191             account_items = self.wallet.accounts.items()
1192         elif self.current_account != -1:
1193             account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1194         else:
1195             account_items = []
1196
1197         for k, account in account_items:
1198             name = self.wallet.get_account_name(k)
1199             c,u = self.wallet.get_account_balance(k)
1200             account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1201             l.addTopLevelItem(account_item)
1202             account_item.setExpanded(True)
1203             account_item.setData(0, 32, k)
1204
1205             if not self.wallet.is_seeded(k):
1206                 icon = QIcon(":icons/key.png")
1207                 account_item.setIcon(0, icon)
1208             
1209             for is_change in ([0,1] if self.expert_mode else [0]):
1210                 if self.expert_mode:
1211                     name = _("Receiving") if not is_change else _("Change")
1212                     seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1213                     account_item.addChild(seq_item)
1214                     if not is_change: seq_item.setExpanded(True)
1215                 else:
1216                     seq_item = account_item
1217                 is_red = False
1218                 gap = 0
1219
1220                 for address in account.get_addresses(is_change):
1221                     h = self.wallet.history.get(address,[])
1222             
1223                     if h == []:
1224                         gap += 1
1225                         if gap > self.wallet.gap_limit:
1226                             is_red = True
1227                     else:
1228                         gap = 0
1229
1230                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1231                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1232                     self.update_receive_item(item)
1233                     if is_red:
1234                         item.setBackgroundColor(1, QColor('red'))
1235                     seq_item.addChild(item)
1236
1237
1238         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1239             c,u = self.wallet.get_imported_balance()
1240             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1241             l.addTopLevelItem(account_item)
1242             account_item.setExpanded(True)
1243             for address in self.wallet.imported_keys.keys():
1244                 item = QTreeWidgetItem( [ address, '', '', ''] )
1245                 self.update_receive_item(item)
1246                 account_item.addChild(item)
1247                 
1248
1249         # we use column 1 because column 0 may be hidden
1250         l.setCurrentItem(l.topLevelItem(0),1)
1251
1252
1253     def update_contacts_tab(self):
1254         l = self.contacts_list
1255         l.clear()
1256
1257         for address in self.wallet.addressbook:
1258             label = self.wallet.labels.get(address,'')
1259             n = self.wallet.get_num_tx(address)
1260             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1261             item.setFont(0, QFont(MONOSPACE_FONT))
1262             # 32 = label can be edited (bool)
1263             item.setData(0,32, True)
1264             # 33 = payto string
1265             item.setData(0,33, address)
1266             l.addTopLevelItem(item)
1267
1268         run_hook('update_contacts_tab', l)
1269         l.setCurrentItem(l.topLevelItem(0))
1270
1271
1272
1273     def create_console_tab(self):
1274         from console import Console
1275         self.console = console = Console()
1276         return console
1277
1278
1279     def update_console(self):
1280         console = self.console
1281         console.history = self.config.get("console-history",[])
1282         console.history_index = len(console.history)
1283
1284         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1285         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1286
1287         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1288         methods = {}
1289         def mkfunc(f, method):
1290             return lambda *args: apply( f, (method, args, self.password_dialog ))
1291         for m in dir(c):
1292             if m[0]=='_' or m=='wallet' or m == 'interface': continue
1293             methods[m] = mkfunc(c._run, m)
1294             
1295         console.updateNamespace(methods)
1296
1297
1298     def change_account(self,s):
1299         if s == _("All accounts"):
1300             self.current_account = None
1301         else:
1302             accounts = self.wallet.get_account_names()
1303             for k, v in accounts.items():
1304                 if v == s:
1305                     self.current_account = k
1306         self.update_history_tab()
1307         self.update_status()
1308         self.update_receive_tab()
1309
1310     def create_status_bar(self):
1311
1312         sb = QStatusBar()
1313         sb.setFixedHeight(35)
1314         qtVersion = qVersion()
1315
1316         self.balance_label = QLabel("")
1317         sb.addWidget(self.balance_label)
1318
1319         from version_getter import UpdateLabel
1320         self.updatelabel = UpdateLabel(self.config, sb)
1321
1322         self.account_selector = QComboBox()
1323         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account) 
1324         sb.addPermanentWidget(self.account_selector)
1325
1326         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1327             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1328
1329         self.lock_icon = QIcon()
1330         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1331         sb.addPermanentWidget( self.password_button )
1332             
1333         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1334         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) 
1335         sb.addPermanentWidget( self.seed_button )
1336         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) 
1337         sb.addPermanentWidget( self.status_button )
1338
1339         run_hook('create_status_bar', (sb,))
1340
1341         self.setStatusBar(sb)
1342
1343
1344     def update_lock_icon(self):
1345         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1346         self.password_button.setIcon( icon )
1347
1348
1349     def update_buttons_on_seed(self):
1350         if self.wallet.seed:
1351            self.seed_button.show()
1352            self.password_button.show()
1353            self.send_button.setText(_("Send"))
1354         else:
1355            self.password_button.hide()
1356            self.seed_button.hide()
1357            self.send_button.setText(_("Create unsigned transaction"))
1358
1359
1360     def change_password_dialog(self):
1361         from password_dialog import PasswordDialog
1362         d = PasswordDialog(self.wallet, self)
1363         d.run()
1364         self.update_lock_icon()
1365
1366
1367     def new_contact_dialog(self):
1368         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1369         address = unicode(text)
1370         if ok:
1371             if is_valid(address):
1372                 self.wallet.add_contact(address)
1373                 self.update_contacts_tab()
1374                 self.update_history_tab()
1375                 self.update_completions()
1376             else:
1377                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1378
1379
1380     def new_account_dialog(self):
1381
1382         dialog = QDialog(self)
1383         dialog.setModal(1)
1384         dialog.setWindowTitle(_("New Account"))
1385
1386         addr = self.wallet.new_account_address()
1387         vbox = QVBoxLayout()
1388         msg = _("Electrum considers that an account exists only if it contains bitcoins.") + '\n' \
1389               + _("To create a new account, please send coins to the first address of that account.") + '\n' \
1390               + _("Note: you will need to wait for 2 confirmations before the account is created.")
1391         vbox.addWidget(QLabel(msg))
1392         vbox.addWidget(QLabel(_('Address')+':'))
1393         e = QLineEdit(addr)
1394         e.setReadOnly(True)
1395         vbox.addWidget(e)
1396
1397         vbox.addLayout(ok_cancel_buttons(dialog))
1398         dialog.setLayout(vbox)
1399         r = dialog.exec_()
1400         if r:
1401             self.payto(addr)
1402
1403             
1404
1405     def show_master_public_key_old(self):
1406         dialog = QDialog(self)
1407         dialog.setModal(1)
1408         dialog.setWindowTitle(_("Master Public Key"))
1409
1410         main_text = QTextEdit()
1411         main_text.setText(self.wallet.get_master_public_key())
1412         main_text.setReadOnly(True)
1413         main_text.setMaximumHeight(170)
1414         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1415
1416         ok_button = QPushButton(_("OK"))
1417         ok_button.setDefault(True)
1418         ok_button.clicked.connect(dialog.accept)
1419
1420         main_layout = QGridLayout()
1421         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1422
1423         main_layout.addWidget(main_text, 1, 0)
1424         main_layout.addWidget(qrw, 1, 1 )
1425
1426         vbox.addLayout(close_button(dialog))
1427         dialog.setLayout(vbox)
1428         dialog.exec_()
1429         
1430
1431     def show_master_public_key(self):
1432
1433         if self.wallet.seed_version == 4:
1434             self.show_master_public_keys_old()
1435             return
1436
1437         dialog = QDialog(self)
1438         dialog.setModal(1)
1439         dialog.setWindowTitle(_("Master Public Keys"))
1440
1441         chain_text = QTextEdit()
1442         chain_text.setReadOnly(True)
1443         chain_text.setMaximumHeight(170)
1444         chain_qrw = QRCodeWidget()
1445
1446         mpk_text = QTextEdit()
1447         mpk_text.setReadOnly(True)
1448         mpk_text.setMaximumHeight(170)
1449         mpk_qrw = QRCodeWidget()
1450
1451         main_layout = QGridLayout()
1452
1453         main_layout.addWidget(QLabel(_('chain')), 1, 0)
1454         main_layout.addWidget(chain_text, 1, 1)
1455         main_layout.addWidget(chain_qrw, 1, 2)
1456         main_layout.addWidget(QLabel(_('public key')), 2, 0)
1457         main_layout.addWidget(mpk_text, 2, 1)
1458         main_layout.addWidget(mpk_qrw, 2, 2)
1459
1460         def update(key):
1461             c, K, cK = self.wallet.master_public_keys[str(key)]
1462             chain_text.setText(c)
1463             chain_qrw.set_addr(c)
1464             chain_qrw.update_qr()
1465             mpk_text.setText(K)
1466             mpk_qrw.set_addr(c)
1467             mpk_qrw.update_qr()
1468             
1469         key_selector = QComboBox()
1470         keys = sorted(self.wallet.master_public_keys.keys())
1471         key_selector.addItems(keys)
1472
1473         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1474         main_layout.addWidget(key_selector, 0, 1)
1475         dialog.connect(key_selector,SIGNAL("activated(QString)"),update) 
1476
1477         update(keys[0])
1478
1479         vbox = QVBoxLayout()
1480         vbox.addLayout(main_layout)
1481         vbox.addLayout(close_button(dialog))
1482
1483         dialog.setLayout(vbox)
1484         dialog.exec_()
1485         
1486
1487     @protected
1488     def show_seed_dialog(self, password):
1489         if not self.wallet.seed:
1490             QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1491             return
1492         try:
1493             seed = self.wallet.decode_seed(password)
1494         except:
1495             QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1496             return
1497
1498         from seed_dialog import SeedDialog
1499         d = SeedDialog(self)
1500         d.show_seed(seed, self.wallet.imported_keys)
1501
1502
1503
1504     def show_qrcode(self, data, title = _("QR code")):
1505         if not data: return
1506         d = QDialog(self)
1507         d.setModal(1)
1508         d.setWindowTitle(title)
1509         d.setMinimumSize(270, 300)
1510         vbox = QVBoxLayout()
1511         qrw = QRCodeWidget(data)
1512         vbox.addWidget(qrw, 1)
1513         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1514         hbox = QHBoxLayout()
1515         hbox.addStretch(1)
1516
1517         def print_qr(self):
1518             filename = "qrcode.bmp"
1519             bmp.save_qrcode(qrw.qr, filename)
1520             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1521
1522         b = QPushButton(_("Save"))
1523         hbox.addWidget(b)
1524         b.clicked.connect(print_qr)
1525
1526         b = QPushButton(_("Close"))
1527         hbox.addWidget(b)
1528         b.clicked.connect(d.accept)
1529         b.setDefault(True)
1530
1531         vbox.addLayout(hbox)
1532         d.setLayout(vbox)
1533         d.exec_()
1534
1535
1536     def do_protect(self, func, args):
1537         if self.wallet.use_encryption:
1538             password = self.password_dialog()
1539             if not password:
1540                 return
1541         else:
1542             password = None
1543             
1544         if args != (False,):
1545             args = (self,) + args + (password,)
1546         else:
1547             args = (self,password)
1548         apply( func, args)
1549
1550
1551     @protected
1552     def show_private_key(self, address, password):
1553         if not address: return
1554         try:
1555             pk_list = self.wallet.get_private_key(address, password)
1556         except BaseException, e:
1557             self.show_message(str(e))
1558             return
1559         QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1560
1561
1562     @protected
1563     def do_sign(self, address, message, signature, password):
1564         message = unicode(message.toPlainText())
1565         message = message.encode('utf-8')
1566         try:
1567             sig = self.wallet.sign_message(str(address.text()), message, password)
1568             signature.setText(sig)
1569         except BaseException, e:
1570             self.show_message(str(e))
1571
1572     def sign_message(self, address):
1573         if not address: return
1574         d = QDialog(self)
1575         d.setModal(1)
1576         d.setWindowTitle(_('Sign Message'))
1577         d.setMinimumSize(410, 290)
1578
1579         tab_widget = QTabWidget()
1580         tab = QWidget()
1581         layout = QGridLayout(tab)
1582
1583         sign_address = QLineEdit()
1584
1585         sign_address.setText(address)
1586         layout.addWidget(QLabel(_('Address')), 1, 0)
1587         layout.addWidget(sign_address, 1, 1)
1588
1589         sign_message = QTextEdit()
1590         layout.addWidget(QLabel(_('Message')), 2, 0)
1591         layout.addWidget(sign_message, 2, 1)
1592         layout.setRowStretch(2,3)
1593
1594         sign_signature = QTextEdit()
1595         layout.addWidget(QLabel(_('Signature')), 3, 0)
1596         layout.addWidget(sign_signature, 3, 1)
1597         layout.setRowStretch(3,1)
1598
1599
1600         hbox = QHBoxLayout()
1601         b = QPushButton(_("Sign"))
1602         hbox.addWidget(b)
1603         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1604         b = QPushButton(_("Close"))
1605         b.clicked.connect(d.accept)
1606         hbox.addWidget(b)
1607         layout.addLayout(hbox, 4, 1)
1608         tab_widget.addTab(tab, _("Sign"))
1609
1610
1611         tab = QWidget()
1612         layout = QGridLayout(tab)
1613
1614         verify_address = QLineEdit()
1615         layout.addWidget(QLabel(_('Address')), 1, 0)
1616         layout.addWidget(verify_address, 1, 1)
1617
1618         verify_message = QTextEdit()
1619         layout.addWidget(QLabel(_('Message')), 2, 0)
1620         layout.addWidget(verify_message, 2, 1)
1621         layout.setRowStretch(2,3)
1622
1623         verify_signature = QTextEdit()
1624         layout.addWidget(QLabel(_('Signature')), 3, 0)
1625         layout.addWidget(verify_signature, 3, 1)
1626         layout.setRowStretch(3,1)
1627
1628         def do_verify():
1629             message = unicode(verify_message.toPlainText())
1630             message = message.encode('utf-8')
1631             if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1632                 self.show_message(_("Signature verified"))
1633             else:
1634                 self.show_message(_("Error: wrong signature"))
1635
1636         hbox = QHBoxLayout()
1637         b = QPushButton(_("Verify"))
1638         b.clicked.connect(do_verify)
1639         hbox.addWidget(b)
1640         b = QPushButton(_("Close"))
1641         b.clicked.connect(d.accept)
1642         hbox.addWidget(b)
1643         layout.addLayout(hbox, 4, 1)
1644         tab_widget.addTab(tab, _("Verify"))
1645
1646         vbox = QVBoxLayout()
1647         vbox.addWidget(tab_widget)
1648         d.setLayout(vbox)
1649         d.exec_()
1650
1651         
1652
1653
1654     def question(self, msg):
1655         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1656
1657     def show_message(self, msg):
1658         QMessageBox.information(self, _('Message'), msg, _('OK'))
1659
1660     def password_dialog(self ):
1661         d = QDialog(self)
1662         d.setModal(1)
1663
1664         pw = QLineEdit()
1665         pw.setEchoMode(2)
1666
1667         vbox = QVBoxLayout()
1668         msg = _('Please enter your password')
1669         vbox.addWidget(QLabel(msg))
1670
1671         grid = QGridLayout()
1672         grid.setSpacing(8)
1673         grid.addWidget(QLabel(_('Password')), 1, 0)
1674         grid.addWidget(pw, 1, 1)
1675         vbox.addLayout(grid)
1676
1677         vbox.addLayout(ok_cancel_buttons(d))
1678         d.setLayout(vbox)
1679
1680         run_hook('password_dialog', pw, grid, 1)
1681         if not d.exec_(): return
1682         return unicode(pw.text())
1683
1684
1685
1686
1687
1688
1689
1690
1691     def tx_from_text(self, txt):
1692         "json or raw hexadecimal"
1693         try:
1694             txt.decode('hex')
1695             tx = Transaction(txt)
1696             return tx
1697         except:
1698             pass
1699
1700         try:
1701             tx_dict = json.loads(str(txt))
1702             assert "hex" in tx_dict.keys()
1703             assert "complete" in tx_dict.keys()
1704             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1705             if not tx_dict["complete"]:
1706                 assert "input_info" in tx_dict.keys()
1707                 input_info = json.loads(tx_dict['input_info'])
1708                 tx.add_input_info(input_info)
1709             return tx
1710         except:
1711             pass
1712         
1713         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1714
1715
1716
1717     def read_tx_from_file(self):
1718         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1719         if not fileName:
1720             return
1721         try:
1722             with open(fileName, "r") as f:
1723                 file_content = f.read()
1724         except (ValueError, IOError, os.error), reason:
1725             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1726
1727         return self.tx_from_text(file_content)
1728
1729
1730     @protected
1731     def sign_raw_transaction(self, tx, input_info, password):
1732         self.wallet.signrawtransaction(tx, input_info, [], password)
1733
1734     def do_process_from_text(self):
1735         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1736         if not text:
1737             return
1738         tx = self.tx_from_text(text)
1739         if tx:
1740             self.show_transaction(tx)
1741
1742     def do_process_from_file(self):
1743         tx = self.read_tx_from_file()
1744         if tx:
1745             self.show_transaction(tx)
1746
1747     def do_process_from_csvReader(self, csvReader):
1748         outputs = []
1749         try:
1750             for row in csvReader:
1751                 address = row[0]
1752                 amount = float(row[1])
1753                 amount = int(100000000*amount)
1754                 outputs.append((address, amount))
1755         except (ValueError, IOError, os.error), reason:
1756             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1757             return
1758
1759         try:
1760             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1761         except BaseException, e:
1762             self.show_message(str(e))
1763             return
1764
1765         self.show_transaction(tx)
1766
1767     def do_process_from_csv_file(self):
1768         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1769         if not fileName:
1770             return
1771         try:
1772             with open(fileName, "r") as f:
1773                 csvReader = csv.reader(f)
1774                 self.do_process_from_csvReader(csvReader)
1775         except (ValueError, IOError, os.error), reason:
1776             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1777             return
1778
1779     def do_process_from_csv_text(self):
1780         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1781                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1782         if not text:
1783             return
1784         f = StringIO.StringIO(text)
1785         csvReader = csv.reader(f)
1786         self.do_process_from_csvReader(csvReader)
1787
1788
1789
1790     @protected
1791     def do_export_privkeys(self, password):
1792         self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),  _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1793
1794         try:
1795             select_export = _('Select file to export your private keys to')
1796             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1797             if fileName:
1798                 with open(fileName, "w+") as csvfile:
1799                     transaction = csv.writer(csvfile)
1800                     transaction.writerow(["address", "private_key"])
1801
1802                     addresses = self.wallet.addresses(True)
1803                     
1804                     for addr in addresses:
1805                         pk = "".join(self.wallet.get_private_key(addr, password))
1806                         transaction.writerow(["%34s"%addr,pk])
1807
1808                     self.show_message(_("Private keys exported."))
1809
1810         except (IOError, os.error), reason:
1811             export_error_label = _("Electrum was unable to produce a private key-export.")
1812             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1813
1814         except BaseException, e:
1815           self.show_message(str(e))
1816           return
1817
1818
1819     def do_import_labels(self):
1820         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1821         if not labelsFile: return
1822         try:
1823             f = open(labelsFile, 'r')
1824             data = f.read()
1825             f.close()
1826             for key, value in json.loads(data).items():
1827                 self.wallet.set_label(key, value)
1828             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1829         except (IOError, os.error), reason:
1830             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1831             
1832
1833     def do_export_labels(self):
1834         labels = self.wallet.labels
1835         try:
1836             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1837             if fileName:
1838                 with open(fileName, 'w+') as f:
1839                     json.dump(labels, f)
1840                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1841         except (IOError, os.error), reason:
1842             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1843
1844
1845     def do_export_history(self):
1846         from lite_window import csv_transaction
1847         csv_transaction(self.wallet)
1848
1849
1850     @protected
1851     def do_import_privkey(self, password):
1852         if not self.wallet.imported_keys:
1853             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1854                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1855                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1856             if r == 4: return
1857
1858         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1859         if not text: return
1860
1861         text = str(text).split()
1862         badkeys = []
1863         addrlist = []
1864         for key in text:
1865             try:
1866                 addr = self.wallet.import_key(key, password)
1867             except BaseException as e:
1868                 badkeys.append(key)
1869                 continue
1870             if not addr: 
1871                 badkeys.append(key)
1872             else:
1873                 addrlist.append(addr)
1874         if addrlist:
1875             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1876         if badkeys:
1877             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1878         self.update_receive_tab()
1879         self.update_history_tab()
1880
1881
1882     def settings_dialog(self):
1883         d = QDialog(self)
1884         d.setWindowTitle(_('Electrum Settings'))
1885         d.setModal(1)
1886         vbox = QVBoxLayout()
1887
1888         tabs = QTabWidget(self)
1889         self.settings_tab = tabs
1890         vbox.addWidget(tabs)
1891
1892         tab1 = QWidget()
1893         grid_ui = QGridLayout(tab1)
1894         grid_ui.setColumnStretch(0,1)
1895         tabs.addTab(tab1, _('Display') )
1896
1897         nz_label = QLabel(_('Display zeros'))
1898         grid_ui.addWidget(nz_label, 0, 0)
1899         nz_e = AmountEdit(None,True)
1900         nz_e.setText("%d"% self.num_zeros)
1901         grid_ui.addWidget(nz_e, 0, 1)
1902         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1903         grid_ui.addWidget(HelpButton(msg), 0, 2)
1904         if not self.config.is_modifiable('num_zeros'):
1905             for w in [nz_e, nz_label]: w.setEnabled(False)
1906         
1907         lang_label=QLabel(_('Language') + ':')
1908         grid_ui.addWidget(lang_label, 1, 0)
1909         lang_combo = QComboBox()
1910         from electrum.i18n import languages
1911         lang_combo.addItems(languages.values())
1912         try:
1913             index = languages.keys().index(self.config.get("language",''))
1914         except:
1915             index = 0
1916         lang_combo.setCurrentIndex(index)
1917         grid_ui.addWidget(lang_combo, 1, 1)
1918         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1919         if not self.config.is_modifiable('language'):
1920             for w in [lang_combo, lang_label]: w.setEnabled(False)
1921
1922         expert_cb = QCheckBox(_('Expert mode'))
1923         expert_cb.setChecked(self.expert_mode)
1924         grid_ui.addWidget(expert_cb, 3, 0)
1925         hh =  _('In expert mode, your client will:') + '\n'  \
1926             + _(' - Show change addresses in the Receive tab') + '\n'  \
1927             + _(' - Display the balance of each address') + '\n'  \
1928             + _(' - Add freeze/prioritize actions to addresses.') 
1929         grid_ui.addWidget(HelpButton(hh), 3, 2)
1930         grid_ui.setRowStretch(4,1)
1931
1932         # wallet tab
1933         tab2 = QWidget()
1934         grid_wallet = QGridLayout(tab2)
1935         grid_wallet.setColumnStretch(0,1)
1936         tabs.addTab(tab2, _('Wallet') )
1937         
1938         fee_label = QLabel(_('Transaction fee'))
1939         grid_wallet.addWidget(fee_label, 0, 0)
1940         fee_e = AmountEdit(self.base_unit)
1941         fee_e.setText(self.format_amount(self.wallet.fee).strip())
1942         grid_wallet.addWidget(fee_e, 0, 2)
1943         msg = _('Fee per kilobyte of transaction.') + ' ' \
1944             + _('Recommended value') + ': ' + self.format_amount(50000)
1945         grid_wallet.addWidget(HelpButton(msg), 0, 3)
1946         if not self.config.is_modifiable('fee_per_kb'):
1947             for w in [fee_e, fee_label]: w.setEnabled(False)
1948
1949         usechange_cb = QCheckBox(_('Use change addresses'))
1950         usechange_cb.setChecked(self.wallet.use_change)
1951         grid_wallet.addWidget(usechange_cb, 1, 0)
1952         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1953         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1954
1955         units = ['BTC', 'mBTC']
1956         unit_label = QLabel(_('Base unit'))
1957         grid_wallet.addWidget(unit_label, 3, 0)
1958         unit_combo = QComboBox()
1959         unit_combo.addItems(units)
1960         unit_combo.setCurrentIndex(units.index(self.base_unit()))
1961         grid_wallet.addWidget(unit_combo, 3, 2)
1962         grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1963                                              + '\n1BTC=1000mBTC.\n' \
1964                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1965         grid_wallet.setRowStretch(4,1)
1966
1967
1968         run_hook('create_settings_tab', tabs)
1969
1970         vbox.addLayout(ok_cancel_buttons(d))
1971         d.setLayout(vbox) 
1972
1973         # run the dialog
1974         if not d.exec_(): return
1975
1976         fee = unicode(fee_e.text())
1977         try:
1978             fee = self.read_amount(fee)
1979         except:
1980             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1981             return
1982
1983         self.wallet.set_fee(fee)
1984         
1985         nz = unicode(nz_e.text())
1986         try:
1987             nz = int( nz )
1988             if nz>8: nz=8
1989         except:
1990             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1991             return
1992
1993         if self.num_zeros != nz:
1994             self.num_zeros = nz
1995             self.config.set_key('num_zeros', nz, True)
1996             self.update_history_tab()
1997             self.update_receive_tab()
1998
1999         usechange_result = usechange_cb.isChecked()
2000         if self.wallet.use_change != usechange_result:
2001             self.wallet.use_change = usechange_result
2002             self.config.set_key('use_change', self.wallet.use_change, True)
2003         
2004         unit_result = units[unit_combo.currentIndex()]
2005         if self.base_unit() != unit_result:
2006             self.decimal_point = 8 if unit_result == 'BTC' else 5
2007             self.config.set_key('decimal_point', self.decimal_point, True)
2008             self.update_history_tab()
2009             self.update_status()
2010         
2011         need_restart = False
2012
2013         lang_request = languages.keys()[lang_combo.currentIndex()]
2014         if lang_request != self.config.get('language'):
2015             self.config.set_key("language", lang_request, True)
2016             need_restart = True
2017             
2018
2019         run_hook('close_settings_dialog')
2020
2021         if need_restart:
2022             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2023
2024         self.receive_tab_set_mode(expert_cb.isChecked())
2025
2026     def run_network_dialog(self):
2027         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2028
2029     def closeEvent(self, event):
2030         g = self.geometry()
2031         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2032         self.save_column_widths()
2033         self.config.set_key("console-history", self.console.history[-50:], True)
2034         event.accept()
2035
2036
2037
2038     def plugins_dialog(self):
2039         from electrum.plugins import plugins
2040
2041         d = QDialog(self)
2042         d.setWindowTitle(_('Electrum Plugins'))
2043         d.setModal(1)
2044
2045         vbox = QVBoxLayout(d)
2046
2047         # plugins
2048         scroll = QScrollArea()
2049         scroll.setEnabled(True)
2050         scroll.setWidgetResizable(True)
2051         scroll.setMinimumSize(400,250)
2052         vbox.addWidget(scroll)
2053
2054         w = QWidget()
2055         scroll.setWidget(w)
2056         w.setMinimumHeight(len(plugins)*35)
2057
2058         grid = QGridLayout()
2059         grid.setColumnStretch(0,1)
2060         w.setLayout(grid)
2061
2062         def mk_toggle(cb, p):
2063             return lambda: cb.setChecked(p.toggle())
2064         for i, p in enumerate(plugins):
2065             try:
2066                 cb = QCheckBox(p.fullname())
2067                 cb.setDisabled(not p.is_available())
2068                 cb.setChecked(p.is_enabled())
2069                 cb.clicked.connect(mk_toggle(cb,p))
2070                 grid.addWidget(cb, i, 0)
2071                 if p.requires_settings():
2072                     b = EnterButton(_('Settings'), p.settings_dialog)
2073                     b.setEnabled( p.is_enabled() )
2074                     grid.addWidget(b, i, 1)
2075                 grid.addWidget(HelpButton(p.description()), i, 2)
2076             except:
2077                 print_msg(_("Error: cannot display plugin"), p)
2078                 traceback.print_exc(file=sys.stdout)
2079         grid.setRowStretch(i+1,1)
2080
2081         vbox.addLayout(close_button(d))
2082
2083         d.exec_()
2084
2085
2086     def show_account_details(self, k):
2087         d = QDialog(self)
2088         d.setWindowTitle(_('Account Details'))
2089         d.setModal(1)
2090
2091         vbox = QVBoxLayout(d)
2092         roots = self.wallet.get_roots(k)
2093
2094         name = self.wallet.get_account_name(k)
2095         label = QLabel('Name: ' + name)
2096         vbox.addWidget(label)
2097
2098         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2099         vbox.addWidget(QLabel('Type: ' + acctype))
2100
2101         label = QLabel('Derivation: ' + k)
2102         vbox.addWidget(label)
2103
2104         #for root in roots:
2105         #    mpk = self.wallet.master_public_keys[root]
2106         #    text = QTextEdit()
2107         #    text.setReadOnly(True)
2108         #    text.setMaximumHeight(120)
2109         #    text.setText(repr(mpk))
2110         #    vbox.addWidget(text)
2111
2112         vbox.addLayout(close_button(d))
2113         d.exec_()