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