add new tray icon, and activate window in showNormal
[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         self.icon = QIcon(':icons/electrum_light_icon.png')
143         self.tray = QSystemTrayIcon(self.icon, self)
144         self.tray.setToolTip('Electrum')
145         self.tray.activated.connect(self.tray_activated)
146
147         self.build_menu()
148         self.tray.show()
149         self.create_status_bar()
150
151         self.need_update = threading.Event()
152
153         self.decimal_point = config.get('decimal_point', 8)
154         self.num_zeros     = int(config.get('num_zeros',0))
155
156         set_language(config.get('language'))
157
158         self.funds_error = False
159         self.completions = QStringListModel()
160
161         self.tabs = tabs = QTabWidget(self)
162         self.column_widths = self.config.get("column_widths_2", default_column_widths )
163         tabs.addTab(self.create_history_tab(), _('History') )
164         tabs.addTab(self.create_send_tab(), _('Send') )
165         tabs.addTab(self.create_receive_tab(), _('Receive') )
166         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
167         tabs.addTab(self.create_console_tab(), _('Console') )
168         tabs.setMinimumSize(600, 400)
169         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
170         self.setCentralWidget(tabs)
171
172         g = self.config.get("winpos-qt",[100, 100, 840, 400])
173         self.setGeometry(g[0], g[1], g[2], g[3])
174
175         self.init_menubar()
176
177         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
178         QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
179         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
180         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
181         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
182         
183         self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
184         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
185         self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
186
187         self.history_list.setFocus(True)
188
189         # network callbacks
190         self.network.register_callback('updated', lambda: self.need_update.set())
191         self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
192         self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
193         self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
194         self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
195         # set initial message
196         self.console.showMessage(self.network.banner)
197
198         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
199         if platform.system() == 'Windows':
200             n = 3 if self.wallet.seed else 2
201             tabs.setCurrentIndex (n)
202             tabs.setCurrentIndex (0)
203
204         self.wallet = None
205         self.init_lite()
206
207
208     def go_full(self):
209         self.config.set_key('lite_mode', False, True)
210         self.mini.hide()
211         self.show()
212
213     def go_lite(self):
214         self.config.set_key('lite_mode', True, True)
215         self.hide()
216         self.mini.show()
217
218
219     def init_lite(self):
220         import lite_window
221         if not self.check_qt_version():
222             if self.config.get('lite_mode') is True:
223                 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
224                 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
225                 self.config.set_key('lite_mode', False, True)
226                 sys.exit(0)
227             self.mini = None
228             return
229
230         actuator = lite_window.MiniActuator(self)
231
232         # Should probably not modify the current path but instead
233         # change the behaviour of rsrc(...)
234         old_path = QDir.currentPath()
235         actuator.load_theme()
236
237         self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
238
239         driver = lite_window.MiniDriver(self, self.mini)
240
241         # Reset path back to original value now that loading the GUI
242         # is completed.
243         QDir.setCurrent(old_path)
244
245         if self.config.get('lite_mode') is True:
246             self.go_lite()
247         else:
248             self.go_full()
249
250
251     def check_qt_version(self):
252         qtVersion = qVersion()
253         return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
254     
255
256     def update_account_selector(self):
257         # account selector
258         accounts = self.wallet.get_account_names()
259         self.account_selector.clear()
260         if len(accounts) > 1:
261             self.account_selector.addItems([_("All accounts")] + accounts.values())
262             self.account_selector.setCurrentIndex(0)
263             self.account_selector.show()
264         else:
265             self.account_selector.hide()
266
267
268     def load_wallet(self, wallet):
269         import electrum
270         self.wallet = wallet
271         self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
272         self.current_account = self.wallet.storage.get("current_account", None)
273         self.pending_accounts = self.wallet.storage.get('pending_accounts',{})
274
275         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.storage.path
276         if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
277         self.setWindowTitle( title )
278         self.update_wallet()
279         # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
280         self.notify_transactions()
281         self.update_account_selector()
282         self.new_account.setEnabled(self.wallet.seed_version>4)
283         self.update_lock_icon()
284         self.update_buttons_on_seed()
285         self.update_console()
286
287         run_hook('load_wallet', wallet)
288
289
290     def select_wallet_file(self):
291         wallet_folder = self.wallet.storage.path
292         re.sub("(\/\w*.dat)$", "", wallet_folder)
293         file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
294         return file_name
295
296
297     def open_wallet(self):
298
299         filename = self.select_wallet_file()
300         if not filename:
301             return
302
303         storage = WalletStorage({'wallet_path': filename})
304         if not storage.file_exists:
305             self.show_message("file not found "+ filename)
306             return
307
308         self.wallet.stop_threads()
309         
310         # create new wallet 
311         wallet = Wallet(storage)
312         wallet.start_threads(self.network)
313
314         self.load_wallet(wallet)
315
316
317
318     def backup_wallet(self):
319         import shutil
320         path = self.wallet.storage.path
321         wallet_folder = os.path.dirname(path)
322         new_filename, ok = QInputDialog.getText(self, _('Filename'), _('Current directory') + ': ' + wallet_folder + '\n' + _('Enter a filename for the copy of your wallet') + ':')
323         new_filename = unicode(new_filename)
324         if not ok or not new_filename:
325             return
326
327         new_path = os.path.join(wallet_folder, new_filename)
328         if new_path != path:
329             try:
330                 shutil.copy2(path, new_path)
331                 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
332             except (IOError, os.error), reason:
333                 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
334
335
336     def new_wallet(self):
337         import installwizard
338
339         wallet_folder = os.path.dirname(self.wallet.storage.path)
340         filename, ok = QInputDialog.getText(self, _('Filename'), _('Current directory') + ': ' + wallet_folder + '\n'+_('Enter a new file name') + ':')
341         filename = unicode(filename)
342         if not ok or not filename:
343             return
344         filename = os.path.join(wallet_folder, filename)
345
346         storage = WalletStorage({'wallet_path': filename})
347         assert not storage.file_exists
348
349         wizard = installwizard.InstallWizard(self.config, self.network, storage)
350         wallet = wizard.run()
351         if wallet: 
352             self.load_wallet(wallet)
353         
354
355
356     def init_menubar(self):
357         menubar = QMenuBar()
358
359         file_menu = menubar.addMenu(_("&File"))
360         open_wallet_action = file_menu.addAction(_("&Open"))
361         open_wallet_action.triggered.connect(self.open_wallet)
362
363         new_wallet_action = file_menu.addAction(_("&Create/Restore"))
364         new_wallet_action.triggered.connect(self.new_wallet)
365
366         wallet_backup = file_menu.addAction(_("&Copy"))
367         wallet_backup.triggered.connect(self.backup_wallet)
368
369         quit_item = file_menu.addAction(_("&Close"))
370         quit_item.triggered.connect(self.close)
371
372         wallet_menu = menubar.addMenu(_("&Wallet"))
373
374         new_contact = wallet_menu.addAction(_("&New contact"))
375         new_contact.triggered.connect(self.new_contact_dialog)
376
377         self.new_account = wallet_menu.addAction(_("&New account"))
378         self.new_account.triggered.connect(self.new_account_dialog)
379
380         wallet_menu.addSeparator()
381
382         pw = wallet_menu.addAction(_("&Password"))
383         pw.triggered.connect(self.change_password_dialog)
384
385         show_seed = wallet_menu.addAction(_("&Seed"))
386         show_seed.triggered.connect(self.show_seed_dialog)
387
388         show_mpk = wallet_menu.addAction(_("&Master Public Key"))
389         show_mpk.triggered.connect(self.show_master_public_key)
390
391         wallet_menu.addSeparator()
392
393         labels_menu = wallet_menu.addMenu(_("&Labels"))
394         import_labels = labels_menu.addAction(_("&Import"))
395         import_labels.triggered.connect(self.do_import_labels)
396         export_labels = labels_menu.addAction(_("&Export"))
397         export_labels.triggered.connect(self.do_export_labels)
398
399         keys_menu = wallet_menu.addMenu(_("&Private keys"))
400         import_keys = keys_menu.addAction(_("&Import"))
401         import_keys.triggered.connect(self.do_import_privkey)
402         export_keys = keys_menu.addAction(_("&Export"))
403         export_keys.triggered.connect(self.do_export_privkeys)
404
405         ex_history = wallet_menu.addAction(_("&Export History"))
406         ex_history.triggered.connect(self.do_export_history)
407
408
409
410         tools_menu = menubar.addMenu(_("&Tools"))
411
412         # Settings / Preferences are all reserved keywords in OSX using this as work around
413         preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
414         preferences_menu = tools_menu.addAction(preferences_name)
415         preferences_menu.triggered.connect(self.settings_dialog)
416
417         network = tools_menu.addAction(_("&Network"))
418         network.triggered.connect(self.run_network_dialog)
419
420         plugins_labels = tools_menu.addAction(_("&Plugins"))
421         plugins_labels.triggered.connect(self.plugins_dialog)
422
423         tools_menu.addSeparator()
424
425         csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
426
427         csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
428         csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
429
430         csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
431         csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
432
433         raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
434
435         raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
436         raw_transaction_file.triggered.connect(self.do_process_from_file)
437
438         raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
439         raw_transaction_text.triggered.connect(self.do_process_from_text)
440
441
442         help_menu = menubar.addMenu(_("&Help"))
443         show_about = help_menu.addAction(_("&About"))
444         show_about.triggered.connect(self.show_about)
445         web_open = help_menu.addAction(_("&Official website")) 
446         web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
447
448         help_menu.addSeparator()
449         doc_open = help_menu.addAction(_("&Documentation"))
450         doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
451         report_bug = help_menu.addAction(_("&Report Bug"))
452         report_bug.triggered.connect(self.show_report_bug)
453
454         self.setMenuBar(menubar)
455
456     def show_about(self):
457         QMessageBox.about(self, "Electrum",
458             _("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."))
459
460     def show_report_bug(self):
461         QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
462             _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
463
464
465     def notify_transactions(self):
466         if not self.network.is_connected(): 
467             return
468
469         print_error("Notifying GUI")
470         if len(self.network.interface.pending_transactions_for_notifications) > 0:
471             # Combine the transactions if there are more then three
472             tx_amount = len(self.network.interface.pending_transactions_for_notifications)
473             if(tx_amount >= 3):
474                 total_amount = 0
475                 for tx in self.network.interface.pending_transactions_for_notifications:
476                     is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
477                     if(v > 0):
478                         total_amount += v
479
480                 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
481                                 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
482
483                 self.network.interface.pending_transactions_for_notifications = []
484             else:
485               for tx in self.network.interface.pending_transactions_for_notifications:
486                   if tx:
487                       self.network.interface.pending_transactions_for_notifications.remove(tx)
488                       is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
489                       if(v > 0):
490                           self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
491
492     def notify(self, message):
493         self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
494
495
496
497     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
498     def getOpenFileName(self, title, filter = ""):
499         directory = self.config.get('io_dir', os.path.expanduser('~'))
500         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
501         if fileName and directory != os.path.dirname(fileName):
502             self.config.set_key('io_dir', os.path.dirname(fileName), True)
503         return fileName
504
505     def getSaveFileName(self, title, filename, filter = ""):
506         directory = self.config.get('io_dir', os.path.expanduser('~'))
507         path = os.path.join( directory, filename )
508         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
509         if fileName and directory != os.path.dirname(fileName):
510             self.config.set_key('io_dir', os.path.dirname(fileName), True)
511         return fileName
512
513     def close(self):
514         QMainWindow.close(self)
515         run_hook('close_main_window')
516
517     def connect_slots(self, sender):
518         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
519         self.previous_payto_e=''
520
521     def timer_actions(self):
522         if self.need_update.is_set():
523             self.update_wallet()
524             self.need_update.clear()
525         run_hook('timer_actions')
526     
527     def format_amount(self, x, is_diff=False, whitespaces=False):
528         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
529
530     def read_amount(self, x):
531         if x in['.', '']: return None
532         p = pow(10, self.decimal_point)
533         return int( p * Decimal(x) )
534
535     def base_unit(self):
536         assert self.decimal_point in [5,8]
537         return "BTC" if self.decimal_point == 8 else "mBTC"
538
539
540     def update_status(self):
541         if self.network.is_connected():
542             if not self.wallet.up_to_date:
543                 text = _("Synchronizing...")
544                 icon = QIcon(":icons/status_waiting.png")
545             elif self.network.server_lag > 1:
546                 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
547                 icon = QIcon(":icons/status_lagging.png")
548             else:
549                 c, u = self.wallet.get_account_balance(self.current_account)
550                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
551                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
552
553                 r = {}
554                 run_hook('set_quote_text', c+u, r)
555                 quote = r.get(0)
556                 if quote:
557                     text += "  (%s)"%quote
558
559                 self.tray.setToolTip(text)
560                 icon = QIcon(":icons/status_connected.png")
561         else:
562             text = _("Not connected")
563             icon = QIcon(":icons/status_disconnected.png")
564
565         self.balance_label.setText(text)
566         self.status_button.setIcon( icon )
567
568
569     def update_wallet(self):
570         self.update_status()
571         if self.wallet.up_to_date or not self.network.is_connected():
572             self.update_history_tab()
573             self.update_receive_tab()
574             self.update_contacts_tab()
575             self.update_completions()
576
577         
578     def create_history_tab(self):
579         self.history_list = l = MyTreeWidget(self)
580         l.setColumnCount(5)
581         for i,width in enumerate(self.column_widths['history']):
582             l.setColumnWidth(i, width)
583         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
584         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
585         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
586
587         l.customContextMenuRequested.connect(self.create_history_menu)
588         return l
589
590
591     def create_history_menu(self, position):
592         self.history_list.selectedIndexes() 
593         item = self.history_list.currentItem()
594         if not item: return
595         tx_hash = str(item.data(0, Qt.UserRole).toString())
596         if not tx_hash: return
597         menu = QMenu()
598         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
599         menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
600         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
601         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
602
603
604     def show_transaction(self, tx):
605         import transaction_dialog
606         d = transaction_dialog.TxDialog(tx, self)
607         d.exec_()
608
609     def tx_label_clicked(self, item, column):
610         if column==2 and item.isSelected():
611             self.is_edit=True
612             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
613             self.history_list.editItem( item, column )
614             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
615             self.is_edit=False
616
617     def tx_label_changed(self, item, column):
618         if self.is_edit: 
619             return
620         self.is_edit=True
621         tx_hash = str(item.data(0, Qt.UserRole).toString())
622         tx = self.wallet.transactions.get(tx_hash)
623         text = unicode( item.text(2) )
624         self.wallet.set_label(tx_hash, text) 
625         if text: 
626             item.setForeground(2, QBrush(QColor('black')))
627         else:
628             text = self.wallet.get_default_label(tx_hash)
629             item.setText(2, text)
630             item.setForeground(2, QBrush(QColor('gray')))
631         self.is_edit=False
632
633
634     def edit_label(self, is_recv):
635         l = self.receive_list if is_recv else self.contacts_list
636         item = l.currentItem()
637         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
638         l.editItem( item, 1 )
639         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
640
641
642
643     def address_label_clicked(self, item, column, l, column_addr, column_label):
644         if column == column_label and item.isSelected():
645             is_editable = item.data(0, 32).toBool()
646             if not is_editable:
647                 return
648             addr = unicode( item.text(column_addr) )
649             label = unicode( item.text(column_label) )
650             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
651             l.editItem( item, column )
652             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
653
654
655     def address_label_changed(self, item, column, l, column_addr, column_label):
656         if column == column_label:
657             addr = unicode( item.text(column_addr) )
658             text = unicode( item.text(column_label) )
659             is_editable = item.data(0, 32).toBool()
660             if not is_editable:
661                 return
662
663             changed = self.wallet.set_label(addr, text)
664             if changed:
665                 self.update_history_tab()
666                 self.update_completions()
667                 
668             self.current_item_changed(item)
669
670         run_hook('item_changed', item, column)
671
672
673     def current_item_changed(self, a):
674         run_hook('current_item_changed', a)
675
676
677
678     def update_history_tab(self):
679
680         self.history_list.clear()
681         for item in self.wallet.get_tx_history(self.current_account):
682             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
683             if conf > 0:
684                 try:
685                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
686                 except:
687                     time_str = _("unknown")
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 = c + u - 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:
869             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
870             return
871         try:
872             fee = self.read_amount(unicode( self.fee_e.text()))
873         except:
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 BaseException, 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:
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 k in self.pending_accounts:
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.pending_accounts.pop(k)
1102         self.wallet.storage.put('pending_accounts', self.pending_accounts)
1103         self.update_receive_tab()
1104
1105     def create_receive_menu(self, position):
1106         # fixme: this function apparently has a side effect.
1107         # if it is not called the menu pops up several times
1108         #self.receive_list.selectedIndexes() 
1109
1110         item = self.receive_list.itemAt(position)
1111         if not item: return
1112
1113         addr = unicode(item.text(0))
1114         if not is_valid(addr): 
1115             k = str(item.data(0,32).toString())
1116             if k:
1117                 self.create_account_menu(position, k, item)
1118             else:
1119                 item.setExpanded(not item.isExpanded())
1120             return 
1121
1122         menu = QMenu()
1123         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1124         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1125         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
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.pending_accounts.items():
1254             if k in self.wallet.accounts:
1255                 self.pending_accounts.pop(k)
1256                 self.wallet.storage.put('pending_accounts', self.pending_accounts)
1257             name = self.wallet.labels.get(k,'')
1258             account_item = QTreeWidgetItem( [ name + "  [ "+_('pending account')+" ]", '', '', ''] )
1259             self.update_receive_item(item)
1260             l.addTopLevelItem(account_item)
1261             account_item.setExpanded(True)
1262             account_item.setData(0, 32, k)
1263             item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1264             account_item.addChild(item)
1265             self.update_receive_item(item)
1266
1267
1268         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1269             c,u = self.wallet.get_imported_balance()
1270             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1271             l.addTopLevelItem(account_item)
1272             account_item.setExpanded(True)
1273             for address in self.wallet.imported_keys.keys():
1274                 item = QTreeWidgetItem( [ address, '', '', ''] )
1275                 self.update_receive_item(item)
1276                 account_item.addChild(item)
1277                 
1278
1279         # we use column 1 because column 0 may be hidden
1280         l.setCurrentItem(l.topLevelItem(0),1)
1281
1282
1283     def update_contacts_tab(self):
1284         l = self.contacts_list
1285         l.clear()
1286
1287         for address in self.wallet.addressbook:
1288             label = self.wallet.labels.get(address,'')
1289             n = self.wallet.get_num_tx(address)
1290             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1291             item.setFont(0, QFont(MONOSPACE_FONT))
1292             # 32 = label can be edited (bool)
1293             item.setData(0,32, True)
1294             # 33 = payto string
1295             item.setData(0,33, address)
1296             l.addTopLevelItem(item)
1297
1298         run_hook('update_contacts_tab', l)
1299         l.setCurrentItem(l.topLevelItem(0))
1300
1301
1302
1303     def create_console_tab(self):
1304         from console import Console
1305         self.console = console = Console()
1306         return console
1307
1308
1309     def update_console(self):
1310         console = self.console
1311         console.history = self.config.get("console-history",[])
1312         console.history_index = len(console.history)
1313
1314         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1315         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1316
1317         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1318         methods = {}
1319         def mkfunc(f, method):
1320             return lambda *args: apply( f, (method, args, self.password_dialog ))
1321         for m in dir(c):
1322             if m[0]=='_' or m in ['network','wallet']: continue
1323             methods[m] = mkfunc(c._run, m)
1324             
1325         console.updateNamespace(methods)
1326
1327
1328     def change_account(self,s):
1329         if s == _("All accounts"):
1330             self.current_account = None
1331         else:
1332             accounts = self.wallet.get_account_names()
1333             for k, v in accounts.items():
1334                 if v == s:
1335                     self.current_account = k
1336         self.update_history_tab()
1337         self.update_status()
1338         self.update_receive_tab()
1339
1340     def create_status_bar(self):
1341
1342         sb = QStatusBar()
1343         sb.setFixedHeight(35)
1344         qtVersion = qVersion()
1345
1346         self.balance_label = QLabel("")
1347         sb.addWidget(self.balance_label)
1348
1349         from version_getter import UpdateLabel
1350         self.updatelabel = UpdateLabel(self.config, sb)
1351
1352         self.account_selector = QComboBox()
1353         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account) 
1354         sb.addPermanentWidget(self.account_selector)
1355
1356         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1357             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1358
1359         self.lock_icon = QIcon()
1360         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1361         sb.addPermanentWidget( self.password_button )
1362             
1363         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1364         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) 
1365         sb.addPermanentWidget( self.seed_button )
1366         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) 
1367         sb.addPermanentWidget( self.status_button )
1368
1369         run_hook('create_status_bar', (sb,))
1370
1371         self.setStatusBar(sb)
1372
1373
1374     def update_lock_icon(self):
1375         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1376         self.password_button.setIcon( icon )
1377
1378
1379     def update_buttons_on_seed(self):
1380         if not self.wallet.is_watching_only():
1381            self.seed_button.show()
1382            self.password_button.show()
1383            self.send_button.setText(_("Send"))
1384         else:
1385            self.password_button.hide()
1386            self.seed_button.hide()
1387            self.send_button.setText(_("Create unsigned transaction"))
1388
1389
1390     def change_password_dialog(self):
1391         from password_dialog import PasswordDialog
1392         d = PasswordDialog(self.wallet, self)
1393         d.run()
1394         self.update_lock_icon()
1395
1396
1397     def new_contact_dialog(self):
1398
1399         d = QDialog(self)
1400         vbox = QVBoxLayout(d)
1401         vbox.addWidget(QLabel(_('New Contact')+':'))
1402         
1403         grid = QGridLayout()
1404         line1 = QLineEdit()
1405         line2 = QLineEdit()
1406         grid.addWidget(QLabel(_("Address")), 1, 0)
1407         grid.addWidget(line1, 1, 1)
1408         grid.addWidget(QLabel(_("Name")), 2, 0)
1409         grid.addWidget(line2, 2, 1)
1410
1411         vbox.addLayout(grid)
1412         vbox.addLayout(ok_cancel_buttons(d))
1413     
1414         if not d.exec_():
1415             return
1416         
1417         address = str(line1.text())
1418         label = unicode(line2.text())
1419         
1420         if not is_valid(address):
1421             QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1422             return
1423         
1424         self.wallet.add_contact(address)
1425         if label:
1426             self.wallet.set_label(address, label)
1427
1428         self.update_contacts_tab()
1429         self.update_history_tab()
1430         self.update_completions()
1431         self.tabs.setCurrentIndex(3)
1432
1433
1434     def new_account_dialog(self):
1435
1436         dialog = QDialog(self)
1437         dialog.setModal(1)
1438         dialog.setWindowTitle(_("New Account"))
1439
1440         vbox = QVBoxLayout()
1441         vbox.addWidget(QLabel(_('Account name')+':'))
1442         e = QLineEdit()
1443         vbox.addWidget(e)
1444         msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1445             + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1446         l = QLabel(msg)
1447         l.setWordWrap(True)
1448         vbox.addWidget(l)
1449
1450         vbox.addLayout(ok_cancel_buttons(dialog))
1451         dialog.setLayout(vbox)
1452         r = dialog.exec_()
1453         if not r: return
1454
1455         name = str(e.text())
1456         if not name: return
1457
1458         k, addr = self.wallet.new_account_address()
1459         self.wallet.set_label(k, name)
1460         self.pending_accounts[k] = addr
1461         self.wallet.storage.put('pending_accounts', self.pending_accounts)
1462         self.update_receive_tab()
1463         self.tabs.setCurrentIndex(2)
1464         
1465             
1466
1467     def show_master_public_key_old(self):
1468         dialog = QDialog(self)
1469         dialog.setModal(1)
1470         dialog.setWindowTitle(_("Master Public Key"))
1471
1472         main_text = QTextEdit()
1473         main_text.setText(self.wallet.get_master_public_key())
1474         main_text.setReadOnly(True)
1475         main_text.setMaximumHeight(170)
1476         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1477
1478         ok_button = QPushButton(_("OK"))
1479         ok_button.setDefault(True)
1480         ok_button.clicked.connect(dialog.accept)
1481
1482         main_layout = QGridLayout()
1483         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1484
1485         main_layout.addWidget(main_text, 1, 0)
1486         main_layout.addWidget(qrw, 1, 1 )
1487
1488         vbox.addLayout(close_button(dialog))
1489         dialog.setLayout(vbox)
1490         dialog.exec_()
1491         
1492
1493     def show_master_public_key(self):
1494
1495         if self.wallet.seed_version == 4:
1496             self.show_master_public_keys_old()
1497             return
1498
1499         dialog = QDialog(self)
1500         dialog.setModal(1)
1501         dialog.setWindowTitle(_("Master Public Keys"))
1502
1503         chain_text = QTextEdit()
1504         chain_text.setReadOnly(True)
1505         chain_text.setMaximumHeight(170)
1506         chain_qrw = QRCodeWidget()
1507
1508         mpk_text = QTextEdit()
1509         mpk_text.setReadOnly(True)
1510         mpk_text.setMaximumHeight(170)
1511         mpk_qrw = QRCodeWidget()
1512
1513         main_layout = QGridLayout()
1514
1515         main_layout.addWidget(QLabel(_('Key')), 1, 0)
1516         main_layout.addWidget(mpk_text, 1, 1)
1517         main_layout.addWidget(mpk_qrw, 1, 2)
1518
1519         main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1520         main_layout.addWidget(chain_text, 2, 1)
1521         main_layout.addWidget(chain_qrw, 2, 2)
1522
1523         def update(key):
1524             c, K, cK = self.wallet.master_public_keys[str(key)]
1525             chain_text.setText(c)
1526             chain_qrw.set_addr(c)
1527             chain_qrw.update_qr()
1528             mpk_text.setText(K)
1529             mpk_qrw.set_addr(c)
1530             mpk_qrw.update_qr()
1531             
1532         key_selector = QComboBox()
1533         keys = sorted(self.wallet.master_public_keys.keys())
1534         key_selector.addItems(keys)
1535
1536         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1537         main_layout.addWidget(key_selector, 0, 1)
1538         dialog.connect(key_selector,SIGNAL("activated(QString)"),update) 
1539
1540         update(keys[0])
1541
1542         vbox = QVBoxLayout()
1543         vbox.addLayout(main_layout)
1544         vbox.addLayout(close_button(dialog))
1545
1546         dialog.setLayout(vbox)
1547         dialog.exec_()
1548         
1549
1550     @protected
1551     def show_seed_dialog(self, password):
1552         if self.wallet.is_watching_only():
1553             QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1554             return
1555
1556         if self.wallet.seed:
1557             try:
1558                 seed = self.wallet.decode_seed(password)
1559             except:
1560                 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1561                 return
1562             from seed_dialog import SeedDialog
1563             d = SeedDialog(self, seed, self.wallet.imported_keys)
1564             d.exec_()
1565         else:
1566             l = {}
1567             for k in self.wallet.master_private_keys.keys():
1568                 pk = self.wallet.get_master_private_key(k, password)
1569                 l[k] = pk
1570             from seed_dialog import PrivateKeysDialog
1571             d = PrivateKeysDialog(self,l)
1572             d.exec_()
1573
1574
1575
1576
1577
1578     def show_qrcode(self, data, title = _("QR code")):
1579         if not data: return
1580         d = QDialog(self)
1581         d.setModal(1)
1582         d.setWindowTitle(title)
1583         d.setMinimumSize(270, 300)
1584         vbox = QVBoxLayout()
1585         qrw = QRCodeWidget(data)
1586         vbox.addWidget(qrw, 1)
1587         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1588         hbox = QHBoxLayout()
1589         hbox.addStretch(1)
1590
1591         def print_qr(self):
1592             filename = "qrcode.bmp"
1593             bmp.save_qrcode(qrw.qr, filename)
1594             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1595
1596         b = QPushButton(_("Save"))
1597         hbox.addWidget(b)
1598         b.clicked.connect(print_qr)
1599
1600         b = QPushButton(_("Close"))
1601         hbox.addWidget(b)
1602         b.clicked.connect(d.accept)
1603         b.setDefault(True)
1604
1605         vbox.addLayout(hbox)
1606         d.setLayout(vbox)
1607         d.exec_()
1608
1609
1610     def do_protect(self, func, args):
1611         if self.wallet.use_encryption:
1612             password = self.password_dialog()
1613             if not password:
1614                 return
1615         else:
1616             password = None
1617             
1618         if args != (False,):
1619             args = (self,) + args + (password,)
1620         else:
1621             args = (self,password)
1622         apply( func, args)
1623
1624
1625     @protected
1626     def show_private_key(self, address, password):
1627         if not address: return
1628         try:
1629             pk_list = self.wallet.get_private_key(address, password)
1630         except BaseException, e:
1631             self.show_message(str(e))
1632             return
1633         QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1634
1635
1636     @protected
1637     def do_sign(self, address, message, signature, password):
1638         message = unicode(message.toPlainText())
1639         message = message.encode('utf-8')
1640         try:
1641             sig = self.wallet.sign_message(str(address.text()), message, password)
1642             signature.setText(sig)
1643         except BaseException, e:
1644             self.show_message(str(e))
1645
1646     def sign_message(self, address):
1647         if not address: return
1648         d = QDialog(self)
1649         d.setModal(1)
1650         d.setWindowTitle(_('Sign Message'))
1651         d.setMinimumSize(410, 290)
1652
1653         tab_widget = QTabWidget()
1654         tab = QWidget()
1655         layout = QGridLayout(tab)
1656
1657         sign_address = QLineEdit()
1658
1659         sign_address.setText(address)
1660         layout.addWidget(QLabel(_('Address')), 1, 0)
1661         layout.addWidget(sign_address, 1, 1)
1662
1663         sign_message = QTextEdit()
1664         layout.addWidget(QLabel(_('Message')), 2, 0)
1665         layout.addWidget(sign_message, 2, 1)
1666         layout.setRowStretch(2,3)
1667
1668         sign_signature = QTextEdit()
1669         layout.addWidget(QLabel(_('Signature')), 3, 0)
1670         layout.addWidget(sign_signature, 3, 1)
1671         layout.setRowStretch(3,1)
1672
1673
1674         hbox = QHBoxLayout()
1675         b = QPushButton(_("Sign"))
1676         hbox.addWidget(b)
1677         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1678         b = QPushButton(_("Close"))
1679         b.clicked.connect(d.accept)
1680         hbox.addWidget(b)
1681         layout.addLayout(hbox, 4, 1)
1682         tab_widget.addTab(tab, _("Sign"))
1683
1684
1685         tab = QWidget()
1686         layout = QGridLayout(tab)
1687
1688         verify_address = QLineEdit()
1689         layout.addWidget(QLabel(_('Address')), 1, 0)
1690         layout.addWidget(verify_address, 1, 1)
1691
1692         verify_message = QTextEdit()
1693         layout.addWidget(QLabel(_('Message')), 2, 0)
1694         layout.addWidget(verify_message, 2, 1)
1695         layout.setRowStretch(2,3)
1696
1697         verify_signature = QTextEdit()
1698         layout.addWidget(QLabel(_('Signature')), 3, 0)
1699         layout.addWidget(verify_signature, 3, 1)
1700         layout.setRowStretch(3,1)
1701
1702         def do_verify():
1703             message = unicode(verify_message.toPlainText())
1704             message = message.encode('utf-8')
1705             if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1706                 self.show_message(_("Signature verified"))
1707             else:
1708                 self.show_message(_("Error: wrong signature"))
1709
1710         hbox = QHBoxLayout()
1711         b = QPushButton(_("Verify"))
1712         b.clicked.connect(do_verify)
1713         hbox.addWidget(b)
1714         b = QPushButton(_("Close"))
1715         b.clicked.connect(d.accept)
1716         hbox.addWidget(b)
1717         layout.addLayout(hbox, 4, 1)
1718         tab_widget.addTab(tab, _("Verify"))
1719
1720         vbox = QVBoxLayout()
1721         vbox.addWidget(tab_widget)
1722         d.setLayout(vbox)
1723         d.exec_()
1724
1725         
1726
1727
1728     def question(self, msg):
1729         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1730
1731     def show_message(self, msg):
1732         QMessageBox.information(self, _('Message'), msg, _('OK'))
1733
1734     def password_dialog(self ):
1735         d = QDialog(self)
1736         d.setModal(1)
1737
1738         pw = QLineEdit()
1739         pw.setEchoMode(2)
1740
1741         vbox = QVBoxLayout()
1742         msg = _('Please enter your password')
1743         vbox.addWidget(QLabel(msg))
1744
1745         grid = QGridLayout()
1746         grid.setSpacing(8)
1747         grid.addWidget(QLabel(_('Password')), 1, 0)
1748         grid.addWidget(pw, 1, 1)
1749         vbox.addLayout(grid)
1750
1751         vbox.addLayout(ok_cancel_buttons(d))
1752         d.setLayout(vbox)
1753
1754         run_hook('password_dialog', pw, grid, 1)
1755         if not d.exec_(): return
1756         return unicode(pw.text())
1757
1758
1759
1760
1761
1762
1763
1764
1765     def tx_from_text(self, txt):
1766         "json or raw hexadecimal"
1767         try:
1768             txt.decode('hex')
1769             tx = Transaction(txt)
1770             return tx
1771         except:
1772             pass
1773
1774         try:
1775             tx_dict = json.loads(str(txt))
1776             assert "hex" in tx_dict.keys()
1777             assert "complete" in tx_dict.keys()
1778             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1779             if not tx_dict["complete"]:
1780                 assert "input_info" in tx_dict.keys()
1781                 input_info = json.loads(tx_dict['input_info'])
1782                 tx.add_input_info(input_info)
1783             return tx
1784         except:
1785             pass
1786         
1787         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1788
1789
1790
1791     def read_tx_from_file(self):
1792         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1793         if not fileName:
1794             return
1795         try:
1796             with open(fileName, "r") as f:
1797                 file_content = f.read()
1798         except (ValueError, IOError, os.error), reason:
1799             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1800
1801         return self.tx_from_text(file_content)
1802
1803
1804     @protected
1805     def sign_raw_transaction(self, tx, input_info, password):
1806         self.wallet.signrawtransaction(tx, input_info, [], password)
1807
1808     def do_process_from_text(self):
1809         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1810         if not text:
1811             return
1812         tx = self.tx_from_text(text)
1813         if tx:
1814             self.show_transaction(tx)
1815
1816     def do_process_from_file(self):
1817         tx = self.read_tx_from_file()
1818         if tx:
1819             self.show_transaction(tx)
1820
1821     def do_process_from_csvReader(self, csvReader):
1822         outputs = []
1823         try:
1824             for row in csvReader:
1825                 address = row[0]
1826                 amount = float(row[1])
1827                 amount = int(100000000*amount)
1828                 outputs.append((address, amount))
1829         except (ValueError, IOError, os.error), reason:
1830             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1831             return
1832
1833         try:
1834             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1835         except BaseException, e:
1836             self.show_message(str(e))
1837             return
1838
1839         self.show_transaction(tx)
1840
1841     def do_process_from_csv_file(self):
1842         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1843         if not fileName:
1844             return
1845         try:
1846             with open(fileName, "r") as f:
1847                 csvReader = csv.reader(f)
1848                 self.do_process_from_csvReader(csvReader)
1849         except (ValueError, IOError, os.error), reason:
1850             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1851             return
1852
1853     def do_process_from_csv_text(self):
1854         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1855                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1856         if not text:
1857             return
1858         f = StringIO.StringIO(text)
1859         csvReader = csv.reader(f)
1860         self.do_process_from_csvReader(csvReader)
1861
1862
1863
1864     @protected
1865     def do_export_privkeys(self, password):
1866         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.")))
1867
1868         try:
1869             select_export = _('Select file to export your private keys to')
1870             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1871             if fileName:
1872                 with open(fileName, "w+") as csvfile:
1873                     transaction = csv.writer(csvfile)
1874                     transaction.writerow(["address", "private_key"])
1875
1876                     addresses = self.wallet.addresses(True)
1877                     
1878                     for addr in addresses:
1879                         pk = "".join(self.wallet.get_private_key(addr, password))
1880                         transaction.writerow(["%34s"%addr,pk])
1881
1882                     self.show_message(_("Private keys exported."))
1883
1884         except (IOError, os.error), reason:
1885             export_error_label = _("Electrum was unable to produce a private key-export.")
1886             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1887
1888         except BaseException, e:
1889           self.show_message(str(e))
1890           return
1891
1892
1893     def do_import_labels(self):
1894         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1895         if not labelsFile: return
1896         try:
1897             f = open(labelsFile, 'r')
1898             data = f.read()
1899             f.close()
1900             for key, value in json.loads(data).items():
1901                 self.wallet.set_label(key, value)
1902             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1903         except (IOError, os.error), reason:
1904             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1905             
1906
1907     def do_export_labels(self):
1908         labels = self.wallet.labels
1909         try:
1910             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1911             if fileName:
1912                 with open(fileName, 'w+') as f:
1913                     json.dump(labels, f)
1914                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1915         except (IOError, os.error), reason:
1916             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1917
1918
1919     def do_export_history(self):
1920         from lite_window import csv_transaction
1921         csv_transaction(self.wallet)
1922
1923
1924     @protected
1925     def do_import_privkey(self, password):
1926         if not self.wallet.imported_keys:
1927             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1928                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1929                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1930             if r == 4: return
1931
1932         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1933         if not text: return
1934
1935         text = str(text).split()
1936         badkeys = []
1937         addrlist = []
1938         for key in text:
1939             try:
1940                 addr = self.wallet.import_key(key, password)
1941             except BaseException as e:
1942                 badkeys.append(key)
1943                 continue
1944             if not addr: 
1945                 badkeys.append(key)
1946             else:
1947                 addrlist.append(addr)
1948         if addrlist:
1949             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1950         if badkeys:
1951             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1952         self.update_receive_tab()
1953         self.update_history_tab()
1954
1955
1956     def settings_dialog(self):
1957         d = QDialog(self)
1958         d.setWindowTitle(_('Electrum Settings'))
1959         d.setModal(1)
1960         vbox = QVBoxLayout()
1961         grid = QGridLayout()
1962         grid.setColumnStretch(0,1)
1963
1964         nz_label = QLabel(_('Display zeros') + ':')
1965         grid.addWidget(nz_label, 0, 0)
1966         nz_e = AmountEdit(None,True)
1967         nz_e.setText("%d"% self.num_zeros)
1968         grid.addWidget(nz_e, 0, 1)
1969         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1970         grid.addWidget(HelpButton(msg), 0, 2)
1971         if not self.config.is_modifiable('num_zeros'):
1972             for w in [nz_e, nz_label]: w.setEnabled(False)
1973         
1974         lang_label=QLabel(_('Language') + ':')
1975         grid.addWidget(lang_label, 1, 0)
1976         lang_combo = QComboBox()
1977         from electrum.i18n import languages
1978         lang_combo.addItems(languages.values())
1979         try:
1980             index = languages.keys().index(self.config.get("language",''))
1981         except:
1982             index = 0
1983         lang_combo.setCurrentIndex(index)
1984         grid.addWidget(lang_combo, 1, 1)
1985         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1986         if not self.config.is_modifiable('language'):
1987             for w in [lang_combo, lang_label]: w.setEnabled(False)
1988
1989         
1990         fee_label = QLabel(_('Transaction fee') + ':')
1991         grid.addWidget(fee_label, 2, 0)
1992         fee_e = AmountEdit(self.base_unit)
1993         fee_e.setText(self.format_amount(self.wallet.fee).strip())
1994         grid.addWidget(fee_e, 2, 1)
1995         msg = _('Fee per kilobyte of transaction.') + ' ' \
1996             + _('Recommended value') + ': ' + self.format_amount(50000)
1997         grid.addWidget(HelpButton(msg), 2, 2)
1998         if not self.config.is_modifiable('fee_per_kb'):
1999             for w in [fee_e, fee_label]: w.setEnabled(False)
2000
2001         units = ['BTC', 'mBTC']
2002         unit_label = QLabel(_('Base unit') + ':')
2003         grid.addWidget(unit_label, 3, 0)
2004         unit_combo = QComboBox()
2005         unit_combo.addItems(units)
2006         unit_combo.setCurrentIndex(units.index(self.base_unit()))
2007         grid.addWidget(unit_combo, 3, 1)
2008         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2009                                              + '\n1BTC=1000mBTC.\n' \
2010                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
2011
2012         usechange_cb = QCheckBox(_('Use change addresses'))
2013         usechange_cb.setChecked(self.wallet.use_change)
2014         grid.addWidget(usechange_cb, 4, 0)
2015         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2016         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2017
2018         grid.setRowStretch(5,1)
2019
2020         vbox.addLayout(grid)
2021         vbox.addLayout(ok_cancel_buttons(d))
2022         d.setLayout(vbox) 
2023
2024         # run the dialog
2025         if not d.exec_(): return
2026
2027         fee = unicode(fee_e.text())
2028         try:
2029             fee = self.read_amount(fee)
2030         except:
2031             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2032             return
2033
2034         self.wallet.set_fee(fee)
2035         
2036         nz = unicode(nz_e.text())
2037         try:
2038             nz = int( nz )
2039             if nz>8: nz=8
2040         except:
2041             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2042             return
2043
2044         if self.num_zeros != nz:
2045             self.num_zeros = nz
2046             self.config.set_key('num_zeros', nz, True)
2047             self.update_history_tab()
2048             self.update_receive_tab()
2049
2050         usechange_result = usechange_cb.isChecked()
2051         if self.wallet.use_change != usechange_result:
2052             self.wallet.use_change = usechange_result
2053             self.config.set_key('use_change', self.wallet.use_change, True)
2054         
2055         unit_result = units[unit_combo.currentIndex()]
2056         if self.base_unit() != unit_result:
2057             self.decimal_point = 8 if unit_result == 'BTC' else 5
2058             self.config.set_key('decimal_point', self.decimal_point, True)
2059             self.update_history_tab()
2060             self.update_status()
2061         
2062         need_restart = False
2063
2064         lang_request = languages.keys()[lang_combo.currentIndex()]
2065         if lang_request != self.config.get('language'):
2066             self.config.set_key("language", lang_request, True)
2067             need_restart = True
2068             
2069         run_hook('close_settings_dialog')
2070
2071         if need_restart:
2072             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2073
2074
2075     def run_network_dialog(self):
2076         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2077
2078     def closeEvent(self, event):
2079         g = self.geometry()
2080         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2081         self.save_column_widths()
2082         self.config.set_key("console-history", self.console.history[-50:], True)
2083         self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2084         event.accept()
2085
2086
2087
2088     def plugins_dialog(self):
2089         from electrum.plugins import plugins
2090
2091         d = QDialog(self)
2092         d.setWindowTitle(_('Electrum Plugins'))
2093         d.setModal(1)
2094
2095         vbox = QVBoxLayout(d)
2096
2097         # plugins
2098         scroll = QScrollArea()
2099         scroll.setEnabled(True)
2100         scroll.setWidgetResizable(True)
2101         scroll.setMinimumSize(400,250)
2102         vbox.addWidget(scroll)
2103
2104         w = QWidget()
2105         scroll.setWidget(w)
2106         w.setMinimumHeight(len(plugins)*35)
2107
2108         grid = QGridLayout()
2109         grid.setColumnStretch(0,1)
2110         w.setLayout(grid)
2111
2112         def do_toggle(cb, p, w):
2113             r = p.toggle()
2114             cb.setChecked(r)
2115             if w: w.setEnabled(r)
2116
2117         def mk_toggle(cb, p, w):
2118             return lambda: do_toggle(cb,p,w)
2119
2120         for i, p in enumerate(plugins):
2121             try:
2122                 cb = QCheckBox(p.fullname())
2123                 cb.setDisabled(not p.is_available())
2124                 cb.setChecked(p.is_enabled())
2125                 grid.addWidget(cb, i, 0)
2126                 if p.requires_settings():
2127                     w = p.settings_widget(self)
2128                     w.setEnabled( p.is_enabled() )
2129                     grid.addWidget(w, i, 1)
2130                 else: 
2131                     w = None
2132                 cb.clicked.connect(mk_toggle(cb,p,w))
2133                 grid.addWidget(HelpButton(p.description()), i, 2)
2134             except:
2135                 print_msg(_("Error: cannot display plugin"), p)
2136                 traceback.print_exc(file=sys.stdout)
2137         grid.setRowStretch(i+1,1)
2138
2139         vbox.addLayout(close_button(d))
2140
2141         d.exec_()
2142
2143
2144     def show_account_details(self, k):
2145         d = QDialog(self)
2146         d.setWindowTitle(_('Account Details'))
2147         d.setModal(1)
2148
2149         vbox = QVBoxLayout(d)
2150         roots = self.wallet.get_roots(k)
2151
2152         name = self.wallet.get_account_name(k)
2153         label = QLabel('Name: ' + name)
2154         vbox.addWidget(label)
2155
2156         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2157         vbox.addWidget(QLabel('Type: ' + acctype))
2158
2159         label = QLabel('Derivation: ' + k)
2160         vbox.addWidget(label)
2161
2162         #for root in roots:
2163         #    mpk = self.wallet.master_public_keys[root]
2164         #    text = QTextEdit()
2165         #    text.setReadOnly(True)
2166         #    text.setMaximumHeight(120)
2167         #    text.setText(repr(mpk))
2168         #    vbox.addWidget(text)
2169
2170         vbox.addLayout(close_button(d))
2171         d.exec_()