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