fix bug with chunk requestor
[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
274         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.storage.path
275         if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
276         self.setWindowTitle( title )
277         self.update_wallet()
278         # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
279         self.notify_transactions()
280         self.update_account_selector()
281         self.new_account.setEnabled(self.wallet.seed_version>4)
282         self.update_lock_icon()
283         self.update_buttons_on_seed()
284         self.update_console()
285
286         run_hook('load_wallet', wallet)
287
288
289     def select_wallet_file(self):
290         wallet_folder = self.wallet.storage.path
291         re.sub("(\/\w*.dat)$", "", wallet_folder)
292         file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
293         return file_name
294
295
296     def open_wallet(self):
297
298         filename = self.select_wallet_file()
299         if not filename:
300             return
301
302         storage = WalletStorage({'wallet_path': filename})
303         if not storage.file_exists:
304             self.show_message("file not found "+ filename)
305             return
306
307         self.wallet.stop_threads()
308         
309         # create new wallet 
310         wallet = Wallet(storage)
311         wallet.start_threads(self.network)
312
313         self.load_wallet(wallet)
314
315
316
317     def backup_wallet(self):
318         import shutil
319         path = self.wallet.storage.path
320         wallet_folder = os.path.dirname(path)
321         new_filename, ok = QInputDialog.getText(self, _('Filename'), _('Current directory') + ': ' + wallet_folder + '\n' + _('Enter a filename for the copy of your wallet') + ':')
322         new_filename = unicode(new_filename)
323         if not ok or not new_filename:
324             return
325
326         new_path = os.path.join(wallet_folder, new_filename)
327         if new_path != path:
328             try:
329                 shutil.copy2(path, new_path)
330                 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
331             except (IOError, os.error), reason:
332                 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
333
334
335     def new_wallet(self):
336         import installwizard
337
338         wallet_folder = os.path.dirname(self.wallet.storage.path)
339         filename, ok = QInputDialog.getText(self, _('Filename'), _('Current directory') + ': ' + wallet_folder + '\n'+_('Enter a new file name') + ':')
340         filename = unicode(filename)
341         if not ok or not filename:
342             return
343         filename = os.path.join(wallet_folder, filename)
344
345         storage = WalletStorage({'wallet_path': filename})
346         assert not storage.file_exists
347
348         wizard = installwizard.InstallWizard(self.config, self.network, storage)
349         wallet = wizard.run()
350         if wallet: 
351             self.load_wallet(wallet)
352         
353
354
355     def init_menubar(self):
356         menubar = QMenuBar()
357
358         file_menu = menubar.addMenu(_("&File"))
359         open_wallet_action = file_menu.addAction(_("&Open"))
360         open_wallet_action.triggered.connect(self.open_wallet)
361
362         new_wallet_action = file_menu.addAction(_("&Create/Restore"))
363         new_wallet_action.triggered.connect(self.new_wallet)
364
365         wallet_backup = file_menu.addAction(_("&Copy"))
366         wallet_backup.triggered.connect(self.backup_wallet)
367
368         quit_item = file_menu.addAction(_("&Close"))
369         quit_item.triggered.connect(self.close)
370
371         wallet_menu = menubar.addMenu(_("&Wallet"))
372
373         new_contact = wallet_menu.addAction(_("&New contact"))
374         new_contact.triggered.connect(self.new_contact_dialog)
375
376         self.new_account = wallet_menu.addAction(_("&New account"))
377         self.new_account.triggered.connect(self.new_account_dialog)
378
379         wallet_menu.addSeparator()
380
381         pw = wallet_menu.addAction(_("&Password"))
382         pw.triggered.connect(self.change_password_dialog)
383
384         show_seed = wallet_menu.addAction(_("&Seed"))
385         show_seed.triggered.connect(self.show_seed_dialog)
386
387         show_mpk = wallet_menu.addAction(_("&Master Public Key"))
388         show_mpk.triggered.connect(self.show_master_public_key)
389
390         wallet_menu.addSeparator()
391
392         labels_menu = wallet_menu.addMenu(_("&Labels"))
393         import_labels = labels_menu.addAction(_("&Import"))
394         import_labels.triggered.connect(self.do_import_labels)
395         export_labels = labels_menu.addAction(_("&Export"))
396         export_labels.triggered.connect(self.do_export_labels)
397
398         keys_menu = wallet_menu.addMenu(_("&Private keys"))
399         import_keys = keys_menu.addAction(_("&Import"))
400         import_keys.triggered.connect(self.do_import_privkey)
401         export_keys = keys_menu.addAction(_("&Export"))
402         export_keys.triggered.connect(self.do_export_privkeys)
403
404         ex_history = wallet_menu.addAction(_("&Export History"))
405         ex_history.triggered.connect(self.do_export_history)
406
407
408
409         tools_menu = menubar.addMenu(_("&Tools"))
410
411         # Settings / Preferences are all reserved keywords in OSX using this as work around
412         preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
413         preferences_menu = tools_menu.addAction(preferences_name)
414         preferences_menu.triggered.connect(self.settings_dialog)
415
416         network = tools_menu.addAction(_("&Network"))
417         network.triggered.connect(self.run_network_dialog)
418
419         plugins_labels = tools_menu.addAction(_("&Plugins"))
420         plugins_labels.triggered.connect(self.plugins_dialog)
421
422         tools_menu.addSeparator()
423
424         csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
425
426         csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
427         csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
428
429         csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
430         csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
431
432         raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
433
434         raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
435         raw_transaction_file.triggered.connect(self.do_process_from_file)
436
437         raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
438         raw_transaction_text.triggered.connect(self.do_process_from_text)
439
440
441         help_menu = menubar.addMenu(_("&Help"))
442         show_about = help_menu.addAction(_("&About"))
443         show_about.triggered.connect(self.show_about)
444         web_open = help_menu.addAction(_("&Official website")) 
445         web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
446
447         help_menu.addSeparator()
448         doc_open = help_menu.addAction(_("&Documentation"))
449         doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
450         report_bug = help_menu.addAction(_("&Report Bug"))
451         report_bug.triggered.connect(self.show_report_bug)
452
453         self.setMenuBar(menubar)
454
455     def show_about(self):
456         QMessageBox.about(self, "Electrum",
457             _("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."))
458
459     def show_report_bug(self):
460         QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
461             _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
462
463
464     def notify_transactions(self):
465         if not self.network.is_connected(): 
466             return
467
468         print_error("Notifying GUI")
469         if len(self.network.interface.pending_transactions_for_notifications) > 0:
470             # Combine the transactions if there are more then three
471             tx_amount = len(self.network.interface.pending_transactions_for_notifications)
472             if(tx_amount >= 3):
473                 total_amount = 0
474                 for tx in self.network.interface.pending_transactions_for_notifications:
475                     is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
476                     if(v > 0):
477                         total_amount += v
478
479                 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
480                                 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
481
482                 self.network.interface.pending_transactions_for_notifications = []
483             else:
484               for tx in self.network.interface.pending_transactions_for_notifications:
485                   if tx:
486                       self.network.interface.pending_transactions_for_notifications.remove(tx)
487                       is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
488                       if(v > 0):
489                           self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
490
491     def notify(self, message):
492         self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
493
494
495
496     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
497     def getOpenFileName(self, title, filter = ""):
498         directory = self.config.get('io_dir', os.path.expanduser('~'))
499         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
500         if fileName and directory != os.path.dirname(fileName):
501             self.config.set_key('io_dir', os.path.dirname(fileName), True)
502         return fileName
503
504     def getSaveFileName(self, title, filename, filter = ""):
505         directory = self.config.get('io_dir', os.path.expanduser('~'))
506         path = os.path.join( directory, filename )
507         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
508         if fileName and directory != os.path.dirname(fileName):
509             self.config.set_key('io_dir', os.path.dirname(fileName), True)
510         return fileName
511
512     def close(self):
513         QMainWindow.close(self)
514         run_hook('close_main_window')
515
516     def connect_slots(self, sender):
517         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
518         self.previous_payto_e=''
519
520     def timer_actions(self):
521         if self.need_update.is_set():
522             self.update_wallet()
523             self.need_update.clear()
524         run_hook('timer_actions')
525     
526     def format_amount(self, x, is_diff=False, whitespaces=False):
527         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
528
529     def read_amount(self, x):
530         if x in['.', '']: return None
531         p = pow(10, self.decimal_point)
532         return int( p * Decimal(x) )
533
534     def base_unit(self):
535         assert self.decimal_point in [5,8]
536         return "BTC" if self.decimal_point == 8 else "mBTC"
537
538
539     def update_status(self):
540         if 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.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:
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 = 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 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         menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1126         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1127         if addr in self.wallet.imported_keys:
1128             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1129
1130         t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1131         menu.addAction(t, lambda: self.toggle_freeze(addr))
1132         t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1133         menu.addAction(t, lambda: self.toggle_priority(addr))
1134             
1135         run_hook('receive_menu', menu)
1136         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1137
1138
1139     def payto(self, addr):
1140         if not addr: return
1141         label = self.wallet.labels.get(addr)
1142         m_addr = label + '  <' + addr + '>' if label else addr
1143         self.tabs.setCurrentIndex(1)
1144         self.payto_e.setText(m_addr)
1145         self.amount_e.setFocus()
1146
1147
1148     def delete_contact(self, x):
1149         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1150             self.wallet.delete_contact(x)
1151             self.wallet.set_label(x, None)
1152             self.update_history_tab()
1153             self.update_contacts_tab()
1154             self.update_completions()
1155
1156
1157     def create_contact_menu(self, position):
1158         item = self.contacts_list.itemAt(position)
1159         if not item: return
1160         addr = unicode(item.text(0))
1161         label = unicode(item.text(1))
1162         is_editable = item.data(0,32).toBool()
1163         payto_addr = item.data(0,33).toString()
1164         menu = QMenu()
1165         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1166         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1167         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1168         if is_editable:
1169             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1170             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1171
1172         run_hook('create_contact_menu', menu, item)
1173         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1174
1175
1176     def update_receive_item(self, item):
1177         item.setFont(0, QFont(MONOSPACE_FONT))
1178         address = str(item.data(0,0).toString())
1179         label = self.wallet.labels.get(address,'')
1180         item.setData(1,0,label)
1181         item.setData(0,32, True) # is editable
1182
1183         run_hook('update_receive_item', address, item)
1184
1185         if not self.wallet.is_mine(address): return
1186
1187         c, u = self.wallet.get_addr_balance(address)
1188         balance = self.format_amount(c + u)
1189         item.setData(2,0,balance)
1190
1191         if address in self.wallet.frozen_addresses: 
1192             item.setBackgroundColor(0, QColor('lightblue'))
1193         elif address in self.wallet.prioritized_addresses: 
1194             item.setBackgroundColor(0, QColor('lightgreen'))
1195         
1196
1197     def update_receive_tab(self):
1198         l = self.receive_list
1199         
1200         l.clear()
1201         l.setColumnHidden(2, False)
1202         l.setColumnHidden(3, False)
1203         for i,width in enumerate(self.column_widths['receive']):
1204             l.setColumnWidth(i, width)
1205
1206         if self.current_account is None:
1207             account_items = self.wallet.accounts.items()
1208         elif self.current_account != -1:
1209             account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1210         else:
1211             account_items = []
1212
1213         for k, account in account_items:
1214             name = self.wallet.get_account_name(k)
1215             c,u = self.wallet.get_account_balance(k)
1216             account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1217             l.addTopLevelItem(account_item)
1218             account_item.setExpanded(self.accounts_expanded.get(k, True))
1219             account_item.setData(0, 32, k)
1220
1221             if not self.wallet.is_seeded(k):
1222                 icon = QIcon(":icons/key.png")
1223                 account_item.setIcon(0, icon)
1224             
1225             for is_change in ([0,1]):
1226                 name = _("Receiving") if not is_change else _("Change")
1227                 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1228                 account_item.addChild(seq_item)
1229                 if not is_change: seq_item.setExpanded(True)
1230
1231                 is_red = False
1232                 gap = 0
1233
1234                 for address in account.get_addresses(is_change):
1235                     h = self.wallet.history.get(address,[])
1236             
1237                     if h == []:
1238                         gap += 1
1239                         if gap > self.wallet.gap_limit:
1240                             is_red = True
1241                     else:
1242                         gap = 0
1243
1244                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1245                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1246                     self.update_receive_item(item)
1247                     if is_red:
1248                         item.setBackgroundColor(1, QColor('red'))
1249                     seq_item.addChild(item)
1250
1251
1252         for k, addr in self.wallet.get_pending_accounts():
1253             name = self.wallet.labels.get(k,'')
1254             account_item = QTreeWidgetItem( [ name + "  [ "+_('pending account')+" ]", '', '', ''] )
1255             self.update_receive_item(item)
1256             l.addTopLevelItem(account_item)
1257             account_item.setExpanded(True)
1258             account_item.setData(0, 32, k)
1259             item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1260             account_item.addChild(item)
1261             self.update_receive_item(item)
1262
1263
1264         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1265             c,u = self.wallet.get_imported_balance()
1266             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1267             l.addTopLevelItem(account_item)
1268             account_item.setExpanded(True)
1269             for address in self.wallet.imported_keys.keys():
1270                 item = QTreeWidgetItem( [ address, '', '', ''] )
1271                 self.update_receive_item(item)
1272                 account_item.addChild(item)
1273                 
1274
1275         # we use column 1 because column 0 may be hidden
1276         l.setCurrentItem(l.topLevelItem(0),1)
1277
1278
1279     def update_contacts_tab(self):
1280         l = self.contacts_list
1281         l.clear()
1282
1283         for address in self.wallet.addressbook:
1284             label = self.wallet.labels.get(address,'')
1285             n = self.wallet.get_num_tx(address)
1286             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1287             item.setFont(0, QFont(MONOSPACE_FONT))
1288             # 32 = label can be edited (bool)
1289             item.setData(0,32, True)
1290             # 33 = payto string
1291             item.setData(0,33, address)
1292             l.addTopLevelItem(item)
1293
1294         run_hook('update_contacts_tab', l)
1295         l.setCurrentItem(l.topLevelItem(0))
1296
1297
1298
1299     def create_console_tab(self):
1300         from console import Console
1301         self.console = console = Console()
1302         return console
1303
1304
1305     def update_console(self):
1306         console = self.console
1307         console.history = self.config.get("console-history",[])
1308         console.history_index = len(console.history)
1309
1310         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1311         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1312
1313         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1314         methods = {}
1315         def mkfunc(f, method):
1316             return lambda *args: apply( f, (method, args, self.password_dialog ))
1317         for m in dir(c):
1318             if m[0]=='_' or m in ['network','wallet']: continue
1319             methods[m] = mkfunc(c._run, m)
1320             
1321         console.updateNamespace(methods)
1322
1323
1324     def change_account(self,s):
1325         if s == _("All accounts"):
1326             self.current_account = None
1327         else:
1328             accounts = self.wallet.get_account_names()
1329             for k, v in accounts.items():
1330                 if v == s:
1331                     self.current_account = k
1332         self.update_history_tab()
1333         self.update_status()
1334         self.update_receive_tab()
1335
1336     def create_status_bar(self):
1337
1338         sb = QStatusBar()
1339         sb.setFixedHeight(35)
1340         qtVersion = qVersion()
1341
1342         self.balance_label = QLabel("")
1343         sb.addWidget(self.balance_label)
1344
1345         from version_getter import UpdateLabel
1346         self.updatelabel = UpdateLabel(self.config, sb)
1347
1348         self.account_selector = QComboBox()
1349         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account) 
1350         sb.addPermanentWidget(self.account_selector)
1351
1352         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1353             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1354
1355         self.lock_icon = QIcon()
1356         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1357         sb.addPermanentWidget( self.password_button )
1358             
1359         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1360         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) 
1361         sb.addPermanentWidget( self.seed_button )
1362         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) 
1363         sb.addPermanentWidget( self.status_button )
1364
1365         run_hook('create_status_bar', (sb,))
1366
1367         self.setStatusBar(sb)
1368
1369
1370     def update_lock_icon(self):
1371         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1372         self.password_button.setIcon( icon )
1373
1374
1375     def update_buttons_on_seed(self):
1376         if not self.wallet.is_watching_only():
1377            self.seed_button.show()
1378            self.password_button.show()
1379            self.send_button.setText(_("Send"))
1380         else:
1381            self.password_button.hide()
1382            self.seed_button.hide()
1383            self.send_button.setText(_("Create unsigned transaction"))
1384
1385
1386     def change_password_dialog(self):
1387         from password_dialog import PasswordDialog
1388         d = PasswordDialog(self.wallet, self)
1389         d.run()
1390         self.update_lock_icon()
1391
1392
1393     def new_contact_dialog(self):
1394
1395         d = QDialog(self)
1396         vbox = QVBoxLayout(d)
1397         vbox.addWidget(QLabel(_('New Contact')+':'))
1398         
1399         grid = QGridLayout()
1400         line1 = QLineEdit()
1401         line2 = QLineEdit()
1402         grid.addWidget(QLabel(_("Address")), 1, 0)
1403         grid.addWidget(line1, 1, 1)
1404         grid.addWidget(QLabel(_("Name")), 2, 0)
1405         grid.addWidget(line2, 2, 1)
1406
1407         vbox.addLayout(grid)
1408         vbox.addLayout(ok_cancel_buttons(d))
1409     
1410         if not d.exec_():
1411             return
1412         
1413         address = str(line1.text())
1414         label = unicode(line2.text())
1415         
1416         if not is_valid(address):
1417             QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1418             return
1419         
1420         self.wallet.add_contact(address)
1421         if label:
1422             self.wallet.set_label(address, label)
1423
1424         self.update_contacts_tab()
1425         self.update_history_tab()
1426         self.update_completions()
1427         self.tabs.setCurrentIndex(3)
1428
1429
1430     def new_account_dialog(self):
1431
1432         dialog = QDialog(self)
1433         dialog.setModal(1)
1434         dialog.setWindowTitle(_("New Account"))
1435
1436         vbox = QVBoxLayout()
1437         vbox.addWidget(QLabel(_('Account name')+':'))
1438         e = QLineEdit()
1439         vbox.addWidget(e)
1440         msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1441             + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1442         l = QLabel(msg)
1443         l.setWordWrap(True)
1444         vbox.addWidget(l)
1445
1446         vbox.addLayout(ok_cancel_buttons(dialog))
1447         dialog.setLayout(vbox)
1448         r = dialog.exec_()
1449         if not r: return
1450
1451         name = str(e.text())
1452         if not name: return
1453
1454         self.wallet.create_pending_account('1', name)
1455         self.update_receive_tab()
1456         self.tabs.setCurrentIndex(2)
1457         
1458             
1459
1460     def show_master_public_key_old(self):
1461         dialog = QDialog(self)
1462         dialog.setModal(1)
1463         dialog.setWindowTitle(_("Master Public Key"))
1464
1465         main_text = QTextEdit()
1466         main_text.setText(self.wallet.get_master_public_key())
1467         main_text.setReadOnly(True)
1468         main_text.setMaximumHeight(170)
1469         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1470
1471         ok_button = QPushButton(_("OK"))
1472         ok_button.setDefault(True)
1473         ok_button.clicked.connect(dialog.accept)
1474
1475         main_layout = QGridLayout()
1476         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1477
1478         main_layout.addWidget(main_text, 1, 0)
1479         main_layout.addWidget(qrw, 1, 1 )
1480
1481         vbox.addLayout(close_button(dialog))
1482         dialog.setLayout(vbox)
1483         dialog.exec_()
1484         
1485
1486     def show_master_public_key(self):
1487
1488         if self.wallet.seed_version == 4:
1489             self.show_master_public_keys_old()
1490             return
1491
1492         dialog = QDialog(self)
1493         dialog.setModal(1)
1494         dialog.setWindowTitle(_("Master Public Keys"))
1495
1496         chain_text = QTextEdit()
1497         chain_text.setReadOnly(True)
1498         chain_text.setMaximumHeight(170)
1499         chain_qrw = QRCodeWidget()
1500
1501         mpk_text = QTextEdit()
1502         mpk_text.setReadOnly(True)
1503         mpk_text.setMaximumHeight(170)
1504         mpk_qrw = QRCodeWidget()
1505
1506         main_layout = QGridLayout()
1507
1508         main_layout.addWidget(QLabel(_('Key')), 1, 0)
1509         main_layout.addWidget(mpk_text, 1, 1)
1510         main_layout.addWidget(mpk_qrw, 1, 2)
1511
1512         main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1513         main_layout.addWidget(chain_text, 2, 1)
1514         main_layout.addWidget(chain_qrw, 2, 2)
1515
1516         def update(key):
1517             c, K, cK = self.wallet.master_public_keys[str(key)]
1518             chain_text.setText(c)
1519             chain_qrw.set_addr(c)
1520             chain_qrw.update_qr()
1521             mpk_text.setText(K)
1522             mpk_qrw.set_addr(c)
1523             mpk_qrw.update_qr()
1524             
1525         key_selector = QComboBox()
1526         keys = sorted(self.wallet.master_public_keys.keys())
1527         key_selector.addItems(keys)
1528
1529         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1530         main_layout.addWidget(key_selector, 0, 1)
1531         dialog.connect(key_selector,SIGNAL("activated(QString)"),update) 
1532
1533         update(keys[0])
1534
1535         vbox = QVBoxLayout()
1536         vbox.addLayout(main_layout)
1537         vbox.addLayout(close_button(dialog))
1538
1539         dialog.setLayout(vbox)
1540         dialog.exec_()
1541         
1542
1543     @protected
1544     def show_seed_dialog(self, password):
1545         if self.wallet.is_watching_only():
1546             QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1547             return
1548
1549         if self.wallet.seed:
1550             try:
1551                 seed = self.wallet.decode_seed(password)
1552             except:
1553                 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1554                 return
1555             from seed_dialog import SeedDialog
1556             d = SeedDialog(self, seed, self.wallet.imported_keys)
1557             d.exec_()
1558         else:
1559             l = {}
1560             for k in self.wallet.master_private_keys.keys():
1561                 pk = self.wallet.get_master_private_key(k, password)
1562                 l[k] = pk
1563             from seed_dialog import PrivateKeysDialog
1564             d = PrivateKeysDialog(self,l)
1565             d.exec_()
1566
1567
1568
1569
1570
1571     def show_qrcode(self, data, title = _("QR code")):
1572         if not data: return
1573         d = QDialog(self)
1574         d.setModal(1)
1575         d.setWindowTitle(title)
1576         d.setMinimumSize(270, 300)
1577         vbox = QVBoxLayout()
1578         qrw = QRCodeWidget(data)
1579         vbox.addWidget(qrw, 1)
1580         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1581         hbox = QHBoxLayout()
1582         hbox.addStretch(1)
1583
1584         def print_qr(self):
1585             filename = "qrcode.bmp"
1586             bmp.save_qrcode(qrw.qr, filename)
1587             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1588
1589         b = QPushButton(_("Save"))
1590         hbox.addWidget(b)
1591         b.clicked.connect(print_qr)
1592
1593         b = QPushButton(_("Close"))
1594         hbox.addWidget(b)
1595         b.clicked.connect(d.accept)
1596         b.setDefault(True)
1597
1598         vbox.addLayout(hbox)
1599         d.setLayout(vbox)
1600         d.exec_()
1601
1602
1603     def do_protect(self, func, args):
1604         if self.wallet.use_encryption:
1605             password = self.password_dialog()
1606             if not password:
1607                 return
1608         else:
1609             password = None
1610             
1611         if args != (False,):
1612             args = (self,) + args + (password,)
1613         else:
1614             args = (self,password)
1615         apply( func, args)
1616
1617
1618     @protected
1619     def show_private_key(self, address, password):
1620         if not address: return
1621         try:
1622             pk_list = self.wallet.get_private_key(address, password)
1623         except BaseException, e:
1624             self.show_message(str(e))
1625             return
1626         QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1627
1628
1629     @protected
1630     def do_sign(self, address, message, signature, password):
1631         message = unicode(message.toPlainText())
1632         message = message.encode('utf-8')
1633         try:
1634             sig = self.wallet.sign_message(str(address.text()), message, password)
1635             signature.setText(sig)
1636         except BaseException, e:
1637             self.show_message(str(e))
1638
1639     def sign_message(self, address):
1640         if not address: return
1641         d = QDialog(self)
1642         d.setModal(1)
1643         d.setWindowTitle(_('Sign Message'))
1644         d.setMinimumSize(410, 290)
1645
1646         tab_widget = QTabWidget()
1647         tab = QWidget()
1648         layout = QGridLayout(tab)
1649
1650         sign_address = QLineEdit()
1651
1652         sign_address.setText(address)
1653         layout.addWidget(QLabel(_('Address')), 1, 0)
1654         layout.addWidget(sign_address, 1, 1)
1655
1656         sign_message = QTextEdit()
1657         layout.addWidget(QLabel(_('Message')), 2, 0)
1658         layout.addWidget(sign_message, 2, 1)
1659         layout.setRowStretch(2,3)
1660
1661         sign_signature = QTextEdit()
1662         layout.addWidget(QLabel(_('Signature')), 3, 0)
1663         layout.addWidget(sign_signature, 3, 1)
1664         layout.setRowStretch(3,1)
1665
1666
1667         hbox = QHBoxLayout()
1668         b = QPushButton(_("Sign"))
1669         hbox.addWidget(b)
1670         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1671         b = QPushButton(_("Close"))
1672         b.clicked.connect(d.accept)
1673         hbox.addWidget(b)
1674         layout.addLayout(hbox, 4, 1)
1675         tab_widget.addTab(tab, _("Sign"))
1676
1677
1678         tab = QWidget()
1679         layout = QGridLayout(tab)
1680
1681         verify_address = QLineEdit()
1682         layout.addWidget(QLabel(_('Address')), 1, 0)
1683         layout.addWidget(verify_address, 1, 1)
1684
1685         verify_message = QTextEdit()
1686         layout.addWidget(QLabel(_('Message')), 2, 0)
1687         layout.addWidget(verify_message, 2, 1)
1688         layout.setRowStretch(2,3)
1689
1690         verify_signature = QTextEdit()
1691         layout.addWidget(QLabel(_('Signature')), 3, 0)
1692         layout.addWidget(verify_signature, 3, 1)
1693         layout.setRowStretch(3,1)
1694
1695         def do_verify():
1696             message = unicode(verify_message.toPlainText())
1697             message = message.encode('utf-8')
1698             if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1699                 self.show_message(_("Signature verified"))
1700             else:
1701                 self.show_message(_("Error: wrong signature"))
1702
1703         hbox = QHBoxLayout()
1704         b = QPushButton(_("Verify"))
1705         b.clicked.connect(do_verify)
1706         hbox.addWidget(b)
1707         b = QPushButton(_("Close"))
1708         b.clicked.connect(d.accept)
1709         hbox.addWidget(b)
1710         layout.addLayout(hbox, 4, 1)
1711         tab_widget.addTab(tab, _("Verify"))
1712
1713         vbox = QVBoxLayout()
1714         vbox.addWidget(tab_widget)
1715         d.setLayout(vbox)
1716         d.exec_()
1717
1718         
1719
1720
1721     def question(self, msg):
1722         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1723
1724     def show_message(self, msg):
1725         QMessageBox.information(self, _('Message'), msg, _('OK'))
1726
1727     def password_dialog(self ):
1728         d = QDialog(self)
1729         d.setModal(1)
1730
1731         pw = QLineEdit()
1732         pw.setEchoMode(2)
1733
1734         vbox = QVBoxLayout()
1735         msg = _('Please enter your password')
1736         vbox.addWidget(QLabel(msg))
1737
1738         grid = QGridLayout()
1739         grid.setSpacing(8)
1740         grid.addWidget(QLabel(_('Password')), 1, 0)
1741         grid.addWidget(pw, 1, 1)
1742         vbox.addLayout(grid)
1743
1744         vbox.addLayout(ok_cancel_buttons(d))
1745         d.setLayout(vbox)
1746
1747         run_hook('password_dialog', pw, grid, 1)
1748         if not d.exec_(): return
1749         return unicode(pw.text())
1750
1751
1752
1753
1754
1755
1756
1757
1758     def tx_from_text(self, txt):
1759         "json or raw hexadecimal"
1760         try:
1761             txt.decode('hex')
1762             tx = Transaction(txt)
1763             return tx
1764         except:
1765             pass
1766
1767         try:
1768             tx_dict = json.loads(str(txt))
1769             assert "hex" in tx_dict.keys()
1770             assert "complete" in tx_dict.keys()
1771             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1772             if not tx_dict["complete"]:
1773                 assert "input_info" in tx_dict.keys()
1774                 input_info = json.loads(tx_dict['input_info'])
1775                 tx.add_input_info(input_info)
1776             return tx
1777         except:
1778             pass
1779         
1780         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1781
1782
1783
1784     def read_tx_from_file(self):
1785         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1786         if not fileName:
1787             return
1788         try:
1789             with open(fileName, "r") as f:
1790                 file_content = f.read()
1791         except (ValueError, IOError, os.error), reason:
1792             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1793
1794         return self.tx_from_text(file_content)
1795
1796
1797     @protected
1798     def sign_raw_transaction(self, tx, input_info, password):
1799         self.wallet.signrawtransaction(tx, input_info, [], password)
1800
1801     def do_process_from_text(self):
1802         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1803         if not text:
1804             return
1805         tx = self.tx_from_text(text)
1806         if tx:
1807             self.show_transaction(tx)
1808
1809     def do_process_from_file(self):
1810         tx = self.read_tx_from_file()
1811         if tx:
1812             self.show_transaction(tx)
1813
1814     def do_process_from_csvReader(self, csvReader):
1815         outputs = []
1816         try:
1817             for row in csvReader:
1818                 address = row[0]
1819                 amount = float(row[1])
1820                 amount = int(100000000*amount)
1821                 outputs.append((address, amount))
1822         except (ValueError, IOError, os.error), reason:
1823             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1824             return
1825
1826         try:
1827             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1828         except BaseException, e:
1829             self.show_message(str(e))
1830             return
1831
1832         self.show_transaction(tx)
1833
1834     def do_process_from_csv_file(self):
1835         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1836         if not fileName:
1837             return
1838         try:
1839             with open(fileName, "r") as f:
1840                 csvReader = csv.reader(f)
1841                 self.do_process_from_csvReader(csvReader)
1842         except (ValueError, IOError, os.error), reason:
1843             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1844             return
1845
1846     def do_process_from_csv_text(self):
1847         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1848                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1849         if not text:
1850             return
1851         f = StringIO.StringIO(text)
1852         csvReader = csv.reader(f)
1853         self.do_process_from_csvReader(csvReader)
1854
1855
1856
1857     @protected
1858     def do_export_privkeys(self, password):
1859         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.")))
1860
1861         try:
1862             select_export = _('Select file to export your private keys to')
1863             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1864             if fileName:
1865                 with open(fileName, "w+") as csvfile:
1866                     transaction = csv.writer(csvfile)
1867                     transaction.writerow(["address", "private_key"])
1868
1869                     addresses = self.wallet.addresses(True)
1870                     
1871                     for addr in addresses:
1872                         pk = "".join(self.wallet.get_private_key(addr, password))
1873                         transaction.writerow(["%34s"%addr,pk])
1874
1875                     self.show_message(_("Private keys exported."))
1876
1877         except (IOError, os.error), reason:
1878             export_error_label = _("Electrum was unable to produce a private key-export.")
1879             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1880
1881         except BaseException, e:
1882           self.show_message(str(e))
1883           return
1884
1885
1886     def do_import_labels(self):
1887         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1888         if not labelsFile: return
1889         try:
1890             f = open(labelsFile, 'r')
1891             data = f.read()
1892             f.close()
1893             for key, value in json.loads(data).items():
1894                 self.wallet.set_label(key, value)
1895             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1896         except (IOError, os.error), reason:
1897             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1898             
1899
1900     def do_export_labels(self):
1901         labels = self.wallet.labels
1902         try:
1903             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1904             if fileName:
1905                 with open(fileName, 'w+') as f:
1906                     json.dump(labels, f)
1907                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1908         except (IOError, os.error), reason:
1909             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1910
1911
1912     def do_export_history(self):
1913         from lite_window import csv_transaction
1914         csv_transaction(self.wallet)
1915
1916
1917     @protected
1918     def do_import_privkey(self, password):
1919         if not self.wallet.imported_keys:
1920             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1921                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1922                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1923             if r == 4: return
1924
1925         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1926         if not text: return
1927
1928         text = str(text).split()
1929         badkeys = []
1930         addrlist = []
1931         for key in text:
1932             try:
1933                 addr = self.wallet.import_key(key, password)
1934             except BaseException as e:
1935                 badkeys.append(key)
1936                 continue
1937             if not addr: 
1938                 badkeys.append(key)
1939             else:
1940                 addrlist.append(addr)
1941         if addrlist:
1942             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1943         if badkeys:
1944             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1945         self.update_receive_tab()
1946         self.update_history_tab()
1947
1948
1949     def settings_dialog(self):
1950         d = QDialog(self)
1951         d.setWindowTitle(_('Electrum Settings'))
1952         d.setModal(1)
1953         vbox = QVBoxLayout()
1954         grid = QGridLayout()
1955         grid.setColumnStretch(0,1)
1956
1957         nz_label = QLabel(_('Display zeros') + ':')
1958         grid.addWidget(nz_label, 0, 0)
1959         nz_e = AmountEdit(None,True)
1960         nz_e.setText("%d"% self.num_zeros)
1961         grid.addWidget(nz_e, 0, 1)
1962         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1963         grid.addWidget(HelpButton(msg), 0, 2)
1964         if not self.config.is_modifiable('num_zeros'):
1965             for w in [nz_e, nz_label]: w.setEnabled(False)
1966         
1967         lang_label=QLabel(_('Language') + ':')
1968         grid.addWidget(lang_label, 1, 0)
1969         lang_combo = QComboBox()
1970         from electrum.i18n import languages
1971         lang_combo.addItems(languages.values())
1972         try:
1973             index = languages.keys().index(self.config.get("language",''))
1974         except:
1975             index = 0
1976         lang_combo.setCurrentIndex(index)
1977         grid.addWidget(lang_combo, 1, 1)
1978         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1979         if not self.config.is_modifiable('language'):
1980             for w in [lang_combo, lang_label]: w.setEnabled(False)
1981
1982         
1983         fee_label = QLabel(_('Transaction fee') + ':')
1984         grid.addWidget(fee_label, 2, 0)
1985         fee_e = AmountEdit(self.base_unit)
1986         fee_e.setText(self.format_amount(self.wallet.fee).strip())
1987         grid.addWidget(fee_e, 2, 1)
1988         msg = _('Fee per kilobyte of transaction.') + ' ' \
1989             + _('Recommended value') + ': ' + self.format_amount(50000)
1990         grid.addWidget(HelpButton(msg), 2, 2)
1991         if not self.config.is_modifiable('fee_per_kb'):
1992             for w in [fee_e, fee_label]: w.setEnabled(False)
1993
1994         units = ['BTC', 'mBTC']
1995         unit_label = QLabel(_('Base unit') + ':')
1996         grid.addWidget(unit_label, 3, 0)
1997         unit_combo = QComboBox()
1998         unit_combo.addItems(units)
1999         unit_combo.setCurrentIndex(units.index(self.base_unit()))
2000         grid.addWidget(unit_combo, 3, 1)
2001         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2002                                              + '\n1BTC=1000mBTC.\n' \
2003                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
2004
2005         usechange_cb = QCheckBox(_('Use change addresses'))
2006         usechange_cb.setChecked(self.wallet.use_change)
2007         grid.addWidget(usechange_cb, 4, 0)
2008         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2009         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2010
2011         grid.setRowStretch(5,1)
2012
2013         vbox.addLayout(grid)
2014         vbox.addLayout(ok_cancel_buttons(d))
2015         d.setLayout(vbox) 
2016
2017         # run the dialog
2018         if not d.exec_(): return
2019
2020         fee = unicode(fee_e.text())
2021         try:
2022             fee = self.read_amount(fee)
2023         except:
2024             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2025             return
2026
2027         self.wallet.set_fee(fee)
2028         
2029         nz = unicode(nz_e.text())
2030         try:
2031             nz = int( nz )
2032             if nz>8: nz=8
2033         except:
2034             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2035             return
2036
2037         if self.num_zeros != nz:
2038             self.num_zeros = nz
2039             self.config.set_key('num_zeros', nz, True)
2040             self.update_history_tab()
2041             self.update_receive_tab()
2042
2043         usechange_result = usechange_cb.isChecked()
2044         if self.wallet.use_change != usechange_result:
2045             self.wallet.use_change = usechange_result
2046             self.config.set_key('use_change', self.wallet.use_change, True)
2047         
2048         unit_result = units[unit_combo.currentIndex()]
2049         if self.base_unit() != unit_result:
2050             self.decimal_point = 8 if unit_result == 'BTC' else 5
2051             self.config.set_key('decimal_point', self.decimal_point, True)
2052             self.update_history_tab()
2053             self.update_status()
2054         
2055         need_restart = False
2056
2057         lang_request = languages.keys()[lang_combo.currentIndex()]
2058         if lang_request != self.config.get('language'):
2059             self.config.set_key("language", lang_request, True)
2060             need_restart = True
2061             
2062         run_hook('close_settings_dialog')
2063
2064         if need_restart:
2065             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2066
2067
2068     def run_network_dialog(self):
2069         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2070
2071     def closeEvent(self, event):
2072         g = self.geometry()
2073         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2074         self.save_column_widths()
2075         self.config.set_key("console-history", self.console.history[-50:], True)
2076         self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2077         event.accept()
2078
2079
2080
2081     def plugins_dialog(self):
2082         from electrum.plugins import plugins
2083
2084         d = QDialog(self)
2085         d.setWindowTitle(_('Electrum Plugins'))
2086         d.setModal(1)
2087
2088         vbox = QVBoxLayout(d)
2089
2090         # plugins
2091         scroll = QScrollArea()
2092         scroll.setEnabled(True)
2093         scroll.setWidgetResizable(True)
2094         scroll.setMinimumSize(400,250)
2095         vbox.addWidget(scroll)
2096
2097         w = QWidget()
2098         scroll.setWidget(w)
2099         w.setMinimumHeight(len(plugins)*35)
2100
2101         grid = QGridLayout()
2102         grid.setColumnStretch(0,1)
2103         w.setLayout(grid)
2104
2105         def do_toggle(cb, p, w):
2106             r = p.toggle()
2107             cb.setChecked(r)
2108             if w: w.setEnabled(r)
2109
2110         def mk_toggle(cb, p, w):
2111             return lambda: do_toggle(cb,p,w)
2112
2113         for i, p in enumerate(plugins):
2114             try:
2115                 cb = QCheckBox(p.fullname())
2116                 cb.setDisabled(not p.is_available())
2117                 cb.setChecked(p.is_enabled())
2118                 grid.addWidget(cb, i, 0)
2119                 if p.requires_settings():
2120                     w = p.settings_widget(self)
2121                     w.setEnabled( p.is_enabled() )
2122                     grid.addWidget(w, i, 1)
2123                 else: 
2124                     w = None
2125                 cb.clicked.connect(mk_toggle(cb,p,w))
2126                 grid.addWidget(HelpButton(p.description()), i, 2)
2127             except:
2128                 print_msg(_("Error: cannot display plugin"), p)
2129                 traceback.print_exc(file=sys.stdout)
2130         grid.setRowStretch(i+1,1)
2131
2132         vbox.addLayout(close_button(d))
2133
2134         d.exec_()
2135
2136
2137     def show_account_details(self, k):
2138         d = QDialog(self)
2139         d.setWindowTitle(_('Account Details'))
2140         d.setModal(1)
2141
2142         vbox = QVBoxLayout(d)
2143         roots = self.wallet.get_roots(k)
2144
2145         name = self.wallet.get_account_name(k)
2146         label = QLabel('Name: ' + name)
2147         vbox.addWidget(label)
2148
2149         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2150         vbox.addWidget(QLabel('Type: ' + acctype))
2151
2152         label = QLabel('Derivation: ' + k)
2153         vbox.addWidget(label)
2154
2155         #for root in roots:
2156         #    mpk = self.wallet.master_public_keys[root]
2157         #    text = QTextEdit()
2158         #    text.setReadOnly(True)
2159         #    text.setMaximumHeight(120)
2160         #    text.setText(repr(mpk))
2161         #    vbox.addWidget(text)
2162
2163         vbox.addLayout(close_button(d))
2164         d.exec_()