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