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