0d78a1f8c05e4f752dde98df24f4246b5d7f29c1
[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", 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
255     def load_wallet(self, wallet):
256         import electrum
257         self.wallet = wallet
258         self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
259         self.current_account = self.wallet.storage.get("current_account", None)
260         self.pending_accounts = self.wallet.storage.get('pending_accounts',{})
261
262         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.storage.path
263         if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
264         self.setWindowTitle( title )
265         self.update_wallet()
266         # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
267         self.notify_transactions()
268
269         # account selector
270         accounts = self.wallet.get_account_names()
271         self.account_selector.clear()
272         if len(accounts) > 1:
273             self.account_selector.addItems([_("All accounts")] + accounts.values())
274             self.account_selector.setCurrentIndex(0)
275             self.account_selector.show()
276         else:
277             self.account_selector.hide()
278
279         self.new_account.setEnabled(self.wallet.seed_version>4)
280
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( 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( 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", 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         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1397         address = unicode(text)
1398         if ok:
1399             if is_valid(address):
1400                 self.wallet.add_contact(address)
1401                 self.update_contacts_tab()
1402                 self.update_history_tab()
1403                 self.update_completions()
1404             else:
1405                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1406
1407
1408     def new_account_dialog(self):
1409
1410         dialog = QDialog(self)
1411         dialog.setModal(1)
1412         dialog.setWindowTitle(_("New Account"))
1413
1414         vbox = QVBoxLayout()
1415         vbox.addWidget(QLabel(_('Account name')+':'))
1416         e = QLineEdit()
1417         vbox.addWidget(e)
1418         msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1419             + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1420         l = QLabel(msg)
1421         l.setWordWrap(True)
1422         vbox.addWidget(l)
1423
1424         vbox.addLayout(ok_cancel_buttons(dialog))
1425         dialog.setLayout(vbox)
1426         r = dialog.exec_()
1427         if not r: return
1428
1429         name = str(e.text())
1430         if not name: return
1431
1432         k, addr = self.wallet.new_account_address()
1433         self.wallet.set_label(k, name)
1434         self.pending_accounts[k] = addr
1435         self.wallet.storage.put('pending_accounts', self.pending_accounts)
1436         self.update_receive_tab()
1437         self.tabs.setCurrentIndex(2)
1438         
1439             
1440
1441     def show_master_public_key_old(self):
1442         dialog = QDialog(self)
1443         dialog.setModal(1)
1444         dialog.setWindowTitle(_("Master Public Key"))
1445
1446         main_text = QTextEdit()
1447         main_text.setText(self.wallet.get_master_public_key())
1448         main_text.setReadOnly(True)
1449         main_text.setMaximumHeight(170)
1450         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1451
1452         ok_button = QPushButton(_("OK"))
1453         ok_button.setDefault(True)
1454         ok_button.clicked.connect(dialog.accept)
1455
1456         main_layout = QGridLayout()
1457         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1458
1459         main_layout.addWidget(main_text, 1, 0)
1460         main_layout.addWidget(qrw, 1, 1 )
1461
1462         vbox.addLayout(close_button(dialog))
1463         dialog.setLayout(vbox)
1464         dialog.exec_()
1465         
1466
1467     def show_master_public_key(self):
1468
1469         if self.wallet.seed_version == 4:
1470             self.show_master_public_keys_old()
1471             return
1472
1473         dialog = QDialog(self)
1474         dialog.setModal(1)
1475         dialog.setWindowTitle(_("Master Public Keys"))
1476
1477         chain_text = QTextEdit()
1478         chain_text.setReadOnly(True)
1479         chain_text.setMaximumHeight(170)
1480         chain_qrw = QRCodeWidget()
1481
1482         mpk_text = QTextEdit()
1483         mpk_text.setReadOnly(True)
1484         mpk_text.setMaximumHeight(170)
1485         mpk_qrw = QRCodeWidget()
1486
1487         main_layout = QGridLayout()
1488
1489         main_layout.addWidget(QLabel(_('Key')), 1, 0)
1490         main_layout.addWidget(mpk_text, 1, 1)
1491         main_layout.addWidget(mpk_qrw, 1, 2)
1492
1493         main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1494         main_layout.addWidget(chain_text, 2, 1)
1495         main_layout.addWidget(chain_qrw, 2, 2)
1496
1497         def update(key):
1498             c, K, cK = self.wallet.master_public_keys[str(key)]
1499             chain_text.setText(c)
1500             chain_qrw.set_addr(c)
1501             chain_qrw.update_qr()
1502             mpk_text.setText(K)
1503             mpk_qrw.set_addr(c)
1504             mpk_qrw.update_qr()
1505             
1506         key_selector = QComboBox()
1507         keys = sorted(self.wallet.master_public_keys.keys())
1508         key_selector.addItems(keys)
1509
1510         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1511         main_layout.addWidget(key_selector, 0, 1)
1512         dialog.connect(key_selector,SIGNAL("activated(QString)"),update) 
1513
1514         update(keys[0])
1515
1516         vbox = QVBoxLayout()
1517         vbox.addLayout(main_layout)
1518         vbox.addLayout(close_button(dialog))
1519
1520         dialog.setLayout(vbox)
1521         dialog.exec_()
1522         
1523
1524     @protected
1525     def show_seed_dialog(self, password):
1526         if self.wallet.is_watching_only():
1527             QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1528             return
1529
1530         if self.wallet.seed:
1531             try:
1532                 seed = self.wallet.decode_seed(password)
1533             except:
1534                 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1535                 return
1536             from seed_dialog import SeedDialog
1537             d = SeedDialog(self, seed, self.wallet.imported_keys)
1538             d.exec_()
1539         else:
1540             l = {}
1541             for k in self.wallet.master_private_keys.keys():
1542                 pk = self.wallet.get_master_private_key(k, password)
1543                 l[k] = pk
1544             from seed_dialog import PrivateKeysDialog
1545             d = PrivateKeysDialog(self,l)
1546             d.exec_()
1547
1548
1549
1550
1551
1552     def show_qrcode(self, data, title = _("QR code")):
1553         if not data: return
1554         d = QDialog(self)
1555         d.setModal(1)
1556         d.setWindowTitle(title)
1557         d.setMinimumSize(270, 300)
1558         vbox = QVBoxLayout()
1559         qrw = QRCodeWidget(data)
1560         vbox.addWidget(qrw, 1)
1561         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1562         hbox = QHBoxLayout()
1563         hbox.addStretch(1)
1564
1565         def print_qr(self):
1566             filename = "qrcode.bmp"
1567             bmp.save_qrcode(qrw.qr, filename)
1568             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1569
1570         b = QPushButton(_("Save"))
1571         hbox.addWidget(b)
1572         b.clicked.connect(print_qr)
1573
1574         b = QPushButton(_("Close"))
1575         hbox.addWidget(b)
1576         b.clicked.connect(d.accept)
1577         b.setDefault(True)
1578
1579         vbox.addLayout(hbox)
1580         d.setLayout(vbox)
1581         d.exec_()
1582
1583
1584     def do_protect(self, func, args):
1585         if self.wallet.use_encryption:
1586             password = self.password_dialog()
1587             if not password:
1588                 return
1589         else:
1590             password = None
1591             
1592         if args != (False,):
1593             args = (self,) + args + (password,)
1594         else:
1595             args = (self,password)
1596         apply( func, args)
1597
1598
1599     @protected
1600     def show_private_key(self, address, password):
1601         if not address: return
1602         try:
1603             pk_list = self.wallet.get_private_key(address, password)
1604         except BaseException, e:
1605             self.show_message(str(e))
1606             return
1607         QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1608
1609
1610     @protected
1611     def do_sign(self, address, message, signature, password):
1612         message = unicode(message.toPlainText())
1613         message = message.encode('utf-8')
1614         try:
1615             sig = self.wallet.sign_message(str(address.text()), message, password)
1616             signature.setText(sig)
1617         except BaseException, e:
1618             self.show_message(str(e))
1619
1620     def sign_message(self, address):
1621         if not address: return
1622         d = QDialog(self)
1623         d.setModal(1)
1624         d.setWindowTitle(_('Sign Message'))
1625         d.setMinimumSize(410, 290)
1626
1627         tab_widget = QTabWidget()
1628         tab = QWidget()
1629         layout = QGridLayout(tab)
1630
1631         sign_address = QLineEdit()
1632
1633         sign_address.setText(address)
1634         layout.addWidget(QLabel(_('Address')), 1, 0)
1635         layout.addWidget(sign_address, 1, 1)
1636
1637         sign_message = QTextEdit()
1638         layout.addWidget(QLabel(_('Message')), 2, 0)
1639         layout.addWidget(sign_message, 2, 1)
1640         layout.setRowStretch(2,3)
1641
1642         sign_signature = QTextEdit()
1643         layout.addWidget(QLabel(_('Signature')), 3, 0)
1644         layout.addWidget(sign_signature, 3, 1)
1645         layout.setRowStretch(3,1)
1646
1647
1648         hbox = QHBoxLayout()
1649         b = QPushButton(_("Sign"))
1650         hbox.addWidget(b)
1651         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1652         b = QPushButton(_("Close"))
1653         b.clicked.connect(d.accept)
1654         hbox.addWidget(b)
1655         layout.addLayout(hbox, 4, 1)
1656         tab_widget.addTab(tab, _("Sign"))
1657
1658
1659         tab = QWidget()
1660         layout = QGridLayout(tab)
1661
1662         verify_address = QLineEdit()
1663         layout.addWidget(QLabel(_('Address')), 1, 0)
1664         layout.addWidget(verify_address, 1, 1)
1665
1666         verify_message = QTextEdit()
1667         layout.addWidget(QLabel(_('Message')), 2, 0)
1668         layout.addWidget(verify_message, 2, 1)
1669         layout.setRowStretch(2,3)
1670
1671         verify_signature = QTextEdit()
1672         layout.addWidget(QLabel(_('Signature')), 3, 0)
1673         layout.addWidget(verify_signature, 3, 1)
1674         layout.setRowStretch(3,1)
1675
1676         def do_verify():
1677             message = unicode(verify_message.toPlainText())
1678             message = message.encode('utf-8')
1679             if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1680                 self.show_message(_("Signature verified"))
1681             else:
1682                 self.show_message(_("Error: wrong signature"))
1683
1684         hbox = QHBoxLayout()
1685         b = QPushButton(_("Verify"))
1686         b.clicked.connect(do_verify)
1687         hbox.addWidget(b)
1688         b = QPushButton(_("Close"))
1689         b.clicked.connect(d.accept)
1690         hbox.addWidget(b)
1691         layout.addLayout(hbox, 4, 1)
1692         tab_widget.addTab(tab, _("Verify"))
1693
1694         vbox = QVBoxLayout()
1695         vbox.addWidget(tab_widget)
1696         d.setLayout(vbox)
1697         d.exec_()
1698
1699         
1700
1701
1702     def question(self, msg):
1703         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1704
1705     def show_message(self, msg):
1706         QMessageBox.information(self, _('Message'), msg, _('OK'))
1707
1708     def password_dialog(self ):
1709         d = QDialog(self)
1710         d.setModal(1)
1711
1712         pw = QLineEdit()
1713         pw.setEchoMode(2)
1714
1715         vbox = QVBoxLayout()
1716         msg = _('Please enter your password')
1717         vbox.addWidget(QLabel(msg))
1718
1719         grid = QGridLayout()
1720         grid.setSpacing(8)
1721         grid.addWidget(QLabel(_('Password')), 1, 0)
1722         grid.addWidget(pw, 1, 1)
1723         vbox.addLayout(grid)
1724
1725         vbox.addLayout(ok_cancel_buttons(d))
1726         d.setLayout(vbox)
1727
1728         run_hook('password_dialog', pw, grid, 1)
1729         if not d.exec_(): return
1730         return unicode(pw.text())
1731
1732
1733
1734
1735
1736
1737
1738
1739     def tx_from_text(self, txt):
1740         "json or raw hexadecimal"
1741         try:
1742             txt.decode('hex')
1743             tx = Transaction(txt)
1744             return tx
1745         except:
1746             pass
1747
1748         try:
1749             tx_dict = json.loads(str(txt))
1750             assert "hex" in tx_dict.keys()
1751             assert "complete" in tx_dict.keys()
1752             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1753             if not tx_dict["complete"]:
1754                 assert "input_info" in tx_dict.keys()
1755                 input_info = json.loads(tx_dict['input_info'])
1756                 tx.add_input_info(input_info)
1757             return tx
1758         except:
1759             pass
1760         
1761         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1762
1763
1764
1765     def read_tx_from_file(self):
1766         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1767         if not fileName:
1768             return
1769         try:
1770             with open(fileName, "r") as f:
1771                 file_content = f.read()
1772         except (ValueError, IOError, os.error), reason:
1773             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1774
1775         return self.tx_from_text(file_content)
1776
1777
1778     @protected
1779     def sign_raw_transaction(self, tx, input_info, password):
1780         self.wallet.signrawtransaction(tx, input_info, [], password)
1781
1782     def do_process_from_text(self):
1783         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1784         if not text:
1785             return
1786         tx = self.tx_from_text(text)
1787         if tx:
1788             self.show_transaction(tx)
1789
1790     def do_process_from_file(self):
1791         tx = self.read_tx_from_file()
1792         if tx:
1793             self.show_transaction(tx)
1794
1795     def do_process_from_csvReader(self, csvReader):
1796         outputs = []
1797         try:
1798             for row in csvReader:
1799                 address = row[0]
1800                 amount = float(row[1])
1801                 amount = int(100000000*amount)
1802                 outputs.append((address, amount))
1803         except (ValueError, IOError, os.error), reason:
1804             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1805             return
1806
1807         try:
1808             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1809         except BaseException, e:
1810             self.show_message(str(e))
1811             return
1812
1813         self.show_transaction(tx)
1814
1815     def do_process_from_csv_file(self):
1816         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1817         if not fileName:
1818             return
1819         try:
1820             with open(fileName, "r") as f:
1821                 csvReader = csv.reader(f)
1822                 self.do_process_from_csvReader(csvReader)
1823         except (ValueError, IOError, os.error), reason:
1824             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1825             return
1826
1827     def do_process_from_csv_text(self):
1828         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1829                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1830         if not text:
1831             return
1832         f = StringIO.StringIO(text)
1833         csvReader = csv.reader(f)
1834         self.do_process_from_csvReader(csvReader)
1835
1836
1837
1838     @protected
1839     def do_export_privkeys(self, password):
1840         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.")))
1841
1842         try:
1843             select_export = _('Select file to export your private keys to')
1844             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1845             if fileName:
1846                 with open(fileName, "w+") as csvfile:
1847                     transaction = csv.writer(csvfile)
1848                     transaction.writerow(["address", "private_key"])
1849
1850                     addresses = self.wallet.addresses(True)
1851                     
1852                     for addr in addresses:
1853                         pk = "".join(self.wallet.get_private_key(addr, password))
1854                         transaction.writerow(["%34s"%addr,pk])
1855
1856                     self.show_message(_("Private keys exported."))
1857
1858         except (IOError, os.error), reason:
1859             export_error_label = _("Electrum was unable to produce a private key-export.")
1860             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1861
1862         except BaseException, e:
1863           self.show_message(str(e))
1864           return
1865
1866
1867     def do_import_labels(self):
1868         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1869         if not labelsFile: return
1870         try:
1871             f = open(labelsFile, 'r')
1872             data = f.read()
1873             f.close()
1874             for key, value in json.loads(data).items():
1875                 self.wallet.set_label(key, value)
1876             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1877         except (IOError, os.error), reason:
1878             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1879             
1880
1881     def do_export_labels(self):
1882         labels = self.wallet.labels
1883         try:
1884             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1885             if fileName:
1886                 with open(fileName, 'w+') as f:
1887                     json.dump(labels, f)
1888                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1889         except (IOError, os.error), reason:
1890             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1891
1892
1893     def do_export_history(self):
1894         from lite_window import csv_transaction
1895         csv_transaction(self.wallet)
1896
1897
1898     @protected
1899     def do_import_privkey(self, password):
1900         if not self.wallet.imported_keys:
1901             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1902                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1903                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1904             if r == 4: return
1905
1906         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1907         if not text: return
1908
1909         text = str(text).split()
1910         badkeys = []
1911         addrlist = []
1912         for key in text:
1913             try:
1914                 addr = self.wallet.import_key(key, password)
1915             except BaseException as e:
1916                 badkeys.append(key)
1917                 continue
1918             if not addr: 
1919                 badkeys.append(key)
1920             else:
1921                 addrlist.append(addr)
1922         if addrlist:
1923             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1924         if badkeys:
1925             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1926         self.update_receive_tab()
1927         self.update_history_tab()
1928
1929
1930     def settings_dialog(self):
1931         d = QDialog(self)
1932         d.setWindowTitle(_('Electrum Settings'))
1933         d.setModal(1)
1934         vbox = QVBoxLayout()
1935         grid = QGridLayout()
1936         grid.setColumnStretch(0,1)
1937
1938         nz_label = QLabel(_('Display zeros') + ':')
1939         grid.addWidget(nz_label, 0, 0)
1940         nz_e = AmountEdit(None,True)
1941         nz_e.setText("%d"% self.num_zeros)
1942         grid.addWidget(nz_e, 0, 1)
1943         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1944         grid.addWidget(HelpButton(msg), 0, 2)
1945         if not self.config.is_modifiable('num_zeros'):
1946             for w in [nz_e, nz_label]: w.setEnabled(False)
1947         
1948         lang_label=QLabel(_('Language') + ':')
1949         grid.addWidget(lang_label, 1, 0)
1950         lang_combo = QComboBox()
1951         from electrum.i18n import languages
1952         lang_combo.addItems(languages.values())
1953         try:
1954             index = languages.keys().index(self.config.get("language",''))
1955         except:
1956             index = 0
1957         lang_combo.setCurrentIndex(index)
1958         grid.addWidget(lang_combo, 1, 1)
1959         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1960         if not self.config.is_modifiable('language'):
1961             for w in [lang_combo, lang_label]: w.setEnabled(False)
1962
1963         
1964         fee_label = QLabel(_('Transaction fee') + ':')
1965         grid.addWidget(fee_label, 2, 0)
1966         fee_e = AmountEdit(self.base_unit)
1967         fee_e.setText(self.format_amount(self.wallet.fee).strip())
1968         grid.addWidget(fee_e, 2, 1)
1969         msg = _('Fee per kilobyte of transaction.') + ' ' \
1970             + _('Recommended value') + ': ' + self.format_amount(50000)
1971         grid.addWidget(HelpButton(msg), 2, 2)
1972         if not self.config.is_modifiable('fee_per_kb'):
1973             for w in [fee_e, fee_label]: w.setEnabled(False)
1974
1975         units = ['BTC', 'mBTC']
1976         unit_label = QLabel(_('Base unit') + ':')
1977         grid.addWidget(unit_label, 3, 0)
1978         unit_combo = QComboBox()
1979         unit_combo.addItems(units)
1980         unit_combo.setCurrentIndex(units.index(self.base_unit()))
1981         grid.addWidget(unit_combo, 3, 1)
1982         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
1983                                              + '\n1BTC=1000mBTC.\n' \
1984                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
1985
1986         usechange_cb = QCheckBox(_('Use change addresses'))
1987         usechange_cb.setChecked(self.wallet.use_change)
1988         grid.addWidget(usechange_cb, 4, 0)
1989         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
1990         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1991
1992         grid.setRowStretch(5,1)
1993
1994         vbox.addLayout(grid)
1995         vbox.addLayout(ok_cancel_buttons(d))
1996         d.setLayout(vbox) 
1997
1998         # run the dialog
1999         if not d.exec_(): return
2000
2001         fee = unicode(fee_e.text())
2002         try:
2003             fee = self.read_amount(fee)
2004         except:
2005             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2006             return
2007
2008         self.wallet.set_fee(fee)
2009         
2010         nz = unicode(nz_e.text())
2011         try:
2012             nz = int( nz )
2013             if nz>8: nz=8
2014         except:
2015             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2016             return
2017
2018         if self.num_zeros != nz:
2019             self.num_zeros = nz
2020             self.config.set_key('num_zeros', nz, True)
2021             self.update_history_tab()
2022             self.update_receive_tab()
2023
2024         usechange_result = usechange_cb.isChecked()
2025         if self.wallet.use_change != usechange_result:
2026             self.wallet.use_change = usechange_result
2027             self.config.set_key('use_change', self.wallet.use_change, True)
2028         
2029         unit_result = units[unit_combo.currentIndex()]
2030         if self.base_unit() != unit_result:
2031             self.decimal_point = 8 if unit_result == 'BTC' else 5
2032             self.config.set_key('decimal_point', self.decimal_point, True)
2033             self.update_history_tab()
2034             self.update_status()
2035         
2036         need_restart = False
2037
2038         lang_request = languages.keys()[lang_combo.currentIndex()]
2039         if lang_request != self.config.get('language'):
2040             self.config.set_key("language", lang_request, True)
2041             need_restart = True
2042             
2043         run_hook('close_settings_dialog')
2044
2045         if need_restart:
2046             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2047
2048
2049     def run_network_dialog(self):
2050         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2051
2052     def closeEvent(self, event):
2053         g = self.geometry()
2054         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2055         self.save_column_widths()
2056         self.config.set_key("console-history", self.console.history[-50:], True)
2057         self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2058         event.accept()
2059
2060
2061
2062     def plugins_dialog(self):
2063         from electrum.plugins import plugins
2064
2065         d = QDialog(self)
2066         d.setWindowTitle(_('Electrum Plugins'))
2067         d.setModal(1)
2068
2069         vbox = QVBoxLayout(d)
2070
2071         # plugins
2072         scroll = QScrollArea()
2073         scroll.setEnabled(True)
2074         scroll.setWidgetResizable(True)
2075         scroll.setMinimumSize(400,250)
2076         vbox.addWidget(scroll)
2077
2078         w = QWidget()
2079         scroll.setWidget(w)
2080         w.setMinimumHeight(len(plugins)*35)
2081
2082         grid = QGridLayout()
2083         grid.setColumnStretch(0,1)
2084         w.setLayout(grid)
2085
2086         def mk_toggle(cb, p):
2087             return lambda: cb.setChecked(p.toggle())
2088         for i, p in enumerate(plugins):
2089             try:
2090                 cb = QCheckBox(p.fullname())
2091                 cb.setDisabled(not p.is_available())
2092                 cb.setChecked(p.is_enabled())
2093                 cb.clicked.connect(mk_toggle(cb,p))
2094                 grid.addWidget(cb, i, 0)
2095                 if p.requires_settings():
2096                     b = EnterButton(_('Settings'), p.settings_dialog)
2097                     b.setEnabled( p.is_enabled() )
2098                     grid.addWidget(b, i, 1)
2099                 grid.addWidget(HelpButton(p.description()), i, 2)
2100             except:
2101                 print_msg(_("Error: cannot display plugin"), p)
2102                 traceback.print_exc(file=sys.stdout)
2103         grid.setRowStretch(i+1,1)
2104
2105         vbox.addLayout(close_button(d))
2106
2107         d.exec_()
2108
2109
2110     def show_account_details(self, k):
2111         d = QDialog(self)
2112         d.setWindowTitle(_('Account Details'))
2113         d.setModal(1)
2114
2115         vbox = QVBoxLayout(d)
2116         roots = self.wallet.get_roots(k)
2117
2118         name = self.wallet.get_account_name(k)
2119         label = QLabel('Name: ' + name)
2120         vbox.addWidget(label)
2121
2122         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2123         vbox.addWidget(QLabel('Type: ' + acctype))
2124
2125         label = QLabel('Derivation: ' + k)
2126         vbox.addWidget(label)
2127
2128         #for root in roots:
2129         #    mpk = self.wallet.master_public_keys[root]
2130         #    text = QTextEdit()
2131         #    text.setReadOnly(True)
2132         #    text.setMaximumHeight(120)
2133         #    text.setText(repr(mpk))
2134         #    vbox.addWidget(text)
2135
2136         vbox.addLayout(close_button(d))
2137         d.exec_()