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