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