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