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