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