set label
[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', 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     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
493     def getOpenFileName(self, title, filter = ""):
494         directory = self.config.get('io_dir', os.path.expanduser('~'))
495         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
496         if fileName and directory != os.path.dirname(fileName):
497             self.config.set_key('io_dir', os.path.dirname(fileName), True)
498         return fileName
499
500     def getSaveFileName(self, title, filename, filter = ""):
501         directory = self.config.get('io_dir', os.path.expanduser('~'))
502         path = os.path.join( directory, filename )
503         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
504         if fileName and directory != os.path.dirname(fileName):
505             self.config.set_key('io_dir', os.path.dirname(fileName), True)
506         return fileName
507
508     def close(self):
509         QMainWindow.close(self)
510         run_hook('close_main_window')
511
512     def connect_slots(self, sender):
513         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
514         self.previous_payto_e=''
515
516     def timer_actions(self):
517         if self.need_update.is_set():
518             self.update_wallet()
519             self.need_update.clear()
520         run_hook('timer_actions')
521     
522     def format_amount(self, x, is_diff=False, whitespaces=False):
523         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
524
525     def read_amount(self, x):
526         if x in['.', '']: return None
527         p = pow(10, self.decimal_point)
528         return int( p * Decimal(x) )
529
530     def base_unit(self):
531         assert self.decimal_point in [5,8]
532         return "BTC" if self.decimal_point == 8 else "mBTC"
533
534
535     def update_status(self):
536         if self.network.interface and self.network.interface.is_connected:
537             if not self.wallet.up_to_date:
538                 text = _("Synchronizing...")
539                 icon = QIcon(":icons/status_waiting.png")
540             else:
541                 c, u = self.wallet.get_account_balance(self.current_account)
542                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
543                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
544
545                 r = {}
546                 run_hook('set_quote_text', c+u, r)
547                 quote = r.get(0)
548                 if quote:
549                     text += "  (%s)"%quote
550
551                 self.tray.setToolTip(text)
552                 icon = QIcon(":icons/status_connected.png")
553         else:
554             text = _("Not connected")
555             icon = QIcon(":icons/status_disconnected.png")
556
557         self.balance_label.setText(text)
558         self.status_button.setIcon( icon )
559
560
561     def update_wallet(self):
562         self.update_status()
563         if self.wallet.up_to_date or not self.network.interface.is_connected:
564             self.update_history_tab()
565             self.update_receive_tab()
566             self.update_contacts_tab()
567             self.update_completions()
568
569         
570     def create_history_tab(self):
571         self.history_list = l = MyTreeWidget(self)
572         l.setColumnCount(5)
573         for i,width in enumerate(self.column_widths['history']):
574             l.setColumnWidth(i, width)
575         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
576         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
577         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
578
579         l.customContextMenuRequested.connect(self.create_history_menu)
580         return l
581
582
583     def create_history_menu(self, position):
584         self.history_list.selectedIndexes() 
585         item = self.history_list.currentItem()
586         if not item: return
587         tx_hash = str(item.data(0, Qt.UserRole).toString())
588         if not tx_hash: return
589         menu = QMenu()
590         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
591         menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
592         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
593         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
594
595
596     def show_transaction(self, tx):
597         import transaction_dialog
598         d = transaction_dialog.TxDialog(tx, self)
599         d.exec_()
600
601     def tx_label_clicked(self, item, column):
602         if column==2 and item.isSelected():
603             self.is_edit=True
604             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
605             self.history_list.editItem( item, column )
606             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
607             self.is_edit=False
608
609     def tx_label_changed(self, item, column):
610         if self.is_edit: 
611             return
612         self.is_edit=True
613         tx_hash = str(item.data(0, Qt.UserRole).toString())
614         tx = self.wallet.transactions.get(tx_hash)
615         text = unicode( item.text(2) )
616         self.wallet.set_label(tx_hash, text) 
617         if text: 
618             item.setForeground(2, QBrush(QColor('black')))
619         else:
620             text = self.wallet.get_default_label(tx_hash)
621             item.setText(2, text)
622             item.setForeground(2, QBrush(QColor('gray')))
623         self.is_edit=False
624
625
626     def edit_label(self, is_recv):
627         l = self.receive_list if is_recv else self.contacts_list
628         item = l.currentItem()
629         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
630         l.editItem( item, 1 )
631         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
632
633
634
635     def address_label_clicked(self, item, column, l, column_addr, column_label):
636         if column == column_label and item.isSelected():
637             is_editable = item.data(0, 32).toBool()
638             if not is_editable:
639                 return
640             addr = unicode( item.text(column_addr) )
641             label = unicode( item.text(column_label) )
642             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
643             l.editItem( item, column )
644             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
645
646
647     def address_label_changed(self, item, column, l, column_addr, column_label):
648         if column == column_label:
649             addr = unicode( item.text(column_addr) )
650             text = unicode( item.text(column_label) )
651             is_editable = item.data(0, 32).toBool()
652             if not is_editable:
653                 return
654
655             changed = self.wallet.set_label(addr, text)
656             if changed:
657                 self.update_history_tab()
658                 self.update_completions()
659                 
660             self.current_item_changed(item)
661
662         run_hook('item_changed', item, column)
663
664
665     def current_item_changed(self, a):
666         run_hook('current_item_changed', a)
667
668
669
670     def update_history_tab(self):
671
672         self.history_list.clear()
673         for item in self.wallet.get_tx_history(self.current_account):
674             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
675             if conf > 0:
676                 try:
677                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
678                 except:
679                     time_str = _("unknown")
680
681             if conf == -1:
682                 time_str = 'unverified'
683                 icon = QIcon(":icons/unconfirmed.png")
684             elif conf == 0:
685                 time_str = 'pending'
686                 icon = QIcon(":icons/unconfirmed.png")
687             elif conf < 6:
688                 icon = QIcon(":icons/clock%d.png"%conf)
689             else:
690                 icon = QIcon(":icons/confirmed.png")
691
692             if value is not None:
693                 v_str = self.format_amount(value, True, whitespaces=True)
694             else:
695                 v_str = '--'
696
697             balance_str = self.format_amount(balance, whitespaces=True)
698             
699             if tx_hash:
700                 label, is_default_label = self.wallet.get_label(tx_hash)
701             else:
702                 label = _('Pruned transaction outputs')
703                 is_default_label = False
704
705             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
706             item.setFont(2, QFont(MONOSPACE_FONT))
707             item.setFont(3, QFont(MONOSPACE_FONT))
708             item.setFont(4, QFont(MONOSPACE_FONT))
709             if value < 0:
710                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
711             if tx_hash:
712                 item.setData(0, Qt.UserRole, tx_hash)
713                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
714             if is_default_label:
715                 item.setForeground(2, QBrush(QColor('grey')))
716
717             item.setIcon(0, icon)
718             self.history_list.insertTopLevelItem(0,item)
719             
720
721         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
722
723
724     def create_send_tab(self):
725         w = QWidget()
726
727         grid = QGridLayout()
728         grid.setSpacing(8)
729         grid.setColumnMinimumWidth(3,300)
730         grid.setColumnStretch(5,1)
731
732
733         self.payto_e = QLineEdit()
734         grid.addWidget(QLabel(_('Pay to')), 1, 0)
735         grid.addWidget(self.payto_e, 1, 1, 1, 3)
736             
737         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)
738
739         completer = QCompleter()
740         completer.setCaseSensitivity(False)
741         self.payto_e.setCompleter(completer)
742         completer.setModel(self.completions)
743
744         self.message_e = QLineEdit()
745         grid.addWidget(QLabel(_('Description')), 2, 0)
746         grid.addWidget(self.message_e, 2, 1, 1, 3)
747         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)
748
749         self.amount_e = AmountEdit(self.base_unit)
750         grid.addWidget(QLabel(_('Amount')), 3, 0)
751         grid.addWidget(self.amount_e, 3, 1, 1, 2)
752         grid.addWidget(HelpButton(
753                 _('Amount to be sent.') + '\n\n' \
754                     + _('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.') \
755                     + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
756         
757         self.fee_e = AmountEdit(self.base_unit)
758         grid.addWidget(QLabel(_('Fee')), 4, 0)
759         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
760         grid.addWidget(HelpButton(
761                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
762                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
763                     + _('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)
764
765
766         self.send_button = EnterButton(_("Send"), self.do_send)
767         grid.addWidget(self.send_button, 6, 1)
768
769         b = EnterButton(_("Clear"),self.do_clear)
770         grid.addWidget(b, 6, 2)
771
772         self.payto_sig = QLabel('')
773         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
774
775         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
776         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
777         w.setLayout(grid) 
778
779         w2 = QWidget()
780         vbox = QVBoxLayout()
781         vbox.addWidget(w)
782         vbox.addStretch(1)
783         w2.setLayout(vbox)
784
785         def entry_changed( is_fee ):
786             self.funds_error = False
787
788             if self.amount_e.is_shortcut:
789                 self.amount_e.is_shortcut = False
790                 c, u = self.wallet.get_account_balance(self.current_account)
791                 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
792                 fee = self.wallet.estimated_fee(inputs)
793                 amount = c + u - fee
794                 self.amount_e.setText( self.format_amount(amount) )
795                 self.fee_e.setText( self.format_amount( fee ) )
796                 return
797                 
798             amount = self.read_amount(str(self.amount_e.text()))
799             fee = self.read_amount(str(self.fee_e.text()))
800
801             if not is_fee: fee = None
802             if amount is None:
803                 return
804             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
805             if not is_fee:
806                 self.fee_e.setText( self.format_amount( fee ) )
807             if inputs:
808                 palette = QPalette()
809                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
810                 text = ""
811             else:
812                 palette = QPalette()
813                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
814                 self.funds_error = True
815                 text = _( "Not enough funds" )
816                 c, u = self.wallet.get_frozen_balance()
817                 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
818
819             self.statusBar().showMessage(text)
820             self.amount_e.setPalette(palette)
821             self.fee_e.setPalette(palette)
822
823         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
824         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
825
826         run_hook('create_send_tab', grid)
827         return w2
828
829
830     def update_completions(self):
831         l = []
832         for addr,label in self.wallet.labels.items():
833             if addr in self.wallet.addressbook:
834                 l.append( label + '  <' + addr + '>')
835
836         run_hook('update_completions', l)
837         self.completions.setStringList(l)
838
839
840     def protected(func):
841         return lambda s, *args: s.do_protect(func, args)
842
843
844     def do_send(self):
845
846         label = unicode( self.message_e.text() )
847         r = unicode( self.payto_e.text() )
848         r = r.strip()
849
850         # label or alias, with address in brackets
851         m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
852         to_address = m.group(2) if m else r
853
854         if not is_valid(to_address):
855             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
856             return
857
858         try:
859             amount = self.read_amount(unicode( self.amount_e.text()))
860         except:
861             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
862             return
863         try:
864             fee = self.read_amount(unicode( self.fee_e.text()))
865         except:
866             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
867             return
868
869         confirm_amount = self.config.get('confirm_amount', 100000000)
870         if amount >= confirm_amount:
871             if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
872                 return
873
874         self.send_tx(to_address, amount, fee, label)
875
876
877     @protected
878     def send_tx(self, to_address, amount, fee, label, password):
879
880         try:
881             tx = self.wallet.mktx_from_account( [(to_address, amount)], password, fee, self.current_account)
882         except BaseException, e:
883             traceback.print_exc(file=sys.stdout)
884             self.show_message(str(e))
885             return
886
887         if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
888             QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
889             return
890
891         if label: 
892             self.wallet.set_label(tx.hash(), label)
893
894         if tx.is_complete:
895             h = self.wallet.send_tx(tx)
896             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
897             status, msg = self.wallet.receive_tx( h )
898             if status:
899                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
900                 self.do_clear()
901                 self.update_contacts_tab()
902             else:
903                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
904         else:
905             filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
906             try:
907                 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
908                 with open(fileName,'w') as f:
909                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
910                 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
911             except:
912                 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
913
914         # add recipient to addressbook
915         if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
916             self.wallet.addressbook.append(to_address)
917
918
919
920
921     def set_url(self, url):
922         address, amount, label, message, signature, identity, url = util.parse_url(url)
923
924         if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
925
926         if self.mini:
927             self.mini.set_payment_fields(address, amount)
928
929         if label and self.wallet.labels.get(address) != label:
930             if self.question('Give label "%s" to address %s ?'%(label,address)):
931                 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
932                     self.wallet.addressbook.append(address)
933                 self.wallet.set_label(address, label)
934
935         run_hook('set_url', url, self.show_message, self.question)
936
937         self.tabs.setCurrentIndex(1)
938         label = self.wallet.labels.get(address)
939         m_addr = label + '  <'+ address +'>' if label else address
940         self.payto_e.setText(m_addr)
941
942         self.message_e.setText(message)
943         if amount:
944             self.amount_e.setText(amount)
945
946         if identity:
947             self.set_frozen(self.payto_e,True)
948             self.set_frozen(self.amount_e,True)
949             self.set_frozen(self.message_e,True)
950             self.payto_sig.setText( '      '+_('The bitcoin URI was signed by')+' ' + identity )
951         else:
952             self.payto_sig.setVisible(False)
953
954     def do_clear(self):
955         self.payto_sig.setVisible(False)
956         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
957             e.setText('')
958             self.set_frozen(e,False)
959         self.update_status()
960
961     def set_frozen(self,entry,frozen):
962         if frozen:
963             entry.setReadOnly(True)
964             entry.setFrame(False)
965             palette = QPalette()
966             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
967             entry.setPalette(palette)
968         else:
969             entry.setReadOnly(False)
970             entry.setFrame(True)
971             palette = QPalette()
972             palette.setColor(entry.backgroundRole(), QColor('white'))
973             entry.setPalette(palette)
974
975
976     def toggle_freeze(self,addr):
977         if not addr: return
978         if addr in self.wallet.frozen_addresses:
979             self.wallet.unfreeze(addr)
980         else:
981             self.wallet.freeze(addr)
982         self.update_receive_tab()
983
984     def toggle_priority(self,addr):
985         if not addr: return
986         if addr in self.wallet.prioritized_addresses:
987             self.wallet.unprioritize(addr)
988         else:
989             self.wallet.prioritize(addr)
990         self.update_receive_tab()
991
992
993     def create_list_tab(self, headers):
994         "generic tab creation method"
995         l = MyTreeWidget(self)
996         l.setColumnCount( len(headers) )
997         l.setHeaderLabels( headers )
998
999         w = QWidget()
1000         vbox = QVBoxLayout()
1001         w.setLayout(vbox)
1002
1003         vbox.setMargin(0)
1004         vbox.setSpacing(0)
1005         vbox.addWidget(l)
1006         buttons = QWidget()
1007         vbox.addWidget(buttons)
1008
1009         hbox = QHBoxLayout()
1010         hbox.setMargin(0)
1011         hbox.setSpacing(0)
1012         buttons.setLayout(hbox)
1013
1014         return l,w,hbox
1015
1016
1017     def create_receive_tab(self):
1018         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1019         l.setContextMenuPolicy(Qt.CustomContextMenu)
1020         l.customContextMenuRequested.connect(self.create_receive_menu)
1021         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1022         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1023         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1024         self.receive_list = l
1025         self.receive_buttons_hbox = hbox
1026         hbox.addStretch(1)
1027         return w
1028
1029
1030     def receive_tab_set_mode(self, i):
1031         self.save_column_widths()
1032         self.expert_mode = (i == 1)
1033         self.config.set_key('classic_expert_mode', self.expert_mode, True)
1034         self.update_receive_tab()
1035
1036
1037     def save_column_widths(self):
1038         if not self.expert_mode:
1039             widths = [ self.receive_list.columnWidth(0) ]
1040         else:
1041             widths = []
1042             for i in range(self.receive_list.columnCount() -1):
1043                 widths.append(self.receive_list.columnWidth(i))
1044         self.column_widths["receive"][self.expert_mode] = widths
1045         
1046         self.column_widths["history"] = []
1047         for i in range(self.history_list.columnCount() - 1):
1048             self.column_widths["history"].append(self.history_list.columnWidth(i))
1049
1050         self.column_widths["contacts"] = []
1051         for i in range(self.contacts_list.columnCount() - 1):
1052             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1053
1054         self.config.set_key("column_widths", self.column_widths, True)
1055
1056
1057     def create_contacts_tab(self):
1058         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1059         l.setContextMenuPolicy(Qt.CustomContextMenu)
1060         l.customContextMenuRequested.connect(self.create_contact_menu)
1061         for i,width in enumerate(self.column_widths['contacts']):
1062             l.setColumnWidth(i, width)
1063
1064         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1065         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1066         self.contacts_list = l
1067         self.contacts_buttons_hbox = hbox
1068         hbox.addStretch(1)
1069         return w
1070
1071
1072     def delete_imported_key(self, addr):
1073         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1074             self.wallet.delete_imported_key(addr)
1075             self.update_receive_tab()
1076             self.update_history_tab()
1077
1078     def edit_account_label(self, k):
1079         text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':')
1080         if ok:
1081             label = unicode(text)
1082             self.wallet.set_label(k,label)
1083             self.update_receive_tab()
1084
1085     def create_account_menu(self, position, k, item):
1086         menu = QMenu()
1087         if item.isExpanded():
1088             menu.addAction(_("Minimize"), lambda: item.setExpanded(False))
1089         else:
1090             menu.addAction(_("Maximize"), lambda: item.setExpanded(True))
1091         menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1092         menu.addAction(_("View details"), lambda: self.show_account_details(k))
1093         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1094
1095     def create_receive_menu(self, position):
1096         # fixme: this function apparently has a side effect.
1097         # if it is not called the menu pops up several times
1098         #self.receive_list.selectedIndexes() 
1099
1100         item = self.receive_list.itemAt(position)
1101         if not item: return
1102
1103         addr = unicode(item.text(0))
1104         if not is_valid(addr): 
1105             k = str(item.data(0,32).toString())
1106             if k:
1107                 self.create_account_menu(position, k, item)
1108             else:
1109                 item.setExpanded(not item.isExpanded())
1110             return 
1111
1112         menu = QMenu()
1113         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1114         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1115         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1116         menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1117         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1118         if addr in self.wallet.imported_keys:
1119             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1120
1121         if self.expert_mode:
1122             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1123             menu.addAction(t, lambda: self.toggle_freeze(addr))
1124             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1125             menu.addAction(t, lambda: self.toggle_priority(addr))
1126             
1127         run_hook('receive_menu', menu)
1128         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1129
1130
1131     def payto(self, addr):
1132         if not addr: return
1133         label = self.wallet.labels.get(addr)
1134         m_addr = label + '  <' + addr + '>' if label else addr
1135         self.tabs.setCurrentIndex(1)
1136         self.payto_e.setText(m_addr)
1137         self.amount_e.setFocus()
1138
1139
1140     def delete_contact(self, x):
1141         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1142             self.wallet.delete_contact(x)
1143             self.wallet.set_label(x, None)
1144             self.update_history_tab()
1145             self.update_contacts_tab()
1146             self.update_completions()
1147
1148
1149     def create_contact_menu(self, position):
1150         item = self.contacts_list.itemAt(position)
1151         if not item: return
1152         addr = unicode(item.text(0))
1153         label = unicode(item.text(1))
1154         is_editable = item.data(0,32).toBool()
1155         payto_addr = item.data(0,33).toString()
1156         menu = QMenu()
1157         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1158         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1159         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1160         if is_editable:
1161             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1162             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1163
1164         run_hook('create_contact_menu', menu, item)
1165         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1166
1167
1168     def update_receive_item(self, item):
1169         item.setFont(0, QFont(MONOSPACE_FONT))
1170         address = str(item.data(0,0).toString())
1171         label = self.wallet.labels.get(address,'')
1172         item.setData(1,0,label)
1173         item.setData(0,32, True) # is editable
1174
1175         run_hook('update_receive_item', address, item)
1176                 
1177         c, u = self.wallet.get_addr_balance(address)
1178         balance = self.format_amount(c + u)
1179         item.setData(2,0,balance)
1180
1181         if self.expert_mode:
1182             if address in self.wallet.frozen_addresses: 
1183                 item.setBackgroundColor(0, QColor('lightblue'))
1184             elif address in self.wallet.prioritized_addresses: 
1185                 item.setBackgroundColor(0, QColor('lightgreen'))
1186         
1187
1188     def update_receive_tab(self):
1189         l = self.receive_list
1190         
1191         l.clear()
1192         l.setColumnHidden(2, not self.expert_mode)
1193         l.setColumnHidden(3, not self.expert_mode)
1194         for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1195             l.setColumnWidth(i, width)
1196
1197         if self.current_account is None:
1198             account_items = self.wallet.accounts.items()
1199         elif self.current_account != -1:
1200             account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1201         else:
1202             account_items = []
1203
1204         for k, account in account_items:
1205             name = self.wallet.get_account_name(k)
1206             c,u = self.wallet.get_account_balance(k)
1207             account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1208             l.addTopLevelItem(account_item)
1209             account_item.setExpanded(True)
1210             account_item.setData(0, 32, k)
1211
1212             if not self.wallet.is_seeded(k):
1213                 icon = QIcon(":icons/key.png")
1214                 account_item.setIcon(0, icon)
1215             
1216             for is_change in ([0,1] if self.expert_mode else [0]):
1217                 if self.expert_mode:
1218                     name = _("Receiving") if not is_change else _("Change")
1219                     seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1220                     account_item.addChild(seq_item)
1221                     if not is_change: seq_item.setExpanded(True)
1222                 else:
1223                     seq_item = account_item
1224                 is_red = False
1225                 gap = 0
1226
1227                 for address in account.get_addresses(is_change):
1228                     h = self.wallet.history.get(address,[])
1229             
1230                     if h == []:
1231                         gap += 1
1232                         if gap > self.wallet.gap_limit:
1233                             is_red = True
1234                     else:
1235                         gap = 0
1236
1237                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1238                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1239                     self.update_receive_item(item)
1240                     if is_red:
1241                         item.setBackgroundColor(1, QColor('red'))
1242                     seq_item.addChild(item)
1243
1244
1245         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1246             c,u = self.wallet.get_imported_balance()
1247             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1248             l.addTopLevelItem(account_item)
1249             account_item.setExpanded(True)
1250             for address in self.wallet.imported_keys.keys():
1251                 item = QTreeWidgetItem( [ address, '', '', ''] )
1252                 self.update_receive_item(item)
1253                 account_item.addChild(item)
1254                 
1255
1256         # we use column 1 because column 0 may be hidden
1257         l.setCurrentItem(l.topLevelItem(0),1)
1258
1259
1260     def update_contacts_tab(self):
1261         l = self.contacts_list
1262         l.clear()
1263
1264         for address in self.wallet.addressbook:
1265             label = self.wallet.labels.get(address,'')
1266             n = self.wallet.get_num_tx(address)
1267             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1268             item.setFont(0, QFont(MONOSPACE_FONT))
1269             # 32 = label can be edited (bool)
1270             item.setData(0,32, True)
1271             # 33 = payto string
1272             item.setData(0,33, address)
1273             l.addTopLevelItem(item)
1274
1275         run_hook('update_contacts_tab', l)
1276         l.setCurrentItem(l.topLevelItem(0))
1277
1278
1279
1280     def create_console_tab(self):
1281         from console import Console
1282         self.console = console = Console()
1283         return console
1284
1285
1286     def update_console(self):
1287         console = self.console
1288         console.history = self.config.get("console-history",[])
1289         console.history_index = len(console.history)
1290
1291         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1292         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1293
1294         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1295         methods = {}
1296         def mkfunc(f, method):
1297             return lambda *args: apply( f, (method, args, self.password_dialog ))
1298         for m in dir(c):
1299             if m[0]=='_' or m=='wallet' or m == 'interface': continue
1300             methods[m] = mkfunc(c._run, m)
1301             
1302         console.updateNamespace(methods)
1303
1304
1305     def change_account(self,s):
1306         if s == _("All accounts"):
1307             self.current_account = None
1308         else:
1309             accounts = self.wallet.get_account_names()
1310             for k, v in accounts.items():
1311                 if v == s:
1312                     self.current_account = k
1313         self.update_history_tab()
1314         self.update_status()
1315         self.update_receive_tab()
1316
1317     def create_status_bar(self):
1318
1319         sb = QStatusBar()
1320         sb.setFixedHeight(35)
1321         qtVersion = qVersion()
1322
1323         self.balance_label = QLabel("")
1324         sb.addWidget(self.balance_label)
1325
1326         from version_getter import UpdateLabel
1327         self.updatelabel = UpdateLabel(self.config, sb)
1328
1329         self.account_selector = QComboBox()
1330         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account) 
1331         sb.addPermanentWidget(self.account_selector)
1332
1333         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1334             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1335
1336         self.lock_icon = QIcon()
1337         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1338         sb.addPermanentWidget( self.password_button )
1339             
1340         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1341         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) 
1342         sb.addPermanentWidget( self.seed_button )
1343         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) 
1344         sb.addPermanentWidget( self.status_button )
1345
1346         run_hook('create_status_bar', (sb,))
1347
1348         self.setStatusBar(sb)
1349
1350
1351     def update_lock_icon(self):
1352         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1353         self.password_button.setIcon( icon )
1354
1355
1356     def update_buttons_on_seed(self):
1357         if self.wallet.seed:
1358            self.seed_button.show()
1359            self.password_button.show()
1360            self.send_button.setText(_("Send"))
1361         else:
1362            self.password_button.hide()
1363            self.seed_button.hide()
1364            self.send_button.setText(_("Create unsigned transaction"))
1365
1366
1367     def change_password_dialog(self):
1368         from password_dialog import PasswordDialog
1369         d = PasswordDialog(self.wallet, self)
1370         d.run()
1371         self.update_lock_icon()
1372
1373
1374     def new_contact_dialog(self):
1375         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1376         address = unicode(text)
1377         if ok:
1378             if is_valid(address):
1379                 self.wallet.add_contact(address)
1380                 self.update_contacts_tab()
1381                 self.update_history_tab()
1382                 self.update_completions()
1383             else:
1384                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1385
1386
1387     def new_account_dialog(self):
1388
1389         dialog = QDialog(self)
1390         dialog.setModal(1)
1391         dialog.setWindowTitle(_("New Account"))
1392
1393         addr = self.wallet.new_account_address()
1394         vbox = QVBoxLayout()
1395         msg = _("Electrum considers that an account exists only if it contains bitcoins.") + '\n' \
1396               + _("To create a new account, please send coins to the first address of that account.") + '\n' \
1397               + _("Note: you will need to wait for 2 confirmations before the account is created.")
1398         vbox.addWidget(QLabel(msg))
1399         vbox.addWidget(QLabel(_('Address')+':'))
1400         e = QLineEdit(addr)
1401         e.setReadOnly(True)
1402         vbox.addWidget(e)
1403
1404         vbox.addLayout(ok_cancel_buttons(dialog))
1405         dialog.setLayout(vbox)
1406         r = dialog.exec_()
1407         if r:
1408             self.payto(addr)
1409
1410             
1411
1412     def show_master_public_key(self):
1413         dialog = QDialog(self)
1414         dialog.setModal(1)
1415         dialog.setWindowTitle(_("Master Public Key"))
1416
1417         main_text = QTextEdit()
1418         main_text.setText(self.wallet.get_master_public_key())
1419         main_text.setReadOnly(True)
1420         main_text.setMaximumHeight(170)
1421         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1422
1423         ok_button = QPushButton(_("OK"))
1424         ok_button.setDefault(True)
1425         ok_button.clicked.connect(dialog.accept)
1426
1427         main_layout = QGridLayout()
1428         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1429
1430         main_layout.addWidget(main_text, 1, 0)
1431         main_layout.addWidget(qrw, 1, 1 )
1432
1433         vbox = QVBoxLayout()
1434         vbox.addLayout(main_layout)
1435         hbox = QHBoxLayout()
1436         hbox.addStretch(1)
1437         hbox.addWidget(ok_button)
1438         vbox.addLayout(hbox)
1439
1440         dialog.setLayout(vbox)
1441         dialog.exec_()
1442         
1443
1444     @protected
1445     def show_seed_dialog(self, password):
1446         if not self.wallet.seed:
1447             QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1448             return
1449         try:
1450             seed = self.wallet.decode_seed(password)
1451         except:
1452             QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1453             return
1454
1455         from seed_dialog import SeedDialog
1456         d = SeedDialog(self)
1457         d.show_seed(seed, self.wallet.imported_keys)
1458
1459
1460
1461     def show_qrcode(self, data, title = _("QR code")):
1462         if not data: return
1463         d = QDialog(self)
1464         d.setModal(1)
1465         d.setWindowTitle(title)
1466         d.setMinimumSize(270, 300)
1467         vbox = QVBoxLayout()
1468         qrw = QRCodeWidget(data)
1469         vbox.addWidget(qrw, 1)
1470         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1471         hbox = QHBoxLayout()
1472         hbox.addStretch(1)
1473
1474         def print_qr(self):
1475             filename = "qrcode.bmp"
1476             bmp.save_qrcode(qrw.qr, filename)
1477             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1478
1479         b = QPushButton(_("Save"))
1480         hbox.addWidget(b)
1481         b.clicked.connect(print_qr)
1482
1483         b = QPushButton(_("Close"))
1484         hbox.addWidget(b)
1485         b.clicked.connect(d.accept)
1486         b.setDefault(True)
1487
1488         vbox.addLayout(hbox)
1489         d.setLayout(vbox)
1490         d.exec_()
1491
1492
1493     def do_protect(self, func, args):
1494         if self.wallet.use_encryption:
1495             password = self.password_dialog()
1496             if not password:
1497                 return
1498         else:
1499             password = None
1500             
1501         if args != (False,):
1502             args = (self,) + args + (password,)
1503         else:
1504             args = (self,password)
1505         apply( func, args)
1506
1507
1508     @protected
1509     def show_private_key(self, address, password):
1510         if not address: return
1511         try:
1512             pk_list = self.wallet.get_private_key(address, password)
1513         except BaseException, e:
1514             self.show_message(str(e))
1515             return
1516         QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1517
1518
1519     @protected
1520     def do_sign(self, address, message, signature, password):
1521         message = unicode(message.toPlainText())
1522         message = message.encode('utf-8')
1523         try:
1524             sig = self.wallet.sign_message(str(address.text()), message, password)
1525             signature.setText(sig)
1526         except BaseException, e:
1527             self.show_message(str(e))
1528
1529     def sign_message(self, address):
1530         if not address: return
1531         d = QDialog(self)
1532         d.setModal(1)
1533         d.setWindowTitle(_('Sign Message'))
1534         d.setMinimumSize(410, 290)
1535
1536         tab_widget = QTabWidget()
1537         tab = QWidget()
1538         layout = QGridLayout(tab)
1539
1540         sign_address = QLineEdit()
1541
1542         sign_address.setText(address)
1543         layout.addWidget(QLabel(_('Address')), 1, 0)
1544         layout.addWidget(sign_address, 1, 1)
1545
1546         sign_message = QTextEdit()
1547         layout.addWidget(QLabel(_('Message')), 2, 0)
1548         layout.addWidget(sign_message, 2, 1)
1549         layout.setRowStretch(2,3)
1550
1551         sign_signature = QTextEdit()
1552         layout.addWidget(QLabel(_('Signature')), 3, 0)
1553         layout.addWidget(sign_signature, 3, 1)
1554         layout.setRowStretch(3,1)
1555
1556
1557         hbox = QHBoxLayout()
1558         b = QPushButton(_("Sign"))
1559         hbox.addWidget(b)
1560         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1561         b = QPushButton(_("Close"))
1562         b.clicked.connect(d.accept)
1563         hbox.addWidget(b)
1564         layout.addLayout(hbox, 4, 1)
1565         tab_widget.addTab(tab, _("Sign"))
1566
1567
1568         tab = QWidget()
1569         layout = QGridLayout(tab)
1570
1571         verify_address = QLineEdit()
1572         layout.addWidget(QLabel(_('Address')), 1, 0)
1573         layout.addWidget(verify_address, 1, 1)
1574
1575         verify_message = QTextEdit()
1576         layout.addWidget(QLabel(_('Message')), 2, 0)
1577         layout.addWidget(verify_message, 2, 1)
1578         layout.setRowStretch(2,3)
1579
1580         verify_signature = QTextEdit()
1581         layout.addWidget(QLabel(_('Signature')), 3, 0)
1582         layout.addWidget(verify_signature, 3, 1)
1583         layout.setRowStretch(3,1)
1584
1585         def do_verify():
1586             message = unicode(verify_message.toPlainText())
1587             message = message.encode('utf-8')
1588             if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1589                 self.show_message(_("Signature verified"))
1590             else:
1591                 self.show_message(_("Error: wrong signature"))
1592
1593         hbox = QHBoxLayout()
1594         b = QPushButton(_("Verify"))
1595         b.clicked.connect(do_verify)
1596         hbox.addWidget(b)
1597         b = QPushButton(_("Close"))
1598         b.clicked.connect(d.accept)
1599         hbox.addWidget(b)
1600         layout.addLayout(hbox, 4, 1)
1601         tab_widget.addTab(tab, _("Verify"))
1602
1603         vbox = QVBoxLayout()
1604         vbox.addWidget(tab_widget)
1605         d.setLayout(vbox)
1606         d.exec_()
1607
1608         
1609
1610
1611     def question(self, msg):
1612         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1613
1614     def show_message(self, msg):
1615         QMessageBox.information(self, _('Message'), msg, _('OK'))
1616
1617     def password_dialog(self ):
1618         d = QDialog(self)
1619         d.setModal(1)
1620
1621         pw = QLineEdit()
1622         pw.setEchoMode(2)
1623
1624         vbox = QVBoxLayout()
1625         msg = _('Please enter your password')
1626         vbox.addWidget(QLabel(msg))
1627
1628         grid = QGridLayout()
1629         grid.setSpacing(8)
1630         grid.addWidget(QLabel(_('Password')), 1, 0)
1631         grid.addWidget(pw, 1, 1)
1632         vbox.addLayout(grid)
1633
1634         vbox.addLayout(ok_cancel_buttons(d))
1635         d.setLayout(vbox)
1636
1637         run_hook('password_dialog', pw, grid, 1)
1638         if not d.exec_(): return
1639         return unicode(pw.text())
1640
1641
1642
1643
1644
1645
1646
1647
1648     def tx_from_text(self, txt):
1649         "json or raw hexadecimal"
1650         try:
1651             txt.decode('hex')
1652             tx = Transaction(txt)
1653             return tx
1654         except:
1655             pass
1656
1657         try:
1658             tx_dict = json.loads(str(txt))
1659             assert "hex" in tx_dict.keys()
1660             assert "complete" in tx_dict.keys()
1661             if not tx_dict["complete"]:
1662                 assert "input_info" in tx_dict.keys()
1663             tx = Transaction(tx_dict["hex"])
1664             return tx
1665         except:
1666             pass
1667         
1668         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1669
1670
1671
1672     def read_tx_from_file(self):
1673         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1674         if not fileName:
1675             return
1676         try:
1677             with open(fileName, "r") as f:
1678                 file_content = f.read()
1679         except (ValueError, IOError, os.error), reason:
1680             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1681
1682         return self.tx_from_text(file_content)
1683
1684
1685     @protected
1686     def sign_raw_transaction(self, tx, input_info, password):
1687         self.wallet.signrawtransaction(tx, input_info, [], password)
1688
1689     def do_process_from_text(self):
1690         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1691         if not text:
1692             return
1693         tx = self.tx_from_text(text)
1694         if tx:
1695             self.show_transaction(tx)
1696
1697     def do_process_from_file(self):
1698         tx = self.read_tx_from_file()
1699         if tx:
1700             self.show_transaction(tx)
1701
1702     def do_process_from_csvReader(self, csvReader):
1703         outputs = []
1704         try:
1705             for row in csvReader:
1706                 address = row[0]
1707                 amount = float(row[1])
1708                 amount = int(100000000*amount)
1709                 outputs.append((address, amount))
1710         except (ValueError, IOError, os.error), reason:
1711             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1712             return
1713
1714         try:
1715             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1716         except BaseException, e:
1717             self.show_message(str(e))
1718             return
1719
1720         self.show_transaction(tx)
1721
1722     def do_process_from_csv_file(self):
1723         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1724         if not fileName:
1725             return
1726         try:
1727             with open(fileName, "r") as f:
1728                 csvReader = csv.reader(f)
1729                 self.do_process_from_csvReader(csvReader)
1730         except (ValueError, IOError, os.error), reason:
1731             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1732             return
1733
1734     def do_process_from_csv_text(self):
1735         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1736                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1737         if not text:
1738             return
1739         f = StringIO.StringIO(text)
1740         csvReader = csv.reader(f)
1741         self.do_process_from_csvReader(csvReader)
1742
1743
1744
1745     @protected
1746     def do_export_privkeys(self, password):
1747         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.")))
1748
1749         try:
1750             select_export = _('Select file to export your private keys to')
1751             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1752             if fileName:
1753                 with open(fileName, "w+") as csvfile:
1754                     transaction = csv.writer(csvfile)
1755                     transaction.writerow(["address", "private_key"])
1756
1757                     addresses = self.wallet.addresses(True)
1758                     
1759                     for addr in addresses:
1760                         pk = "".join(self.wallet.get_private_key(addr, password))
1761                         transaction.writerow(["%34s"%addr,pk])
1762
1763                     self.show_message(_("Private keys exported."))
1764
1765         except (IOError, os.error), reason:
1766             export_error_label = _("Electrum was unable to produce a private key-export.")
1767             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1768
1769         except BaseException, e:
1770           self.show_message(str(e))
1771           return
1772
1773
1774     def do_import_labels(self):
1775         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1776         if not labelsFile: return
1777         try:
1778             f = open(labelsFile, 'r')
1779             data = f.read()
1780             f.close()
1781             for key, value in json.loads(data).items():
1782                 self.wallet.set_label(key, value)
1783             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1784         except (IOError, os.error), reason:
1785             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1786             
1787
1788     def do_export_labels(self):
1789         labels = self.wallet.labels
1790         try:
1791             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1792             if fileName:
1793                 with open(fileName, 'w+') as f:
1794                     json.dump(labels, f)
1795                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1796         except (IOError, os.error), reason:
1797             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1798
1799
1800     def do_export_history(self):
1801         from lite_window import csv_transaction
1802         csv_transaction(self.wallet)
1803
1804
1805     @protected
1806     def do_import_privkey(self, password):
1807         if not self.wallet.imported_keys:
1808             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1809                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1810                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1811             if r == 4: return
1812
1813         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1814         if not text: return
1815
1816         text = str(text).split()
1817         badkeys = []
1818         addrlist = []
1819         for key in text:
1820             try:
1821                 addr = self.wallet.import_key(key, password)
1822             except BaseException as e:
1823                 badkeys.append(key)
1824                 continue
1825             if not addr: 
1826                 badkeys.append(key)
1827             else:
1828                 addrlist.append(addr)
1829         if addrlist:
1830             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1831         if badkeys:
1832             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1833         self.update_receive_tab()
1834         self.update_history_tab()
1835
1836
1837     def settings_dialog(self):
1838         d = QDialog(self)
1839         d.setWindowTitle(_('Electrum Settings'))
1840         d.setModal(1)
1841         vbox = QVBoxLayout()
1842
1843         tabs = QTabWidget(self)
1844         self.settings_tab = tabs
1845         vbox.addWidget(tabs)
1846
1847         tab1 = QWidget()
1848         grid_ui = QGridLayout(tab1)
1849         grid_ui.setColumnStretch(0,1)
1850         tabs.addTab(tab1, _('Display') )
1851
1852         nz_label = QLabel(_('Display zeros'))
1853         grid_ui.addWidget(nz_label, 0, 0)
1854         nz_e = AmountEdit(None,True)
1855         nz_e.setText("%d"% self.num_zeros)
1856         grid_ui.addWidget(nz_e, 0, 1)
1857         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1858         grid_ui.addWidget(HelpButton(msg), 0, 2)
1859         if not self.config.is_modifiable('num_zeros'):
1860             for w in [nz_e, nz_label]: w.setEnabled(False)
1861         
1862         lang_label=QLabel(_('Language') + ':')
1863         grid_ui.addWidget(lang_label, 1, 0)
1864         lang_combo = QComboBox()
1865         from electrum.i18n import languages
1866         lang_combo.addItems(languages.values())
1867         try:
1868             index = languages.keys().index(self.config.get("language",''))
1869         except:
1870             index = 0
1871         lang_combo.setCurrentIndex(index)
1872         grid_ui.addWidget(lang_combo, 1, 1)
1873         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1874         if not self.config.is_modifiable('language'):
1875             for w in [lang_combo, lang_label]: w.setEnabled(False)
1876
1877         expert_cb = QCheckBox(_('Expert mode'))
1878         expert_cb.setChecked(self.expert_mode)
1879         grid_ui.addWidget(expert_cb, 3, 0)
1880         hh =  _('In expert mode, your client will:') + '\n'  \
1881             + _(' - Show change addresses in the Receive tab') + '\n'  \
1882             + _(' - Display the balance of each address') + '\n'  \
1883             + _(' - Add freeze/prioritize actions to addresses.') 
1884         grid_ui.addWidget(HelpButton(hh), 3, 2)
1885         grid_ui.setRowStretch(4,1)
1886
1887         # wallet tab
1888         tab2 = QWidget()
1889         grid_wallet = QGridLayout(tab2)
1890         grid_wallet.setColumnStretch(0,1)
1891         tabs.addTab(tab2, _('Wallet') )
1892         
1893         fee_label = QLabel(_('Transaction fee'))
1894         grid_wallet.addWidget(fee_label, 0, 0)
1895         fee_e = AmountEdit(self.base_unit)
1896         fee_e.setText(self.format_amount(self.wallet.fee).strip())
1897         grid_wallet.addWidget(fee_e, 0, 2)
1898         msg = _('Fee per kilobyte of transaction.') + ' ' \
1899             + _('Recommended value') + ': ' + self.format_amount(50000)
1900         grid_wallet.addWidget(HelpButton(msg), 0, 3)
1901         if not self.config.is_modifiable('fee_per_kb'):
1902             for w in [fee_e, fee_label]: w.setEnabled(False)
1903
1904         usechange_cb = QCheckBox(_('Use change addresses'))
1905         usechange_cb.setChecked(self.wallet.use_change)
1906         grid_wallet.addWidget(usechange_cb, 1, 0)
1907         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1908         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1909
1910         units = ['BTC', 'mBTC']
1911         unit_label = QLabel(_('Base unit'))
1912         grid_wallet.addWidget(unit_label, 3, 0)
1913         unit_combo = QComboBox()
1914         unit_combo.addItems(units)
1915         unit_combo.setCurrentIndex(units.index(self.base_unit()))
1916         grid_wallet.addWidget(unit_combo, 3, 2)
1917         grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1918                                              + '\n1BTC=1000mBTC.\n' \
1919                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1920         grid_wallet.setRowStretch(4,1)
1921
1922
1923         run_hook('create_settings_tab', tabs)
1924
1925         vbox.addLayout(ok_cancel_buttons(d))
1926         d.setLayout(vbox) 
1927
1928         # run the dialog
1929         if not d.exec_(): return
1930
1931         fee = unicode(fee_e.text())
1932         try:
1933             fee = self.read_amount(fee)
1934         except:
1935             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1936             return
1937
1938         self.wallet.set_fee(fee)
1939         
1940         nz = unicode(nz_e.text())
1941         try:
1942             nz = int( nz )
1943             if nz>8: nz=8
1944         except:
1945             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1946             return
1947
1948         if self.num_zeros != nz:
1949             self.num_zeros = nz
1950             self.config.set_key('num_zeros', nz, True)
1951             self.update_history_tab()
1952             self.update_receive_tab()
1953
1954         usechange_result = usechange_cb.isChecked()
1955         if self.wallet.use_change != usechange_result:
1956             self.wallet.use_change = usechange_result
1957             self.config.set_key('use_change', self.wallet.use_change, True)
1958         
1959         unit_result = units[unit_combo.currentIndex()]
1960         if self.base_unit() != unit_result:
1961             self.decimal_point = 8 if unit_result == 'BTC' else 5
1962             self.config.set_key('decimal_point', self.decimal_point, True)
1963             self.update_history_tab()
1964             self.update_status()
1965         
1966         need_restart = False
1967
1968         lang_request = languages.keys()[lang_combo.currentIndex()]
1969         if lang_request != self.config.get('language'):
1970             self.config.set_key("language", lang_request, True)
1971             need_restart = True
1972             
1973
1974         run_hook('close_settings_dialog')
1975
1976         if need_restart:
1977             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1978
1979         self.receive_tab_set_mode(expert_cb.isChecked())
1980
1981     def run_network_dialog(self):
1982         NetworkDialog(self.wallet.network, self.config, self).do_exec()
1983
1984     def closeEvent(self, event):
1985         g = self.geometry()
1986         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1987         self.save_column_widths()
1988         self.config.set_key("console-history", self.console.history[-50:], True)
1989         event.accept()
1990
1991
1992
1993     def plugins_dialog(self):
1994         from electrum.plugins import plugins
1995
1996         d = QDialog(self)
1997         d.setWindowTitle(_('Electrum Plugins'))
1998         d.setModal(1)
1999
2000         vbox = QVBoxLayout(d)
2001
2002         # plugins
2003         scroll = QScrollArea()
2004         scroll.setEnabled(True)
2005         scroll.setWidgetResizable(True)
2006         scroll.setMinimumSize(400,250)
2007         vbox.addWidget(scroll)
2008
2009         w = QWidget()
2010         scroll.setWidget(w)
2011         w.setMinimumHeight(len(plugins)*35)
2012
2013         grid = QGridLayout()
2014         grid.setColumnStretch(0,1)
2015         w.setLayout(grid)
2016
2017         def mk_toggle(cb, p):
2018             return lambda: cb.setChecked(p.toggle())
2019         for i, p in enumerate(plugins):
2020             try:
2021                 cb = QCheckBox(p.fullname())
2022                 cb.setDisabled(not p.is_available())
2023                 cb.setChecked(p.is_enabled())
2024                 cb.clicked.connect(mk_toggle(cb,p))
2025                 grid.addWidget(cb, i, 0)
2026                 if p.requires_settings():
2027                     grid.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2028                 grid.addWidget(HelpButton(p.description()), i, 2)
2029             except:
2030                 print_msg(_("Error: cannot display plugin"), p)
2031                 traceback.print_exc(file=sys.stdout)
2032         grid.setRowStretch(i+1,1)
2033
2034         vbox.addLayout(close_button(d))
2035
2036         d.exec_()
2037
2038
2039     def show_account_details(self, k):
2040         d = QDialog(self)
2041         d.setWindowTitle(_('Account Details'))
2042         d.setModal(1)
2043
2044         vbox = QVBoxLayout(d)
2045         roots = self.wallet.get_roots(k)
2046
2047         name = self.wallet.get_account_name(k)
2048         label = QLabel('Name: ' + name)
2049         vbox.addWidget(label)
2050
2051         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2052         vbox.addWidget(QLabel('Type: ' + acctype))
2053
2054         label = QLabel('Derivation: ' + k)
2055         vbox.addWidget(label)
2056
2057         #for root in roots:
2058         #    mpk = self.wallet.master_public_keys[root]
2059         #    text = QTextEdit()
2060         #    text.setReadOnly(True)
2061         #    text.setMaximumHeight(120)
2062         #    text.setText(repr(mpk))
2063         #    vbox.addWidget(text)
2064
2065         vbox.addLayout(close_button(d))
2066         d.exec_()