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