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