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