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