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