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