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