better seed dialog + fix bug with restore
[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], 
258         "receive":[[370],[370,200,130]] }
259
260 class ElectrumWindow(QMainWindow):
261
262     def __init__(self, wallet, config):
263         QMainWindow.__init__(self)
264         self.lite = None
265         self.wallet = wallet
266         self.config = config
267         self.current_account = self.config.get("current_account", None)
268
269         self.init_plugins()
270         self.create_status_bar()
271
272         self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
273         self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
274         self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
275         self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
276
277         self.expert_mode = config.get('classic_expert_mode', False)
278         self.decimal_point = config.get('decimal_point', 8)
279
280         set_language(config.get('language'))
281
282         self.funds_error = False
283         self.completions = QStringListModel()
284
285         self.tabs = tabs = QTabWidget(self)
286         self.column_widths = self.config.get("column_widths", default_column_widths )
287         tabs.addTab(self.create_history_tab(), _('History') )
288         tabs.addTab(self.create_send_tab(), _('Send') )
289         tabs.addTab(self.create_receive_tab(), _('Receive') )
290         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
291         tabs.addTab(self.create_console_tab(), _('Console') )
292         tabs.setMinimumSize(600, 400)
293         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
294         self.setCentralWidget(tabs)
295
296         g = self.config.get("winpos-qt",[100, 100, 840, 400])
297         self.setGeometry(g[0], g[1], g[2], g[3])
298         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
299         if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
300         self.setWindowTitle( title )
301
302         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
303         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
304         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
305         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
306         
307         self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
308         self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
309         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
310         self.history_list.setFocus(True)
311         
312         self.exchanger = exchange_rate.Exchanger(self)
313         self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
314
315         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
316         if platform.system() == 'Windows':
317             n = 3 if self.wallet.seed else 2
318             tabs.setCurrentIndex (n)
319             tabs.setCurrentIndex (0)
320
321         # set initial message
322         self.console.showMessage(self.wallet.interface.banner)
323
324         # plugins that need to change the GUI do it here
325         self.run_hook('init_gui')
326
327
328     # plugins
329     def init_plugins(self):
330         import imp, pkgutil, __builtin__
331         if __builtin__.use_local_modules:
332             fp, pathname, description = imp.find_module('plugins')
333             plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
334             plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
335             imp.load_module('electrum_plugins', fp, pathname, description)
336             plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
337         else:
338             import electrum_plugins
339             plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
340             plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
341
342         self.plugins = []
343         for p in plugins:
344             try:
345                 self.plugins.append( p.Plugin(self) )
346             except:
347                 print_msg("Error:cannot initialize plugin",p)
348                 traceback.print_exc(file=sys.stdout)
349
350
351     def run_hook(self, name, *args):
352         for p in self.plugins:
353             if not p.is_enabled():
354                 continue
355             try:
356                 f = eval('p.'+name)
357             except:
358                 continue
359             apply(f, args)
360         return
361
362         
363     def set_label(self, name, text = None):
364         changed = False
365         old_text = self.wallet.labels.get(name)
366         if text:
367             if old_text != text:
368                 self.wallet.labels[name] = text
369                 changed = True
370         else:
371             if old_text:
372                 self.wallet.labels.pop(name)
373                 changed = True
374         self.run_hook('set_label', name, text, changed)
375         return changed
376
377
378     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
379     def getOpenFileName(self, title, filter = None):
380         directory = self.config.get('io_dir', os.path.expanduser('~'))
381         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
382         if fileName and directory != os.path.dirname(fileName):
383             self.config.set_key('io_dir', os.path.dirname(fileName), True)
384         return fileName
385
386     def getSaveFileName(self, title, filename, filter = None):
387         directory = self.config.get('io_dir', os.path.expanduser('~'))
388         path = os.path.join( directory, filename )
389         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
390         if fileName and directory != os.path.dirname(fileName):
391             self.config.set_key('io_dir', os.path.dirname(fileName), True)
392         return fileName
393
394
395
396     def close(self):
397         QMainWindow.close(self)
398         self.run_hook('close_main_window')
399
400     def connect_slots(self, sender):
401         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
402         self.previous_payto_e=''
403
404     def timer_actions(self):
405         self.run_hook('timer_actions')
406     
407     def format_amount(self, x, is_diff=False):
408         return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
409
410     def read_amount(self, x):
411         if x in['.', '']: return None
412         p = pow(10, self.decimal_point)
413         return int( p * Decimal(x) )
414
415     def base_unit(self):
416         assert self.decimal_point in [5,8]
417         return "BTC" if self.decimal_point == 8 else "mBTC"
418
419     def update_status(self):
420         if self.wallet.interface and self.wallet.interface.is_connected:
421             if not self.wallet.up_to_date:
422                 text = _("Synchronizing...")
423                 icon = QIcon(":icons/status_waiting.png")
424             else:
425                 c, u = self.wallet.get_account_balance(self.current_account)
426                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
427                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
428                 text += self.create_quote_text(Decimal(c+u)/100000000)
429                 icon = QIcon(":icons/status_connected.png")
430         else:
431             text = _("Not connected")
432             icon = QIcon(":icons/status_disconnected.png")
433
434         self.status_text = text
435         self.statusBar().showMessage(text)
436         self.status_button.setIcon( icon )
437
438     def update_wallet(self):
439         self.update_status()
440         if self.wallet.up_to_date or not self.wallet.interface.is_connected:
441             self.update_history_tab()
442             self.update_receive_tab()
443             self.update_contacts_tab()
444             self.update_completions()
445
446
447     def create_quote_text(self, btc_balance):
448         quote_currency = self.config.get("currency", "None")
449         quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
450         if quote_balance is None:
451             quote_text = ""
452         else:
453             quote_text = "  (%.2f %s)" % (quote_balance, quote_currency)
454         return quote_text
455         
456     def create_history_tab(self):
457         self.history_list = l = MyTreeWidget(self)
458         l.setColumnCount(5)
459         for i,width in enumerate(self.column_widths['history']):
460             l.setColumnWidth(i, width)
461         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
462         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
463         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
464
465         l.setContextMenuPolicy(Qt.CustomContextMenu)
466         l.customContextMenuRequested.connect(self.create_history_menu)
467         return l
468
469
470     def create_history_menu(self, position):
471         self.history_list.selectedIndexes() 
472         item = self.history_list.currentItem()
473         if not item: return
474         tx_hash = str(item.data(0, Qt.UserRole).toString())
475         if not tx_hash: return
476         menu = QMenu()
477         #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
478         menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
479         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
480         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
481
482
483     def show_tx_details(self, tx):
484         dialog = QDialog(self)
485         dialog.setModal(1)
486         dialog.setWindowTitle(_("Transaction Details"))
487         vbox = QVBoxLayout()
488         dialog.setLayout(vbox)
489         dialog.setMinimumSize(600,300)
490
491         tx_hash = tx.hash()
492         if tx_hash in self.wallet.transactions.keys():
493             is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
494             conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
495             if timestamp:
496                 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
497             else:
498                 time_str = 'pending'
499         else:
500             is_mine = False
501
502         vbox.addWidget(QLabel("Transaction ID:"))
503         e  = QLineEdit(tx_hash)
504         e.setReadOnly(True)
505         vbox.addWidget(e)
506
507         vbox.addWidget(QLabel("Date: %s"%time_str))
508         vbox.addWidget(QLabel("Status: %d confirmations"%conf))
509         if is_mine:
510             if fee is not None: 
511                 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
512                 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
513             else:
514                 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
515                 vbox.addWidget(QLabel("Transaction fee: unknown"))
516         else:
517             vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
518
519         vbox.addWidget( self.generate_transaction_information_widget(tx) )
520
521         ok_button = QPushButton(_("Close"))
522         ok_button.setDefault(True)
523         ok_button.clicked.connect(dialog.accept)
524         
525         hbox = QHBoxLayout()
526         hbox.addStretch(1)
527         hbox.addWidget(ok_button)
528         vbox.addLayout(hbox)
529         dialog.exec_()
530
531     def tx_label_clicked(self, item, column):
532         if column==2 and item.isSelected():
533             self.is_edit=True
534             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
535             self.history_list.editItem( item, column )
536             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
537             self.is_edit=False
538
539     def tx_label_changed(self, item, column):
540         if self.is_edit: 
541             return
542         self.is_edit=True
543         tx_hash = str(item.data(0, Qt.UserRole).toString())
544         tx = self.wallet.transactions.get(tx_hash)
545         text = unicode( item.text(2) )
546         self.set_label(tx_hash, text) 
547         if text: 
548             item.setForeground(2, QBrush(QColor('black')))
549         else:
550             text = self.wallet.get_default_label(tx_hash)
551             item.setText(2, text)
552             item.setForeground(2, QBrush(QColor('gray')))
553         self.is_edit=False
554
555
556     def edit_label(self, is_recv):
557         l = self.receive_list if is_recv else self.contacts_list
558         item = l.currentItem()
559         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
560         l.editItem( item, 1 )
561         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
562
563
564
565     def address_label_clicked(self, item, column, l, column_addr, column_label):
566         if column == column_label and item.isSelected():
567             is_editable = item.data(0, 32).toBool()
568             if not is_editable:
569                 return
570             addr = unicode( item.text(column_addr) )
571             label = unicode( item.text(column_label) )
572             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
573             l.editItem( item, column )
574             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
575
576
577     def address_label_changed(self, item, column, l, column_addr, column_label):
578         if column == column_label:
579             addr = unicode( item.text(column_addr) )
580             text = unicode( item.text(column_label) )
581             is_editable = item.data(0, 32).toBool()
582             if not is_editable:
583                 return
584
585             changed = self.set_label(addr, text)
586             if changed:
587                 self.update_history_tab()
588                 self.update_completions()
589                 
590             self.current_item_changed(item)
591
592         self.run_hook('item_changed', item, column)
593
594
595     def current_item_changed(self, a):
596         self.run_hook('current_item_changed', a)
597
598
599
600     def update_history_tab(self):
601
602         self.history_list.clear()
603         for item in self.wallet.get_tx_history(self.current_account):
604             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
605             if conf:
606                 try:
607                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
608                 except:
609                     time_str = "unknown"
610                 if conf == -1:
611                     icon = None
612                 if conf == 0:
613                     icon = QIcon(":icons/unconfirmed.png")
614                 elif conf < 6:
615                     icon = QIcon(":icons/clock%d.png"%conf)
616                 else:
617                     icon = QIcon(":icons/confirmed.png")
618             else:
619                 time_str = 'pending'
620                 icon = QIcon(":icons/unconfirmed.png")
621
622             if value is not None:
623                 v_str = self.format_amount(value, True)
624             else:
625                 v_str = '--'
626
627             balance_str = self.format_amount(balance)
628             
629             if tx_hash:
630                 label, is_default_label = self.wallet.get_label(tx_hash)
631             else:
632                 label = _('Pruned transaction outputs')
633                 is_default_label = False
634
635             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
636             item.setFont(2, QFont(MONOSPACE_FONT))
637             item.setFont(3, QFont(MONOSPACE_FONT))
638             item.setFont(4, QFont(MONOSPACE_FONT))
639             if value < 0:
640                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
641             if tx_hash:
642                 item.setData(0, Qt.UserRole, tx_hash)
643                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
644             if is_default_label:
645                 item.setForeground(2, QBrush(QColor('grey')))
646
647             item.setIcon(0, icon)
648             self.history_list.insertTopLevelItem(0,item)
649             
650
651         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
652
653
654     def create_send_tab(self):
655         w = QWidget()
656
657         grid = QGridLayout()
658         grid.setSpacing(8)
659         grid.setColumnMinimumWidth(3,300)
660         grid.setColumnStretch(5,1)
661
662
663         self.payto_e = QLineEdit()
664         grid.addWidget(QLabel(_('Pay to')), 1, 0)
665         grid.addWidget(self.payto_e, 1, 1, 1, 3)
666             
667         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)
668
669         completer = QCompleter()
670         completer.setCaseSensitivity(False)
671         self.payto_e.setCompleter(completer)
672         completer.setModel(self.completions)
673
674         self.message_e = QLineEdit()
675         grid.addWidget(QLabel(_('Description')), 2, 0)
676         grid.addWidget(self.message_e, 2, 1, 1, 3)
677         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)
678
679         self.amount_e = AmountEdit(self.base_unit)
680         grid.addWidget(QLabel(_('Amount')), 3, 0)
681         grid.addWidget(self.amount_e, 3, 1, 1, 2)
682         grid.addWidget(HelpButton(
683                 _('Amount to be sent.') + '\n\n' \
684                     + _('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.') \
685                     + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
686         
687         self.fee_e = AmountEdit(self.base_unit)
688         grid.addWidget(QLabel(_('Fee')), 4, 0)
689         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
690         grid.addWidget(HelpButton(
691                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
692                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
693                     + _('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)
694         b = ''
695         if self.wallet.seed: 
696             b = EnterButton(_("Send"), self.do_send)
697         else:
698             b = EnterButton(_("Create unsigned transaction"), self.do_send)
699         grid.addWidget(b, 6, 1)
700
701         b = EnterButton(_("Clear"),self.do_clear)
702         grid.addWidget(b, 6, 2)
703
704         self.payto_sig = QLabel('')
705         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
706
707         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
708         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
709         w.setLayout(grid) 
710
711         w2 = QWidget()
712         vbox = QVBoxLayout()
713         vbox.addWidget(w)
714         vbox.addStretch(1)
715         w2.setLayout(vbox)
716
717         def entry_changed( is_fee ):
718             self.funds_error = False
719
720             if self.amount_e.is_shortcut:
721                 self.amount_e.is_shortcut = False
722                 c, u = self.wallet.get_account_balance(self.current_account)
723                 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
724                 fee = self.wallet.estimated_fee(inputs)
725                 amount = c + u - fee
726                 self.amount_e.setText( self.format_amount(amount) )
727                 self.fee_e.setText( self.format_amount( fee ) )
728                 return
729                 
730             amount = self.read_amount(str(self.amount_e.text()))
731             fee = self.read_amount(str(self.fee_e.text()))
732
733             if not is_fee: fee = None
734             if amount is None:
735                 return
736             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
737             if not is_fee:
738                 self.fee_e.setText( self.format_amount( fee ) )
739             if inputs:
740                 palette = QPalette()
741                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
742                 text = self.status_text
743             else:
744                 palette = QPalette()
745                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
746                 self.funds_error = True
747                 text = _( "Not enough funds" )
748
749             self.statusBar().showMessage(text)
750             self.amount_e.setPalette(palette)
751             self.fee_e.setPalette(palette)
752
753         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
754         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
755
756         self.run_hook('create_send_tab', grid)
757         return w2
758
759
760     def update_completions(self):
761         l = []
762         for addr,label in self.wallet.labels.items():
763             if addr in self.wallet.addressbook:
764                 l.append( label + '  <' + addr + '>')
765
766         self.run_hook('update_completions', l)
767         self.completions.setStringList(l)
768
769
770     def protected(func):
771         return lambda s, *args: s.do_protect(func, args)
772
773
774     @protected
775     def do_send(self, password):
776
777         label = unicode( self.message_e.text() )
778         r = unicode( self.payto_e.text() )
779         r = r.strip()
780
781         # label or alias, with address in brackets
782         m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
783         to_address = m.group(2) if m else r
784
785         if not is_valid(to_address):
786             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
787             return
788
789         try:
790             amount = self.read_amount(unicode( self.amount_e.text()))
791         except:
792             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
793             return
794         try:
795             fee = self.read_amount(unicode( self.fee_e.text()))
796         except:
797             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
798             return
799
800         try:
801             tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
802         except BaseException, e:
803             self.show_message(str(e))
804             return
805
806         if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
807             QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
808             return
809
810         self.run_hook('send_tx', tx)
811
812         if label: 
813             self.set_label(tx.hash(), label)
814
815         if tx.is_complete:
816             h = self.wallet.send_tx(tx)
817             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
818             status, msg = self.wallet.receive_tx( h )
819             if status:
820                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
821                 self.do_clear()
822                 self.update_contacts_tab()
823             else:
824                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
825         else:
826             filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
827             try:
828                 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
829                 with open(fileName,'w') as f:
830                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
831                 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
832             except:
833                 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
834
835
836
837
838     def set_url(self, url):
839         address, amount, label, message, signature, identity, url = util.parse_url(url)
840         if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
841
842         if label and self.wallet.labels.get(address) != label:
843             if self.question('Give label "%s" to address %s ?'%(label,address)):
844                 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
845                     self.wallet.addressbook.append(address)
846                 self.set_label(address, label)
847
848         self.run_hook('set_url', url, self.show_message, self.question)
849
850         self.tabs.setCurrentIndex(1)
851         label = self.wallet.labels.get(address)
852         m_addr = label + '  <'+ address +'>' if label else address
853         self.payto_e.setText(m_addr)
854
855         self.message_e.setText(message)
856         self.amount_e.setText(amount)
857         if identity:
858             self.set_frozen(self.payto_e,True)
859             self.set_frozen(self.amount_e,True)
860             self.set_frozen(self.message_e,True)
861             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
862         else:
863             self.payto_sig.setVisible(False)
864
865     def do_clear(self):
866         self.payto_sig.setVisible(False)
867         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
868             e.setText('')
869             self.set_frozen(e,False)
870
871     def set_frozen(self,entry,frozen):
872         if frozen:
873             entry.setReadOnly(True)
874             entry.setFrame(False)
875             palette = QPalette()
876             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
877             entry.setPalette(palette)
878         else:
879             entry.setReadOnly(False)
880             entry.setFrame(True)
881             palette = QPalette()
882             palette.setColor(entry.backgroundRole(), QColor('white'))
883             entry.setPalette(palette)
884
885
886     def toggle_freeze(self,addr):
887         if not addr: return
888         if addr in self.wallet.frozen_addresses:
889             self.wallet.unfreeze(addr)
890         else:
891             self.wallet.freeze(addr)
892         self.update_receive_tab()
893
894     def toggle_priority(self,addr):
895         if not addr: return
896         if addr in self.wallet.prioritized_addresses:
897             self.wallet.unprioritize(addr)
898         else:
899             self.wallet.prioritize(addr)
900         self.update_receive_tab()
901
902
903     def create_list_tab(self, headers):
904         "generic tab creation method"
905         l = MyTreeWidget(self)
906         l.setColumnCount( len(headers) )
907         l.setHeaderLabels( headers )
908
909         w = QWidget()
910         vbox = QVBoxLayout()
911         w.setLayout(vbox)
912
913         vbox.setMargin(0)
914         vbox.setSpacing(0)
915         vbox.addWidget(l)
916         buttons = QWidget()
917         vbox.addWidget(buttons)
918
919         hbox = QHBoxLayout()
920         hbox.setMargin(0)
921         hbox.setSpacing(0)
922         buttons.setLayout(hbox)
923
924         return l,w,hbox
925
926
927     def create_receive_tab(self):
928         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
929         l.setContextMenuPolicy(Qt.CustomContextMenu)
930         l.customContextMenuRequested.connect(self.create_receive_menu)
931         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
932         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
933         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
934         self.receive_list = l
935         self.receive_buttons_hbox = hbox
936         hbox.addStretch(1)
937         return w
938
939
940     def receive_tab_set_mode(self, i):
941         self.save_column_widths()
942         self.expert_mode = (i == 1)
943         self.config.set_key('classic_expert_mode', self.expert_mode, True)
944         self.wallet.save()
945         self.update_receive_tab()
946
947
948     def save_column_widths(self):
949         if not self.expert_mode:
950             widths = [ self.receive_list.columnWidth(0) ]
951         else:
952             widths = []
953             for i in range(self.receive_list.columnCount() -1):
954                 widths.append(self.receive_list.columnWidth(i))
955         self.column_widths["receive"][self.expert_mode] = widths
956         
957         self.column_widths["history"] = []
958         for i in range(self.history_list.columnCount() - 1):
959             self.column_widths["history"].append(self.history_list.columnWidth(i))
960
961         self.column_widths["contacts"] = []
962         for i in range(self.contacts_list.columnCount() - 1):
963             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
964
965
966     def create_contacts_tab(self):
967         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
968         l.setContextMenuPolicy(Qt.CustomContextMenu)
969         l.customContextMenuRequested.connect(self.create_contact_menu)
970         for i,width in enumerate(self.column_widths['contacts']):
971             l.setColumnWidth(i, width)
972
973         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
974         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
975         self.contacts_list = l
976         self.contacts_buttons_hbox = hbox
977         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
978         hbox.addStretch(1)
979         return w
980
981
982     def delete_imported_key(self, addr):
983         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
984             self.wallet.imported_keys.pop(addr)
985             self.update_receive_tab()
986             self.update_history_tab()
987             self.wallet.save()
988
989
990     def create_receive_menu(self, position):
991         # fixme: this function apparently has a side effect.
992         # if it is not called the menu pops up several times
993         #self.receive_list.selectedIndexes() 
994
995         item = self.receive_list.itemAt(position)
996         if not item: return
997         addr = unicode(item.text(0))
998         if not is_valid(addr): 
999             item.setExpanded(not item.isExpanded())
1000             return 
1001         menu = QMenu()
1002         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1003         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1004         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1005         menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1006         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1007         if addr in self.wallet.imported_keys:
1008             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1009
1010         if self.expert_mode:
1011             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1012             menu.addAction(t, lambda: self.toggle_freeze(addr))
1013             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1014             menu.addAction(t, lambda: self.toggle_priority(addr))
1015             
1016         self.run_hook('receive_menu', menu)
1017         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1018
1019
1020     def payto(self, addr):
1021         if not addr: return
1022         label = self.wallet.labels.get(addr)
1023         m_addr = label + '  <' + addr + '>' if label else addr
1024         self.tabs.setCurrentIndex(1)
1025         self.payto_e.setText(m_addr)
1026         self.amount_e.setFocus()
1027
1028
1029     def delete_contact(self, x):
1030         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1031             if x in self.wallet.addressbook:
1032                 self.wallet.addressbook.remove(x)
1033                 self.set_label(x, None)
1034                 self.update_history_tab()
1035                 self.update_contacts_tab()
1036                 self.update_completions()
1037
1038
1039     def create_contact_menu(self, position):
1040         item = self.contacts_list.itemAt(position)
1041         if not item: return
1042         addr = unicode(item.text(0))
1043         label = unicode(item.text(1))
1044         is_editable = item.data(0,32).toBool()
1045         payto_addr = item.data(0,33).toString()
1046         menu = QMenu()
1047         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1048         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1049         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1050         if is_editable:
1051             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1052             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1053
1054         self.run_hook('create_contact_menu', menu, item)
1055         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1056
1057
1058     def update_receive_item(self, item):
1059         item.setFont(0, QFont(MONOSPACE_FONT))
1060         address = str(item.data(0,0).toString())
1061         label = self.wallet.labels.get(address,'')
1062         item.setData(1,0,label)
1063         item.setData(0,32, True) # is editable
1064
1065         self.run_hook('update_receive_item', address, item)
1066                 
1067         c, u = self.wallet.get_addr_balance(address)
1068         balance = self.format_amount(c + u)
1069         item.setData(2,0,balance)
1070
1071         if self.expert_mode:
1072             if address in self.wallet.frozen_addresses: 
1073                 item.setBackgroundColor(0, QColor('lightblue'))
1074             elif address in self.wallet.prioritized_addresses: 
1075                 item.setBackgroundColor(0, QColor('lightgreen'))
1076         
1077
1078     def update_receive_tab(self):
1079         l = self.receive_list
1080         
1081         l.clear()
1082         l.setColumnHidden(2, not self.expert_mode)
1083         l.setColumnHidden(3, not self.expert_mode)
1084         if not self.expert_mode:
1085             width = self.column_widths['receive'][0][0]
1086             l.setColumnWidth(0, width)
1087         else:
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(20000)
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("column_widths", self.column_widths, True)
2284         self.config.set_key("console-history",self.console.history[-50:])
2285         event.accept()
2286
2287
2288 class ElectrumGui:
2289
2290     def __init__(self, wallet, config, app=None):
2291         self.wallet = wallet
2292         self.config = config
2293         if app is None:
2294             self.app = QApplication(sys.argv)
2295
2296
2297     def restore_or_create(self):
2298         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2299         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2300         if r==2: return None
2301         return 'restore' if r==1 else 'create'
2302
2303
2304     def verify_seed(self):
2305         r = self.seed_dialog(False)
2306         if r != self.wallet.seed:
2307             QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2308             return False
2309         else:
2310             self.wallet.save_seed()
2311             return True
2312         
2313
2314
2315     def seed_dialog(self, is_restore=True):
2316         d = QDialog()
2317         d.setModal(1)
2318
2319         vbox = QVBoxLayout()
2320         if is_restore:
2321             msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2322         else:
2323             msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2324
2325         msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2326         
2327         label=QLabel(msg)
2328         label.setWordWrap(True)
2329         vbox.addWidget(label)
2330
2331         seed_e = QTextEdit()
2332         seed_e.setMaximumHeight(100)
2333         vbox.addWidget(seed_e)
2334
2335         if is_restore:
2336             grid = QGridLayout()
2337             grid.setSpacing(8)
2338             gap_e = AmountEdit(None, True)
2339             gap_e.setText("5")
2340             grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2341             grid.addWidget(gap_e, 2, 1)
2342             grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2343             vbox.addLayout(grid)
2344
2345         vbox.addLayout(ok_cancel_buttons(d))
2346         d.setLayout(vbox) 
2347
2348         if not d.exec_(): return
2349
2350         try:
2351             seed = str(seed_e.toPlainText())
2352             seed.decode('hex')
2353         except:
2354             try:
2355                 seed = mnemonic.mn_decode( seed.split(' ') )
2356             except:
2357                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2358                 return
2359
2360         if not seed:
2361             QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2362             return
2363
2364         if not is_restore:
2365             return seed
2366         else:
2367             try:
2368                 gap = int(unicode(gap_e.text()))
2369             except:
2370                 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2371                 return
2372             return seed, gap
2373
2374
2375     def network_dialog(self):
2376         return ElectrumWindow.network_dialog( self.wallet, parent=None )
2377         
2378
2379     def show_seed(self):
2380         ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2381
2382     def password_dialog(self):
2383         if self.wallet.seed:
2384             ElectrumWindow.change_password_dialog(self.wallet)
2385
2386
2387     def restore_wallet(self):
2388         wallet = self.wallet
2389         # wait until we are connected, because the user might have selected another server
2390         if not wallet.interface.is_connected:
2391             waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2392             waiting_dialog(waiting)
2393
2394         waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2395             %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2396
2397         wallet.set_up_to_date(False)
2398         wallet.interface.poke('synchronizer')
2399         waiting_dialog(waiting)
2400         if wallet.is_found():
2401             print_error( "Recovery successful" )
2402         else:
2403             QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2404
2405         return True
2406
2407     def main(self,url):
2408         s = Timer()
2409         s.start()
2410         w = ElectrumWindow(self.wallet, self.config)
2411         if url: w.set_url(url)
2412         w.app = self.app
2413         w.connect_slots(s)
2414         w.update_wallet()
2415         w.show()
2416
2417         self.app.exec_()
2418
2419