show lag in status line
[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,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         if not self.network.is_connected(): 
463             return
464
465         print_error("Notifying GUI")
466         if len(self.network.interface.pending_transactions_for_notifications) > 0:
467             # Combine the transactions if there are more then three
468             tx_amount = len(self.network.interface.pending_transactions_for_notifications)
469             if(tx_amount >= 3):
470                 total_amount = 0
471                 for tx in self.network.interface.pending_transactions_for_notifications:
472                     is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
473                     if(v > 0):
474                         total_amount += v
475
476                 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
477                                 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
478
479                 self.network.interface.pending_transactions_for_notifications = []
480             else:
481               for tx in self.network.interface.pending_transactions_for_notifications:
482                   if tx:
483                       self.network.interface.pending_transactions_for_notifications.remove(tx)
484                       is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
485                       if(v > 0):
486                           self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
487
488     def notify(self, message):
489         self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
490
491
492
493     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
494     def getOpenFileName(self, title, filter = ""):
495         directory = self.config.get('io_dir', os.path.expanduser('~'))
496         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, 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 getSaveFileName(self, title, filename, filter = ""):
502         directory = self.config.get('io_dir', os.path.expanduser('~'))
503         path = os.path.join( directory, filename )
504         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
505         if fileName and directory != os.path.dirname(fileName):
506             self.config.set_key('io_dir', os.path.dirname(fileName), True)
507         return fileName
508
509     def close(self):
510         QMainWindow.close(self)
511         run_hook('close_main_window')
512
513     def connect_slots(self, sender):
514         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
515         self.previous_payto_e=''
516
517     def timer_actions(self):
518         if self.need_update.is_set():
519             self.update_wallet()
520             self.need_update.clear()
521         run_hook('timer_actions')
522     
523     def format_amount(self, x, is_diff=False, whitespaces=False):
524         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
525
526     def read_amount(self, x):
527         if x in['.', '']: return None
528         p = pow(10, self.decimal_point)
529         return int( p * Decimal(x) )
530
531     def base_unit(self):
532         assert self.decimal_point in [5,8]
533         return "BTC" if self.decimal_point == 8 else "mBTC"
534
535
536     def update_status(self):
537         if self.network.is_connected():
538             if not self.wallet.up_to_date:
539                 text = _("Synchronizing...")
540                 icon = QIcon(":icons/status_waiting.png")
541             elif self.network.server_lag > 1:
542                 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
543                 icon = QIcon(":icons/status_lagging.png")
544             else:
545                 c, u = self.wallet.get_account_balance(self.current_account)
546                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
547                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
548
549                 r = {}
550                 run_hook('set_quote_text', c+u, r)
551                 quote = r.get(0)
552                 if quote:
553                     text += "  (%s)"%quote
554
555                 self.tray.setToolTip(text)
556                 icon = QIcon(":icons/status_connected.png")
557         else:
558             text = _("Not connected")
559             icon = QIcon(":icons/status_disconnected.png")
560
561         self.balance_label.setText(text)
562         self.status_button.setIcon( icon )
563
564
565     def update_wallet(self):
566         self.update_status()
567         if self.wallet.up_to_date or not self.network.is_connected():
568             self.update_history_tab()
569             self.update_receive_tab()
570             self.update_contacts_tab()
571             self.update_completions()
572
573         
574     def create_history_tab(self):
575         self.history_list = l = MyTreeWidget(self)
576         l.setColumnCount(5)
577         for i,width in enumerate(self.column_widths['history']):
578             l.setColumnWidth(i, width)
579         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
580         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
581         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
582
583         l.customContextMenuRequested.connect(self.create_history_menu)
584         return l
585
586
587     def create_history_menu(self, position):
588         self.history_list.selectedIndexes() 
589         item = self.history_list.currentItem()
590         if not item: return
591         tx_hash = str(item.data(0, Qt.UserRole).toString())
592         if not tx_hash: return
593         menu = QMenu()
594         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
595         menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
596         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
597         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
598
599
600     def show_transaction(self, tx):
601         import transaction_dialog
602         d = transaction_dialog.TxDialog(tx, self)
603         d.exec_()
604
605     def tx_label_clicked(self, item, column):
606         if column==2 and item.isSelected():
607             self.is_edit=True
608             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
609             self.history_list.editItem( item, column )
610             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
611             self.is_edit=False
612
613     def tx_label_changed(self, item, column):
614         if self.is_edit: 
615             return
616         self.is_edit=True
617         tx_hash = str(item.data(0, Qt.UserRole).toString())
618         tx = self.wallet.transactions.get(tx_hash)
619         text = unicode( item.text(2) )
620         self.wallet.set_label(tx_hash, text) 
621         if text: 
622             item.setForeground(2, QBrush(QColor('black')))
623         else:
624             text = self.wallet.get_default_label(tx_hash)
625             item.setText(2, text)
626             item.setForeground(2, QBrush(QColor('gray')))
627         self.is_edit=False
628
629
630     def edit_label(self, is_recv):
631         l = self.receive_list if is_recv else self.contacts_list
632         item = l.currentItem()
633         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
634         l.editItem( item, 1 )
635         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
636
637
638
639     def address_label_clicked(self, item, column, l, column_addr, column_label):
640         if column == column_label and item.isSelected():
641             is_editable = item.data(0, 32).toBool()
642             if not is_editable:
643                 return
644             addr = unicode( item.text(column_addr) )
645             label = unicode( item.text(column_label) )
646             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
647             l.editItem( item, column )
648             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
649
650
651     def address_label_changed(self, item, column, l, column_addr, column_label):
652         if column == column_label:
653             addr = unicode( item.text(column_addr) )
654             text = unicode( item.text(column_label) )
655             is_editable = item.data(0, 32).toBool()
656             if not is_editable:
657                 return
658
659             changed = self.wallet.set_label(addr, text)
660             if changed:
661                 self.update_history_tab()
662                 self.update_completions()
663                 
664             self.current_item_changed(item)
665
666         run_hook('item_changed', item, column)
667
668
669     def current_item_changed(self, a):
670         run_hook('current_item_changed', a)
671
672
673
674     def update_history_tab(self):
675
676         self.history_list.clear()
677         for item in self.wallet.get_tx_history(self.current_account):
678             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
679             if conf > 0:
680                 try:
681                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
682                 except:
683                     time_str = _("unknown")
684
685             if conf == -1:
686                 time_str = 'unverified'
687                 icon = QIcon(":icons/unconfirmed.png")
688             elif conf == 0:
689                 time_str = 'pending'
690                 icon = QIcon(":icons/unconfirmed.png")
691             elif conf < 6:
692                 icon = QIcon(":icons/clock%d.png"%conf)
693             else:
694                 icon = QIcon(":icons/confirmed.png")
695
696             if value is not None:
697                 v_str = self.format_amount(value, True, whitespaces=True)
698             else:
699                 v_str = '--'
700
701             balance_str = self.format_amount(balance, whitespaces=True)
702             
703             if tx_hash:
704                 label, is_default_label = self.wallet.get_label(tx_hash)
705             else:
706                 label = _('Pruned transaction outputs')
707                 is_default_label = False
708
709             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
710             item.setFont(2, QFont(MONOSPACE_FONT))
711             item.setFont(3, QFont(MONOSPACE_FONT))
712             item.setFont(4, QFont(MONOSPACE_FONT))
713             if value < 0:
714                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
715             if tx_hash:
716                 item.setData(0, Qt.UserRole, tx_hash)
717                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
718             if is_default_label:
719                 item.setForeground(2, QBrush(QColor('grey')))
720
721             item.setIcon(0, icon)
722             self.history_list.insertTopLevelItem(0,item)
723             
724
725         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
726
727
728     def create_send_tab(self):
729         w = QWidget()
730
731         grid = QGridLayout()
732         grid.setSpacing(8)
733         grid.setColumnMinimumWidth(3,300)
734         grid.setColumnStretch(5,1)
735
736
737         self.payto_e = QLineEdit()
738         grid.addWidget(QLabel(_('Pay to')), 1, 0)
739         grid.addWidget(self.payto_e, 1, 1, 1, 3)
740             
741         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)
742
743         completer = QCompleter()
744         completer.setCaseSensitivity(False)
745         self.payto_e.setCompleter(completer)
746         completer.setModel(self.completions)
747
748         self.message_e = QLineEdit()
749         grid.addWidget(QLabel(_('Description')), 2, 0)
750         grid.addWidget(self.message_e, 2, 1, 1, 3)
751         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)
752
753         self.amount_e = AmountEdit(self.base_unit)
754         grid.addWidget(QLabel(_('Amount')), 3, 0)
755         grid.addWidget(self.amount_e, 3, 1, 1, 2)
756         grid.addWidget(HelpButton(
757                 _('Amount to be sent.') + '\n\n' \
758                     + _('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.') \
759                     + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
760         
761         self.fee_e = AmountEdit(self.base_unit)
762         grid.addWidget(QLabel(_('Fee')), 4, 0)
763         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
764         grid.addWidget(HelpButton(
765                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
766                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
767                     + _('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)
768
769
770         self.send_button = EnterButton(_("Send"), self.do_send)
771         grid.addWidget(self.send_button, 6, 1)
772
773         b = EnterButton(_("Clear"),self.do_clear)
774         grid.addWidget(b, 6, 2)
775
776         self.payto_sig = QLabel('')
777         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
778
779         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
780         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
781         w.setLayout(grid) 
782
783         w2 = QWidget()
784         vbox = QVBoxLayout()
785         vbox.addWidget(w)
786         vbox.addStretch(1)
787         w2.setLayout(vbox)
788
789         def entry_changed( is_fee ):
790             self.funds_error = False
791
792             if self.amount_e.is_shortcut:
793                 self.amount_e.is_shortcut = False
794                 c, u = self.wallet.get_account_balance(self.current_account)
795                 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
796                 fee = self.wallet.estimated_fee(inputs)
797                 amount = c + u - fee
798                 self.amount_e.setText( self.format_amount(amount) )
799                 self.fee_e.setText( self.format_amount( fee ) )
800                 return
801                 
802             amount = self.read_amount(str(self.amount_e.text()))
803             fee = self.read_amount(str(self.fee_e.text()))
804
805             if not is_fee: fee = None
806             if amount is None:
807                 return
808             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
809             if not is_fee:
810                 self.fee_e.setText( self.format_amount( fee ) )
811             if inputs:
812                 palette = QPalette()
813                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
814                 text = ""
815             else:
816                 palette = QPalette()
817                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
818                 self.funds_error = True
819                 text = _( "Not enough funds" )
820                 c, u = self.wallet.get_frozen_balance()
821                 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
822
823             self.statusBar().showMessage(text)
824             self.amount_e.setPalette(palette)
825             self.fee_e.setPalette(palette)
826
827         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
828         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
829
830         run_hook('create_send_tab', grid)
831         return w2
832
833
834     def update_completions(self):
835         l = []
836         for addr,label in self.wallet.labels.items():
837             if addr in self.wallet.addressbook:
838                 l.append( label + '  <' + addr + '>')
839
840         run_hook('update_completions', l)
841         self.completions.setStringList(l)
842
843
844     def protected(func):
845         return lambda s, *args: s.do_protect(func, args)
846
847
848     def do_send(self):
849
850         label = unicode( self.message_e.text() )
851         r = unicode( self.payto_e.text() )
852         r = r.strip()
853
854         # label or alias, with address in brackets
855         m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
856         to_address = m.group(2) if m else r
857
858         if not is_valid(to_address):
859             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
860             return
861
862         try:
863             amount = self.read_amount(unicode( self.amount_e.text()))
864         except:
865             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
866             return
867         try:
868             fee = self.read_amount(unicode( self.fee_e.text()))
869         except:
870             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
871             return
872
873         confirm_amount = self.config.get('confirm_amount', 100000000)
874         if amount >= confirm_amount:
875             if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
876                 return
877
878         self.send_tx(to_address, amount, fee, label)
879
880
881     @protected
882     def send_tx(self, to_address, amount, fee, label, password):
883
884         try:
885             tx = self.wallet.mktx_from_account( [(to_address, amount)], password, fee, self.current_account)
886         except BaseException, e:
887             traceback.print_exc(file=sys.stdout)
888             self.show_message(str(e))
889             return
890
891         if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
892             QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
893             return
894
895         if label: 
896             self.wallet.set_label(tx.hash(), label)
897
898         if tx.is_complete:
899             h = self.wallet.send_tx(tx)
900             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
901             status, msg = self.wallet.receive_tx( h )
902             if status:
903                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
904                 self.do_clear()
905                 self.update_contacts_tab()
906             else:
907                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
908         else:
909             filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
910             try:
911                 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
912                 with open(fileName,'w') as f:
913                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
914                 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
915             except:
916                 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
917
918         # add recipient to addressbook
919         if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
920             self.wallet.addressbook.append(to_address)
921
922
923
924
925     def set_url(self, url):
926         address, amount, label, message, signature, identity, url = util.parse_url(url)
927
928         if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
929
930         if self.mini:
931             self.mini.set_payment_fields(address, amount)
932
933         if label and self.wallet.labels.get(address) != label:
934             if self.question('Give label "%s" to address %s ?'%(label,address)):
935                 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
936                     self.wallet.addressbook.append(address)
937                 self.wallet.set_label(address, label)
938
939         run_hook('set_url', url, self.show_message, self.question)
940
941         self.tabs.setCurrentIndex(1)
942         label = self.wallet.labels.get(address)
943         m_addr = label + '  <'+ address +'>' if label else address
944         self.payto_e.setText(m_addr)
945
946         self.message_e.setText(message)
947         if amount:
948             self.amount_e.setText(amount)
949
950         if identity:
951             self.set_frozen(self.payto_e,True)
952             self.set_frozen(self.amount_e,True)
953             self.set_frozen(self.message_e,True)
954             self.payto_sig.setText( '      '+_('The bitcoin URI was signed by')+' ' + identity )
955         else:
956             self.payto_sig.setVisible(False)
957
958     def do_clear(self):
959         self.payto_sig.setVisible(False)
960         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
961             e.setText('')
962             self.set_frozen(e,False)
963         self.update_status()
964
965     def set_frozen(self,entry,frozen):
966         if frozen:
967             entry.setReadOnly(True)
968             entry.setFrame(False)
969             palette = QPalette()
970             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
971             entry.setPalette(palette)
972         else:
973             entry.setReadOnly(False)
974             entry.setFrame(True)
975             palette = QPalette()
976             palette.setColor(entry.backgroundRole(), QColor('white'))
977             entry.setPalette(palette)
978
979
980     def toggle_freeze(self,addr):
981         if not addr: return
982         if addr in self.wallet.frozen_addresses:
983             self.wallet.unfreeze(addr)
984         else:
985             self.wallet.freeze(addr)
986         self.update_receive_tab()
987
988     def toggle_priority(self,addr):
989         if not addr: return
990         if addr in self.wallet.prioritized_addresses:
991             self.wallet.unprioritize(addr)
992         else:
993             self.wallet.prioritize(addr)
994         self.update_receive_tab()
995
996
997     def create_list_tab(self, headers):
998         "generic tab creation method"
999         l = MyTreeWidget(self)
1000         l.setColumnCount( len(headers) )
1001         l.setHeaderLabels( headers )
1002
1003         w = QWidget()
1004         vbox = QVBoxLayout()
1005         w.setLayout(vbox)
1006
1007         vbox.setMargin(0)
1008         vbox.setSpacing(0)
1009         vbox.addWidget(l)
1010         buttons = QWidget()
1011         vbox.addWidget(buttons)
1012
1013         hbox = QHBoxLayout()
1014         hbox.setMargin(0)
1015         hbox.setSpacing(0)
1016         buttons.setLayout(hbox)
1017
1018         return l,w,hbox
1019
1020
1021     def create_receive_tab(self):
1022         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1023         l.setContextMenuPolicy(Qt.CustomContextMenu)
1024         l.customContextMenuRequested.connect(self.create_receive_menu)
1025         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1026         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1027         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1028         self.receive_list = l
1029         self.receive_buttons_hbox = hbox
1030         hbox.addStretch(1)
1031         return w
1032
1033
1034
1035
1036     def save_column_widths(self):
1037         self.column_widths["receive"] = []
1038         for i in range(self.receive_list.columnCount() -1):
1039             self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1040         
1041         self.column_widths["history"] = []
1042         for i in range(self.history_list.columnCount() - 1):
1043             self.column_widths["history"].append(self.history_list.columnWidth(i))
1044
1045         self.column_widths["contacts"] = []
1046         for i in range(self.contacts_list.columnCount() - 1):
1047             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1048
1049         self.config.set_key("column_widths", self.column_widths, True)
1050
1051
1052     def create_contacts_tab(self):
1053         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1054         l.setContextMenuPolicy(Qt.CustomContextMenu)
1055         l.customContextMenuRequested.connect(self.create_contact_menu)
1056         for i,width in enumerate(self.column_widths['contacts']):
1057             l.setColumnWidth(i, width)
1058
1059         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1060         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1061         self.contacts_list = l
1062         self.contacts_buttons_hbox = hbox
1063         hbox.addStretch(1)
1064         return w
1065
1066
1067     def delete_imported_key(self, addr):
1068         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1069             self.wallet.delete_imported_key(addr)
1070             self.update_receive_tab()
1071             self.update_history_tab()
1072
1073     def edit_account_label(self, k):
1074         text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1075         if ok:
1076             label = unicode(text)
1077             self.wallet.set_label(k,label)
1078             self.update_receive_tab()
1079
1080     def create_account_menu(self, position, k, item):
1081         menu = QMenu()
1082         if item.isExpanded():
1083             menu.addAction(_("Minimize"), lambda: item.setExpanded(False))
1084         else:
1085             menu.addAction(_("Maximize"), lambda: item.setExpanded(True))
1086         menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1087         menu.addAction(_("View details"), lambda: self.show_account_details(k))
1088         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1089
1090     def create_receive_menu(self, position):
1091         # fixme: this function apparently has a side effect.
1092         # if it is not called the menu pops up several times
1093         #self.receive_list.selectedIndexes() 
1094
1095         item = self.receive_list.itemAt(position)
1096         if not item: return
1097
1098         addr = unicode(item.text(0))
1099         if not is_valid(addr): 
1100             k = str(item.data(0,32).toString())
1101             if k:
1102                 self.create_account_menu(position, k, item)
1103             else:
1104                 item.setExpanded(not item.isExpanded())
1105             return 
1106
1107         menu = QMenu()
1108         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1109         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1110         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1111         menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1112         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1113         if addr in self.wallet.imported_keys:
1114             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1115
1116         t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1117         menu.addAction(t, lambda: self.toggle_freeze(addr))
1118         t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1119         menu.addAction(t, lambda: self.toggle_priority(addr))
1120             
1121         run_hook('receive_menu', menu)
1122         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1123
1124
1125     def payto(self, addr):
1126         if not addr: return
1127         label = self.wallet.labels.get(addr)
1128         m_addr = label + '  <' + addr + '>' if label else addr
1129         self.tabs.setCurrentIndex(1)
1130         self.payto_e.setText(m_addr)
1131         self.amount_e.setFocus()
1132
1133
1134     def delete_contact(self, x):
1135         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1136             self.wallet.delete_contact(x)
1137             self.wallet.set_label(x, None)
1138             self.update_history_tab()
1139             self.update_contacts_tab()
1140             self.update_completions()
1141
1142
1143     def create_contact_menu(self, position):
1144         item = self.contacts_list.itemAt(position)
1145         if not item: return
1146         addr = unicode(item.text(0))
1147         label = unicode(item.text(1))
1148         is_editable = item.data(0,32).toBool()
1149         payto_addr = item.data(0,33).toString()
1150         menu = QMenu()
1151         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1152         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1153         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1154         if is_editable:
1155             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1156             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1157
1158         run_hook('create_contact_menu', menu, item)
1159         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1160
1161
1162     def update_receive_item(self, item):
1163         item.setFont(0, QFont(MONOSPACE_FONT))
1164         address = str(item.data(0,0).toString())
1165         label = self.wallet.labels.get(address,'')
1166         item.setData(1,0,label)
1167         item.setData(0,32, True) # is editable
1168
1169         run_hook('update_receive_item', address, item)
1170                 
1171         c, u = self.wallet.get_addr_balance(address)
1172         balance = self.format_amount(c + u)
1173         item.setData(2,0,balance)
1174
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, False)
1186         l.setColumnHidden(3, False)
1187         for i,width in enumerate(self.column_widths['receive']):
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]):
1210                 name = _("Receiving") if not is_change else _("Change")
1211                 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1212                 account_item.addChild(seq_item)
1213                 if not is_change: seq_item.setExpanded(True)
1214
1215                 is_red = False
1216                 gap = 0
1217
1218                 for address in account.get_addresses(is_change):
1219                     h = self.wallet.history.get(address,[])
1220             
1221                     if h == []:
1222                         gap += 1
1223                         if gap > self.wallet.gap_limit:
1224                             is_red = True
1225                     else:
1226                         gap = 0
1227
1228                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1229                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1230                     self.update_receive_item(item)
1231                     if is_red:
1232                         item.setBackgroundColor(1, QColor('red'))
1233                     seq_item.addChild(item)
1234
1235
1236         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1237             c,u = self.wallet.get_imported_balance()
1238             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1239             l.addTopLevelItem(account_item)
1240             account_item.setExpanded(True)
1241             for address in self.wallet.imported_keys.keys():
1242                 item = QTreeWidgetItem( [ address, '', '', ''] )
1243                 self.update_receive_item(item)
1244                 account_item.addChild(item)
1245                 
1246
1247         # we use column 1 because column 0 may be hidden
1248         l.setCurrentItem(l.topLevelItem(0),1)
1249
1250
1251     def update_contacts_tab(self):
1252         l = self.contacts_list
1253         l.clear()
1254
1255         for address in self.wallet.addressbook:
1256             label = self.wallet.labels.get(address,'')
1257             n = self.wallet.get_num_tx(address)
1258             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1259             item.setFont(0, QFont(MONOSPACE_FONT))
1260             # 32 = label can be edited (bool)
1261             item.setData(0,32, True)
1262             # 33 = payto string
1263             item.setData(0,33, address)
1264             l.addTopLevelItem(item)
1265
1266         run_hook('update_contacts_tab', l)
1267         l.setCurrentItem(l.topLevelItem(0))
1268
1269
1270
1271     def create_console_tab(self):
1272         from console import Console
1273         self.console = console = Console()
1274         return console
1275
1276
1277     def update_console(self):
1278         console = self.console
1279         console.history = self.config.get("console-history",[])
1280         console.history_index = len(console.history)
1281
1282         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1283         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1284
1285         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1286         methods = {}
1287         def mkfunc(f, method):
1288             return lambda *args: apply( f, (method, args, self.password_dialog ))
1289         for m in dir(c):
1290             if m[0]=='_' or m in ['network','wallet']: continue
1291             methods[m] = mkfunc(c._run, m)
1292             
1293         console.updateNamespace(methods)
1294
1295
1296     def change_account(self,s):
1297         if s == _("All accounts"):
1298             self.current_account = None
1299         else:
1300             accounts = self.wallet.get_account_names()
1301             for k, v in accounts.items():
1302                 if v == s:
1303                     self.current_account = k
1304         self.update_history_tab()
1305         self.update_status()
1306         self.update_receive_tab()
1307
1308     def create_status_bar(self):
1309
1310         sb = QStatusBar()
1311         sb.setFixedHeight(35)
1312         qtVersion = qVersion()
1313
1314         self.balance_label = QLabel("")
1315         sb.addWidget(self.balance_label)
1316
1317         from version_getter import UpdateLabel
1318         self.updatelabel = UpdateLabel(self.config, sb)
1319
1320         self.account_selector = QComboBox()
1321         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account) 
1322         sb.addPermanentWidget(self.account_selector)
1323
1324         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1325             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1326
1327         self.lock_icon = QIcon()
1328         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1329         sb.addPermanentWidget( self.password_button )
1330             
1331         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1332         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) 
1333         sb.addPermanentWidget( self.seed_button )
1334         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) 
1335         sb.addPermanentWidget( self.status_button )
1336
1337         run_hook('create_status_bar', (sb,))
1338
1339         self.setStatusBar(sb)
1340
1341
1342     def update_lock_icon(self):
1343         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1344         self.password_button.setIcon( icon )
1345
1346
1347     def update_buttons_on_seed(self):
1348         if not self.wallet.is_watching_only():
1349            self.seed_button.show()
1350            self.password_button.show()
1351            self.send_button.setText(_("Send"))
1352         else:
1353            self.password_button.hide()
1354            self.seed_button.hide()
1355            self.send_button.setText(_("Create unsigned transaction"))
1356
1357
1358     def change_password_dialog(self):
1359         from password_dialog import PasswordDialog
1360         d = PasswordDialog(self.wallet, self)
1361         d.run()
1362         self.update_lock_icon()
1363
1364
1365     def new_contact_dialog(self):
1366         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1367         address = unicode(text)
1368         if ok:
1369             if is_valid(address):
1370                 self.wallet.add_contact(address)
1371                 self.update_contacts_tab()
1372                 self.update_history_tab()
1373                 self.update_completions()
1374             else:
1375                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1376
1377
1378     def new_account_dialog(self):
1379
1380         dialog = QDialog(self)
1381         dialog.setModal(1)
1382         dialog.setWindowTitle(_("New Account"))
1383
1384         addr = self.wallet.new_account_address()
1385         vbox = QVBoxLayout()
1386         msg = _("Electrum considers that an account exists only if it contains bitcoins.") + '\n' \
1387               + _("To create a new account, please send coins to the first address of that account.") + '\n' \
1388               + _("Note: you will need to wait for 2 confirmations before the account is created.")
1389         vbox.addWidget(QLabel(msg))
1390         vbox.addWidget(QLabel(_('Address')+':'))
1391         e = QLineEdit(addr)
1392         e.setReadOnly(True)
1393         vbox.addWidget(e)
1394
1395         vbox.addLayout(ok_cancel_buttons(dialog))
1396         dialog.setLayout(vbox)
1397         r = dialog.exec_()
1398         if r:
1399             self.payto(addr)
1400
1401             
1402
1403     def show_master_public_key_old(self):
1404         dialog = QDialog(self)
1405         dialog.setModal(1)
1406         dialog.setWindowTitle(_("Master Public Key"))
1407
1408         main_text = QTextEdit()
1409         main_text.setText(self.wallet.get_master_public_key())
1410         main_text.setReadOnly(True)
1411         main_text.setMaximumHeight(170)
1412         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1413
1414         ok_button = QPushButton(_("OK"))
1415         ok_button.setDefault(True)
1416         ok_button.clicked.connect(dialog.accept)
1417
1418         main_layout = QGridLayout()
1419         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1420
1421         main_layout.addWidget(main_text, 1, 0)
1422         main_layout.addWidget(qrw, 1, 1 )
1423
1424         vbox.addLayout(close_button(dialog))
1425         dialog.setLayout(vbox)
1426         dialog.exec_()
1427         
1428
1429     def show_master_public_key(self):
1430
1431         if self.wallet.seed_version == 4:
1432             self.show_master_public_keys_old()
1433             return
1434
1435         dialog = QDialog(self)
1436         dialog.setModal(1)
1437         dialog.setWindowTitle(_("Master Public Keys"))
1438
1439         chain_text = QTextEdit()
1440         chain_text.setReadOnly(True)
1441         chain_text.setMaximumHeight(170)
1442         chain_qrw = QRCodeWidget()
1443
1444         mpk_text = QTextEdit()
1445         mpk_text.setReadOnly(True)
1446         mpk_text.setMaximumHeight(170)
1447         mpk_qrw = QRCodeWidget()
1448
1449         main_layout = QGridLayout()
1450
1451         main_layout.addWidget(QLabel(_('Key')), 1, 0)
1452         main_layout.addWidget(mpk_text, 1, 1)
1453         main_layout.addWidget(mpk_qrw, 1, 2)
1454
1455         main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1456         main_layout.addWidget(chain_text, 2, 1)
1457         main_layout.addWidget(chain_qrw, 2, 2)
1458
1459         def update(key):
1460             c, K, cK = self.wallet.master_public_keys[str(key)]
1461             chain_text.setText(c)
1462             chain_qrw.set_addr(c)
1463             chain_qrw.update_qr()
1464             mpk_text.setText(K)
1465             mpk_qrw.set_addr(c)
1466             mpk_qrw.update_qr()
1467             
1468         key_selector = QComboBox()
1469         keys = sorted(self.wallet.master_public_keys.keys())
1470         key_selector.addItems(keys)
1471
1472         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1473         main_layout.addWidget(key_selector, 0, 1)
1474         dialog.connect(key_selector,SIGNAL("activated(QString)"),update) 
1475
1476         update(keys[0])
1477
1478         vbox = QVBoxLayout()
1479         vbox.addLayout(main_layout)
1480         vbox.addLayout(close_button(dialog))
1481
1482         dialog.setLayout(vbox)
1483         dialog.exec_()
1484         
1485
1486     @protected
1487     def show_seed_dialog(self, password):
1488         if self.wallet.is_watching_only():
1489             QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1490             return
1491
1492         if self.wallet.seed:
1493             try:
1494                 seed = self.wallet.decode_seed(password)
1495             except:
1496                 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1497                 return
1498             from seed_dialog import SeedDialog
1499             d = SeedDialog(self, seed, self.wallet.imported_keys)
1500             d.exec_()
1501         else:
1502             l = {}
1503             for k in self.wallet.master_private_keys.keys():
1504                 pk = self.wallet.get_master_private_key(k, password)
1505                 l[k] = pk
1506             from seed_dialog import PrivateKeysDialog
1507             d = PrivateKeysDialog(self,l)
1508             d.exec_()
1509
1510
1511
1512
1513
1514     def show_qrcode(self, data, title = _("QR code")):
1515         if not data: return
1516         d = QDialog(self)
1517         d.setModal(1)
1518         d.setWindowTitle(title)
1519         d.setMinimumSize(270, 300)
1520         vbox = QVBoxLayout()
1521         qrw = QRCodeWidget(data)
1522         vbox.addWidget(qrw, 1)
1523         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1524         hbox = QHBoxLayout()
1525         hbox.addStretch(1)
1526
1527         def print_qr(self):
1528             filename = "qrcode.bmp"
1529             bmp.save_qrcode(qrw.qr, filename)
1530             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1531
1532         b = QPushButton(_("Save"))
1533         hbox.addWidget(b)
1534         b.clicked.connect(print_qr)
1535
1536         b = QPushButton(_("Close"))
1537         hbox.addWidget(b)
1538         b.clicked.connect(d.accept)
1539         b.setDefault(True)
1540
1541         vbox.addLayout(hbox)
1542         d.setLayout(vbox)
1543         d.exec_()
1544
1545
1546     def do_protect(self, func, args):
1547         if self.wallet.use_encryption:
1548             password = self.password_dialog()
1549             if not password:
1550                 return
1551         else:
1552             password = None
1553             
1554         if args != (False,):
1555             args = (self,) + args + (password,)
1556         else:
1557             args = (self,password)
1558         apply( func, args)
1559
1560
1561     @protected
1562     def show_private_key(self, address, password):
1563         if not address: return
1564         try:
1565             pk_list = self.wallet.get_private_key(address, password)
1566         except BaseException, e:
1567             self.show_message(str(e))
1568             return
1569         QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1570
1571
1572     @protected
1573     def do_sign(self, address, message, signature, password):
1574         message = unicode(message.toPlainText())
1575         message = message.encode('utf-8')
1576         try:
1577             sig = self.wallet.sign_message(str(address.text()), message, password)
1578             signature.setText(sig)
1579         except BaseException, e:
1580             self.show_message(str(e))
1581
1582     def sign_message(self, address):
1583         if not address: return
1584         d = QDialog(self)
1585         d.setModal(1)
1586         d.setWindowTitle(_('Sign Message'))
1587         d.setMinimumSize(410, 290)
1588
1589         tab_widget = QTabWidget()
1590         tab = QWidget()
1591         layout = QGridLayout(tab)
1592
1593         sign_address = QLineEdit()
1594
1595         sign_address.setText(address)
1596         layout.addWidget(QLabel(_('Address')), 1, 0)
1597         layout.addWidget(sign_address, 1, 1)
1598
1599         sign_message = QTextEdit()
1600         layout.addWidget(QLabel(_('Message')), 2, 0)
1601         layout.addWidget(sign_message, 2, 1)
1602         layout.setRowStretch(2,3)
1603
1604         sign_signature = QTextEdit()
1605         layout.addWidget(QLabel(_('Signature')), 3, 0)
1606         layout.addWidget(sign_signature, 3, 1)
1607         layout.setRowStretch(3,1)
1608
1609
1610         hbox = QHBoxLayout()
1611         b = QPushButton(_("Sign"))
1612         hbox.addWidget(b)
1613         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1614         b = QPushButton(_("Close"))
1615         b.clicked.connect(d.accept)
1616         hbox.addWidget(b)
1617         layout.addLayout(hbox, 4, 1)
1618         tab_widget.addTab(tab, _("Sign"))
1619
1620
1621         tab = QWidget()
1622         layout = QGridLayout(tab)
1623
1624         verify_address = QLineEdit()
1625         layout.addWidget(QLabel(_('Address')), 1, 0)
1626         layout.addWidget(verify_address, 1, 1)
1627
1628         verify_message = QTextEdit()
1629         layout.addWidget(QLabel(_('Message')), 2, 0)
1630         layout.addWidget(verify_message, 2, 1)
1631         layout.setRowStretch(2,3)
1632
1633         verify_signature = QTextEdit()
1634         layout.addWidget(QLabel(_('Signature')), 3, 0)
1635         layout.addWidget(verify_signature, 3, 1)
1636         layout.setRowStretch(3,1)
1637
1638         def do_verify():
1639             message = unicode(verify_message.toPlainText())
1640             message = message.encode('utf-8')
1641             if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1642                 self.show_message(_("Signature verified"))
1643             else:
1644                 self.show_message(_("Error: wrong signature"))
1645
1646         hbox = QHBoxLayout()
1647         b = QPushButton(_("Verify"))
1648         b.clicked.connect(do_verify)
1649         hbox.addWidget(b)
1650         b = QPushButton(_("Close"))
1651         b.clicked.connect(d.accept)
1652         hbox.addWidget(b)
1653         layout.addLayout(hbox, 4, 1)
1654         tab_widget.addTab(tab, _("Verify"))
1655
1656         vbox = QVBoxLayout()
1657         vbox.addWidget(tab_widget)
1658         d.setLayout(vbox)
1659         d.exec_()
1660
1661         
1662
1663
1664     def question(self, msg):
1665         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1666
1667     def show_message(self, msg):
1668         QMessageBox.information(self, _('Message'), msg, _('OK'))
1669
1670     def password_dialog(self ):
1671         d = QDialog(self)
1672         d.setModal(1)
1673
1674         pw = QLineEdit()
1675         pw.setEchoMode(2)
1676
1677         vbox = QVBoxLayout()
1678         msg = _('Please enter your password')
1679         vbox.addWidget(QLabel(msg))
1680
1681         grid = QGridLayout()
1682         grid.setSpacing(8)
1683         grid.addWidget(QLabel(_('Password')), 1, 0)
1684         grid.addWidget(pw, 1, 1)
1685         vbox.addLayout(grid)
1686
1687         vbox.addLayout(ok_cancel_buttons(d))
1688         d.setLayout(vbox)
1689
1690         run_hook('password_dialog', pw, grid, 1)
1691         if not d.exec_(): return
1692         return unicode(pw.text())
1693
1694
1695
1696
1697
1698
1699
1700
1701     def tx_from_text(self, txt):
1702         "json or raw hexadecimal"
1703         try:
1704             txt.decode('hex')
1705             tx = Transaction(txt)
1706             return tx
1707         except:
1708             pass
1709
1710         try:
1711             tx_dict = json.loads(str(txt))
1712             assert "hex" in tx_dict.keys()
1713             assert "complete" in tx_dict.keys()
1714             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1715             if not tx_dict["complete"]:
1716                 assert "input_info" in tx_dict.keys()
1717                 input_info = json.loads(tx_dict['input_info'])
1718                 tx.add_input_info(input_info)
1719             return tx
1720         except:
1721             pass
1722         
1723         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1724
1725
1726
1727     def read_tx_from_file(self):
1728         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1729         if not fileName:
1730             return
1731         try:
1732             with open(fileName, "r") as f:
1733                 file_content = f.read()
1734         except (ValueError, IOError, os.error), reason:
1735             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1736
1737         return self.tx_from_text(file_content)
1738
1739
1740     @protected
1741     def sign_raw_transaction(self, tx, input_info, password):
1742         self.wallet.signrawtransaction(tx, input_info, [], password)
1743
1744     def do_process_from_text(self):
1745         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1746         if not text:
1747             return
1748         tx = self.tx_from_text(text)
1749         if tx:
1750             self.show_transaction(tx)
1751
1752     def do_process_from_file(self):
1753         tx = self.read_tx_from_file()
1754         if tx:
1755             self.show_transaction(tx)
1756
1757     def do_process_from_csvReader(self, csvReader):
1758         outputs = []
1759         try:
1760             for row in csvReader:
1761                 address = row[0]
1762                 amount = float(row[1])
1763                 amount = int(100000000*amount)
1764                 outputs.append((address, amount))
1765         except (ValueError, IOError, os.error), reason:
1766             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1767             return
1768
1769         try:
1770             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1771         except BaseException, e:
1772             self.show_message(str(e))
1773             return
1774
1775         self.show_transaction(tx)
1776
1777     def do_process_from_csv_file(self):
1778         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1779         if not fileName:
1780             return
1781         try:
1782             with open(fileName, "r") as f:
1783                 csvReader = csv.reader(f)
1784                 self.do_process_from_csvReader(csvReader)
1785         except (ValueError, IOError, os.error), reason:
1786             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1787             return
1788
1789     def do_process_from_csv_text(self):
1790         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1791                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1792         if not text:
1793             return
1794         f = StringIO.StringIO(text)
1795         csvReader = csv.reader(f)
1796         self.do_process_from_csvReader(csvReader)
1797
1798
1799
1800     @protected
1801     def do_export_privkeys(self, password):
1802         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.")))
1803
1804         try:
1805             select_export = _('Select file to export your private keys to')
1806             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1807             if fileName:
1808                 with open(fileName, "w+") as csvfile:
1809                     transaction = csv.writer(csvfile)
1810                     transaction.writerow(["address", "private_key"])
1811
1812                     addresses = self.wallet.addresses(True)
1813                     
1814                     for addr in addresses:
1815                         pk = "".join(self.wallet.get_private_key(addr, password))
1816                         transaction.writerow(["%34s"%addr,pk])
1817
1818                     self.show_message(_("Private keys exported."))
1819
1820         except (IOError, os.error), reason:
1821             export_error_label = _("Electrum was unable to produce a private key-export.")
1822             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1823
1824         except BaseException, e:
1825           self.show_message(str(e))
1826           return
1827
1828
1829     def do_import_labels(self):
1830         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1831         if not labelsFile: return
1832         try:
1833             f = open(labelsFile, 'r')
1834             data = f.read()
1835             f.close()
1836             for key, value in json.loads(data).items():
1837                 self.wallet.set_label(key, value)
1838             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1839         except (IOError, os.error), reason:
1840             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1841             
1842
1843     def do_export_labels(self):
1844         labels = self.wallet.labels
1845         try:
1846             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1847             if fileName:
1848                 with open(fileName, 'w+') as f:
1849                     json.dump(labels, f)
1850                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1851         except (IOError, os.error), reason:
1852             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1853
1854
1855     def do_export_history(self):
1856         from lite_window import csv_transaction
1857         csv_transaction(self.wallet)
1858
1859
1860     @protected
1861     def do_import_privkey(self, password):
1862         if not self.wallet.imported_keys:
1863             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1864                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1865                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1866             if r == 4: return
1867
1868         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1869         if not text: return
1870
1871         text = str(text).split()
1872         badkeys = []
1873         addrlist = []
1874         for key in text:
1875             try:
1876                 addr = self.wallet.import_key(key, password)
1877             except BaseException as e:
1878                 badkeys.append(key)
1879                 continue
1880             if not addr: 
1881                 badkeys.append(key)
1882             else:
1883                 addrlist.append(addr)
1884         if addrlist:
1885             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1886         if badkeys:
1887             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1888         self.update_receive_tab()
1889         self.update_history_tab()
1890
1891
1892     def settings_dialog(self):
1893         d = QDialog(self)
1894         d.setWindowTitle(_('Electrum Settings'))
1895         d.setModal(1)
1896         vbox = QVBoxLayout()
1897         grid = QGridLayout()
1898         grid.setColumnStretch(0,1)
1899
1900         nz_label = QLabel(_('Display zeros') + ':')
1901         grid.addWidget(nz_label, 0, 0)
1902         nz_e = AmountEdit(None,True)
1903         nz_e.setText("%d"% self.num_zeros)
1904         grid.addWidget(nz_e, 0, 1)
1905         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1906         grid.addWidget(HelpButton(msg), 0, 2)
1907         if not self.config.is_modifiable('num_zeros'):
1908             for w in [nz_e, nz_label]: w.setEnabled(False)
1909         
1910         lang_label=QLabel(_('Language') + ':')
1911         grid.addWidget(lang_label, 1, 0)
1912         lang_combo = QComboBox()
1913         from electrum.i18n import languages
1914         lang_combo.addItems(languages.values())
1915         try:
1916             index = languages.keys().index(self.config.get("language",''))
1917         except:
1918             index = 0
1919         lang_combo.setCurrentIndex(index)
1920         grid.addWidget(lang_combo, 1, 1)
1921         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1922         if not self.config.is_modifiable('language'):
1923             for w in [lang_combo, lang_label]: w.setEnabled(False)
1924
1925         
1926         fee_label = QLabel(_('Transaction fee') + ':')
1927         grid.addWidget(fee_label, 2, 0)
1928         fee_e = AmountEdit(self.base_unit)
1929         fee_e.setText(self.format_amount(self.wallet.fee).strip())
1930         grid.addWidget(fee_e, 2, 1)
1931         msg = _('Fee per kilobyte of transaction.') + ' ' \
1932             + _('Recommended value') + ': ' + self.format_amount(50000)
1933         grid.addWidget(HelpButton(msg), 2, 2)
1934         if not self.config.is_modifiable('fee_per_kb'):
1935             for w in [fee_e, fee_label]: w.setEnabled(False)
1936
1937         units = ['BTC', 'mBTC']
1938         unit_label = QLabel(_('Base unit') + ':')
1939         grid.addWidget(unit_label, 3, 0)
1940         unit_combo = QComboBox()
1941         unit_combo.addItems(units)
1942         unit_combo.setCurrentIndex(units.index(self.base_unit()))
1943         grid.addWidget(unit_combo, 3, 1)
1944         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
1945                                              + '\n1BTC=1000mBTC.\n' \
1946                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
1947
1948         usechange_cb = QCheckBox(_('Use change addresses'))
1949         usechange_cb.setChecked(self.wallet.use_change)
1950         grid.addWidget(usechange_cb, 4, 0)
1951         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
1952         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1953
1954         grid.setRowStretch(5,1)
1955
1956         vbox.addLayout(grid)
1957         vbox.addLayout(ok_cancel_buttons(d))
1958         d.setLayout(vbox) 
1959
1960         # run the dialog
1961         if not d.exec_(): return
1962
1963         fee = unicode(fee_e.text())
1964         try:
1965             fee = self.read_amount(fee)
1966         except:
1967             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1968             return
1969
1970         self.wallet.set_fee(fee)
1971         
1972         nz = unicode(nz_e.text())
1973         try:
1974             nz = int( nz )
1975             if nz>8: nz=8
1976         except:
1977             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1978             return
1979
1980         if self.num_zeros != nz:
1981             self.num_zeros = nz
1982             self.config.set_key('num_zeros', nz, True)
1983             self.update_history_tab()
1984             self.update_receive_tab()
1985
1986         usechange_result = usechange_cb.isChecked()
1987         if self.wallet.use_change != usechange_result:
1988             self.wallet.use_change = usechange_result
1989             self.config.set_key('use_change', self.wallet.use_change, True)
1990         
1991         unit_result = units[unit_combo.currentIndex()]
1992         if self.base_unit() != unit_result:
1993             self.decimal_point = 8 if unit_result == 'BTC' else 5
1994             self.config.set_key('decimal_point', self.decimal_point, True)
1995             self.update_history_tab()
1996             self.update_status()
1997         
1998         need_restart = False
1999
2000         lang_request = languages.keys()[lang_combo.currentIndex()]
2001         if lang_request != self.config.get('language'):
2002             self.config.set_key("language", lang_request, True)
2003             need_restart = True
2004             
2005         run_hook('close_settings_dialog')
2006
2007         if need_restart:
2008             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2009
2010
2011     def run_network_dialog(self):
2012         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2013
2014     def closeEvent(self, event):
2015         g = self.geometry()
2016         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2017         self.save_column_widths()
2018         self.config.set_key("console-history", self.console.history[-50:], True)
2019         event.accept()
2020
2021
2022
2023     def plugins_dialog(self):
2024         from electrum.plugins import plugins
2025
2026         d = QDialog(self)
2027         d.setWindowTitle(_('Electrum Plugins'))
2028         d.setModal(1)
2029
2030         vbox = QVBoxLayout(d)
2031
2032         # plugins
2033         scroll = QScrollArea()
2034         scroll.setEnabled(True)
2035         scroll.setWidgetResizable(True)
2036         scroll.setMinimumSize(400,250)
2037         vbox.addWidget(scroll)
2038
2039         w = QWidget()
2040         scroll.setWidget(w)
2041         w.setMinimumHeight(len(plugins)*35)
2042
2043         grid = QGridLayout()
2044         grid.setColumnStretch(0,1)
2045         w.setLayout(grid)
2046
2047         def mk_toggle(cb, p):
2048             return lambda: cb.setChecked(p.toggle())
2049         for i, p in enumerate(plugins):
2050             try:
2051                 cb = QCheckBox(p.fullname())
2052                 cb.setDisabled(not p.is_available())
2053                 cb.setChecked(p.is_enabled())
2054                 cb.clicked.connect(mk_toggle(cb,p))
2055                 grid.addWidget(cb, i, 0)
2056                 if p.requires_settings():
2057                     b = EnterButton(_('Settings'), p.settings_dialog)
2058                     b.setEnabled( p.is_enabled() )
2059                     grid.addWidget(b, i, 1)
2060                 grid.addWidget(HelpButton(p.description()), i, 2)
2061             except:
2062                 print_msg(_("Error: cannot display plugin"), p)
2063                 traceback.print_exc(file=sys.stdout)
2064         grid.setRowStretch(i+1,1)
2065
2066         vbox.addLayout(close_button(d))
2067
2068         d.exec_()
2069
2070
2071     def show_account_details(self, k):
2072         d = QDialog(self)
2073         d.setWindowTitle(_('Account Details'))
2074         d.setModal(1)
2075
2076         vbox = QVBoxLayout(d)
2077         roots = self.wallet.get_roots(k)
2078
2079         name = self.wallet.get_account_name(k)
2080         label = QLabel('Name: ' + name)
2081         vbox.addWidget(label)
2082
2083         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2084         vbox.addWidget(QLabel('Type: ' + acctype))
2085
2086         label = QLabel('Derivation: ' + k)
2087         vbox.addWidget(label)
2088
2089         #for root in roots:
2090         #    mpk = self.wallet.master_public_keys[root]
2091         #    text = QTextEdit()
2092         #    text.setReadOnly(True)
2093         #    text.setMaximumHeight(120)
2094         #    text.setText(repr(mpk))
2095         #    vbox.addWidget(text)
2096
2097         vbox.addLayout(close_button(d))
2098         d.exec_()