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