684100cd1d1e1438bcb402ee26e690e49fc57fd8
[electrum-nvc.git] / gui / qt / main_window.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 import shutil
24 import StringIO
25
26
27 import PyQt4
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31
32 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
33 from electrum.plugins import run_hook
34
35 import icons_rc
36
37 from electrum.wallet import format_satoshis
38 from electrum import Transaction
39 from electrum import mnemonic
40 from electrum import util, bitcoin, commands, Interface, Wallet
41 from electrum import SimpleConfig, Wallet, WalletStorage
42
43
44 from electrum import bmp, pyqrnative
45
46 from amountedit import AmountEdit
47 from network_dialog import NetworkDialog
48 from qrcodewidget import QRCodeWidget
49
50 from decimal import Decimal
51
52 import platform
53 import httplib
54 import socket
55 import webbrowser
56 import csv
57
58 if platform.system() == 'Windows':
59     MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61     MONOSPACE_FONT = 'Monaco'
62 else:
63     MONOSPACE_FONT = 'monospace'
64
65 from electrum import ELECTRUM_VERSION
66 import re
67
68 from util import *
69
70
71         
72
73
74
75 class StatusBarButton(QPushButton):
76     def __init__(self, icon, tooltip, func):
77         QPushButton.__init__(self, icon, '')
78         self.setToolTip(tooltip)
79         self.setFlat(True)
80         self.setMaximumWidth(25)
81         self.clicked.connect(func)
82         self.func = func
83         self.setIconSize(QSize(25,25))
84
85     def keyPressEvent(self, e):
86         if e.key() == QtCore.Qt.Key_Return:
87             apply(self.func,())
88
89
90
91
92
93
94
95
96
97
98 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
99
100 class ElectrumWindow(QMainWindow):
101     def changeEvent(self, event):
102         flags = self.windowFlags();
103         if event and event.type() == QtCore.QEvent.WindowStateChange:
104             if self.windowState() & QtCore.Qt.WindowMinimized:
105                 self.build_menu(True)
106                 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
107                 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
108                 # Electrum from closing.
109                 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
110                 # self.setWindowFlags(flags & ~Qt.ToolTip)
111             elif event.oldState() & QtCore.Qt.WindowMinimized:
112                 self.build_menu(False)
113                 #self.setWindowFlags(flags | Qt.ToolTip)
114
115     def build_menu(self, is_hidden = False):
116         m = QMenu()
117         if self.isMinimized():
118             m.addAction(_("Show"), self.showNormal)
119         else:
120             m.addAction(_("Hide"), self.showMinimized)
121
122         m.addSeparator()
123         m.addAction(_("Exit Electrum"), self.close)
124         self.tray.setContextMenu(m)
125
126     def tray_activated(self, reason):
127         if reason == QSystemTrayIcon.DoubleClick:
128             self.showNormal()
129
130     def showNormal(self):
131         self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
132
133     def __init__(self, config, network):
134         QMainWindow.__init__(self)
135
136         self.config = config
137         self.network = network
138
139         self._close_electrum = False
140         self.lite = None
141
142         if sys.platform == 'darwin':
143           self.icon = QIcon(":icons/electrum_dark_icon.png")
144           #self.icon = QIcon(":icons/lock.png")
145         else:
146           self.icon = QIcon(':icons/electrum_light_icon.png')
147
148         self.tray = QSystemTrayIcon(self.icon, self)
149         self.tray.setToolTip('Electrum')
150         self.tray.activated.connect(self.tray_activated)
151
152         self.build_menu()
153         self.tray.show()
154         self.create_status_bar()
155
156         self.need_update = threading.Event()
157
158         self.decimal_point = config.get('decimal_point', 8)
159         self.num_zeros     = int(config.get('num_zeros',0))
160
161         set_language(config.get('language'))
162
163         self.funds_error = False
164         self.completions = QStringListModel()
165
166         self.tabs = tabs = QTabWidget(self)
167         self.column_widths = self.config.get("column_widths_2", default_column_widths )
168         tabs.addTab(self.create_history_tab(), _('History') )
169         tabs.addTab(self.create_send_tab(), _('Send') )
170         tabs.addTab(self.create_receive_tab(), _('Receive') )
171         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
172         tabs.addTab(self.create_console_tab(), _('Console') )
173         tabs.setMinimumSize(600, 400)
174         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
175         self.setCentralWidget(tabs)
176
177         g = self.config.get("winpos-qt",[100, 100, 840, 400])
178         self.setGeometry(g[0], g[1], g[2], g[3])
179
180         self.setWindowIcon(QIcon(":icons/electrum.png"))
181         self.init_menubar()
182
183         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
184         QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
185         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
186         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
187         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
188         
189         self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
190         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
191         self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
192
193         self.history_list.setFocus(True)
194
195         # network callbacks
196         if self.network:
197             self.network.register_callback('updated', lambda: self.need_update.set())
198             self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
199             self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
200             self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
201             self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
202
203             # set initial message
204             self.console.showMessage(self.network.banner)
205
206         self.wallet = None
207         self.init_lite()
208
209
210     def go_full(self):
211         self.config.set_key('lite_mode', False, True)
212         self.mini.hide()
213         self.show()
214
215     def go_lite(self):
216         self.config.set_key('lite_mode', True, True)
217         self.hide()
218         self.mini.show()
219
220
221     def init_lite(self):
222         import lite_window
223         if not self.check_qt_version():
224             if self.config.get('lite_mode') is True:
225                 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
226                 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
227                 self.config.set_key('lite_mode', False, True)
228                 sys.exit(0)
229             self.mini = None
230             self.show()
231             return
232
233         actuator = lite_window.MiniActuator(self)
234
235         # Should probably not modify the current path but instead
236         # change the behaviour of rsrc(...)
237         old_path = QDir.currentPath()
238         actuator.load_theme()
239
240         self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
241
242         driver = lite_window.MiniDriver(self, self.mini)
243
244         # Reset path back to original value now that loading the GUI
245         # is completed.
246         QDir.setCurrent(old_path)
247
248         if self.config.get('lite_mode') is True:
249             self.go_lite()
250         else:
251             self.go_full()
252
253
254     def check_qt_version(self):
255         qtVersion = qVersion()
256         return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
257     
258
259     def update_account_selector(self):
260         # account selector
261         accounts = self.wallet.get_account_names()
262         self.account_selector.clear()
263         if len(accounts) > 1:
264             self.account_selector.addItems([_("All accounts")] + accounts.values())
265             self.account_selector.setCurrentIndex(0)
266             self.account_selector.show()
267         else:
268             self.account_selector.hide()
269
270
271     def load_wallet(self, wallet):
272         import electrum
273         self.wallet = wallet
274         self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
275         self.current_account = self.wallet.storage.get("current_account", None)
276
277         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.storage.path
278         if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
279         self.setWindowTitle( title )
280         self.update_wallet()
281         # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
282         self.notify_transactions()
283         self.update_account_selector()
284         self.new_account.setEnabled(self.wallet.seed_version>4)
285         self.update_lock_icon()
286         self.update_buttons_on_seed()
287         self.update_console()
288
289         run_hook('load_wallet', wallet)
290
291
292     def open_wallet(self):
293         wallet_folder = self.wallet.storage.path
294         filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
295         if not filename:
296             return
297
298         storage = WalletStorage({'wallet_path': filename})
299         if not storage.file_exists:
300             self.show_message("file not found "+ filename)
301             return
302
303         self.wallet.stop_threads()
304         
305         # create new wallet 
306         wallet = Wallet(storage)
307         wallet.start_threads(self.network)
308
309         self.load_wallet(wallet)
310
311
312
313     def backup_wallet(self):
314         import shutil
315         path = self.wallet.storage.path
316         wallet_folder = os.path.dirname(path)
317         filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
318         if not filename:
319             return
320
321         new_path = os.path.join(wallet_folder, filename)
322         if new_path != path:
323             try:
324                 shutil.copy2(path, new_path)
325                 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
326             except (IOError, os.error), reason:
327                 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
328
329
330     def new_wallet(self):
331         import installwizard
332
333         wallet_folder = os.path.dirname(self.wallet.storage.path)
334         filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
335         if not filename:
336             return
337         filename = os.path.join(wallet_folder, filename)
338
339         storage = WalletStorage({'wallet_path': filename})
340         if storage.file_exists:
341             QMessageBox.critical(None, "Error", _("File exists"))
342             return
343
344         wizard = installwizard.InstallWizard(self.config, self.network, storage)
345         wallet = wizard.run()
346         if wallet: 
347             self.load_wallet(wallet)
348         
349
350
351     def init_menubar(self):
352         menubar = QMenuBar()
353
354         file_menu = menubar.addMenu(_("&File"))
355         open_wallet_action = file_menu.addAction(_("&Open"))
356         open_wallet_action.triggered.connect(self.open_wallet)
357
358         new_wallet_action = file_menu.addAction(_("&Create/Restore"))
359         new_wallet_action.triggered.connect(self.new_wallet)
360
361         wallet_backup = file_menu.addAction(_("&Copy"))
362         wallet_backup.triggered.connect(self.backup_wallet)
363
364         quit_item = file_menu.addAction(_("&Close"))
365         quit_item.triggered.connect(self.close)
366
367         wallet_menu = menubar.addMenu(_("&Wallet"))
368
369         new_contact = wallet_menu.addAction(_("&New contact"))
370         new_contact.triggered.connect(self.new_contact_dialog)
371
372         self.new_account = wallet_menu.addAction(_("&New account"))
373         self.new_account.triggered.connect(self.new_account_dialog)
374
375         wallet_menu.addSeparator()
376
377         pw = wallet_menu.addAction(_("&Password"))
378         pw.triggered.connect(self.change_password_dialog)
379
380         show_seed = wallet_menu.addAction(_("&Seed"))
381         show_seed.triggered.connect(self.show_seed_dialog)
382
383         show_mpk = wallet_menu.addAction(_("&Master Public Key"))
384         show_mpk.triggered.connect(self.show_master_public_key)
385
386         wallet_menu.addSeparator()
387
388         labels_menu = wallet_menu.addMenu(_("&Labels"))
389         import_labels = labels_menu.addAction(_("&Import"))
390         import_labels.triggered.connect(self.do_import_labels)
391         export_labels = labels_menu.addAction(_("&Export"))
392         export_labels.triggered.connect(self.do_export_labels)
393
394         keys_menu = wallet_menu.addMenu(_("&Private keys"))
395         import_keys = keys_menu.addAction(_("&Import"))
396         import_keys.triggered.connect(self.do_import_privkey)
397         export_keys = keys_menu.addAction(_("&Export"))
398         export_keys.triggered.connect(self.do_export_privkeys)
399
400         ex_history = wallet_menu.addAction(_("&Export History"))
401         ex_history.triggered.connect(self.do_export_history)
402
403
404
405         tools_menu = menubar.addMenu(_("&Tools"))
406
407         # Settings / Preferences are all reserved keywords in OSX using this as work around
408         preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
409         preferences_menu = tools_menu.addAction(preferences_name)
410         preferences_menu.triggered.connect(self.settings_dialog)
411
412         network = tools_menu.addAction(_("&Network"))
413         network.triggered.connect(self.run_network_dialog)
414
415         plugins_labels = tools_menu.addAction(_("&Plugins"))
416         plugins_labels.triggered.connect(self.plugins_dialog)
417
418         tools_menu.addSeparator()
419
420         csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
421
422         csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
423         csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
424
425         csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
426         csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
427
428         raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
429
430         raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
431         raw_transaction_file.triggered.connect(self.do_process_from_file)
432
433         raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
434         raw_transaction_text.triggered.connect(self.do_process_from_text)
435
436
437         help_menu = menubar.addMenu(_("&Help"))
438         show_about = help_menu.addAction(_("&About"))
439         show_about.triggered.connect(self.show_about)
440         web_open = help_menu.addAction(_("&Official website")) 
441         web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
442
443         help_menu.addSeparator()
444         doc_open = help_menu.addAction(_("&Documentation"))
445         doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
446         report_bug = help_menu.addAction(_("&Report Bug"))
447         report_bug.triggered.connect(self.show_report_bug)
448
449         self.setMenuBar(menubar)
450
451     def show_about(self):
452         QMessageBox.about(self, "Electrum",
453             _("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."))
454
455     def show_report_bug(self):
456         QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
457             _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
458
459
460     def notify_transactions(self):
461         if not self.network or not self.network.is_connected(): 
462             return
463
464         print_error("Notifying GUI")
465         if len(self.network.interface.pending_transactions_for_notifications) > 0:
466             # Combine the transactions if there are more then three
467             tx_amount = len(self.network.interface.pending_transactions_for_notifications)
468             if(tx_amount >= 3):
469                 total_amount = 0
470                 for tx in self.network.interface.pending_transactions_for_notifications:
471                     is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
472                     if(v > 0):
473                         total_amount += v
474
475                 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
476                                 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
477
478                 self.network.interface.pending_transactions_for_notifications = []
479             else:
480               for tx in self.network.interface.pending_transactions_for_notifications:
481                   if tx:
482                       self.network.interface.pending_transactions_for_notifications.remove(tx)
483                       is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
484                       if(v > 0):
485                           self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
486
487     def notify(self, message):
488         self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
489
490
491
492     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
493     def getOpenFileName(self, title, filter = ""):
494         directory = self.config.get('io_dir', os.path.expanduser('~'))
495         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
496         if fileName and directory != os.path.dirname(fileName):
497             self.config.set_key('io_dir', os.path.dirname(fileName), True)
498         return fileName
499
500     def getSaveFileName(self, title, filename, filter = ""):
501         directory = self.config.get('io_dir', os.path.expanduser('~'))
502         path = os.path.join( directory, filename )
503         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
504         if fileName and directory != os.path.dirname(fileName):
505             self.config.set_key('io_dir', os.path.dirname(fileName), True)
506         return fileName
507
508     def close(self):
509         QMainWindow.close(self)
510         run_hook('close_main_window')
511
512     def connect_slots(self, sender):
513         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
514         self.previous_payto_e=''
515
516     def timer_actions(self):
517         if self.need_update.is_set():
518             self.update_wallet()
519             self.need_update.clear()
520         run_hook('timer_actions')
521     
522     def format_amount(self, x, is_diff=False, whitespaces=False):
523         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
524
525     def read_amount(self, x):
526         if x in['.', '']: return None
527         p = pow(10, self.decimal_point)
528         return int( p * Decimal(x) )
529
530     def base_unit(self):
531         assert self.decimal_point in [5,8]
532         return "BTC" if self.decimal_point == 8 else "mBTC"
533
534
535     def update_status(self):
536         if self.network is None:
537             text = _("Offline")
538             icon = QIcon(":icons/status_disconnected.png")
539
540         elif 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 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 Exception:
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 = total - 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 Exception:
869             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
870             return
871         try:
872             fee = self.read_amount(unicode( self.fee_e.text()))
873         except Exception:
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 Exception as 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
914             self.show_transaction(tx)
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 Exception:
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         if self.wallet.seed:
1128             menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1129             menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1130         if addr in self.wallet.imported_keys:
1131             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1132
1133         t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1134         menu.addAction(t, lambda: self.toggle_freeze(addr))
1135         t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1136         menu.addAction(t, lambda: self.toggle_priority(addr))
1137             
1138         run_hook('receive_menu', menu)
1139         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1140
1141
1142     def payto(self, addr):
1143         if not addr: return
1144         label = self.wallet.labels.get(addr)
1145         m_addr = label + '  <' + addr + '>' if label else addr
1146         self.tabs.setCurrentIndex(1)
1147         self.payto_e.setText(m_addr)
1148         self.amount_e.setFocus()
1149
1150
1151     def delete_contact(self, x):
1152         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1153             self.wallet.delete_contact(x)
1154             self.wallet.set_label(x, None)
1155             self.update_history_tab()
1156             self.update_contacts_tab()
1157             self.update_completions()
1158
1159
1160     def create_contact_menu(self, position):
1161         item = self.contacts_list.itemAt(position)
1162         if not item: return
1163         addr = unicode(item.text(0))
1164         label = unicode(item.text(1))
1165         is_editable = item.data(0,32).toBool()
1166         payto_addr = item.data(0,33).toString()
1167         menu = QMenu()
1168         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1169         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1170         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1171         if is_editable:
1172             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1173             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1174
1175         run_hook('create_contact_menu', menu, item)
1176         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1177
1178
1179     def update_receive_item(self, item):
1180         item.setFont(0, QFont(MONOSPACE_FONT))
1181         address = str(item.data(0,0).toString())
1182         label = self.wallet.labels.get(address,'')
1183         item.setData(1,0,label)
1184         item.setData(0,32, True) # is editable
1185
1186         run_hook('update_receive_item', address, item)
1187
1188         if not self.wallet.is_mine(address): return
1189
1190         c, u = self.wallet.get_addr_balance(address)
1191         balance = self.format_amount(c + u)
1192         item.setData(2,0,balance)
1193
1194         if address in self.wallet.frozen_addresses: 
1195             item.setBackgroundColor(0, QColor('lightblue'))
1196         elif address in self.wallet.prioritized_addresses: 
1197             item.setBackgroundColor(0, QColor('lightgreen'))
1198         
1199
1200     def update_receive_tab(self):
1201         l = self.receive_list
1202         
1203         l.clear()
1204         l.setColumnHidden(2, False)
1205         l.setColumnHidden(3, False)
1206         for i,width in enumerate(self.column_widths['receive']):
1207             l.setColumnWidth(i, width)
1208
1209         if self.current_account is None:
1210             account_items = self.wallet.accounts.items()
1211         elif self.current_account != -1:
1212             account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1213         else:
1214             account_items = []
1215
1216         for k, account in account_items:
1217             name = self.wallet.get_account_name(k)
1218             c,u = self.wallet.get_account_balance(k)
1219             account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1220             l.addTopLevelItem(account_item)
1221             account_item.setExpanded(self.accounts_expanded.get(k, True))
1222             account_item.setData(0, 32, k)
1223
1224             if not self.wallet.is_seeded(k):
1225                 icon = QIcon(":icons/key.png")
1226                 account_item.setIcon(0, icon)
1227             
1228             for is_change in ([0,1]):
1229                 name = _("Receiving") if not is_change else _("Change")
1230                 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1231                 account_item.addChild(seq_item)
1232                 if not is_change: seq_item.setExpanded(True)
1233
1234                 is_red = False
1235                 gap = 0
1236
1237                 for address in account.get_addresses(is_change):
1238                     h = self.wallet.history.get(address,[])
1239             
1240                     if h == []:
1241                         gap += 1
1242                         if gap > self.wallet.gap_limit:
1243                             is_red = True
1244                     else:
1245                         gap = 0
1246
1247                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1248                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1249                     self.update_receive_item(item)
1250                     if is_red:
1251                         item.setBackgroundColor(1, QColor('red'))
1252                     seq_item.addChild(item)
1253
1254
1255         for k, addr in self.wallet.get_pending_accounts():
1256             name = self.wallet.labels.get(k,'')
1257             account_item = QTreeWidgetItem( [ name + "  [ "+_('pending account')+" ]", '', '', ''] )
1258             self.update_receive_item(item)
1259             l.addTopLevelItem(account_item)
1260             account_item.setExpanded(True)
1261             account_item.setData(0, 32, k)
1262             item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1263             account_item.addChild(item)
1264             self.update_receive_item(item)
1265
1266
1267         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1268             c,u = self.wallet.get_imported_balance()
1269             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1270             l.addTopLevelItem(account_item)
1271             account_item.setExpanded(True)
1272             for address in self.wallet.imported_keys.keys():
1273                 item = QTreeWidgetItem( [ address, '', '', ''] )
1274                 self.update_receive_item(item)
1275                 account_item.addChild(item)
1276                 
1277
1278         # we use column 1 because column 0 may be hidden
1279         l.setCurrentItem(l.topLevelItem(0),1)
1280
1281
1282     def update_contacts_tab(self):
1283         l = self.contacts_list
1284         l.clear()
1285
1286         for address in self.wallet.addressbook:
1287             label = self.wallet.labels.get(address,'')
1288             n = self.wallet.get_num_tx(address)
1289             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1290             item.setFont(0, QFont(MONOSPACE_FONT))
1291             # 32 = label can be edited (bool)
1292             item.setData(0,32, True)
1293             # 33 = payto string
1294             item.setData(0,33, address)
1295             l.addTopLevelItem(item)
1296
1297         run_hook('update_contacts_tab', l)
1298         l.setCurrentItem(l.topLevelItem(0))
1299
1300
1301
1302     def create_console_tab(self):
1303         from console import Console
1304         self.console = console = Console()
1305         return console
1306
1307
1308     def update_console(self):
1309         console = self.console
1310         console.history = self.config.get("console-history",[])
1311         console.history_index = len(console.history)
1312
1313         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1314         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1315
1316         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1317         methods = {}
1318         def mkfunc(f, method):
1319             return lambda *args: apply( f, (method, args, self.password_dialog ))
1320         for m in dir(c):
1321             if m[0]=='_' or m in ['network','wallet']: continue
1322             methods[m] = mkfunc(c._run, m)
1323             
1324         console.updateNamespace(methods)
1325
1326
1327     def change_account(self,s):
1328         if s == _("All accounts"):
1329             self.current_account = None
1330         else:
1331             accounts = self.wallet.get_account_names()
1332             for k, v in accounts.items():
1333                 if v == s:
1334                     self.current_account = k
1335         self.update_history_tab()
1336         self.update_status()
1337         self.update_receive_tab()
1338
1339     def create_status_bar(self):
1340
1341         sb = QStatusBar()
1342         sb.setFixedHeight(35)
1343         qtVersion = qVersion()
1344
1345         self.balance_label = QLabel("")
1346         sb.addWidget(self.balance_label)
1347
1348         from version_getter import UpdateLabel
1349         self.updatelabel = UpdateLabel(self.config, sb)
1350
1351         self.account_selector = QComboBox()
1352         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account) 
1353         sb.addPermanentWidget(self.account_selector)
1354
1355         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1356             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1357
1358         self.lock_icon = QIcon()
1359         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1360         sb.addPermanentWidget( self.password_button )
1361             
1362         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1363         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) 
1364         sb.addPermanentWidget( self.seed_button )
1365         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) 
1366         sb.addPermanentWidget( self.status_button )
1367
1368         run_hook('create_status_bar', (sb,))
1369
1370         self.setStatusBar(sb)
1371
1372
1373     def update_lock_icon(self):
1374         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1375         self.password_button.setIcon( icon )
1376
1377
1378     def update_buttons_on_seed(self):
1379         if not self.wallet.is_watching_only():
1380            self.seed_button.show()
1381            self.password_button.show()
1382            self.send_button.setText(_("Send"))
1383         else:
1384            self.password_button.hide()
1385            self.seed_button.hide()
1386            self.send_button.setText(_("Create unsigned transaction"))
1387
1388
1389     def change_password_dialog(self):
1390         from password_dialog import PasswordDialog
1391         d = PasswordDialog(self.wallet, self)
1392         d.run()
1393         self.update_lock_icon()
1394
1395
1396     def new_contact_dialog(self):
1397
1398         d = QDialog(self)
1399         vbox = QVBoxLayout(d)
1400         vbox.addWidget(QLabel(_('New Contact')+':'))
1401         
1402         grid = QGridLayout()
1403         line1 = QLineEdit()
1404         line2 = QLineEdit()
1405         grid.addWidget(QLabel(_("Address")), 1, 0)
1406         grid.addWidget(line1, 1, 1)
1407         grid.addWidget(QLabel(_("Name")), 2, 0)
1408         grid.addWidget(line2, 2, 1)
1409
1410         vbox.addLayout(grid)
1411         vbox.addLayout(ok_cancel_buttons(d))
1412     
1413         if not d.exec_():
1414             return
1415         
1416         address = str(line1.text())
1417         label = unicode(line2.text())
1418         
1419         if not is_valid(address):
1420             QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1421             return
1422         
1423         self.wallet.add_contact(address)
1424         if label:
1425             self.wallet.set_label(address, label)
1426
1427         self.update_contacts_tab()
1428         self.update_history_tab()
1429         self.update_completions()
1430         self.tabs.setCurrentIndex(3)
1431
1432
1433     def new_account_dialog(self):
1434
1435         dialog = QDialog(self)
1436         dialog.setModal(1)
1437         dialog.setWindowTitle(_("New Account"))
1438
1439         vbox = QVBoxLayout()
1440         vbox.addWidget(QLabel(_('Account name')+':'))
1441         e = QLineEdit()
1442         vbox.addWidget(e)
1443         msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1444             + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1445         l = QLabel(msg)
1446         l.setWordWrap(True)
1447         vbox.addWidget(l)
1448
1449         vbox.addLayout(ok_cancel_buttons(dialog))
1450         dialog.setLayout(vbox)
1451         r = dialog.exec_()
1452         if not r: return
1453
1454         name = str(e.text())
1455         if not name: return
1456
1457         self.wallet.create_pending_account('1', name)
1458         self.update_receive_tab()
1459         self.tabs.setCurrentIndex(2)
1460         
1461             
1462
1463     def show_master_public_key_old(self):
1464         dialog = QDialog(self)
1465         dialog.setModal(1)
1466         dialog.setWindowTitle(_("Master Public Key"))
1467
1468         main_text = QTextEdit()
1469         main_text.setText(self.wallet.get_master_public_key())
1470         main_text.setReadOnly(True)
1471         main_text.setMaximumHeight(170)
1472         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1473
1474         ok_button = QPushButton(_("OK"))
1475         ok_button.setDefault(True)
1476         ok_button.clicked.connect(dialog.accept)
1477
1478         main_layout = QGridLayout()
1479         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1480
1481         main_layout.addWidget(main_text, 1, 0)
1482         main_layout.addWidget(qrw, 1, 1 )
1483
1484         vbox = QVBoxLayout()
1485         vbox.addLayout(main_layout)
1486         vbox.addLayout(close_button(dialog))
1487         dialog.setLayout(vbox)
1488         dialog.exec_()
1489         
1490
1491     def show_master_public_key(self):
1492
1493         if self.wallet.seed_version == 4:
1494             self.show_master_public_key_old()
1495             return
1496
1497         dialog = QDialog(self)
1498         dialog.setModal(1)
1499         dialog.setWindowTitle(_("Master Public Keys"))
1500
1501         chain_text = QTextEdit()
1502         chain_text.setReadOnly(True)
1503         chain_text.setMaximumHeight(170)
1504         chain_qrw = QRCodeWidget()
1505
1506         mpk_text = QTextEdit()
1507         mpk_text.setReadOnly(True)
1508         mpk_text.setMaximumHeight(170)
1509         mpk_qrw = QRCodeWidget()
1510
1511         main_layout = QGridLayout()
1512
1513         main_layout.addWidget(QLabel(_('Key')), 1, 0)
1514         main_layout.addWidget(mpk_text, 1, 1)
1515         main_layout.addWidget(mpk_qrw, 1, 2)
1516
1517         main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1518         main_layout.addWidget(chain_text, 2, 1)
1519         main_layout.addWidget(chain_qrw, 2, 2)
1520
1521         def update(key):
1522             c, K, cK = self.wallet.master_public_keys[str(key)]
1523             chain_text.setText(c)
1524             chain_qrw.set_addr(c)
1525             chain_qrw.update_qr()
1526             mpk_text.setText(K)
1527             mpk_qrw.set_addr(K)
1528             mpk_qrw.update_qr()
1529             
1530         key_selector = QComboBox()
1531         keys = sorted(self.wallet.master_public_keys.keys())
1532         key_selector.addItems(keys)
1533
1534         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1535         main_layout.addWidget(key_selector, 0, 1)
1536         dialog.connect(key_selector,SIGNAL("activated(QString)"),update) 
1537
1538         update(keys[0])
1539
1540         vbox = QVBoxLayout()
1541         vbox.addLayout(main_layout)
1542         vbox.addLayout(close_button(dialog))
1543
1544         dialog.setLayout(vbox)
1545         dialog.exec_()
1546         
1547
1548     @protected
1549     def show_seed_dialog(self, password):
1550         if self.wallet.is_watching_only():
1551             QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1552             return
1553
1554         if self.wallet.seed:
1555             try:
1556                 mnemonic = self.wallet.get_mnemonic(password)
1557             except Exception:
1558                 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1559                 return
1560             from seed_dialog import SeedDialog
1561             d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1562             d.exec_()
1563         else:
1564             l = {}
1565             for k in self.wallet.master_private_keys.keys():
1566                 pk = self.wallet.get_master_private_key(k, password)
1567                 l[k] = pk
1568             from seed_dialog import PrivateKeysDialog
1569             d = PrivateKeysDialog(self,l)
1570             d.exec_()
1571
1572
1573
1574
1575
1576     def show_qrcode(self, data, title = _("QR code")):
1577         if not data: return
1578         d = QDialog(self)
1579         d.setModal(1)
1580         d.setWindowTitle(title)
1581         d.setMinimumSize(270, 300)
1582         vbox = QVBoxLayout()
1583         qrw = QRCodeWidget(data)
1584         vbox.addWidget(qrw, 1)
1585         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1586         hbox = QHBoxLayout()
1587         hbox.addStretch(1)
1588
1589         filename = os.path.join(self.config.path, "qrcode.bmp")
1590
1591         def print_qr():
1592             bmp.save_qrcode(qrw.qr, filename)
1593             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1594
1595         def copy_to_clipboard():
1596             bmp.save_qrcode(qrw.qr, filename)
1597             self.app.clipboard().setImage(QImage(filename))
1598             QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1599
1600         b = QPushButton(_("Copy"))
1601         hbox.addWidget(b)
1602         b.clicked.connect(copy_to_clipboard)
1603
1604         b = QPushButton(_("Save"))
1605         hbox.addWidget(b)
1606         b.clicked.connect(print_qr)
1607
1608         b = QPushButton(_("Close"))
1609         hbox.addWidget(b)
1610         b.clicked.connect(d.accept)
1611         b.setDefault(True)
1612
1613         vbox.addLayout(hbox)
1614         d.setLayout(vbox)
1615         d.exec_()
1616
1617
1618     def do_protect(self, func, args):
1619         if self.wallet.use_encryption:
1620             password = self.password_dialog()
1621             if not password:
1622                 return
1623         else:
1624             password = None
1625             
1626         if args != (False,):
1627             args = (self,) + args + (password,)
1628         else:
1629             args = (self,password)
1630         apply( func, args)
1631
1632
1633     @protected
1634     def show_private_key(self, address, password):
1635         if not address: return
1636         try:
1637             pk_list = self.wallet.get_private_key(address, password)
1638         except Exception as e:
1639             self.show_message(str(e))
1640             return
1641         QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1642
1643
1644     @protected
1645     def do_sign(self, address, message, signature, password):
1646         message = unicode(message.toPlainText())
1647         message = message.encode('utf-8')
1648         try:
1649             sig = self.wallet.sign_message(str(address.text()), message, password)
1650             signature.setText(sig)
1651         except Exception as e:
1652             self.show_message(str(e))
1653
1654     def sign_message(self, address):
1655         if not address: return
1656         d = QDialog(self)
1657         d.setModal(1)
1658         d.setWindowTitle(_('Sign Message'))
1659         d.setMinimumSize(410, 290)
1660
1661         tab_widget = QTabWidget()
1662         tab = QWidget()
1663         layout = QGridLayout(tab)
1664
1665         sign_address = QLineEdit()
1666
1667         sign_address.setText(address)
1668         layout.addWidget(QLabel(_('Address')), 1, 0)
1669         layout.addWidget(sign_address, 1, 1)
1670
1671         sign_message = QTextEdit()
1672         layout.addWidget(QLabel(_('Message')), 2, 0)
1673         layout.addWidget(sign_message, 2, 1)
1674         layout.setRowStretch(2,3)
1675
1676         sign_signature = QTextEdit()
1677         layout.addWidget(QLabel(_('Signature')), 3, 0)
1678         layout.addWidget(sign_signature, 3, 1)
1679         layout.setRowStretch(3,1)
1680
1681
1682         hbox = QHBoxLayout()
1683         b = QPushButton(_("Sign"))
1684         hbox.addWidget(b)
1685         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1686         b = QPushButton(_("Close"))
1687         b.clicked.connect(d.accept)
1688         hbox.addWidget(b)
1689         layout.addLayout(hbox, 4, 1)
1690         tab_widget.addTab(tab, _("Sign"))
1691
1692
1693         tab = QWidget()
1694         layout = QGridLayout(tab)
1695
1696         verify_address = QLineEdit()
1697         layout.addWidget(QLabel(_('Address')), 1, 0)
1698         layout.addWidget(verify_address, 1, 1)
1699
1700         verify_message = QTextEdit()
1701         layout.addWidget(QLabel(_('Message')), 2, 0)
1702         layout.addWidget(verify_message, 2, 1)
1703         layout.setRowStretch(2,3)
1704
1705         verify_signature = QTextEdit()
1706         layout.addWidget(QLabel(_('Signature')), 3, 0)
1707         layout.addWidget(verify_signature, 3, 1)
1708         layout.setRowStretch(3,1)
1709
1710         def do_verify():
1711             message = unicode(verify_message.toPlainText())
1712             message = message.encode('utf-8')
1713             if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1714                 self.show_message(_("Signature verified"))
1715             else:
1716                 self.show_message(_("Error: wrong signature"))
1717
1718         hbox = QHBoxLayout()
1719         b = QPushButton(_("Verify"))
1720         b.clicked.connect(do_verify)
1721         hbox.addWidget(b)
1722         b = QPushButton(_("Close"))
1723         b.clicked.connect(d.accept)
1724         hbox.addWidget(b)
1725         layout.addLayout(hbox, 4, 1)
1726         tab_widget.addTab(tab, _("Verify"))
1727
1728         vbox = QVBoxLayout()
1729         vbox.addWidget(tab_widget)
1730         d.setLayout(vbox)
1731         d.exec_()
1732
1733         
1734
1735
1736     def question(self, msg):
1737         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1738
1739     def show_message(self, msg):
1740         QMessageBox.information(self, _('Message'), msg, _('OK'))
1741
1742     def password_dialog(self ):
1743         d = QDialog(self)
1744         d.setModal(1)
1745
1746         pw = QLineEdit()
1747         pw.setEchoMode(2)
1748
1749         vbox = QVBoxLayout()
1750         msg = _('Please enter your password')
1751         vbox.addWidget(QLabel(msg))
1752
1753         grid = QGridLayout()
1754         grid.setSpacing(8)
1755         grid.addWidget(QLabel(_('Password')), 1, 0)
1756         grid.addWidget(pw, 1, 1)
1757         vbox.addLayout(grid)
1758
1759         vbox.addLayout(ok_cancel_buttons(d))
1760         d.setLayout(vbox)
1761
1762         run_hook('password_dialog', pw, grid, 1)
1763         if not d.exec_(): return
1764         return unicode(pw.text())
1765
1766
1767
1768
1769
1770
1771
1772
1773     def tx_from_text(self, txt):
1774         "json or raw hexadecimal"
1775         try:
1776             txt.decode('hex')
1777             tx = Transaction(txt)
1778             return tx
1779         except Exception:
1780             pass
1781
1782         try:
1783             tx_dict = json.loads(str(txt))
1784             assert "hex" in tx_dict.keys()
1785             assert "complete" in tx_dict.keys()
1786             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1787             if not tx_dict["complete"]:
1788                 assert "input_info" in tx_dict.keys()
1789                 input_info = json.loads(tx_dict['input_info'])
1790                 tx.add_input_info(input_info)
1791             return tx
1792         except Exception:
1793             pass
1794         
1795         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1796
1797
1798
1799     def read_tx_from_file(self):
1800         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1801         if not fileName:
1802             return
1803         try:
1804             with open(fileName, "r") as f:
1805                 file_content = f.read()
1806         except (ValueError, IOError, os.error), reason:
1807             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1808
1809         return self.tx_from_text(file_content)
1810
1811
1812     @protected
1813     def sign_raw_transaction(self, tx, input_info, password):
1814         self.wallet.signrawtransaction(tx, input_info, [], password)
1815
1816     def do_process_from_text(self):
1817         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1818         if not text:
1819             return
1820         tx = self.tx_from_text(text)
1821         if tx:
1822             self.show_transaction(tx)
1823
1824     def do_process_from_file(self):
1825         tx = self.read_tx_from_file()
1826         if tx:
1827             self.show_transaction(tx)
1828
1829     def do_process_from_csvReader(self, csvReader):
1830         outputs = []
1831         try:
1832             for row in csvReader:
1833                 address = row[0]
1834                 amount = float(row[1])
1835                 amount = int(100000000*amount)
1836                 outputs.append((address, amount))
1837         except (ValueError, IOError, os.error), reason:
1838             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1839             return
1840
1841         try:
1842             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1843         except Exception as e:
1844             self.show_message(str(e))
1845             return
1846
1847         self.show_transaction(tx)
1848
1849     def do_process_from_csv_file(self):
1850         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1851         if not fileName:
1852             return
1853         try:
1854             with open(fileName, "r") as f:
1855                 csvReader = csv.reader(f)
1856                 self.do_process_from_csvReader(csvReader)
1857         except (ValueError, IOError, os.error), reason:
1858             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1859             return
1860
1861     def do_process_from_csv_text(self):
1862         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1863                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1864         if not text:
1865             return
1866         f = StringIO.StringIO(text)
1867         csvReader = csv.reader(f)
1868         self.do_process_from_csvReader(csvReader)
1869
1870
1871
1872     @protected
1873     def do_export_privkeys(self, password):
1874         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.")))
1875
1876         try:
1877             select_export = _('Select file to export your private keys to')
1878             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1879             if fileName:
1880                 with open(fileName, "w+") as csvfile:
1881                     transaction = csv.writer(csvfile)
1882                     transaction.writerow(["address", "private_key"])
1883
1884                     addresses = self.wallet.addresses(True)
1885                     
1886                     for addr in addresses:
1887                         pk = "".join(self.wallet.get_private_key(addr, password))
1888                         transaction.writerow(["%34s"%addr,pk])
1889
1890                     self.show_message(_("Private keys exported."))
1891
1892         except (IOError, os.error), reason:
1893             export_error_label = _("Electrum was unable to produce a private key-export.")
1894             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1895
1896         except Exception as e:
1897           self.show_message(str(e))
1898           return
1899
1900
1901     def do_import_labels(self):
1902         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1903         if not labelsFile: return
1904         try:
1905             f = open(labelsFile, 'r')
1906             data = f.read()
1907             f.close()
1908             for key, value in json.loads(data).items():
1909                 self.wallet.set_label(key, value)
1910             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1911         except (IOError, os.error), reason:
1912             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1913             
1914
1915     def do_export_labels(self):
1916         labels = self.wallet.labels
1917         try:
1918             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1919             if fileName:
1920                 with open(fileName, 'w+') as f:
1921                     json.dump(labels, f)
1922                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1923         except (IOError, os.error), reason:
1924             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1925
1926
1927     def do_export_history(self):
1928         from lite_window import csv_transaction
1929         csv_transaction(self.wallet)
1930
1931
1932     @protected
1933     def do_import_privkey(self, password):
1934         if not self.wallet.imported_keys:
1935             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1936                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1937                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1938             if r == 4: return
1939
1940         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1941         if not text: return
1942
1943         text = str(text).split()
1944         badkeys = []
1945         addrlist = []
1946         for key in text:
1947             try:
1948                 addr = self.wallet.import_key(key, password)
1949             except Exception as e:
1950                 badkeys.append(key)
1951                 continue
1952             if not addr: 
1953                 badkeys.append(key)
1954             else:
1955                 addrlist.append(addr)
1956         if addrlist:
1957             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1958         if badkeys:
1959             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1960         self.update_receive_tab()
1961         self.update_history_tab()
1962
1963
1964     def settings_dialog(self):
1965         d = QDialog(self)
1966         d.setWindowTitle(_('Electrum Settings'))
1967         d.setModal(1)
1968         vbox = QVBoxLayout()
1969         grid = QGridLayout()
1970         grid.setColumnStretch(0,1)
1971
1972         nz_label = QLabel(_('Display zeros') + ':')
1973         grid.addWidget(nz_label, 0, 0)
1974         nz_e = AmountEdit(None,True)
1975         nz_e.setText("%d"% self.num_zeros)
1976         grid.addWidget(nz_e, 0, 1)
1977         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1978         grid.addWidget(HelpButton(msg), 0, 2)
1979         if not self.config.is_modifiable('num_zeros'):
1980             for w in [nz_e, nz_label]: w.setEnabled(False)
1981         
1982         lang_label=QLabel(_('Language') + ':')
1983         grid.addWidget(lang_label, 1, 0)
1984         lang_combo = QComboBox()
1985         from electrum.i18n import languages
1986         lang_combo.addItems(languages.values())
1987         try:
1988             index = languages.keys().index(self.config.get("language",''))
1989         except Exception:
1990             index = 0
1991         lang_combo.setCurrentIndex(index)
1992         grid.addWidget(lang_combo, 1, 1)
1993         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1994         if not self.config.is_modifiable('language'):
1995             for w in [lang_combo, lang_label]: w.setEnabled(False)
1996
1997         
1998         fee_label = QLabel(_('Transaction fee') + ':')
1999         grid.addWidget(fee_label, 2, 0)
2000         fee_e = AmountEdit(self.base_unit)
2001         fee_e.setText(self.format_amount(self.wallet.fee).strip())
2002         grid.addWidget(fee_e, 2, 1)
2003         msg = _('Fee per kilobyte of transaction.') + ' ' \
2004             + _('Recommended value') + ': ' + self.format_amount(50000)
2005         grid.addWidget(HelpButton(msg), 2, 2)
2006         if not self.config.is_modifiable('fee_per_kb'):
2007             for w in [fee_e, fee_label]: w.setEnabled(False)
2008
2009         units = ['BTC', 'mBTC']
2010         unit_label = QLabel(_('Base unit') + ':')
2011         grid.addWidget(unit_label, 3, 0)
2012         unit_combo = QComboBox()
2013         unit_combo.addItems(units)
2014         unit_combo.setCurrentIndex(units.index(self.base_unit()))
2015         grid.addWidget(unit_combo, 3, 1)
2016         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2017                                              + '\n1BTC=1000mBTC.\n' \
2018                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
2019
2020         usechange_cb = QCheckBox(_('Use change addresses'))
2021         usechange_cb.setChecked(self.wallet.use_change)
2022         grid.addWidget(usechange_cb, 4, 0)
2023         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2024         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2025
2026         grid.setRowStretch(5,1)
2027
2028         vbox.addLayout(grid)
2029         vbox.addLayout(ok_cancel_buttons(d))
2030         d.setLayout(vbox) 
2031
2032         # run the dialog
2033         if not d.exec_(): return
2034
2035         fee = unicode(fee_e.text())
2036         try:
2037             fee = self.read_amount(fee)
2038         except Exception:
2039             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2040             return
2041
2042         self.wallet.set_fee(fee)
2043         
2044         nz = unicode(nz_e.text())
2045         try:
2046             nz = int( nz )
2047             if nz>8: nz=8
2048         except Exception:
2049             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2050             return
2051
2052         if self.num_zeros != nz:
2053             self.num_zeros = nz
2054             self.config.set_key('num_zeros', nz, True)
2055             self.update_history_tab()
2056             self.update_receive_tab()
2057
2058         usechange_result = usechange_cb.isChecked()
2059         if self.wallet.use_change != usechange_result:
2060             self.wallet.use_change = usechange_result
2061             self.wallet.storage.put('use_change', self.wallet.use_change)
2062         
2063         unit_result = units[unit_combo.currentIndex()]
2064         if self.base_unit() != unit_result:
2065             self.decimal_point = 8 if unit_result == 'BTC' else 5
2066             self.config.set_key('decimal_point', self.decimal_point, True)
2067             self.update_history_tab()
2068             self.update_status()
2069         
2070         need_restart = False
2071
2072         lang_request = languages.keys()[lang_combo.currentIndex()]
2073         if lang_request != self.config.get('language'):
2074             self.config.set_key("language", lang_request, True)
2075             need_restart = True
2076             
2077         run_hook('close_settings_dialog')
2078
2079         if need_restart:
2080             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2081
2082
2083     def run_network_dialog(self):
2084         if not self.network:
2085             return
2086         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2087
2088     def closeEvent(self, event):
2089         g = self.geometry()
2090         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2091         self.save_column_widths()
2092         self.config.set_key("console-history", self.console.history[-50:], True)
2093         self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2094         event.accept()
2095
2096
2097
2098     def plugins_dialog(self):
2099         from electrum.plugins import plugins
2100
2101         d = QDialog(self)
2102         d.setWindowTitle(_('Electrum Plugins'))
2103         d.setModal(1)
2104
2105         vbox = QVBoxLayout(d)
2106
2107         # plugins
2108         scroll = QScrollArea()
2109         scroll.setEnabled(True)
2110         scroll.setWidgetResizable(True)
2111         scroll.setMinimumSize(400,250)
2112         vbox.addWidget(scroll)
2113
2114         w = QWidget()
2115         scroll.setWidget(w)
2116         w.setMinimumHeight(len(plugins)*35)
2117
2118         grid = QGridLayout()
2119         grid.setColumnStretch(0,1)
2120         w.setLayout(grid)
2121
2122         def do_toggle(cb, p, w):
2123             r = p.toggle()
2124             cb.setChecked(r)
2125             if w: w.setEnabled(r)
2126
2127         def mk_toggle(cb, p, w):
2128             return lambda: do_toggle(cb,p,w)
2129
2130         for i, p in enumerate(plugins):
2131             try:
2132                 cb = QCheckBox(p.fullname())
2133                 cb.setDisabled(not p.is_available())
2134                 cb.setChecked(p.is_enabled())
2135                 grid.addWidget(cb, i, 0)
2136                 if p.requires_settings():
2137                     w = p.settings_widget(self)
2138                     w.setEnabled( p.is_enabled() )
2139                     grid.addWidget(w, i, 1)
2140                 else: 
2141                     w = None
2142                 cb.clicked.connect(mk_toggle(cb,p,w))
2143                 grid.addWidget(HelpButton(p.description()), i, 2)
2144             except Exception:
2145                 print_msg(_("Error: cannot display plugin"), p)
2146                 traceback.print_exc(file=sys.stdout)
2147         grid.setRowStretch(i+1,1)
2148
2149         vbox.addLayout(close_button(d))
2150
2151         d.exec_()
2152
2153
2154     def show_account_details(self, k):
2155         d = QDialog(self)
2156         d.setWindowTitle(_('Account Details'))
2157         d.setModal(1)
2158
2159         vbox = QVBoxLayout(d)
2160         roots = self.wallet.get_roots(k)
2161
2162         name = self.wallet.get_account_name(k)
2163         label = QLabel('Name: ' + name)
2164         vbox.addWidget(label)
2165
2166         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2167         vbox.addWidget(QLabel('Type: ' + acctype))
2168
2169         label = QLabel('Derivation: ' + k)
2170         vbox.addWidget(label)
2171
2172         #for root in roots:
2173         #    mpk = self.wallet.master_public_keys[root]
2174         #    text = QTextEdit()
2175         #    text.setReadOnly(True)
2176         #    text.setMaximumHeight(120)
2177         #    text.setText(repr(mpk))
2178         #    vbox.addWidget(text)
2179
2180         vbox.addLayout(close_button(d))
2181         d.exec_()