add white space before BTC frozen message
[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         run_hook('exchange_rate_button', grid)
728
729         self.send_button = EnterButton(_("Send"), self.do_send)
730         grid.addWidget(self.send_button, 6, 1)
731
732         b = EnterButton(_("Clear"),self.do_clear)
733         grid.addWidget(b, 6, 2)
734
735         self.payto_sig = QLabel('')
736         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
737
738         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
739         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
740         w.setLayout(grid)
741
742         w2 = QWidget()
743         vbox = QVBoxLayout()
744         vbox.addWidget(w)
745         vbox.addStretch(1)
746         w2.setLayout(vbox)
747
748         def entry_changed( is_fee ):
749             self.funds_error = False
750
751             if self.amount_e.is_shortcut:
752                 self.amount_e.is_shortcut = False
753                 sendable = self.get_sendable_balance()
754                 # there is only one output because we are completely spending inputs
755                 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
756                 fee = self.wallet.estimated_fee(inputs, 1)
757                 amount = total - fee
758                 self.amount_e.setText( self.format_amount(amount) )
759                 self.fee_e.setText( self.format_amount( fee ) )
760                 return
761
762             amount = self.read_amount(str(self.amount_e.text()))
763             fee = self.read_amount(str(self.fee_e.text()))
764
765             if not is_fee: fee = None
766             if amount is None:
767                 return
768             # assume that there will be 2 outputs (one for change)
769             inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
770             if not is_fee:
771                 self.fee_e.setText( self.format_amount( fee ) )
772             if inputs:
773                 palette = QPalette()
774                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
775                 text = ""
776             else:
777                 palette = QPalette()
778                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
779                 self.funds_error = True
780                 text = _( "Not enough funds" )
781                 c, u = self.wallet.get_frozen_balance()
782                 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
783
784             self.statusBar().showMessage(text)
785             self.amount_e.setPalette(palette)
786             self.fee_e.setPalette(palette)
787
788         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
789         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
790
791         run_hook('create_send_tab', grid)
792         return w2
793
794
795     def set_pay_from(self, l):
796         self.pay_from = l
797         self.from_list.clear()
798         self.from_label.setHidden(len(self.pay_from) == 0)
799         self.from_list.setHidden(len(self.pay_from) == 0)
800         for addr in self.pay_from:
801             c, u = self.wallet.get_addr_balance(addr)
802             balance = self.format_amount(c + u)
803             self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
804
805
806     def update_completions(self):
807         l = []
808         for addr,label in self.wallet.labels.items():
809             if addr in self.wallet.addressbook:
810                 l.append( label + '  <' + addr + '>')
811
812         run_hook('update_completions', l)
813         self.completions.setStringList(l)
814
815
816     def protected(func):
817         return lambda s, *args: s.do_protect(func, args)
818
819
820     def do_send(self):
821
822         label = unicode( self.message_e.text() )
823         r = unicode( self.payto_e.text() )
824         r = r.strip()
825
826         # label or alias, with address in brackets
827         m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
828         to_address = m.group(2) if m else r
829
830         if not is_valid(to_address):
831             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
832             return
833
834         try:
835             amount = self.read_amount(unicode( self.amount_e.text()))
836         except Exception:
837             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
838             return
839         try:
840             fee = self.read_amount(unicode( self.fee_e.text()))
841         except Exception:
842             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
843             return
844
845         confirm_amount = self.config.get('confirm_amount', 100000000)
846         if amount >= confirm_amount:
847             if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
848                 return
849             
850         confirm_fee = self.config.get('confirm_fee', 100000)
851         if fee >= confirm_fee:
852             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()}):
853                 return
854
855         self.send_tx(to_address, amount, fee, label)
856
857
858     @protected
859     def send_tx(self, to_address, amount, fee, label, password):
860         try:
861             tx = self.wallet.mktx( [(to_address, amount)], password, fee,
862                     domain=self.get_payment_sources())
863         except Exception as e:
864             traceback.print_exc(file=sys.stdout)
865             self.show_message(str(e))
866             return
867
868         if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
869             QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
870             return
871
872         if label:
873             self.wallet.set_label(tx.hash(), label)
874
875         if tx.is_complete:
876             h = self.wallet.send_tx(tx)
877             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
878             status, msg = self.wallet.receive_tx( h, tx )
879             if status:
880                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
881                 self.do_clear()
882                 self.update_contacts_tab()
883             else:
884                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
885         else:
886
887             self.show_transaction(tx)
888
889         # add recipient to addressbook
890         if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
891             self.wallet.addressbook.append(to_address)
892
893
894
895
896     def set_url(self, url):
897         address, amount, label, message, signature, identity, url = util.parse_url(url)
898
899         try:
900             if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
901             elif amount: amount = str(Decimal(amount))
902         except Exception:
903             amount = "0.0"
904             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
905
906         if self.mini:
907             self.mini.set_payment_fields(address, amount)
908
909         if label and self.wallet.labels.get(address) != label:
910             if self.question('Give label "%s" to address %s ?'%(label,address)):
911                 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
912                     self.wallet.addressbook.append(address)
913                 self.wallet.set_label(address, label)
914
915         run_hook('set_url', url, self.show_message, self.question)
916
917         self.tabs.setCurrentIndex(1)
918         label = self.wallet.labels.get(address)
919         m_addr = label + '  <'+ address +'>' if label else address
920         self.payto_e.setText(m_addr)
921
922         self.message_e.setText(message)
923         if amount:
924             self.amount_e.setText(amount)
925
926         if identity:
927             self.set_frozen(self.payto_e,True)
928             self.set_frozen(self.amount_e,True)
929             self.set_frozen(self.message_e,True)
930             self.payto_sig.setText( '      '+_('The bitcoin URI was signed by')+' ' + identity )
931         else:
932             self.payto_sig.setVisible(False)
933
934     def do_clear(self):
935         self.payto_sig.setVisible(False)
936         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
937             e.setText('')
938             self.set_frozen(e,False)
939
940         self.set_pay_from([])
941         self.update_status()
942
943     def set_frozen(self,entry,frozen):
944         if frozen:
945             entry.setReadOnly(True)
946             entry.setFrame(False)
947             palette = QPalette()
948             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
949             entry.setPalette(palette)
950         else:
951             entry.setReadOnly(False)
952             entry.setFrame(True)
953             palette = QPalette()
954             palette.setColor(entry.backgroundRole(), QColor('white'))
955             entry.setPalette(palette)
956
957
958     def set_addrs_frozen(self,addrs,freeze):
959         for addr in addrs:
960             if not addr: continue
961             if addr in self.wallet.frozen_addresses and not freeze:
962                 self.wallet.unfreeze(addr)
963             elif addr not in self.wallet.frozen_addresses and freeze:
964                 self.wallet.freeze(addr)
965         self.update_receive_tab()
966
967
968
969     def create_list_tab(self, headers):
970         "generic tab creation method"
971         l = MyTreeWidget(self)
972         l.setColumnCount( len(headers) )
973         l.setHeaderLabels( headers )
974
975         w = QWidget()
976         vbox = QVBoxLayout()
977         w.setLayout(vbox)
978
979         vbox.setMargin(0)
980         vbox.setSpacing(0)
981         vbox.addWidget(l)
982         buttons = QWidget()
983         vbox.addWidget(buttons)
984
985         hbox = QHBoxLayout()
986         hbox.setMargin(0)
987         hbox.setSpacing(0)
988         buttons.setLayout(hbox)
989
990         return l,w,hbox
991
992
993     def create_receive_tab(self):
994         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
995         l.setContextMenuPolicy(Qt.CustomContextMenu)
996         l.customContextMenuRequested.connect(self.create_receive_menu)
997         l.setSelectionMode(QAbstractItemView.ExtendedSelection)
998         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
999         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1000         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1001         self.receive_list = l
1002         self.receive_buttons_hbox = hbox
1003         hbox.addStretch(1)
1004         return w
1005
1006
1007
1008
1009     def save_column_widths(self):
1010         self.column_widths["receive"] = []
1011         for i in range(self.receive_list.columnCount() -1):
1012             self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1013
1014         self.column_widths["history"] = []
1015         for i in range(self.history_list.columnCount() - 1):
1016             self.column_widths["history"].append(self.history_list.columnWidth(i))
1017
1018         self.column_widths["contacts"] = []
1019         for i in range(self.contacts_list.columnCount() - 1):
1020             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1021
1022         self.config.set_key("column_widths_2", self.column_widths, True)
1023
1024
1025     def create_contacts_tab(self):
1026         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1027         l.setContextMenuPolicy(Qt.CustomContextMenu)
1028         l.customContextMenuRequested.connect(self.create_contact_menu)
1029         for i,width in enumerate(self.column_widths['contacts']):
1030             l.setColumnWidth(i, width)
1031
1032         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1033         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1034         self.contacts_list = l
1035         self.contacts_buttons_hbox = hbox
1036         hbox.addStretch(1)
1037         return w
1038
1039
1040     def delete_imported_key(self, addr):
1041         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1042             self.wallet.delete_imported_key(addr)
1043             self.update_receive_tab()
1044             self.update_history_tab()
1045
1046     def edit_account_label(self, k):
1047         text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1048         if ok:
1049             label = unicode(text)
1050             self.wallet.set_label(k,label)
1051             self.update_receive_tab()
1052
1053     def account_set_expanded(self, item, k, b):
1054         item.setExpanded(b)
1055         self.accounts_expanded[k] = b
1056
1057     def create_account_menu(self, position, k, item):
1058         menu = QMenu()
1059         if item.isExpanded():
1060             menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1061         else:
1062             menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1063         menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1064         if self.wallet.seed_version > 4:
1065             menu.addAction(_("View details"), lambda: self.show_account_details(k))
1066         if self.wallet.account_is_pending(k):
1067             menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1068         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1069
1070     def delete_pending_account(self, k):
1071         self.wallet.delete_pending_account(k)
1072         self.update_receive_tab()
1073
1074     def create_receive_menu(self, position):
1075         # fixme: this function apparently has a side effect.
1076         # if it is not called the menu pops up several times
1077         #self.receive_list.selectedIndexes()
1078
1079         selected = self.receive_list.selectedItems()
1080         multi_select = len(selected) > 1
1081         addrs = [unicode(item.text(0)) for item in selected]
1082         if not multi_select:
1083             item = self.receive_list.itemAt(position)
1084             if not item: return
1085
1086             addr = addrs[0]
1087             if not is_valid(addr):
1088                 k = str(item.data(0,32).toString())
1089                 if k:
1090                     self.create_account_menu(position, k, item)
1091                 else:
1092                     item.setExpanded(not item.isExpanded())
1093                 return
1094
1095         menu = QMenu()
1096         if not multi_select:
1097             menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1098             menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1099             menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1100             if self.wallet.seed:
1101                 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1102                 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1103                 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1104             if addr in self.wallet.imported_keys:
1105                 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1106
1107         if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1108             menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1109         if any(addr in self.wallet.frozen_addresses for addr in addrs):
1110             menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1111
1112         if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1113             menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1114
1115         run_hook('receive_menu', menu, addrs)
1116         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1117
1118
1119     def get_sendable_balance(self):
1120         return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1121
1122
1123     def get_payment_sources(self):
1124         if self.pay_from:
1125             return self.pay_from
1126         else:
1127             return self.wallet.get_account_addresses(self.current_account)
1128
1129
1130     def send_from_addresses(self, addrs):
1131         self.set_pay_from( addrs )
1132         self.tabs.setCurrentIndex(1)
1133
1134
1135     def payto(self, addr):
1136         if not addr: return
1137         label = self.wallet.labels.get(addr)
1138         m_addr = label + '  <' + addr + '>' if label else addr
1139         self.tabs.setCurrentIndex(1)
1140         self.payto_e.setText(m_addr)
1141         self.amount_e.setFocus()
1142
1143
1144     def delete_contact(self, x):
1145         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1146             self.wallet.delete_contact(x)
1147             self.wallet.set_label(x, None)
1148             self.update_history_tab()
1149             self.update_contacts_tab()
1150             self.update_completions()
1151
1152
1153     def create_contact_menu(self, position):
1154         item = self.contacts_list.itemAt(position)
1155         menu = QMenu()
1156         if not item:
1157             menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1158         else:
1159             addr = unicode(item.text(0))
1160             label = unicode(item.text(1))
1161             is_editable = item.data(0,32).toBool()
1162             payto_addr = item.data(0,33).toString()
1163             menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1164             menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1165             menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1166             if is_editable:
1167                 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1168                 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1169
1170         run_hook('create_contact_menu', menu, item)
1171         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1172
1173
1174     def update_receive_item(self, item):
1175         item.setFont(0, QFont(MONOSPACE_FONT))
1176         address = str(item.data(0,0).toString())
1177         label = self.wallet.labels.get(address,'')
1178         item.setData(1,0,label)
1179         item.setData(0,32, True) # is editable
1180
1181         run_hook('update_receive_item', address, item)
1182
1183         if not self.wallet.is_mine(address): return
1184
1185         c, u = self.wallet.get_addr_balance(address)
1186         balance = self.format_amount(c + u)
1187         item.setData(2,0,balance)
1188
1189         if address in self.wallet.frozen_addresses:
1190             item.setBackgroundColor(0, QColor('lightblue'))
1191
1192
1193     def update_receive_tab(self):
1194         l = self.receive_list
1195
1196         l.clear()
1197         l.setColumnHidden(2, False)
1198         l.setColumnHidden(3, False)
1199         for i,width in enumerate(self.column_widths['receive']):
1200             l.setColumnWidth(i, width)
1201
1202         if self.current_account is None:
1203             account_items = self.wallet.accounts.items()
1204         elif self.current_account != -1:
1205             account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1206         else:
1207             account_items = []
1208
1209         for k, account in account_items:
1210             name = self.wallet.get_account_name(k)
1211             c,u = self.wallet.get_account_balance(k)
1212             account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1213             l.addTopLevelItem(account_item)
1214             account_item.setExpanded(self.accounts_expanded.get(k, True))
1215             account_item.setData(0, 32, k)
1216
1217             if not self.wallet.is_seeded(k):
1218                 icon = QIcon(":icons/key.png")
1219                 account_item.setIcon(0, icon)
1220
1221             for is_change in ([0,1]):
1222                 name = _("Receiving") if not is_change else _("Change")
1223                 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1224                 account_item.addChild(seq_item)
1225                 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1226                 used_flag = False
1227                 if not is_change: seq_item.setExpanded(True)
1228
1229                 is_red = False
1230                 gap = 0
1231
1232                 for address in account.get_addresses(is_change):
1233                     h = self.wallet.history.get(address,[])
1234
1235                     if h == []:
1236                         gap += 1
1237                         if gap > self.wallet.gap_limit:
1238                             is_red = True
1239                     else:
1240                         gap = 0
1241
1242                     c, u = self.wallet.get_addr_balance(address)
1243                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1244                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1245                     self.update_receive_item(item)
1246                     if is_red:
1247                         item.setBackgroundColor(1, QColor('red'))
1248                     if len(h) > 0 and c == -u:
1249                         if not used_flag:
1250                             seq_item.insertChild(0,used_item)
1251                             used_flag = True
1252                         used_item.addChild(item)
1253                     else:
1254                         seq_item.addChild(item)
1255
1256
1257         for k, addr in self.wallet.get_pending_accounts():
1258             name = self.wallet.labels.get(k,'')
1259             account_item = QTreeWidgetItem( [ name + "  [ "+_('pending account')+" ]", '', '', ''] )
1260             self.update_receive_item(item)
1261             l.addTopLevelItem(account_item)
1262             account_item.setExpanded(True)
1263             account_item.setData(0, 32, k)
1264             item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1265             account_item.addChild(item)
1266             self.update_receive_item(item)
1267
1268
1269         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1270             c,u = self.wallet.get_imported_balance()
1271             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1272             l.addTopLevelItem(account_item)
1273             account_item.setExpanded(True)
1274             for address in self.wallet.imported_keys.keys():
1275                 item = QTreeWidgetItem( [ address, '', '', ''] )
1276                 self.update_receive_item(item)
1277                 account_item.addChild(item)
1278
1279
1280         # we use column 1 because column 0 may be hidden
1281         l.setCurrentItem(l.topLevelItem(0),1)
1282
1283
1284     def update_contacts_tab(self):
1285         l = self.contacts_list
1286         l.clear()
1287
1288         for address in self.wallet.addressbook:
1289             label = self.wallet.labels.get(address,'')
1290             n = self.wallet.get_num_tx(address)
1291             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1292             item.setFont(0, QFont(MONOSPACE_FONT))
1293             # 32 = label can be edited (bool)
1294             item.setData(0,32, True)
1295             # 33 = payto string
1296             item.setData(0,33, address)
1297             l.addTopLevelItem(item)
1298
1299         run_hook('update_contacts_tab', l)
1300         l.setCurrentItem(l.topLevelItem(0))
1301
1302
1303
1304     def create_console_tab(self):
1305         from console import Console
1306         self.console = console = Console()
1307         return console
1308
1309
1310     def update_console(self):
1311         console = self.console
1312         console.history = self.config.get("console-history",[])
1313         console.history_index = len(console.history)
1314
1315         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1316         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1317
1318         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1319         methods = {}
1320         def mkfunc(f, method):
1321             return lambda *args: apply( f, (method, args, self.password_dialog ))
1322         for m in dir(c):
1323             if m[0]=='_' or m in ['network','wallet']: continue
1324             methods[m] = mkfunc(c._run, m)
1325
1326         console.updateNamespace(methods)
1327
1328
1329     def change_account(self,s):
1330         if s == _("All accounts"):
1331             self.current_account = None
1332         else:
1333             accounts = self.wallet.get_account_names()
1334             for k, v in accounts.items():
1335                 if v == s:
1336                     self.current_account = k
1337         self.update_history_tab()
1338         self.update_status()
1339         self.update_receive_tab()
1340
1341     def create_status_bar(self):
1342
1343         sb = QStatusBar()
1344         sb.setFixedHeight(35)
1345         qtVersion = qVersion()
1346
1347         self.balance_label = QLabel("")
1348         sb.addWidget(self.balance_label)
1349
1350         from version_getter import UpdateLabel
1351         self.updatelabel = UpdateLabel(self.config, sb)
1352
1353         self.account_selector = QComboBox()
1354         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1355         sb.addPermanentWidget(self.account_selector)
1356
1357         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1358             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1359
1360         self.lock_icon = QIcon()
1361         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1362         sb.addPermanentWidget( self.password_button )
1363
1364         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1365         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1366         sb.addPermanentWidget( self.seed_button )
1367         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1368         sb.addPermanentWidget( self.status_button )
1369
1370         run_hook('create_status_bar', (sb,))
1371
1372         self.setStatusBar(sb)
1373
1374
1375     def update_lock_icon(self):
1376         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1377         self.password_button.setIcon( icon )
1378
1379
1380     def update_buttons_on_seed(self):
1381         if not self.wallet.is_watching_only():
1382            self.seed_button.show()
1383            self.password_button.show()
1384            self.send_button.setText(_("Send"))
1385         else:
1386            self.password_button.hide()
1387            self.seed_button.hide()
1388            self.send_button.setText(_("Create unsigned transaction"))
1389
1390
1391     def change_password_dialog(self):
1392         from password_dialog import PasswordDialog
1393         d = PasswordDialog(self.wallet, self)
1394         d.run()
1395         self.update_lock_icon()
1396
1397
1398     def new_contact_dialog(self):
1399
1400         d = QDialog(self)
1401         vbox = QVBoxLayout(d)
1402         vbox.addWidget(QLabel(_('New Contact')+':'))
1403
1404         grid = QGridLayout()
1405         line1 = QLineEdit()
1406         line2 = QLineEdit()
1407         grid.addWidget(QLabel(_("Address")), 1, 0)
1408         grid.addWidget(line1, 1, 1)
1409         grid.addWidget(QLabel(_("Name")), 2, 0)
1410         grid.addWidget(line2, 2, 1)
1411
1412         vbox.addLayout(grid)
1413         vbox.addLayout(ok_cancel_buttons(d))
1414
1415         if not d.exec_():
1416             return
1417
1418         address = str(line1.text())
1419         label = unicode(line2.text())
1420
1421         if not is_valid(address):
1422             QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1423             return
1424
1425         self.wallet.add_contact(address)
1426         if label:
1427             self.wallet.set_label(address, label)
1428
1429         self.update_contacts_tab()
1430         self.update_history_tab()
1431         self.update_completions()
1432         self.tabs.setCurrentIndex(3)
1433
1434
1435     def new_account_dialog(self):
1436
1437         dialog = QDialog(self)
1438         dialog.setModal(1)
1439         dialog.setWindowTitle(_("New Account"))
1440
1441         vbox = QVBoxLayout()
1442         vbox.addWidget(QLabel(_('Account name')+':'))
1443         e = QLineEdit()
1444         vbox.addWidget(e)
1445         msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1446             + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1447         l = QLabel(msg)
1448         l.setWordWrap(True)
1449         vbox.addWidget(l)
1450
1451         vbox.addLayout(ok_cancel_buttons(dialog))
1452         dialog.setLayout(vbox)
1453         r = dialog.exec_()
1454         if not r: return
1455
1456         name = str(e.text())
1457         if not name: return
1458
1459         self.wallet.create_pending_account('1', name)
1460         self.update_receive_tab()
1461         self.tabs.setCurrentIndex(2)
1462
1463
1464
1465     def show_master_public_key_old(self):
1466         dialog = QDialog(self)
1467         dialog.setModal(1)
1468         dialog.setWindowTitle(_("Master Public Key"))
1469
1470         main_text = QTextEdit()
1471         main_text.setText(self.wallet.get_master_public_key())
1472         main_text.setReadOnly(True)
1473         main_text.setMaximumHeight(170)
1474         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1475
1476         ok_button = QPushButton(_("OK"))
1477         ok_button.setDefault(True)
1478         ok_button.clicked.connect(dialog.accept)
1479
1480         main_layout = QGridLayout()
1481         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1482
1483         main_layout.addWidget(main_text, 1, 0)
1484         main_layout.addWidget(qrw, 1, 1 )
1485
1486         vbox = QVBoxLayout()
1487         vbox.addLayout(main_layout)
1488         vbox.addLayout(close_button(dialog))
1489         dialog.setLayout(vbox)
1490         dialog.exec_()
1491
1492
1493     def show_master_public_key(self):
1494
1495         if self.wallet.seed_version == 4:
1496             self.show_master_public_key_old()
1497             return
1498
1499         dialog = QDialog(self)
1500         dialog.setModal(1)
1501         dialog.setWindowTitle(_("Master Public Keys"))
1502
1503         chain_text = QTextEdit()
1504         chain_text.setReadOnly(True)
1505         chain_text.setMaximumHeight(170)
1506         chain_qrw = QRCodeWidget()
1507
1508         mpk_text = QTextEdit()
1509         mpk_text.setReadOnly(True)
1510         mpk_text.setMaximumHeight(170)
1511         mpk_qrw = QRCodeWidget()
1512
1513         main_layout = QGridLayout()
1514
1515         main_layout.addWidget(QLabel(_('Key')), 1, 0)
1516         main_layout.addWidget(mpk_text, 1, 1)
1517         main_layout.addWidget(mpk_qrw, 1, 2)
1518
1519         main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1520         main_layout.addWidget(chain_text, 2, 1)
1521         main_layout.addWidget(chain_qrw, 2, 2)
1522
1523         def update(key):
1524             c, K, cK = self.wallet.master_public_keys[str(key)]
1525             chain_text.setText(c)
1526             chain_qrw.set_addr(c)
1527             chain_qrw.update_qr()
1528             mpk_text.setText(K)
1529             mpk_qrw.set_addr(K)
1530             mpk_qrw.update_qr()
1531
1532         key_selector = QComboBox()
1533         keys = sorted(self.wallet.master_public_keys.keys())
1534         key_selector.addItems(keys)
1535
1536         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1537         main_layout.addWidget(key_selector, 0, 1)
1538         dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1539
1540         update(keys[0])
1541
1542         vbox = QVBoxLayout()
1543         vbox.addLayout(main_layout)
1544         vbox.addLayout(close_button(dialog))
1545
1546         dialog.setLayout(vbox)
1547         dialog.exec_()
1548
1549
1550     @protected
1551     def show_seed_dialog(self, password):
1552         if self.wallet.is_watching_only():
1553             QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1554             return
1555
1556         if self.wallet.seed:
1557             try:
1558                 mnemonic = self.wallet.get_mnemonic(password)
1559             except Exception:
1560                 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1561                 return
1562             from seed_dialog import SeedDialog
1563             d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1564             d.exec_()
1565         else:
1566             l = {}
1567             for k in self.wallet.master_private_keys.keys():
1568                 pk = self.wallet.get_master_private_key(k, password)
1569                 l[k] = pk
1570             from seed_dialog import PrivateKeysDialog
1571             d = PrivateKeysDialog(self,l)
1572             d.exec_()
1573
1574
1575
1576
1577
1578     def show_qrcode(self, data, title = _("QR code")):
1579         if not data: return
1580         d = QDialog(self)
1581         d.setModal(1)
1582         d.setWindowTitle(title)
1583         d.setMinimumSize(270, 300)
1584         vbox = QVBoxLayout()
1585         qrw = QRCodeWidget(data)
1586         vbox.addWidget(qrw, 1)
1587         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1588         hbox = QHBoxLayout()
1589         hbox.addStretch(1)
1590
1591         filename = os.path.join(self.config.path, "qrcode.bmp")
1592
1593         def print_qr():
1594             bmp.save_qrcode(qrw.qr, filename)
1595             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1596
1597         def copy_to_clipboard():
1598             bmp.save_qrcode(qrw.qr, filename)
1599             self.app.clipboard().setImage(QImage(filename))
1600             QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1601
1602         b = QPushButton(_("Copy"))
1603         hbox.addWidget(b)
1604         b.clicked.connect(copy_to_clipboard)
1605
1606         b = QPushButton(_("Save"))
1607         hbox.addWidget(b)
1608         b.clicked.connect(print_qr)
1609
1610         b = QPushButton(_("Close"))
1611         hbox.addWidget(b)
1612         b.clicked.connect(d.accept)
1613         b.setDefault(True)
1614
1615         vbox.addLayout(hbox)
1616         d.setLayout(vbox)
1617         d.exec_()
1618
1619
1620     def do_protect(self, func, args):
1621         if self.wallet.use_encryption:
1622             password = self.password_dialog()
1623             if not password:
1624                 return
1625         else:
1626             password = None
1627
1628         if args != (False,):
1629             args = (self,) + args + (password,)
1630         else:
1631             args = (self,password)
1632         apply( func, args)
1633
1634
1635     @protected
1636     def show_private_key(self, address, password):
1637         if not address: return
1638         try:
1639             pk_list = self.wallet.get_private_key(address, password)
1640         except Exception as e:
1641             self.show_message(str(e))
1642             return
1643
1644         d = QDialog(self)
1645         d.setMinimumSize(600, 200)
1646         d.setModal(1)
1647         vbox = QVBoxLayout()
1648         vbox.addWidget( QLabel(_("Address") + ': ' + address))
1649         vbox.addWidget( QLabel(_("Private key") + ':'))
1650         keys = QTextEdit()
1651         keys.setReadOnly(True)
1652         keys.setText('\n'.join(pk_list))
1653         vbox.addWidget(keys)
1654         vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1655         vbox.addLayout(close_button(d))
1656         d.setLayout(vbox)
1657         d.exec_()
1658
1659
1660     @protected
1661     def do_sign(self, address, message, signature, password):
1662         message = unicode(message.toPlainText())
1663         message = message.encode('utf-8')
1664         try:
1665             sig = self.wallet.sign_message(str(address.text()), message, password)
1666             signature.setText(sig)
1667         except Exception as e:
1668             self.show_message(str(e))
1669
1670     def do_verify(self, address, message, signature):
1671         message = unicode(message.toPlainText())
1672         message = message.encode('utf-8')
1673         if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1674             self.show_message(_("Signature verified"))
1675         else:
1676             self.show_message(_("Error: wrong signature"))
1677
1678
1679     def sign_verify_message(self, address=''):
1680         d = QDialog(self)
1681         d.setModal(1)
1682         d.setWindowTitle(_('Sign/verify Message'))
1683         d.setMinimumSize(410, 290)
1684
1685         layout = QGridLayout(d)
1686
1687         message_e = QTextEdit()
1688         layout.addWidget(QLabel(_('Message')), 1, 0)
1689         layout.addWidget(message_e, 1, 1)
1690         layout.setRowStretch(2,3)
1691
1692         address_e = QLineEdit()
1693         address_e.setText(address)
1694         layout.addWidget(QLabel(_('Address')), 2, 0)
1695         layout.addWidget(address_e, 2, 1)
1696
1697         signature_e = QTextEdit()
1698         layout.addWidget(QLabel(_('Signature')), 3, 0)
1699         layout.addWidget(signature_e, 3, 1)
1700         layout.setRowStretch(3,1)
1701
1702         hbox = QHBoxLayout()
1703
1704         b = QPushButton(_("Sign"))
1705         b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1706         hbox.addWidget(b)
1707
1708         b = QPushButton(_("Verify"))
1709         b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1710         hbox.addWidget(b)
1711
1712         b = QPushButton(_("Close"))
1713         b.clicked.connect(d.accept)
1714         hbox.addWidget(b)
1715         layout.addLayout(hbox, 4, 1)
1716         d.exec_()
1717
1718
1719     @protected
1720     def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1721         try:
1722             decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1723             message_e.setText(decrypted)
1724         except Exception as e:
1725             self.show_message(str(e))
1726
1727
1728     def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1729         message = unicode(message_e.toPlainText())
1730         message = message.encode('utf-8')
1731         try:
1732             encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1733             encrypted_e.setText(encrypted)
1734         except Exception as e:
1735             self.show_message(str(e))
1736
1737
1738
1739     def encrypt_message(self, address = ''):
1740         d = QDialog(self)
1741         d.setModal(1)
1742         d.setWindowTitle(_('Encrypt/decrypt Message'))
1743         d.setMinimumSize(610, 490)
1744
1745         layout = QGridLayout(d)
1746
1747         message_e = QTextEdit()
1748         layout.addWidget(QLabel(_('Message')), 1, 0)
1749         layout.addWidget(message_e, 1, 1)
1750         layout.setRowStretch(2,3)
1751
1752         pubkey_e = QLineEdit()
1753         if address:
1754             pubkey = self.wallet.getpubkeys(address)[0]
1755             pubkey_e.setText(pubkey)
1756         layout.addWidget(QLabel(_('Public key')), 2, 0)
1757         layout.addWidget(pubkey_e, 2, 1)
1758
1759         encrypted_e = QTextEdit()
1760         layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1761         layout.addWidget(encrypted_e, 3, 1)
1762         layout.setRowStretch(3,1)
1763
1764         hbox = QHBoxLayout()
1765         b = QPushButton(_("Encrypt"))
1766         b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1767         hbox.addWidget(b)
1768
1769         b = QPushButton(_("Decrypt"))
1770         b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1771         hbox.addWidget(b)
1772
1773         b = QPushButton(_("Close"))
1774         b.clicked.connect(d.accept)
1775         hbox.addWidget(b)
1776
1777         layout.addLayout(hbox, 4, 1)
1778         d.exec_()
1779
1780
1781     def question(self, msg):
1782         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1783
1784     def show_message(self, msg):
1785         QMessageBox.information(self, _('Message'), msg, _('OK'))
1786
1787     def password_dialog(self ):
1788         d = QDialog(self)
1789         d.setModal(1)
1790
1791         pw = QLineEdit()
1792         pw.setEchoMode(2)
1793
1794         vbox = QVBoxLayout()
1795         msg = _('Please enter your password')
1796         vbox.addWidget(QLabel(msg))
1797
1798         grid = QGridLayout()
1799         grid.setSpacing(8)
1800         grid.addWidget(QLabel(_('Password')), 1, 0)
1801         grid.addWidget(pw, 1, 1)
1802         vbox.addLayout(grid)
1803
1804         vbox.addLayout(ok_cancel_buttons(d))
1805         d.setLayout(vbox)
1806
1807         run_hook('password_dialog', pw, grid, 1)
1808         if not d.exec_(): return
1809         return unicode(pw.text())
1810
1811
1812
1813
1814
1815
1816
1817
1818     def tx_from_text(self, txt):
1819         "json or raw hexadecimal"
1820         try:
1821             txt.decode('hex')
1822             tx = Transaction(txt)
1823             return tx
1824         except Exception:
1825             pass
1826
1827         try:
1828             tx_dict = json.loads(str(txt))
1829             assert "hex" in tx_dict.keys()
1830             assert "complete" in tx_dict.keys()
1831             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1832             if not tx_dict["complete"]:
1833                 assert "input_info" in tx_dict.keys()
1834                 input_info = json.loads(tx_dict['input_info'])
1835                 tx.add_input_info(input_info)
1836             return tx
1837         except Exception:
1838             pass
1839
1840         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1841
1842
1843
1844     def read_tx_from_file(self):
1845         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1846         if not fileName:
1847             return
1848         try:
1849             with open(fileName, "r") as f:
1850                 file_content = f.read()
1851         except (ValueError, IOError, os.error), reason:
1852             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1853
1854         return self.tx_from_text(file_content)
1855
1856
1857     @protected
1858     def sign_raw_transaction(self, tx, input_info, password):
1859         self.wallet.signrawtransaction(tx, input_info, [], password)
1860
1861     def do_process_from_text(self):
1862         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1863         if not text:
1864             return
1865         tx = self.tx_from_text(text)
1866         if tx:
1867             self.show_transaction(tx)
1868
1869     def do_process_from_file(self):
1870         tx = self.read_tx_from_file()
1871         if tx:
1872             self.show_transaction(tx)
1873
1874     def do_process_from_txid(self):
1875         from electrum import transaction
1876         txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1877         if ok and txid:
1878             r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1879             if r:
1880                 tx = transaction.Transaction(r)
1881                 if tx:
1882                     self.show_transaction(tx)
1883                 else:
1884                     self.show_message("unknown transaction")
1885
1886     def do_process_from_csvReader(self, csvReader):
1887         outputs = []
1888         errors = []
1889         errtext = ""
1890         try:
1891             for position, row in enumerate(csvReader):
1892                 address = row[0]
1893                 if not is_valid(address):
1894                     errors.append((position, address))
1895                     continue
1896                 amount = Decimal(row[1])
1897                 amount = int(100000000*amount)
1898                 outputs.append((address, amount))
1899         except (ValueError, IOError, os.error), reason:
1900             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1901             return
1902         if errors != []:
1903             for x in errors:
1904                 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1905             QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1906             return
1907
1908         try:
1909             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1910         except Exception as e:
1911             self.show_message(str(e))
1912             return
1913
1914         self.show_transaction(tx)
1915
1916     def do_process_from_csv_file(self):
1917         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1918         if not fileName:
1919             return
1920         try:
1921             with open(fileName, "r") as f:
1922                 csvReader = csv.reader(f)
1923                 self.do_process_from_csvReader(csvReader)
1924         except (ValueError, IOError, os.error), reason:
1925             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1926             return
1927
1928     def do_process_from_csv_text(self):
1929         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1930                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1931         if not text:
1932             return
1933         f = StringIO.StringIO(text)
1934         csvReader = csv.reader(f)
1935         self.do_process_from_csvReader(csvReader)
1936
1937
1938
1939     @protected
1940     def do_export_privkeys(self, password):
1941         if not self.wallet.seed:
1942             self.show_message(_("This wallet has no seed"))
1943             return
1944
1945         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.")))
1946
1947         try:
1948             select_export = _('Select file to export your private keys to')
1949             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1950             if fileName:
1951                 with open(fileName, "w+") as csvfile:
1952                     transaction = csv.writer(csvfile)
1953                     transaction.writerow(["address", "private_key"])
1954
1955                     addresses = self.wallet.addresses(True)
1956
1957                     for addr in addresses:
1958                         pk = "".join(self.wallet.get_private_key(addr, password))
1959                         transaction.writerow(["%34s"%addr,pk])
1960
1961                     self.show_message(_("Private keys exported."))
1962
1963         except (IOError, os.error), reason:
1964             export_error_label = _("Electrum was unable to produce a private key-export.")
1965             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1966
1967         except Exception as e:
1968           self.show_message(str(e))
1969           return
1970
1971
1972     def do_import_labels(self):
1973         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1974         if not labelsFile: return
1975         try:
1976             f = open(labelsFile, 'r')
1977             data = f.read()
1978             f.close()
1979             for key, value in json.loads(data).items():
1980                 self.wallet.set_label(key, value)
1981             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1982         except (IOError, os.error), reason:
1983             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1984
1985
1986     def do_export_labels(self):
1987         labels = self.wallet.labels
1988         try:
1989             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1990             if fileName:
1991                 with open(fileName, 'w+') as f:
1992                     json.dump(labels, f)
1993                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1994         except (IOError, os.error), reason:
1995             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1996
1997
1998     def do_export_history(self):
1999         from lite_window import csv_transaction
2000         csv_transaction(self.wallet)
2001
2002
2003     @protected
2004     def do_import_privkey(self, password):
2005         if not self.wallet.imported_keys:
2006             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2007                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2008                                          + _('Are you sure you understand what you are doing?'), 3, 4)
2009             if r == 4: return
2010
2011         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2012         if not text: return
2013
2014         text = str(text).split()
2015         badkeys = []
2016         addrlist = []
2017         for key in text:
2018             try:
2019                 addr = self.wallet.import_key(key, password)
2020             except Exception as e:
2021                 badkeys.append(key)
2022                 continue
2023             if not addr:
2024                 badkeys.append(key)
2025             else:
2026                 addrlist.append(addr)
2027         if addrlist:
2028             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2029         if badkeys:
2030             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2031         self.update_receive_tab()
2032         self.update_history_tab()
2033
2034
2035     def settings_dialog(self):
2036         d = QDialog(self)
2037         d.setWindowTitle(_('Electrum Settings'))
2038         d.setModal(1)
2039         vbox = QVBoxLayout()
2040         grid = QGridLayout()
2041         grid.setColumnStretch(0,1)
2042
2043         nz_label = QLabel(_('Display zeros') + ':')
2044         grid.addWidget(nz_label, 0, 0)
2045         nz_e = AmountEdit(None,True)
2046         nz_e.setText("%d"% self.num_zeros)
2047         grid.addWidget(nz_e, 0, 1)
2048         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2049         grid.addWidget(HelpButton(msg), 0, 2)
2050         if not self.config.is_modifiable('num_zeros'):
2051             for w in [nz_e, nz_label]: w.setEnabled(False)
2052
2053         lang_label=QLabel(_('Language') + ':')
2054         grid.addWidget(lang_label, 1, 0)
2055         lang_combo = QComboBox()
2056         from electrum.i18n import languages
2057         lang_combo.addItems(languages.values())
2058         try:
2059             index = languages.keys().index(self.config.get("language",''))
2060         except Exception:
2061             index = 0
2062         lang_combo.setCurrentIndex(index)
2063         grid.addWidget(lang_combo, 1, 1)
2064         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2065         if not self.config.is_modifiable('language'):
2066             for w in [lang_combo, lang_label]: w.setEnabled(False)
2067
2068
2069         fee_label = QLabel(_('Transaction fee') + ':')
2070         grid.addWidget(fee_label, 2, 0)
2071         fee_e = AmountEdit(self.base_unit)
2072         fee_e.setText(self.format_amount(self.wallet.fee).strip())
2073         grid.addWidget(fee_e, 2, 1)
2074         msg = _('Fee per kilobyte of transaction.') + ' ' \
2075             + _('Recommended value') + ': ' + self.format_amount(20000)
2076         grid.addWidget(HelpButton(msg), 2, 2)
2077         if not self.config.is_modifiable('fee_per_kb'):
2078             for w in [fee_e, fee_label]: w.setEnabled(False)
2079
2080         units = ['BTC', 'mBTC']
2081         unit_label = QLabel(_('Base unit') + ':')
2082         grid.addWidget(unit_label, 3, 0)
2083         unit_combo = QComboBox()
2084         unit_combo.addItems(units)
2085         unit_combo.setCurrentIndex(units.index(self.base_unit()))
2086         grid.addWidget(unit_combo, 3, 1)
2087         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2088                                              + '\n1BTC=1000mBTC.\n' \
2089                                              + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2090
2091         usechange_cb = QCheckBox(_('Use change addresses'))
2092         usechange_cb.setChecked(self.wallet.use_change)
2093         grid.addWidget(usechange_cb, 4, 0)
2094         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2095         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2096
2097         grid.setRowStretch(5,1)
2098
2099         vbox.addLayout(grid)
2100         vbox.addLayout(ok_cancel_buttons(d))
2101         d.setLayout(vbox)
2102
2103         # run the dialog
2104         if not d.exec_(): return
2105
2106         fee = unicode(fee_e.text())
2107         try:
2108             fee = self.read_amount(fee)
2109         except Exception:
2110             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2111             return
2112
2113         self.wallet.set_fee(fee)
2114
2115         nz = unicode(nz_e.text())
2116         try:
2117             nz = int( nz )
2118             if nz>8: nz=8
2119         except Exception:
2120             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2121             return
2122
2123         if self.num_zeros != nz:
2124             self.num_zeros = nz
2125             self.config.set_key('num_zeros', nz, True)
2126             self.update_history_tab()
2127             self.update_receive_tab()
2128
2129         usechange_result = usechange_cb.isChecked()
2130         if self.wallet.use_change != usechange_result:
2131             self.wallet.use_change = usechange_result
2132             self.wallet.storage.put('use_change', self.wallet.use_change)
2133
2134         unit_result = units[unit_combo.currentIndex()]
2135         if self.base_unit() != unit_result:
2136             self.decimal_point = 8 if unit_result == 'BTC' else 5
2137             self.config.set_key('decimal_point', self.decimal_point, True)
2138             self.update_history_tab()
2139             self.update_status()
2140
2141         need_restart = False
2142
2143         lang_request = languages.keys()[lang_combo.currentIndex()]
2144         if lang_request != self.config.get('language'):
2145             self.config.set_key("language", lang_request, True)
2146             need_restart = True
2147
2148         run_hook('close_settings_dialog')
2149
2150         if need_restart:
2151             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2152
2153
2154     def run_network_dialog(self):
2155         if not self.network:
2156             return
2157         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2158
2159     def closeEvent(self, event):
2160         self.tray.hide()
2161         g = self.geometry()
2162         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2163         self.save_column_widths()
2164         self.config.set_key("console-history", self.console.history[-50:], True)
2165         self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2166         event.accept()
2167
2168
2169     def plugins_dialog(self):
2170         from electrum.plugins import plugins
2171
2172         d = QDialog(self)
2173         d.setWindowTitle(_('Electrum Plugins'))
2174         d.setModal(1)
2175
2176         vbox = QVBoxLayout(d)
2177
2178         # plugins
2179         scroll = QScrollArea()
2180         scroll.setEnabled(True)
2181         scroll.setWidgetResizable(True)
2182         scroll.setMinimumSize(400,250)
2183         vbox.addWidget(scroll)
2184
2185         w = QWidget()
2186         scroll.setWidget(w)
2187         w.setMinimumHeight(len(plugins)*35)
2188
2189         grid = QGridLayout()
2190         grid.setColumnStretch(0,1)
2191         w.setLayout(grid)
2192
2193         def do_toggle(cb, p, w):
2194             r = p.toggle()
2195             cb.setChecked(r)
2196             if w: w.setEnabled(r)
2197
2198         def mk_toggle(cb, p, w):
2199             return lambda: do_toggle(cb,p,w)
2200
2201         for i, p in enumerate(plugins):
2202             try:
2203                 cb = QCheckBox(p.fullname())
2204                 cb.setDisabled(not p.is_available())
2205                 cb.setChecked(p.is_enabled())
2206                 grid.addWidget(cb, i, 0)
2207                 if p.requires_settings():
2208                     w = p.settings_widget(self)
2209                     w.setEnabled( p.is_enabled() )
2210                     grid.addWidget(w, i, 1)
2211                 else:
2212                     w = None
2213                 cb.clicked.connect(mk_toggle(cb,p,w))
2214                 grid.addWidget(HelpButton(p.description()), i, 2)
2215             except Exception:
2216                 print_msg(_("Error: cannot display plugin"), p)
2217                 traceback.print_exc(file=sys.stdout)
2218         grid.setRowStretch(i+1,1)
2219
2220         vbox.addLayout(close_button(d))
2221
2222         d.exec_()
2223
2224
2225     def show_account_details(self, k):
2226         d = QDialog(self)
2227         d.setWindowTitle(_('Account Details'))
2228         d.setModal(1)
2229
2230         vbox = QVBoxLayout(d)
2231         roots = self.wallet.get_roots(k)
2232
2233         name = self.wallet.get_account_name(k)
2234         label = QLabel('Name: ' + name)
2235         vbox.addWidget(label)
2236
2237         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2238         vbox.addWidget(QLabel('Type: ' + acctype))
2239
2240         label = QLabel('Derivation: ' + k)
2241         vbox.addWidget(label)
2242
2243         #for root in roots:
2244         #    mpk = self.wallet.master_public_keys[root]
2245         #    text = QTextEdit()
2246         #    text.setReadOnly(True)
2247         #    text.setMaximumHeight(120)
2248         #    text.setText(repr(mpk))
2249         #    vbox.addWidget(text)
2250
2251         vbox.addLayout(close_button(d))
2252         d.exec_()