Fixed size issue on account combo box in status panel. If you started Electrum with...
[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.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1355         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1356         sb.addPermanentWidget(self.account_selector)
1357
1358         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1359             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1360
1361         self.lock_icon = QIcon()
1362         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1363         sb.addPermanentWidget( self.password_button )
1364
1365         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1366         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1367         sb.addPermanentWidget( self.seed_button )
1368         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1369         sb.addPermanentWidget( self.status_button )
1370
1371         run_hook('create_status_bar', (sb,))
1372
1373         self.setStatusBar(sb)
1374
1375
1376     def update_lock_icon(self):
1377         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1378         self.password_button.setIcon( icon )
1379
1380
1381     def update_buttons_on_seed(self):
1382         if not self.wallet.is_watching_only():
1383            self.seed_button.show()
1384            self.password_button.show()
1385            self.send_button.setText(_("Send"))
1386         else:
1387            self.password_button.hide()
1388            self.seed_button.hide()
1389            self.send_button.setText(_("Create unsigned transaction"))
1390
1391
1392     def change_password_dialog(self):
1393         from password_dialog import PasswordDialog
1394         d = PasswordDialog(self.wallet, self)
1395         d.run()
1396         self.update_lock_icon()
1397
1398
1399     def new_contact_dialog(self):
1400
1401         d = QDialog(self)
1402         d.setWindowTitle(_("New Contact"))
1403         vbox = QVBoxLayout(d)
1404         vbox.addWidget(QLabel(_('New Contact')+':'))
1405
1406         grid = QGridLayout()
1407         line1 = QLineEdit()
1408         line2 = QLineEdit()
1409         grid.addWidget(QLabel(_("Address")), 1, 0)
1410         grid.addWidget(line1, 1, 1)
1411         grid.addWidget(QLabel(_("Name")), 2, 0)
1412         grid.addWidget(line2, 2, 1)
1413
1414         vbox.addLayout(grid)
1415         vbox.addLayout(ok_cancel_buttons(d))
1416
1417         if not d.exec_():
1418             return
1419
1420         address = str(line1.text())
1421         label = unicode(line2.text())
1422
1423         if not is_valid(address):
1424             QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1425             return
1426
1427         self.wallet.add_contact(address)
1428         if label:
1429             self.wallet.set_label(address, label)
1430
1431         self.update_contacts_tab()
1432         self.update_history_tab()
1433         self.update_completions()
1434         self.tabs.setCurrentIndex(3)
1435
1436
1437     def new_account_dialog(self):
1438
1439         dialog = QDialog(self)
1440         dialog.setModal(1)
1441         dialog.setWindowTitle(_("New Account"))
1442
1443         vbox = QVBoxLayout()
1444         vbox.addWidget(QLabel(_('Account name')+':'))
1445         e = QLineEdit()
1446         vbox.addWidget(e)
1447         msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1448             + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1449         l = QLabel(msg)
1450         l.setWordWrap(True)
1451         vbox.addWidget(l)
1452
1453         vbox.addLayout(ok_cancel_buttons(dialog))
1454         dialog.setLayout(vbox)
1455         r = dialog.exec_()
1456         if not r: return
1457
1458         name = str(e.text())
1459         if not name: return
1460
1461         self.wallet.create_pending_account('1', name)
1462         self.update_receive_tab()
1463         self.tabs.setCurrentIndex(2)
1464
1465
1466
1467     def show_master_public_key_old(self):
1468         dialog = QDialog(self)
1469         dialog.setModal(1)
1470         dialog.setWindowTitle(_("Master Public Key"))
1471
1472         main_text = QTextEdit()
1473         main_text.setText(self.wallet.get_master_public_key())
1474         main_text.setReadOnly(True)
1475         main_text.setMaximumHeight(170)
1476         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1477
1478         ok_button = QPushButton(_("OK"))
1479         ok_button.setDefault(True)
1480         ok_button.clicked.connect(dialog.accept)
1481
1482         main_layout = QGridLayout()
1483         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1484
1485         main_layout.addWidget(main_text, 1, 0)
1486         main_layout.addWidget(qrw, 1, 1 )
1487
1488         vbox = QVBoxLayout()
1489         vbox.addLayout(main_layout)
1490         vbox.addLayout(close_button(dialog))
1491         dialog.setLayout(vbox)
1492         dialog.exec_()
1493
1494
1495     def show_master_public_key(self):
1496
1497         if self.wallet.seed_version == 4:
1498             self.show_master_public_key_old()
1499             return
1500
1501         dialog = QDialog(self)
1502         dialog.setModal(1)
1503         dialog.setWindowTitle(_("Master Public Keys"))
1504
1505         chain_text = QTextEdit()
1506         chain_text.setReadOnly(True)
1507         chain_text.setMaximumHeight(170)
1508         chain_qrw = QRCodeWidget()
1509
1510         mpk_text = QTextEdit()
1511         mpk_text.setReadOnly(True)
1512         mpk_text.setMaximumHeight(170)
1513         mpk_qrw = QRCodeWidget()
1514
1515         main_layout = QGridLayout()
1516
1517         main_layout.addWidget(QLabel(_('Key')), 1, 0)
1518         main_layout.addWidget(mpk_text, 1, 1)
1519         main_layout.addWidget(mpk_qrw, 1, 2)
1520
1521         main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1522         main_layout.addWidget(chain_text, 2, 1)
1523         main_layout.addWidget(chain_qrw, 2, 2)
1524
1525         def update(key):
1526             c, K, cK = self.wallet.master_public_keys[str(key)]
1527             chain_text.setText(c)
1528             chain_qrw.set_addr(c)
1529             chain_qrw.update_qr()
1530             mpk_text.setText(K)
1531             mpk_qrw.set_addr(K)
1532             mpk_qrw.update_qr()
1533
1534         key_selector = QComboBox()
1535         keys = sorted(self.wallet.master_public_keys.keys())
1536         key_selector.addItems(keys)
1537
1538         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1539         main_layout.addWidget(key_selector, 0, 1)
1540         dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1541
1542         update(keys[0])
1543
1544         vbox = QVBoxLayout()
1545         vbox.addLayout(main_layout)
1546         vbox.addLayout(close_button(dialog))
1547
1548         dialog.setLayout(vbox)
1549         dialog.exec_()
1550
1551
1552     @protected
1553     def show_seed_dialog(self, password):
1554         if self.wallet.is_watching_only():
1555             QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1556             return
1557
1558         if self.wallet.seed:
1559             try:
1560                 mnemonic = self.wallet.get_mnemonic(password)
1561             except Exception:
1562                 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1563                 return
1564             from seed_dialog import SeedDialog
1565             d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1566             d.exec_()
1567         else:
1568             l = {}
1569             for k in self.wallet.master_private_keys.keys():
1570                 pk = self.wallet.get_master_private_key(k, password)
1571                 l[k] = pk
1572             from seed_dialog import PrivateKeysDialog
1573             d = PrivateKeysDialog(self,l)
1574             d.exec_()
1575
1576
1577
1578
1579
1580     def show_qrcode(self, data, title = _("QR code")):
1581         if not data: return
1582         d = QDialog(self)
1583         d.setModal(1)
1584         d.setWindowTitle(title)
1585         d.setMinimumSize(270, 300)
1586         vbox = QVBoxLayout()
1587         qrw = QRCodeWidget(data)
1588         vbox.addWidget(qrw, 1)
1589         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1590         hbox = QHBoxLayout()
1591         hbox.addStretch(1)
1592
1593         filename = os.path.join(self.config.path, "qrcode.bmp")
1594
1595         def print_qr():
1596             bmp.save_qrcode(qrw.qr, filename)
1597             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1598
1599         def copy_to_clipboard():
1600             bmp.save_qrcode(qrw.qr, filename)
1601             self.app.clipboard().setImage(QImage(filename))
1602             QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1603
1604         b = QPushButton(_("Copy"))
1605         hbox.addWidget(b)
1606         b.clicked.connect(copy_to_clipboard)
1607
1608         b = QPushButton(_("Save"))
1609         hbox.addWidget(b)
1610         b.clicked.connect(print_qr)
1611
1612         b = QPushButton(_("Close"))
1613         hbox.addWidget(b)
1614         b.clicked.connect(d.accept)
1615         b.setDefault(True)
1616
1617         vbox.addLayout(hbox)
1618         d.setLayout(vbox)
1619         d.exec_()
1620
1621
1622     def do_protect(self, func, args):
1623         if self.wallet.use_encryption:
1624             password = self.password_dialog()
1625             if not password:
1626                 return
1627         else:
1628             password = None
1629
1630         if args != (False,):
1631             args = (self,) + args + (password,)
1632         else:
1633             args = (self,password)
1634         apply( func, args)
1635
1636
1637     @protected
1638     def show_private_key(self, address, password):
1639         if not address: return
1640         try:
1641             pk_list = self.wallet.get_private_key(address, password)
1642         except Exception as e:
1643             self.show_message(str(e))
1644             return
1645
1646         d = QDialog(self)
1647         d.setMinimumSize(600, 200)
1648         d.setModal(1)
1649         vbox = QVBoxLayout()
1650         vbox.addWidget( QLabel(_("Address") + ': ' + address))
1651         vbox.addWidget( QLabel(_("Private key") + ':'))
1652         keys = QTextEdit()
1653         keys.setReadOnly(True)
1654         keys.setText('\n'.join(pk_list))
1655         vbox.addWidget(keys)
1656         vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1657         vbox.addLayout(close_button(d))
1658         d.setLayout(vbox)
1659         d.exec_()
1660
1661
1662     @protected
1663     def do_sign(self, address, message, signature, password):
1664         message = unicode(message.toPlainText())
1665         message = message.encode('utf-8')
1666         try:
1667             sig = self.wallet.sign_message(str(address.text()), message, password)
1668             signature.setText(sig)
1669         except Exception as e:
1670             self.show_message(str(e))
1671
1672     def do_verify(self, address, message, signature):
1673         message = unicode(message.toPlainText())
1674         message = message.encode('utf-8')
1675         if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1676             self.show_message(_("Signature verified"))
1677         else:
1678             self.show_message(_("Error: wrong signature"))
1679
1680
1681     def sign_verify_message(self, address=''):
1682         d = QDialog(self)
1683         d.setModal(1)
1684         d.setWindowTitle(_('Sign/verify Message'))
1685         d.setMinimumSize(410, 290)
1686
1687         layout = QGridLayout(d)
1688
1689         message_e = QTextEdit()
1690         layout.addWidget(QLabel(_('Message')), 1, 0)
1691         layout.addWidget(message_e, 1, 1)
1692         layout.setRowStretch(2,3)
1693
1694         address_e = QLineEdit()
1695         address_e.setText(address)
1696         layout.addWidget(QLabel(_('Address')), 2, 0)
1697         layout.addWidget(address_e, 2, 1)
1698
1699         signature_e = QTextEdit()
1700         layout.addWidget(QLabel(_('Signature')), 3, 0)
1701         layout.addWidget(signature_e, 3, 1)
1702         layout.setRowStretch(3,1)
1703
1704         hbox = QHBoxLayout()
1705
1706         b = QPushButton(_("Sign"))
1707         b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1708         hbox.addWidget(b)
1709
1710         b = QPushButton(_("Verify"))
1711         b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1712         hbox.addWidget(b)
1713
1714         b = QPushButton(_("Close"))
1715         b.clicked.connect(d.accept)
1716         hbox.addWidget(b)
1717         layout.addLayout(hbox, 4, 1)
1718         d.exec_()
1719
1720
1721     @protected
1722     def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1723         try:
1724             decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1725             message_e.setText(decrypted)
1726         except Exception as e:
1727             self.show_message(str(e))
1728
1729
1730     def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1731         message = unicode(message_e.toPlainText())
1732         message = message.encode('utf-8')
1733         try:
1734             encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1735             encrypted_e.setText(encrypted)
1736         except Exception as e:
1737             self.show_message(str(e))
1738
1739
1740
1741     def encrypt_message(self, address = ''):
1742         d = QDialog(self)
1743         d.setModal(1)
1744         d.setWindowTitle(_('Encrypt/decrypt Message'))
1745         d.setMinimumSize(610, 490)
1746
1747         layout = QGridLayout(d)
1748
1749         message_e = QTextEdit()
1750         layout.addWidget(QLabel(_('Message')), 1, 0)
1751         layout.addWidget(message_e, 1, 1)
1752         layout.setRowStretch(2,3)
1753
1754         pubkey_e = QLineEdit()
1755         if address:
1756             pubkey = self.wallet.getpubkeys(address)[0]
1757             pubkey_e.setText(pubkey)
1758         layout.addWidget(QLabel(_('Public key')), 2, 0)
1759         layout.addWidget(pubkey_e, 2, 1)
1760
1761         encrypted_e = QTextEdit()
1762         layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1763         layout.addWidget(encrypted_e, 3, 1)
1764         layout.setRowStretch(3,1)
1765
1766         hbox = QHBoxLayout()
1767         b = QPushButton(_("Encrypt"))
1768         b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1769         hbox.addWidget(b)
1770
1771         b = QPushButton(_("Decrypt"))
1772         b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1773         hbox.addWidget(b)
1774
1775         b = QPushButton(_("Close"))
1776         b.clicked.connect(d.accept)
1777         hbox.addWidget(b)
1778
1779         layout.addLayout(hbox, 4, 1)
1780         d.exec_()
1781
1782
1783     def question(self, msg):
1784         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1785
1786     def show_message(self, msg):
1787         QMessageBox.information(self, _('Message'), msg, _('OK'))
1788
1789     def password_dialog(self ):
1790         d = QDialog(self)
1791         d.setModal(1)
1792         d.setWindowTitle(_("Enter Password"))
1793
1794         pw = QLineEdit()
1795         pw.setEchoMode(2)
1796
1797         vbox = QVBoxLayout()
1798         msg = _('Please enter your password')
1799         vbox.addWidget(QLabel(msg))
1800
1801         grid = QGridLayout()
1802         grid.setSpacing(8)
1803         grid.addWidget(QLabel(_('Password')), 1, 0)
1804         grid.addWidget(pw, 1, 1)
1805         vbox.addLayout(grid)
1806
1807         vbox.addLayout(ok_cancel_buttons(d))
1808         d.setLayout(vbox)
1809
1810         run_hook('password_dialog', pw, grid, 1)
1811         if not d.exec_(): return
1812         return unicode(pw.text())
1813
1814
1815
1816
1817
1818
1819
1820
1821     def tx_from_text(self, txt):
1822         "json or raw hexadecimal"
1823         try:
1824             txt.decode('hex')
1825             tx = Transaction(txt)
1826             return tx
1827         except Exception:
1828             pass
1829
1830         try:
1831             tx_dict = json.loads(str(txt))
1832             assert "hex" in tx_dict.keys()
1833             assert "complete" in tx_dict.keys()
1834             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1835             if not tx_dict["complete"]:
1836                 assert "input_info" in tx_dict.keys()
1837                 input_info = json.loads(tx_dict['input_info'])
1838                 tx.add_input_info(input_info)
1839             return tx
1840         except Exception:
1841             pass
1842
1843         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1844
1845
1846
1847     def read_tx_from_file(self):
1848         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1849         if not fileName:
1850             return
1851         try:
1852             with open(fileName, "r") as f:
1853                 file_content = f.read()
1854         except (ValueError, IOError, os.error), reason:
1855             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1856
1857         return self.tx_from_text(file_content)
1858
1859
1860     @protected
1861     def sign_raw_transaction(self, tx, input_info, password):
1862         self.wallet.signrawtransaction(tx, input_info, [], password)
1863
1864     def do_process_from_text(self):
1865         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1866         if not text:
1867             return
1868         tx = self.tx_from_text(text)
1869         if tx:
1870             self.show_transaction(tx)
1871
1872     def do_process_from_file(self):
1873         tx = self.read_tx_from_file()
1874         if tx:
1875             self.show_transaction(tx)
1876
1877     def do_process_from_txid(self):
1878         from electrum import transaction
1879         txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1880         if ok and txid:
1881             r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1882             if r:
1883                 tx = transaction.Transaction(r)
1884                 if tx:
1885                     self.show_transaction(tx)
1886                 else:
1887                     self.show_message("unknown transaction")
1888
1889     def do_process_from_csvReader(self, csvReader):
1890         outputs = []
1891         errors = []
1892         errtext = ""
1893         try:
1894             for position, row in enumerate(csvReader):
1895                 address = row[0]
1896                 if not is_valid(address):
1897                     errors.append((position, address))
1898                     continue
1899                 amount = Decimal(row[1])
1900                 amount = int(100000000*amount)
1901                 outputs.append((address, amount))
1902         except (ValueError, IOError, os.error), reason:
1903             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1904             return
1905         if errors != []:
1906             for x in errors:
1907                 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1908             QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1909             return
1910
1911         try:
1912             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1913         except Exception as e:
1914             self.show_message(str(e))
1915             return
1916
1917         self.show_transaction(tx)
1918
1919     def do_process_from_csv_file(self):
1920         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1921         if not fileName:
1922             return
1923         try:
1924             with open(fileName, "r") as f:
1925                 csvReader = csv.reader(f)
1926                 self.do_process_from_csvReader(csvReader)
1927         except (ValueError, IOError, os.error), reason:
1928             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1929             return
1930
1931     def do_process_from_csv_text(self):
1932         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1933                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1934         if not text:
1935             return
1936         f = StringIO.StringIO(text)
1937         csvReader = csv.reader(f)
1938         self.do_process_from_csvReader(csvReader)
1939
1940
1941
1942     @protected
1943     def do_export_privkeys(self, password):
1944         if not self.wallet.seed:
1945             self.show_message(_("This wallet has no seed"))
1946             return
1947
1948         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.")))
1949
1950         try:
1951             select_export = _('Select file to export your private keys to')
1952             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1953             if fileName:
1954                 with open(fileName, "w+") as csvfile:
1955                     transaction = csv.writer(csvfile)
1956                     transaction.writerow(["address", "private_key"])
1957
1958                     addresses = self.wallet.addresses(True)
1959
1960                     for addr in addresses:
1961                         pk = "".join(self.wallet.get_private_key(addr, password))
1962                         transaction.writerow(["%34s"%addr,pk])
1963
1964                     self.show_message(_("Private keys exported."))
1965
1966         except (IOError, os.error), reason:
1967             export_error_label = _("Electrum was unable to produce a private key-export.")
1968             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1969
1970         except Exception as e:
1971           self.show_message(str(e))
1972           return
1973
1974
1975     def do_import_labels(self):
1976         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1977         if not labelsFile: return
1978         try:
1979             f = open(labelsFile, 'r')
1980             data = f.read()
1981             f.close()
1982             for key, value in json.loads(data).items():
1983                 self.wallet.set_label(key, value)
1984             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1985         except (IOError, os.error), reason:
1986             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1987
1988
1989     def do_export_labels(self):
1990         labels = self.wallet.labels
1991         try:
1992             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1993             if fileName:
1994                 with open(fileName, 'w+') as f:
1995                     json.dump(labels, f)
1996                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1997         except (IOError, os.error), reason:
1998             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1999
2000
2001     def do_export_history(self):
2002         from lite_window import csv_transaction
2003         csv_transaction(self.wallet)
2004
2005
2006     @protected
2007     def do_import_privkey(self, password):
2008         if not self.wallet.imported_keys:
2009             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2010                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2011                                          + _('Are you sure you understand what you are doing?'), 3, 4)
2012             if r == 4: return
2013
2014         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2015         if not text: return
2016
2017         text = str(text).split()
2018         badkeys = []
2019         addrlist = []
2020         for key in text:
2021             try:
2022                 addr = self.wallet.import_key(key, password)
2023             except Exception as e:
2024                 badkeys.append(key)
2025                 continue
2026             if not addr:
2027                 badkeys.append(key)
2028             else:
2029                 addrlist.append(addr)
2030         if addrlist:
2031             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2032         if badkeys:
2033             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2034         self.update_receive_tab()
2035         self.update_history_tab()
2036
2037
2038     def settings_dialog(self):
2039         d = QDialog(self)
2040         d.setWindowTitle(_('Electrum Settings'))
2041         d.setModal(1)
2042         vbox = QVBoxLayout()
2043         grid = QGridLayout()
2044         grid.setColumnStretch(0,1)
2045
2046         nz_label = QLabel(_('Display zeros') + ':')
2047         grid.addWidget(nz_label, 0, 0)
2048         nz_e = AmountEdit(None,True)
2049         nz_e.setText("%d"% self.num_zeros)
2050         grid.addWidget(nz_e, 0, 1)
2051         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2052         grid.addWidget(HelpButton(msg), 0, 2)
2053         if not self.config.is_modifiable('num_zeros'):
2054             for w in [nz_e, nz_label]: w.setEnabled(False)
2055
2056         lang_label=QLabel(_('Language') + ':')
2057         grid.addWidget(lang_label, 1, 0)
2058         lang_combo = QComboBox()
2059         from electrum.i18n import languages
2060         lang_combo.addItems(languages.values())
2061         try:
2062             index = languages.keys().index(self.config.get("language",''))
2063         except Exception:
2064             index = 0
2065         lang_combo.setCurrentIndex(index)
2066         grid.addWidget(lang_combo, 1, 1)
2067         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2068         if not self.config.is_modifiable('language'):
2069             for w in [lang_combo, lang_label]: w.setEnabled(False)
2070
2071
2072         fee_label = QLabel(_('Transaction fee') + ':')
2073         grid.addWidget(fee_label, 2, 0)
2074         fee_e = AmountEdit(self.base_unit)
2075         fee_e.setText(self.format_amount(self.wallet.fee).strip())
2076         grid.addWidget(fee_e, 2, 1)
2077         msg = _('Fee per kilobyte of transaction.') + ' ' \
2078             + _('Recommended value') + ': ' + self.format_amount(20000)
2079         grid.addWidget(HelpButton(msg), 2, 2)
2080         if not self.config.is_modifiable('fee_per_kb'):
2081             for w in [fee_e, fee_label]: w.setEnabled(False)
2082
2083         units = ['BTC', 'mBTC']
2084         unit_label = QLabel(_('Base unit') + ':')
2085         grid.addWidget(unit_label, 3, 0)
2086         unit_combo = QComboBox()
2087         unit_combo.addItems(units)
2088         unit_combo.setCurrentIndex(units.index(self.base_unit()))
2089         grid.addWidget(unit_combo, 3, 1)
2090         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2091                                              + '\n1BTC=1000mBTC.\n' \
2092                                              + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2093
2094         usechange_cb = QCheckBox(_('Use change addresses'))
2095         usechange_cb.setChecked(self.wallet.use_change)
2096         grid.addWidget(usechange_cb, 4, 0)
2097         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2098         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2099
2100         grid.setRowStretch(5,1)
2101
2102         vbox.addLayout(grid)
2103         vbox.addLayout(ok_cancel_buttons(d))
2104         d.setLayout(vbox)
2105
2106         # run the dialog
2107         if not d.exec_(): return
2108
2109         fee = unicode(fee_e.text())
2110         try:
2111             fee = self.read_amount(fee)
2112         except Exception:
2113             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2114             return
2115
2116         self.wallet.set_fee(fee)
2117
2118         nz = unicode(nz_e.text())
2119         try:
2120             nz = int( nz )
2121             if nz>8: nz=8
2122         except Exception:
2123             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2124             return
2125
2126         if self.num_zeros != nz:
2127             self.num_zeros = nz
2128             self.config.set_key('num_zeros', nz, True)
2129             self.update_history_tab()
2130             self.update_receive_tab()
2131
2132         usechange_result = usechange_cb.isChecked()
2133         if self.wallet.use_change != usechange_result:
2134             self.wallet.use_change = usechange_result
2135             self.wallet.storage.put('use_change', self.wallet.use_change)
2136
2137         unit_result = units[unit_combo.currentIndex()]
2138         if self.base_unit() != unit_result:
2139             self.decimal_point = 8 if unit_result == 'BTC' else 5
2140             self.config.set_key('decimal_point', self.decimal_point, True)
2141             self.update_history_tab()
2142             self.update_status()
2143
2144         need_restart = False
2145
2146         lang_request = languages.keys()[lang_combo.currentIndex()]
2147         if lang_request != self.config.get('language'):
2148             self.config.set_key("language", lang_request, True)
2149             need_restart = True
2150
2151         run_hook('close_settings_dialog')
2152
2153         if need_restart:
2154             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2155
2156
2157     def run_network_dialog(self):
2158         if not self.network:
2159             return
2160         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2161
2162     def closeEvent(self, event):
2163         self.tray.hide()
2164         g = self.geometry()
2165         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2166         self.save_column_widths()
2167         self.config.set_key("console-history", self.console.history[-50:], True)
2168         self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2169         event.accept()
2170
2171
2172     def plugins_dialog(self):
2173         from electrum.plugins import plugins
2174
2175         d = QDialog(self)
2176         d.setWindowTitle(_('Electrum Plugins'))
2177         d.setModal(1)
2178
2179         vbox = QVBoxLayout(d)
2180
2181         # plugins
2182         scroll = QScrollArea()
2183         scroll.setEnabled(True)
2184         scroll.setWidgetResizable(True)
2185         scroll.setMinimumSize(400,250)
2186         vbox.addWidget(scroll)
2187
2188         w = QWidget()
2189         scroll.setWidget(w)
2190         w.setMinimumHeight(len(plugins)*35)
2191
2192         grid = QGridLayout()
2193         grid.setColumnStretch(0,1)
2194         w.setLayout(grid)
2195
2196         def do_toggle(cb, p, w):
2197             r = p.toggle()
2198             cb.setChecked(r)
2199             if w: w.setEnabled(r)
2200
2201         def mk_toggle(cb, p, w):
2202             return lambda: do_toggle(cb,p,w)
2203
2204         for i, p in enumerate(plugins):
2205             try:
2206                 cb = QCheckBox(p.fullname())
2207                 cb.setDisabled(not p.is_available())
2208                 cb.setChecked(p.is_enabled())
2209                 grid.addWidget(cb, i, 0)
2210                 if p.requires_settings():
2211                     w = p.settings_widget(self)
2212                     w.setEnabled( p.is_enabled() )
2213                     grid.addWidget(w, i, 1)
2214                 else:
2215                     w = None
2216                 cb.clicked.connect(mk_toggle(cb,p,w))
2217                 grid.addWidget(HelpButton(p.description()), i, 2)
2218             except Exception:
2219                 print_msg(_("Error: cannot display plugin"), p)
2220                 traceback.print_exc(file=sys.stdout)
2221         grid.setRowStretch(i+1,1)
2222
2223         vbox.addLayout(close_button(d))
2224
2225         d.exec_()
2226
2227
2228     def show_account_details(self, k):
2229         d = QDialog(self)
2230         d.setWindowTitle(_('Account Details'))
2231         d.setModal(1)
2232
2233         vbox = QVBoxLayout(d)
2234         roots = self.wallet.get_roots(k)
2235
2236         name = self.wallet.get_account_name(k)
2237         label = QLabel('Name: ' + name)
2238         vbox.addWidget(label)
2239
2240         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2241         vbox.addWidget(QLabel('Type: ' + acctype))
2242
2243         label = QLabel('Derivation: ' + k)
2244         vbox.addWidget(label)
2245
2246         #for root in roots:
2247         #    mpk = self.wallet.master_public_keys[root]
2248         #    text = QTextEdit()
2249         #    text.setReadOnly(True)
2250         #    text.setMaximumHeight(120)
2251         #    text.setText(repr(mpk))
2252         #    vbox.addWidget(text)
2253
2254         vbox.addLayout(close_button(d))
2255         d.exec_()