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