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