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