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