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