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