reset status on clear
[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         self.update_status()
871
872     def set_frozen(self,entry,frozen):
873         if frozen:
874             entry.setReadOnly(True)
875             entry.setFrame(False)
876             palette = QPalette()
877             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
878             entry.setPalette(palette)
879         else:
880             entry.setReadOnly(False)
881             entry.setFrame(True)
882             palette = QPalette()
883             palette.setColor(entry.backgroundRole(), QColor('white'))
884             entry.setPalette(palette)
885
886
887     def toggle_freeze(self,addr):
888         if not addr: return
889         if addr in self.wallet.frozen_addresses:
890             self.wallet.unfreeze(addr)
891         else:
892             self.wallet.freeze(addr)
893         self.update_receive_tab()
894
895     def toggle_priority(self,addr):
896         if not addr: return
897         if addr in self.wallet.prioritized_addresses:
898             self.wallet.unprioritize(addr)
899         else:
900             self.wallet.prioritize(addr)
901         self.update_receive_tab()
902
903
904     def create_list_tab(self, headers):
905         "generic tab creation method"
906         l = MyTreeWidget(self)
907         l.setColumnCount( len(headers) )
908         l.setHeaderLabels( headers )
909
910         w = QWidget()
911         vbox = QVBoxLayout()
912         w.setLayout(vbox)
913
914         vbox.setMargin(0)
915         vbox.setSpacing(0)
916         vbox.addWidget(l)
917         buttons = QWidget()
918         vbox.addWidget(buttons)
919
920         hbox = QHBoxLayout()
921         hbox.setMargin(0)
922         hbox.setSpacing(0)
923         buttons.setLayout(hbox)
924
925         return l,w,hbox
926
927
928     def create_receive_tab(self):
929         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
930         l.setContextMenuPolicy(Qt.CustomContextMenu)
931         l.customContextMenuRequested.connect(self.create_receive_menu)
932         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
933         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
934         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
935         self.receive_list = l
936         self.receive_buttons_hbox = hbox
937         hbox.addStretch(1)
938         return w
939
940
941     def receive_tab_set_mode(self, i):
942         self.save_column_widths()
943         self.expert_mode = (i == 1)
944         self.config.set_key('classic_expert_mode', self.expert_mode, True)
945         self.wallet.save()
946         self.update_receive_tab()
947
948
949     def save_column_widths(self):
950         if not self.expert_mode:
951             widths = [ self.receive_list.columnWidth(0) ]
952         else:
953             widths = []
954             for i in range(self.receive_list.columnCount() -1):
955                 widths.append(self.receive_list.columnWidth(i))
956         self.column_widths["receive"][self.expert_mode] = widths
957         
958         self.column_widths["history"] = []
959         for i in range(self.history_list.columnCount() - 1):
960             self.column_widths["history"].append(self.history_list.columnWidth(i))
961
962         self.column_widths["contacts"] = []
963         for i in range(self.contacts_list.columnCount() - 1):
964             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
965
966
967     def create_contacts_tab(self):
968         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
969         l.setContextMenuPolicy(Qt.CustomContextMenu)
970         l.customContextMenuRequested.connect(self.create_contact_menu)
971         for i,width in enumerate(self.column_widths['contacts']):
972             l.setColumnWidth(i, width)
973
974         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
975         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
976         self.contacts_list = l
977         self.contacts_buttons_hbox = hbox
978         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
979         hbox.addStretch(1)
980         return w
981
982
983     def delete_imported_key(self, addr):
984         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
985             self.wallet.imported_keys.pop(addr)
986             self.update_receive_tab()
987             self.update_history_tab()
988             self.wallet.save()
989
990
991     def create_receive_menu(self, position):
992         # fixme: this function apparently has a side effect.
993         # if it is not called the menu pops up several times
994         #self.receive_list.selectedIndexes() 
995
996         item = self.receive_list.itemAt(position)
997         if not item: return
998         addr = unicode(item.text(0))
999         if not is_valid(addr): 
1000             item.setExpanded(not item.isExpanded())
1001             return 
1002         menu = QMenu()
1003         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1004         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1005         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1006         menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1007         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1008         if addr in self.wallet.imported_keys:
1009             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1010
1011         if self.expert_mode:
1012             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1013             menu.addAction(t, lambda: self.toggle_freeze(addr))
1014             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1015             menu.addAction(t, lambda: self.toggle_priority(addr))
1016             
1017         self.run_hook('receive_menu', menu)
1018         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1019
1020
1021     def payto(self, addr):
1022         if not addr: return
1023         label = self.wallet.labels.get(addr)
1024         m_addr = label + '  <' + addr + '>' if label else addr
1025         self.tabs.setCurrentIndex(1)
1026         self.payto_e.setText(m_addr)
1027         self.amount_e.setFocus()
1028
1029
1030     def delete_contact(self, x):
1031         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1032             if x in self.wallet.addressbook:
1033                 self.wallet.addressbook.remove(x)
1034                 self.set_label(x, None)
1035                 self.update_history_tab()
1036                 self.update_contacts_tab()
1037                 self.update_completions()
1038
1039
1040     def create_contact_menu(self, position):
1041         item = self.contacts_list.itemAt(position)
1042         if not item: return
1043         addr = unicode(item.text(0))
1044         label = unicode(item.text(1))
1045         is_editable = item.data(0,32).toBool()
1046         payto_addr = item.data(0,33).toString()
1047         menu = QMenu()
1048         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1049         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1050         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1051         if is_editable:
1052             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1053             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1054
1055         self.run_hook('create_contact_menu', menu, item)
1056         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1057
1058
1059     def update_receive_item(self, item):
1060         item.setFont(0, QFont(MONOSPACE_FONT))
1061         address = str(item.data(0,0).toString())
1062         label = self.wallet.labels.get(address,'')
1063         item.setData(1,0,label)
1064         item.setData(0,32, True) # is editable
1065
1066         self.run_hook('update_receive_item', address, item)
1067                 
1068         c, u = self.wallet.get_addr_balance(address)
1069         balance = self.format_amount(c + u)
1070         item.setData(2,0,balance)
1071
1072         if self.expert_mode:
1073             if address in self.wallet.frozen_addresses: 
1074                 item.setBackgroundColor(0, QColor('lightblue'))
1075             elif address in self.wallet.prioritized_addresses: 
1076                 item.setBackgroundColor(0, QColor('lightgreen'))
1077         
1078
1079     def update_receive_tab(self):
1080         l = self.receive_list
1081         
1082         l.clear()
1083         l.setColumnHidden(2, not self.expert_mode)
1084         l.setColumnHidden(3, not self.expert_mode)
1085         if not self.expert_mode:
1086             width = self.column_widths['receive'][0][0]
1087             l.setColumnWidth(0, width)
1088         else:
1089             for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1090                 l.setColumnWidth(i, width)        
1091
1092         if self.current_account is None:
1093             account_items = self.wallet.accounts.items()
1094         elif self.current_account != -1:
1095             account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1096         else:
1097             account_items = []
1098
1099         for k, account in account_items:
1100             name = account.get('name',str(k))
1101             c,u = self.wallet.get_account_balance(k)
1102             account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1103             l.addTopLevelItem(account_item)
1104             account_item.setExpanded(True)
1105             
1106             for is_change in ([0,1] if self.expert_mode else [0]):
1107                 if self.expert_mode:
1108                     name = "Receiving" if not is_change else "Change"
1109                     seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1110                     account_item.addChild(seq_item)
1111                     if not is_change: seq_item.setExpanded(True)
1112                 else:
1113                     seq_item = account_item
1114                 is_red = False
1115                 gap = 0
1116
1117                 for address in account[is_change]:
1118                     h = self.wallet.history.get(address,[])
1119             
1120                     if h == []:
1121                         gap += 1
1122                         if gap > self.wallet.gap_limit:
1123                             is_red = True
1124                     else:
1125                         gap = 0
1126
1127                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1128                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1129                     self.update_receive_item(item)
1130                     if is_red:
1131                         item.setBackgroundColor(1, QColor('red'))
1132                     seq_item.addChild(item)
1133
1134
1135         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1136             c,u = self.wallet.get_imported_balance()
1137             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1138             l.addTopLevelItem(account_item)
1139             account_item.setExpanded(True)
1140             for address in self.wallet.imported_keys.keys():
1141                 item = QTreeWidgetItem( [ address, '', '', ''] )
1142                 self.update_receive_item(item)
1143                 account_item.addChild(item)
1144                 
1145
1146         # we use column 1 because column 0 may be hidden
1147         l.setCurrentItem(l.topLevelItem(0),1)
1148
1149
1150     def update_contacts_tab(self):
1151
1152         l = self.contacts_list
1153         l.clear()
1154
1155         for address in self.wallet.addressbook:
1156             label = self.wallet.labels.get(address,'')
1157             n = self.wallet.get_num_tx(address)
1158             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1159             item.setFont(0, QFont(MONOSPACE_FONT))
1160             # 32 = label can be edited (bool)
1161             item.setData(0,32, True)
1162             # 33 = payto string
1163             item.setData(0,33, address)
1164             l.addTopLevelItem(item)
1165
1166         self.run_hook('update_contacts_tab', l)
1167         l.setCurrentItem(l.topLevelItem(0))
1168
1169
1170
1171     def create_console_tab(self):
1172         from qt_console import Console
1173         self.console = console = Console()
1174         self.console.history = self.config.get("console-history",[])
1175         self.console.history_index = len(self.console.history)
1176
1177         console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1178         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1179
1180         c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1181         methods = {}
1182         def mkfunc(f, method):
1183             return lambda *args: apply( f, (method, args, self.password_dialog ))
1184         for m in dir(c):
1185             if m[0]=='_' or m=='wallet' or m == 'interface': continue
1186             methods[m] = mkfunc(c._run, m)
1187             
1188         console.updateNamespace(methods)
1189         return console
1190
1191     def change_account(self,s):
1192         if s == _("All accounts"):
1193             self.current_account = None
1194         else:
1195             accounts = self.wallet.get_accounts()
1196             for k, v in accounts.items():
1197                 if v == s:
1198                     self.current_account = k
1199         self.update_history_tab()
1200         self.update_status()
1201         self.update_receive_tab()
1202
1203     def create_status_bar(self):
1204         self.status_text = ""
1205         sb = QStatusBar()
1206         sb.setFixedHeight(35)
1207         qtVersion = qVersion()
1208
1209         update_notification = UpdateLabel(self.config)
1210         if(update_notification.new_version):
1211             sb.addPermanentWidget(update_notification)
1212
1213         accounts = self.wallet.get_accounts()
1214         if len(accounts) > 1:
1215             from_combo = QComboBox()
1216             from_combo.addItems([_("All accounts")] + accounts.values())
1217             from_combo.setCurrentIndex(0)
1218             self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account) 
1219             sb.addPermanentWidget(from_combo)
1220
1221         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1222             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1223         if self.wallet.seed:
1224             self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1225             self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1226             sb.addPermanentWidget( self.password_button )
1227         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1228         if self.wallet.seed:
1229             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1230         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) ) 
1231         sb.addPermanentWidget( self.status_button )
1232
1233         self.run_hook('create_status_bar', (sb,))
1234
1235         self.setStatusBar(sb)
1236         
1237     def go_lite(self):
1238         import gui_lite
1239         self.config.set_key('gui', 'lite', True)
1240         self.hide()
1241         if self.lite:
1242             self.lite.mini.show()
1243         else:
1244             self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1245             self.lite.main(None)
1246
1247     def new_contact_dialog(self):
1248         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1249         address = unicode(text)
1250         if ok:
1251             if is_valid(address):
1252                 self.wallet.addressbook.append(address)
1253                 self.wallet.save()
1254                 self.update_contacts_tab()
1255                 self.update_history_tab()
1256                 self.update_completions()
1257             else:
1258                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1259
1260     def show_master_public_key(self):
1261         dialog = QDialog(self)
1262         dialog.setModal(1)
1263         dialog.setWindowTitle(_("Master Public Key"))
1264
1265         main_text = QTextEdit()
1266         main_text.setText(self.wallet.get_master_public_key())
1267         main_text.setReadOnly(True)
1268         main_text.setMaximumHeight(170)
1269         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1270
1271         ok_button = QPushButton(_("OK"))
1272         ok_button.setDefault(True)
1273         ok_button.clicked.connect(dialog.accept)
1274
1275         main_layout = QGridLayout()
1276         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1277
1278         main_layout.addWidget(main_text, 1, 0)
1279         main_layout.addWidget(qrw, 1, 1 )
1280
1281         vbox = QVBoxLayout()
1282         vbox.addLayout(main_layout)
1283         hbox = QHBoxLayout()
1284         hbox.addStretch(1)
1285         hbox.addWidget(ok_button)
1286         vbox.addLayout(hbox)
1287
1288         dialog.setLayout(vbox)
1289         dialog.exec_()
1290         
1291
1292     @protected
1293     def show_seed_dialog(self, password):
1294         if not self.wallet.seed:
1295             QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1296             return
1297         try:
1298             seed = self.wallet.decode_seed(password)
1299         except:
1300             QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1301             return
1302         self.show_seed(seed, self.wallet.imported_keys, self)
1303
1304
1305     @classmethod
1306     def show_seed(self, seed, imported_keys, parent=None):
1307         dialog = QDialog(parent)
1308         dialog.setModal(1)
1309         dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1310
1311         brainwallet = ' '.join(mnemonic.mn_encode(seed))
1312
1313         label1 = QLabel(_("Your wallet generation seed is")+ ":")
1314
1315         seed_text = QTextEdit(brainwallet)
1316         seed_text.setReadOnly(True)
1317         seed_text.setMaximumHeight(130)
1318         
1319         msg2 =  _("Please write down or memorize these 12 words (order is important).") + " " \
1320               + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1321               + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1322               + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1323         if imported_keys:
1324             msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1325         label2 = QLabel(msg2)
1326         label2.setWordWrap(True)
1327
1328         logo = QLabel()
1329         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1330         logo.setMaximumWidth(60)
1331
1332         qrw = QRCodeWidget(seed)
1333
1334         ok_button = QPushButton(_("OK"))
1335         ok_button.setDefault(True)
1336         ok_button.clicked.connect(dialog.accept)
1337
1338         grid = QGridLayout()
1339         #main_layout.addWidget(logo, 0, 0)
1340
1341         grid.addWidget(logo, 0, 0)
1342         grid.addWidget(label1, 0, 1)
1343
1344         grid.addWidget(seed_text, 1, 0, 1, 2)
1345
1346         grid.addWidget(qrw, 0, 2, 2, 1)
1347
1348         vbox = QVBoxLayout()
1349         vbox.addLayout(grid)
1350         vbox.addWidget(label2)
1351
1352         hbox = QHBoxLayout()
1353         hbox.addStretch(1)
1354         hbox.addWidget(ok_button)
1355         vbox.addLayout(hbox)
1356
1357         dialog.setLayout(vbox)
1358         dialog.exec_()
1359
1360     def show_qrcode(self, data, title = "QR code"):
1361         if not data: return
1362         d = QDialog(self)
1363         d.setModal(1)
1364         d.setWindowTitle(title)
1365         d.setMinimumSize(270, 300)
1366         vbox = QVBoxLayout()
1367         qrw = QRCodeWidget(data)
1368         vbox.addWidget(qrw, 1)
1369         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1370         hbox = QHBoxLayout()
1371         hbox.addStretch(1)
1372
1373         def print_qr(self):
1374             filename = "qrcode.bmp"
1375             bmp.save_qrcode(qrw.qr, filename)
1376             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1377
1378         b = QPushButton(_("Save"))
1379         hbox.addWidget(b)
1380         b.clicked.connect(print_qr)
1381
1382         b = QPushButton(_("Close"))
1383         hbox.addWidget(b)
1384         b.clicked.connect(d.accept)
1385         b.setDefault(True)
1386
1387         vbox.addLayout(hbox)
1388         d.setLayout(vbox)
1389         d.exec_()
1390
1391
1392     def do_protect(self, func, args):
1393         if self.wallet.use_encryption:
1394             password = self.password_dialog()
1395             if not password:
1396                 return
1397         else:
1398             password = None
1399             
1400         if args != (False,):
1401             args = (self,) + args + (password,)
1402         else:
1403             args = (self,password)
1404         apply( func, args)
1405
1406
1407     @protected
1408     def show_private_key(self, address, password):
1409         if not address: return
1410         try:
1411             pk = self.wallet.get_private_key(address, password)
1412         except BaseException, e:
1413             self.show_message(str(e))
1414             return
1415         QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1416
1417
1418     @protected
1419     def do_sign(self, address, message, signature, password):
1420         try:
1421             sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1422             signature.setText(sig)
1423         except BaseException, e:
1424             self.show_message(str(e))
1425
1426     def sign_message(self, address):
1427         if not address: return
1428         d = QDialog(self)
1429         d.setModal(1)
1430         d.setWindowTitle(_('Sign Message'))
1431         d.setMinimumSize(410, 290)
1432
1433         tab_widget = QTabWidget()
1434         tab = QWidget()
1435         layout = QGridLayout(tab)
1436
1437         sign_address = QLineEdit()
1438
1439         sign_address.setText(address)
1440         layout.addWidget(QLabel(_('Address')), 1, 0)
1441         layout.addWidget(sign_address, 1, 1)
1442
1443         sign_message = QTextEdit()
1444         layout.addWidget(QLabel(_('Message')), 2, 0)
1445         layout.addWidget(sign_message, 2, 1)
1446         layout.setRowStretch(2,3)
1447
1448         sign_signature = QTextEdit()
1449         layout.addWidget(QLabel(_('Signature')), 3, 0)
1450         layout.addWidget(sign_signature, 3, 1)
1451         layout.setRowStretch(3,1)
1452
1453
1454         hbox = QHBoxLayout()
1455         b = QPushButton(_("Sign"))
1456         hbox.addWidget(b)
1457         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1458         b = QPushButton(_("Close"))
1459         b.clicked.connect(d.accept)
1460         hbox.addWidget(b)
1461         layout.addLayout(hbox, 4, 1)
1462         tab_widget.addTab(tab, _("Sign"))
1463
1464
1465         tab = QWidget()
1466         layout = QGridLayout(tab)
1467
1468         verify_address = QLineEdit()
1469         layout.addWidget(QLabel(_('Address')), 1, 0)
1470         layout.addWidget(verify_address, 1, 1)
1471
1472         verify_message = QTextEdit()
1473         layout.addWidget(QLabel(_('Message')), 2, 0)
1474         layout.addWidget(verify_message, 2, 1)
1475         layout.setRowStretch(2,3)
1476
1477         verify_signature = QTextEdit()
1478         layout.addWidget(QLabel(_('Signature')), 3, 0)
1479         layout.addWidget(verify_signature, 3, 1)
1480         layout.setRowStretch(3,1)
1481
1482         def do_verify():
1483             try:
1484                 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1485                 self.show_message(_("Signature verified"))
1486             except BaseException, e:
1487                 self.show_message(str(e))
1488                 return
1489
1490         hbox = QHBoxLayout()
1491         b = QPushButton(_("Verify"))
1492         b.clicked.connect(do_verify)
1493         hbox.addWidget(b)
1494         b = QPushButton(_("Close"))
1495         b.clicked.connect(d.accept)
1496         hbox.addWidget(b)
1497         layout.addLayout(hbox, 4, 1)
1498         tab_widget.addTab(tab, _("Verify"))
1499
1500         vbox = QVBoxLayout()
1501         vbox.addWidget(tab_widget)
1502         d.setLayout(vbox)
1503         d.exec_()
1504
1505         
1506
1507
1508     def question(self, msg):
1509         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1510
1511     def show_message(self, msg):
1512         QMessageBox.information(self, _('Message'), msg, _('OK'))
1513
1514     def password_dialog(self ):
1515         d = QDialog(self)
1516         d.setModal(1)
1517
1518         pw = QLineEdit()
1519         pw.setEchoMode(2)
1520
1521         vbox = QVBoxLayout()
1522         msg = _('Please enter your password')
1523         vbox.addWidget(QLabel(msg))
1524
1525         grid = QGridLayout()
1526         grid.setSpacing(8)
1527         grid.addWidget(QLabel(_('Password')), 1, 0)
1528         grid.addWidget(pw, 1, 1)
1529         vbox.addLayout(grid)
1530
1531         vbox.addLayout(ok_cancel_buttons(d))
1532         d.setLayout(vbox)
1533
1534         self.run_hook('password_dialog', pw, grid, 1)
1535         if not d.exec_(): return
1536         return unicode(pw.text())
1537
1538
1539
1540
1541
1542     @staticmethod
1543     def change_password_dialog( wallet, parent=None ):
1544
1545         if not wallet.seed:
1546             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1547             return
1548
1549         d = QDialog(parent)
1550         d.setModal(1)
1551
1552         pw = QLineEdit()
1553         pw.setEchoMode(2)
1554         new_pw = QLineEdit()
1555         new_pw.setEchoMode(2)
1556         conf_pw = QLineEdit()
1557         conf_pw.setEchoMode(2)
1558
1559         vbox = QVBoxLayout()
1560         if parent:
1561             msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1562                    +_('To disable wallet encryption, enter an empty new password.')) \
1563                    if wallet.use_encryption else _('Your wallet keys are not encrypted')
1564         else:
1565             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1566                   +_("Leave these fields empty if you want to disable encryption.")
1567         vbox.addWidget(QLabel(msg))
1568
1569         grid = QGridLayout()
1570         grid.setSpacing(8)
1571
1572         if wallet.use_encryption:
1573             grid.addWidget(QLabel(_('Password')), 1, 0)
1574             grid.addWidget(pw, 1, 1)
1575
1576         grid.addWidget(QLabel(_('New Password')), 2, 0)
1577         grid.addWidget(new_pw, 2, 1)
1578
1579         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1580         grid.addWidget(conf_pw, 3, 1)
1581         vbox.addLayout(grid)
1582
1583         vbox.addLayout(ok_cancel_buttons(d))
1584         d.setLayout(vbox) 
1585
1586         if not d.exec_(): return
1587
1588         password = unicode(pw.text()) if wallet.use_encryption else None
1589         new_password = unicode(new_pw.text())
1590         new_password2 = unicode(conf_pw.text())
1591
1592         try:
1593             seed = wallet.decode_seed(password)
1594         except:
1595             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1596             return
1597
1598         if new_password != new_password2:
1599             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1600             return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1601
1602         wallet.update_password(seed, password, new_password)
1603         if parent: 
1604             icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1605             parent.password_button.setIcon( icon )
1606
1607
1608
1609     def generate_transaction_information_widget(self, tx):
1610         tabs = QTabWidget(self)
1611
1612         tab1 = QWidget()
1613         grid_ui = QGridLayout(tab1)
1614         grid_ui.setColumnStretch(0,1)
1615         tabs.addTab(tab1, _('Outputs') )
1616
1617         tree_widget = MyTreeWidget(self)
1618         tree_widget.setColumnCount(2)
1619         tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1620         tree_widget.setColumnWidth(0, 300)
1621         tree_widget.setColumnWidth(1, 50)
1622
1623         for address, value in tx.outputs:
1624             item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1625             tree_widget.addTopLevelItem(item)
1626
1627         tree_widget.setMaximumHeight(100)
1628
1629         grid_ui.addWidget(tree_widget)
1630
1631         tab2 = QWidget()
1632         grid_ui = QGridLayout(tab2)
1633         grid_ui.setColumnStretch(0,1)
1634         tabs.addTab(tab2, _('Inputs') )
1635         
1636         tree_widget = MyTreeWidget(self)
1637         tree_widget.setColumnCount(2)
1638         tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1639
1640         for input_line in tx.inputs:
1641             item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1642             tree_widget.addTopLevelItem(item)
1643
1644         tree_widget.setMaximumHeight(100)
1645
1646         grid_ui.addWidget(tree_widget)
1647         return tabs
1648
1649
1650     def tx_dict_from_text(self, txt):
1651         try:
1652             tx_dict = json.loads(str(txt))
1653             assert "hex" in tx_dict.keys()
1654             assert "complete" in tx_dict.keys()
1655             if not tx_dict["complete"]:
1656                 assert "input_info" in tx_dict.keys()
1657         except:
1658             QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1659             return None
1660         return tx_dict
1661
1662
1663     def read_tx_from_file(self):
1664         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1665         if not fileName:
1666             return
1667         try:
1668             with open(fileName, "r") as f:
1669                 file_content = f.read()
1670         except (ValueError, IOError, os.error), reason:
1671             QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1672
1673         return self.tx_dict_from_text(file_content)
1674
1675
1676     @protected
1677     def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1678         try:
1679             self.wallet.signrawtransaction(tx, input_info, [], password)
1680             
1681             fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1682             if fileName:
1683                 with open(fileName, "w+") as f:
1684                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1685                 self.show_message(_("Transaction saved successfully"))
1686                 if dialog:
1687                     dialog.done(0)
1688         except BaseException, e:
1689             self.show_message(str(e))
1690     
1691
1692     def send_raw_transaction(self, raw_tx, dialog = ""):
1693         result, result_message = self.wallet.sendtx( raw_tx )
1694         if result:
1695             self.show_message("Transaction successfully sent: %s" % (result_message))
1696             if dialog:
1697                 dialog.done(0)
1698         else:
1699             self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1700
1701     def do_process_from_text(self):
1702         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1703         if not text:
1704             return
1705         tx_dict = self.tx_dict_from_text(text)
1706         if tx_dict:
1707             self.create_process_transaction_window(tx_dict)
1708
1709     def do_process_from_file(self):
1710         tx_dict = self.read_tx_from_file()
1711         if tx_dict: 
1712             self.create_process_transaction_window(tx_dict)
1713
1714     def create_process_transaction_window(self, tx_dict):
1715         tx = Transaction(tx_dict["hex"])
1716             
1717         dialog = QDialog(self)
1718         dialog.setMinimumWidth(500)
1719         dialog.setWindowTitle(_('Process raw transaction'))
1720         dialog.setModal(1)
1721
1722         l = QGridLayout()
1723         dialog.setLayout(l)
1724
1725         l.addWidget(QLabel(_("Transaction status:")), 3,0)
1726         l.addWidget(QLabel(_("Actions")), 4,0)
1727
1728         if tx_dict["complete"] == False:
1729             l.addWidget(QLabel(_("Unsigned")), 3,1)
1730             if self.wallet.seed :
1731                 b = QPushButton("Sign transaction")
1732                 input_info = json.loads(tx_dict["input_info"])
1733                 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1734                 l.addWidget(b, 4, 1)
1735             else:
1736                 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1737         else:
1738             l.addWidget(QLabel(_("Signed")), 3,1)
1739             b = QPushButton("Broadcast transaction")
1740             b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1741             l.addWidget(b,4,1)
1742
1743         l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1744         cancelButton = QPushButton(_("Cancel"))
1745         cancelButton.clicked.connect(lambda: dialog.done(0))
1746         l.addWidget(cancelButton, 4,2)
1747
1748         dialog.exec_()
1749
1750
1751     @protected
1752     def do_export_privkeys(self, password):
1753         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.")))
1754
1755         try:
1756             select_export = _('Select file to export your private keys to')
1757             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1758             if fileName:
1759                 with open(fileName, "w+") as csvfile:
1760                     transaction = csv.writer(csvfile)
1761                     transaction.writerow(["address", "private_key"])
1762
1763                     
1764                     for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1765                         transaction.writerow(["%34s"%addr,pk])
1766
1767                     self.show_message(_("Private keys exported."))
1768
1769         except (IOError, os.error), reason:
1770             export_error_label = _("Electrum was unable to produce a private key-export.")
1771             QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1772
1773         except BaseException, e:
1774           self.show_message(str(e))
1775           return
1776
1777
1778     def do_import_labels(self):
1779         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1780         if not labelsFile: return
1781         try:
1782             f = open(labelsFile, 'r')
1783             data = f.read()
1784             f.close()
1785             for key, value in json.loads(data).items():
1786                 self.wallet.labels[key] = value
1787             self.wallet.save()
1788             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1789         except (IOError, os.error), reason:
1790             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1791             
1792
1793     def do_export_labels(self):
1794         labels = self.wallet.labels
1795         try:
1796             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1797             if fileName:
1798                 with open(fileName, 'w+') as f:
1799                     json.dump(labels, f)
1800                 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1801         except (IOError, os.error), reason:
1802             QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1803
1804
1805     def do_export_history(self):
1806         from gui_lite import csv_transaction
1807         csv_transaction(self.wallet)
1808
1809
1810     @protected
1811     def do_import_privkey(self, password):
1812         if not self.wallet.imported_keys:
1813             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1814                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1815                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1816             if r == 4: return
1817
1818         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1819         if not text: return
1820
1821         text = str(text).split()
1822         badkeys = []
1823         addrlist = []
1824         for key in text:
1825             try:
1826                 addr = self.wallet.import_key(key, password)
1827             except BaseException as e:
1828                 badkeys.append(key)
1829                 continue
1830             if not addr: 
1831                 badkeys.append(key)
1832             else:
1833                 addrlist.append(addr)
1834         if addrlist:
1835             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1836         if badkeys:
1837             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1838         self.update_receive_tab()
1839         self.update_history_tab()
1840
1841
1842     def settings_dialog(self):
1843         d = QDialog(self)
1844         d.setWindowTitle(_('Electrum Settings'))
1845         d.setModal(1)
1846         vbox = QVBoxLayout()
1847
1848         tabs = QTabWidget(self)
1849         self.settings_tab = tabs
1850         vbox.addWidget(tabs)
1851
1852         tab1 = QWidget()
1853         grid_ui = QGridLayout(tab1)
1854         grid_ui.setColumnStretch(0,1)
1855         tabs.addTab(tab1, _('Display') )
1856
1857         nz_label = QLabel(_('Display zeros'))
1858         grid_ui.addWidget(nz_label, 0, 0)
1859         nz_e = AmountEdit(None,True)
1860         nz_e.setText("%d"% self.wallet.num_zeros)
1861         grid_ui.addWidget(nz_e, 0, 1)
1862         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1863         grid_ui.addWidget(HelpButton(msg), 0, 2)
1864         if not self.config.is_modifiable('num_zeros'):
1865             for w in [nz_e, nz_label]: w.setEnabled(False)
1866         
1867         lang_label=QLabel(_('Language') + ':')
1868         grid_ui.addWidget(lang_label, 1, 0)
1869         lang_combo = QComboBox()
1870         from i18n import languages
1871         lang_combo.addItems(languages.values())
1872         try:
1873             index = languages.keys().index(self.config.get("language",''))
1874         except:
1875             index = 0
1876         lang_combo.setCurrentIndex(index)
1877         grid_ui.addWidget(lang_combo, 1, 1)
1878         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1879         if not self.config.is_modifiable('language'):
1880             for w in [lang_combo, lang_label]: w.setEnabled(False)
1881
1882         currencies = self.exchanger.get_currencies()
1883         currencies.insert(0, "None")
1884
1885         cur_label=QLabel(_('Currency') + ':')
1886         grid_ui.addWidget(cur_label , 2, 0)
1887         cur_combo = QComboBox()
1888         cur_combo.addItems(currencies)
1889         try:
1890             index = currencies.index(self.config.get('currency', "None"))
1891         except:
1892             index = 0
1893         cur_combo.setCurrentIndex(index)
1894         grid_ui.addWidget(cur_combo, 2, 1)
1895         grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1896         
1897         expert_cb = QCheckBox(_('Expert mode'))
1898         expert_cb.setChecked(self.expert_mode)
1899         grid_ui.addWidget(expert_cb, 3, 0)
1900         hh =  _('In expert mode, your client will:') + '\n'  \
1901             + _(' - Show change addresses in the Receive tab') + '\n'  \
1902             + _(' - Display the balance of each address') + '\n'  \
1903             + _(' - Add freeze/prioritize actions to addresses.') 
1904         grid_ui.addWidget(HelpButton(hh), 3, 2)
1905         grid_ui.setRowStretch(4,1)
1906
1907         # wallet tab
1908         tab2 = QWidget()
1909         grid_wallet = QGridLayout(tab2)
1910         grid_wallet.setColumnStretch(0,1)
1911         tabs.addTab(tab2, _('Wallet') )
1912         
1913         fee_label = QLabel(_('Transaction fee'))
1914         grid_wallet.addWidget(fee_label, 0, 0)
1915         fee_e = AmountEdit(self.base_unit)
1916         fee_e.setText(self.format_amount(self.wallet.fee).strip())
1917         grid_wallet.addWidget(fee_e, 0, 2)
1918         msg = _('Fee per kilobyte of transaction.') + ' ' \
1919             + _('Recommended value') + ': ' + self.format_amount(20000)
1920         grid_wallet.addWidget(HelpButton(msg), 0, 3)
1921         if not self.config.is_modifiable('fee_per_kb'):
1922             for w in [fee_e, fee_label]: w.setEnabled(False)
1923
1924         usechange_cb = QCheckBox(_('Use change addresses'))
1925         usechange_cb.setChecked(self.wallet.use_change)
1926         grid_wallet.addWidget(usechange_cb, 1, 0)
1927         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1928         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1929
1930         gap_label = QLabel(_('Gap limit'))
1931         grid_wallet.addWidget(gap_label, 2, 0)
1932         gap_e = AmountEdit(None,True)
1933         gap_e.setText("%d"% self.wallet.gap_limit)
1934         grid_wallet.addWidget(gap_e, 2, 2)
1935         msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1936               + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1937               + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1938               + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1939               + _('Warning') + ': ' \
1940               + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1941               + _('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' 
1942         grid_wallet.addWidget(HelpButton(msg), 2, 3)
1943         if not self.config.is_modifiable('gap_limit'):
1944             for w in [gap_e, gap_label]: w.setEnabled(False)
1945
1946         units = ['BTC', 'mBTC']
1947         unit_label = QLabel(_('Base unit'))
1948         grid_wallet.addWidget(unit_label, 3, 0)
1949         unit_combo = QComboBox()
1950         unit_combo.addItems(units)
1951         unit_combo.setCurrentIndex(units.index(self.base_unit()))
1952         grid_wallet.addWidget(unit_combo, 3, 2)
1953         grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1954                                              + '\n1BTC=1000mBTC.\n' \
1955                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1956         grid_wallet.setRowStretch(4,1)
1957
1958
1959         # import/export tab
1960         tab3 = QWidget()
1961         grid_io = QGridLayout(tab3)
1962         grid_io.setColumnStretch(0,1)
1963         tabs.addTab(tab3, _('Import/Export') )
1964         
1965         grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1966         grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1967         grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1968         grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1969
1970         grid_io.addWidget(QLabel(_('History')), 2, 0)
1971         grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1972         grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1973
1974         grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
1975
1976         grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
1977         grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1978         grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1979
1980         grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1981         grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1982         grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1983                               + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1984                               + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1985
1986
1987         grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
1988         grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
1989         grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
1990         grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
1991
1992         grid_io.setRowStretch(6,1)
1993
1994
1995         # plugins
1996         if self.plugins:
1997             tab5 = QScrollArea()
1998             tab5.setEnabled(True)
1999             tab5.setWidgetResizable(True)
2000
2001             grid_plugins = QGridLayout()
2002             grid_plugins.setColumnStretch(0,1)
2003
2004             w = QtGui.QWidget()
2005             w.setLayout(grid_plugins)
2006             tab5.setWidget(w)
2007             tab5.setMaximumSize(tab3.size())  # optional
2008
2009             w.setMinimumHeight(len(self.plugins)*35)
2010
2011             tabs.addTab(tab5, _('Plugins') )
2012             def mk_toggle(cb, p):
2013                 return lambda: cb.setChecked(p.toggle())
2014             for i, p in enumerate(self.plugins):
2015                 try:
2016                     name, description = p.get_info()
2017                     cb = QCheckBox(name)
2018                     cb.setDisabled(not p.is_available())
2019                     cb.setChecked(p.is_enabled())
2020                     cb.clicked.connect(mk_toggle(cb,p))
2021                     grid_plugins.addWidget(cb, i, 0)
2022                     if p.requires_settings():
2023                         grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2024                     grid_plugins.addWidget(HelpButton(description), i, 2)
2025                 except:
2026                     print_msg("Error: cannot display plugin", p)
2027                     traceback.print_exc(file=sys.stdout)
2028             grid_plugins.setRowStretch(i+1,1)
2029
2030         self.run_hook('create_settings_tab', tabs)
2031
2032         vbox.addLayout(ok_cancel_buttons(d))
2033         d.setLayout(vbox) 
2034
2035         # run the dialog
2036         if not d.exec_(): return
2037
2038         fee = unicode(fee_e.text())
2039         try:
2040             fee = self.read_amount(fee)
2041         except:
2042             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2043             return
2044
2045         if self.wallet.fee != fee:
2046             self.wallet.fee = fee
2047             self.wallet.save()
2048         
2049         nz = unicode(nz_e.text())
2050         try:
2051             nz = int( nz )
2052             if nz>8: nz=8
2053         except:
2054             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2055             return
2056
2057         if self.wallet.num_zeros != nz:
2058             self.wallet.num_zeros = nz
2059             self.config.set_key('num_zeros', nz, True)
2060             self.update_history_tab()
2061             self.update_receive_tab()
2062
2063         usechange_result = usechange_cb.isChecked()
2064         if self.wallet.use_change != usechange_result:
2065             self.wallet.use_change = usechange_result
2066             self.config.set_key('use_change', self.wallet.use_change, True)
2067         
2068         unit_result = units[unit_combo.currentIndex()]
2069         if self.base_unit() != unit_result:
2070             self.decimal_point = 8 if unit_result == 'BTC' else 5
2071             self.config.set_key('decimal_point', self.decimal_point, True)
2072             self.update_history_tab()
2073             self.update_status()
2074         
2075         try:
2076             n = int(gap_e.text())
2077         except:
2078             QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2079             return
2080
2081         if self.wallet.gap_limit != n:
2082             r = self.wallet.change_gap_limit(n)
2083             if r:
2084                 self.update_receive_tab()
2085                 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2086             else:
2087                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2088
2089         need_restart = False
2090
2091         lang_request = languages.keys()[lang_combo.currentIndex()]
2092         if lang_request != self.config.get('language'):
2093             self.config.set_key("language", lang_request, True)
2094             need_restart = True
2095             
2096         cur_request = str(currencies[cur_combo.currentIndex()])
2097         if cur_request != self.config.get('currency', "None"):
2098             self.config.set_key('currency', cur_request, True)
2099             self.update_wallet()
2100
2101         self.run_hook('close_settings_dialog')
2102
2103         if need_restart:
2104             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2105
2106         self.receive_tab_set_mode(expert_cb.isChecked())
2107
2108
2109     @staticmethod 
2110     def network_dialog(wallet, parent=None):
2111         interface = wallet.interface
2112         if parent:
2113             if interface.is_connected:
2114                 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2115             else:
2116                 status = _("Not connected")
2117             server = interface.server
2118         else:
2119             import random
2120             status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2121             server = interface.server
2122
2123         plist, servers_list = interface.get_servers_list()
2124
2125         d = QDialog(parent)
2126         d.setModal(1)
2127         d.setWindowTitle(_('Server'))
2128         d.setMinimumSize(375, 20)
2129
2130         vbox = QVBoxLayout()
2131         vbox.setSpacing(30)
2132
2133         hbox = QHBoxLayout()
2134         l = QLabel()
2135         l.setPixmap(QPixmap(":icons/network.png"))
2136         hbox.addStretch(10)
2137         hbox.addWidget(l)
2138         hbox.addWidget(QLabel(status))
2139         hbox.addStretch(50)
2140         vbox.addLayout(hbox)
2141
2142
2143         # grid layout
2144         grid = QGridLayout()
2145         grid.setSpacing(8)
2146         vbox.addLayout(grid)
2147
2148         # server
2149         server_protocol = QComboBox()
2150         server_host = QLineEdit()
2151         server_host.setFixedWidth(200)
2152         server_port = QLineEdit()
2153         server_port.setFixedWidth(60)
2154
2155         protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
2156         protocol_letters = 'thsg'
2157         server_protocol.addItems(protocol_names)
2158
2159         grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2160         grid.addWidget(server_protocol, 0, 1)
2161         grid.addWidget(server_host, 0, 2)
2162         grid.addWidget(server_port, 0, 3)
2163
2164         def change_protocol(p):
2165             protocol = protocol_letters[p]
2166             host = unicode(server_host.text())
2167             pp = plist.get(host,DEFAULT_PORTS)
2168             if protocol not in pp.keys():
2169                 protocol = pp.keys()[0]
2170             port = pp[protocol]
2171             server_host.setText( host )
2172             server_port.setText( port )
2173
2174         server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2175         
2176         label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2177         servers_list_widget = QTreeWidget(parent)
2178         servers_list_widget.setHeaderLabels( [ label, _('Limit') ] )
2179         servers_list_widget.setMaximumHeight(150)
2180         servers_list_widget.setColumnWidth(0, 240)
2181         for _host in servers_list.keys():
2182             pruning_level = servers_list[_host].get('pruning','')
2183             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, pruning_level ] ))
2184         servers_list_widget.setColumnHidden(1, not parent.expert_mode if parent else True)
2185
2186         def change_server(host, protocol=None):
2187             pp = plist.get(host,DEFAULT_PORTS)
2188             if protocol:
2189                 port = pp.get(protocol)
2190                 if not port: protocol = None
2191                     
2192             if not protocol:
2193                 if 's' in pp.keys():
2194                     protocol = 's'
2195                     port = pp.get(protocol)
2196                 else:
2197                     protocol = pp.keys()[0]
2198                     port = pp.get(protocol)
2199             
2200             server_host.setText( host )
2201             server_port.setText( port )
2202             server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2203
2204             if not plist: return
2205             for p in protocol_letters:
2206                 i = protocol_letters.index(p)
2207                 j = server_protocol.model().index(i,0)
2208                 if p not in pp.keys() and interface.is_connected:
2209                     server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2210                 else:
2211                     server_protocol.model().setData(j, QtCore.QVariant(33), QtCore.Qt.UserRole-1)
2212
2213         if server:
2214             host, port, protocol = server.split(':')
2215             change_server(host,protocol)
2216
2217         servers_list_widget.connect(servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'), 
2218                                     lambda x,y: change_server(unicode(x.text(0))))
2219         grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2220
2221         if not wallet.config.is_modifiable('server'):
2222             for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2223
2224         # auto cycle
2225         autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2226         autocycle_cb.setChecked(wallet.config.get('auto_cycle', True))
2227         grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2228         if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2229
2230         # proxy setting
2231         proxy_mode = QComboBox()
2232         proxy_host = QLineEdit()
2233         proxy_host.setFixedWidth(200)
2234         proxy_port = QLineEdit()
2235         proxy_port.setFixedWidth(60)
2236         proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2237
2238         def check_for_disable(index = False):
2239             if proxy_mode.currentText() != 'NONE':
2240                 proxy_host.setEnabled(True)
2241                 proxy_port.setEnabled(True)
2242             else:
2243                 proxy_host.setEnabled(False)
2244                 proxy_port.setEnabled(False)
2245
2246         check_for_disable()
2247         proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2248
2249         if not wallet.config.is_modifiable('proxy'):
2250             for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2251
2252         proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2253         proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2254         proxy_host.setText(proxy_config.get("host"))
2255         proxy_port.setText(proxy_config.get("port"))
2256
2257         grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2258         grid.addWidget(proxy_mode, 2, 1)
2259         grid.addWidget(proxy_host, 2, 2)
2260         grid.addWidget(proxy_port, 2, 3)
2261
2262         # buttons
2263         vbox.addLayout(ok_cancel_buttons(d))
2264         d.setLayout(vbox) 
2265
2266         if not d.exec_(): return
2267
2268         server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2269         if proxy_mode.currentText() != 'NONE':
2270             proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2271         else:
2272             proxy = None
2273
2274         wallet.config.set_key("proxy", proxy, True)
2275         wallet.config.set_key("server", server, True)
2276         interface.set_server(server, proxy)
2277         wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2278         return True
2279
2280     def closeEvent(self, event):
2281         g = self.geometry()
2282         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2283         self.save_column_widths()
2284         self.config.set_key("column_widths", self.column_widths, True)
2285         self.config.set_key("console-history",self.console.history[-50:])
2286         event.accept()
2287
2288
2289 class ElectrumGui:
2290
2291     def __init__(self, wallet, config, app=None):
2292         self.wallet = wallet
2293         self.config = config
2294         if app is None:
2295             self.app = QApplication(sys.argv)
2296
2297
2298     def restore_or_create(self):
2299         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2300         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2301         if r==2: return None
2302         return 'restore' if r==1 else 'create'
2303
2304
2305     def verify_seed(self):
2306         r = self.seed_dialog(False)
2307         if r != self.wallet.seed:
2308             QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2309             return False
2310         else:
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