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