Merge pull request #115 from rdymac/patch-14
[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             n = 0 
1185             h = self.wallet.history.get(address,[])
1186
1187             if h != ['*']: 
1188                 for tx_hash, tx_height in h:
1189                     tx = self.wallet.transactions.get(tx_hash)
1190                     if tx: n += 1
1191                 num_tx = "%d "%n
1192             else:
1193                 n = -1
1194                 num_tx = "*"
1195
1196             if n==0:
1197                 if address in self.wallet.addresses:
1198                     gap += 1
1199                     if gap > self.wallet.gap_limit:
1200                         is_red = True
1201             else:
1202                 if address in self.wallet.addresses:
1203                     gap = 0
1204
1205             item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1206             item.setFont(0, QFont(MONOSPACE_FONT))
1207             item.setFont(1, QFont(MONOSPACE_FONT))
1208             item.setFont(3, QFont(MONOSPACE_FONT))
1209             self.update_receive_item(item)
1210             if is_red and address in self.wallet.addresses:
1211                 item.setBackgroundColor(1, QColor('red'))
1212             l.addTopLevelItem(item)
1213
1214         # we use column 1 because column 0 may be hidden
1215         l.setCurrentItem(l.topLevelItem(0),1)
1216
1217     def show_contact_details(self, m):
1218         a = self.wallet.aliases.get(m)
1219         if a:
1220             if a[0] in self.wallet.authorities.keys():
1221                 s = self.wallet.authorities.get(a[0])
1222             else:
1223                 s = "self-signed"
1224             msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1225             QMessageBox.information(self, 'Alias', msg, 'OK')
1226
1227     def update_contacts_tab(self):
1228
1229         l = self.contacts_list
1230         l.clear()
1231         for i,width in enumerate(self.column_widths['contacts']):
1232             l.setColumnWidth(i, width)
1233
1234         alias_targets = []
1235         for alias, v in self.wallet.aliases.items():
1236             s, target = v
1237             alias_targets.append(target)
1238             item = QTreeWidgetItem( [ target, alias, '-'] )
1239             item.setBackgroundColor(0, QColor('lightgray'))
1240             l.addTopLevelItem(item)
1241             
1242         for address in self.wallet.addressbook:
1243             if address in alias_targets: continue
1244             label = self.wallet.labels.get(address,'')
1245             n = 0 
1246             for item in self.wallet.transactions.values():
1247                 if address in item['outputs'] : n=n+1
1248             tx = "%d"%n
1249             item = QTreeWidgetItem( [ address, label, tx] )
1250             item.setFont(0, QFont(MONOSPACE_FONT))
1251             l.addTopLevelItem(item)
1252
1253         l.setCurrentItem(l.topLevelItem(0))
1254
1255     def create_wall_tab(self):
1256         self.textbox = textbox = QTextEdit(self)
1257         textbox.setFont(QFont(MONOSPACE_FONT))
1258         textbox.setReadOnly(True)
1259         return textbox
1260
1261
1262     def create_status_bar(self):
1263         self.status_text = ""
1264         sb = QStatusBar()
1265         sb.setFixedHeight(35)
1266         qtVersion = qVersion()
1267
1268         update_notification = UpdateLabel(self.config)
1269         sb.addPermanentWidget(update_notification)
1270
1271         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1272              sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1273         if self.wallet.seed:
1274             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1275         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1276         if self.wallet.seed:
1277             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1278         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) ) 
1279         sb.addPermanentWidget( self.status_button )
1280
1281         self.setStatusBar(sb)
1282         
1283     def go_lite(self):
1284         import gui_lite
1285         self.config.set_key('gui', 'lite', True)
1286         self.hide()
1287         if self.lite:
1288             self.lite.mini.show()
1289         else:
1290             self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1291             self.lite.main(None)
1292
1293     def new_contact_dialog(self):
1294         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1295         address = unicode(text)
1296         if ok:
1297             if self.wallet.is_valid(address):
1298                 self.wallet.addressbook.append(address)
1299                 self.wallet.save()
1300                 self.update_contacts_tab()
1301                 self.update_history_tab()
1302                 self.update_completions()
1303             else:
1304                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1305
1306     def show_master_public_key(self):
1307         dialog = QDialog(None)
1308         dialog.setModal(1)
1309         dialog.setWindowTitle("Master Public Key")
1310
1311         main_text = QTextEdit()
1312         main_text.setText(self.wallet.master_public_key)
1313         main_text.setReadOnly(True)
1314         main_text.setMaximumHeight(170)
1315         qrw = QRCodeWidget(self.wallet.master_public_key, 6)
1316
1317         ok_button = QPushButton(_("OK"))
1318         ok_button.setDefault(True)
1319         ok_button.clicked.connect(dialog.accept)
1320
1321         main_layout = QGridLayout()
1322         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1323
1324         main_layout.addWidget(main_text, 1, 0)
1325         main_layout.addWidget(qrw, 1, 1 )
1326
1327         vbox = QVBoxLayout()
1328         vbox.addLayout(main_layout)
1329         hbox = QHBoxLayout()
1330         hbox.addStretch(1)
1331         hbox.addWidget(ok_button)
1332         vbox.addLayout(hbox)
1333
1334         dialog.setLayout(vbox)
1335         dialog.exec_()
1336         
1337
1338     @staticmethod
1339     def show_seed_dialog(wallet, parent=None):
1340         if not wallet.seed:
1341             QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1342             return
1343
1344         if wallet.use_encryption:
1345             password = parent.password_dialog()
1346             if not password:
1347                 return
1348         else:
1349             password = None
1350             
1351         try:
1352             seed = wallet.decode_seed(password)
1353         except:
1354             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1355             return
1356
1357         dialog = QDialog(None)
1358         dialog.setModal(1)
1359         dialog.setWindowTitle(_("Electrum") + ' - ' + _('Seed'))
1360
1361         brainwallet = ' '.join(mnemonic.mn_encode(seed))
1362
1363         label1 = QLabel(_("Your wallet generation seed is")+ ":")
1364
1365         seed_text = QTextEdit(brainwallet)
1366         seed_text.setReadOnly(True)
1367         seed_text.setMaximumHeight(130)
1368         
1369         msg2 =  _("Please write down or memorize these 12 words (order is important).") + " " \
1370               + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1371               + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1372               + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1373         label2 = QLabel(msg2)
1374         label2.setWordWrap(True)
1375
1376         logo = QLabel()
1377         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1378         logo.setMaximumWidth(60)
1379
1380         qrw = QRCodeWidget(seed, 4)
1381
1382         ok_button = QPushButton(_("OK"))
1383         ok_button.setDefault(True)
1384         ok_button.clicked.connect(dialog.accept)
1385
1386         grid = QGridLayout()
1387         #main_layout.addWidget(logo, 0, 0)
1388
1389         grid.addWidget(logo, 0, 0)
1390         grid.addWidget(label1, 0, 1)
1391
1392         grid.addWidget(seed_text, 1, 0, 1, 2)
1393
1394         grid.addWidget(qrw, 0, 2, 2, 1)
1395
1396         vbox = QVBoxLayout()
1397         vbox.addLayout(grid)
1398         vbox.addWidget(label2)
1399
1400         hbox = QHBoxLayout()
1401         hbox.addStretch(1)
1402         hbox.addWidget(ok_button)
1403         vbox.addLayout(hbox)
1404
1405         dialog.setLayout(vbox)
1406         dialog.exec_()
1407
1408     @staticmethod
1409     def show_qrcode(title, data):
1410         if not data: return
1411         d = QDialog(None)
1412         d.setModal(1)
1413         d.setWindowTitle(title)
1414         d.setMinimumSize(270, 300)
1415         vbox = QVBoxLayout()
1416         qrw = QRCodeWidget(data)
1417         vbox.addWidget(qrw, 1)
1418         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1419         hbox = QHBoxLayout()
1420         hbox.addStretch(1)
1421
1422         def print_qr(self):
1423             filename = "qrcode.bmp"
1424             bmp.save_qrcode(qrw.qr, filename)
1425             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1426
1427         b = QPushButton(_("Print"))
1428         hbox.addWidget(b)
1429         b.clicked.connect(print_qr)
1430
1431         b = QPushButton(_("Close"))
1432         hbox.addWidget(b)
1433         b.clicked.connect(d.accept)
1434
1435         vbox.addLayout(hbox)
1436         d.setLayout(vbox)
1437         d.exec_()
1438
1439     def sign_message(self,address):
1440         if not address: return
1441         d = QDialog(self)
1442         d.setModal(1)
1443         d.setWindowTitle('Sign Message')
1444         d.setMinimumSize(410, 290)
1445
1446         tab_widget = QTabWidget()
1447         tab = QWidget()
1448         layout = QGridLayout(tab)
1449
1450         sign_address = QLineEdit()
1451
1452         sign_address.setText(address)
1453         layout.addWidget(QLabel(_('Address')), 1, 0)
1454         layout.addWidget(sign_address, 1, 1)
1455
1456         sign_message = QTextEdit()
1457         layout.addWidget(QLabel(_('Message')), 2, 0)
1458         layout.addWidget(sign_message, 2, 1)
1459         layout.setRowStretch(2,3)
1460
1461         sign_signature = QTextEdit()
1462         layout.addWidget(QLabel(_('Signature')), 3, 0)
1463         layout.addWidget(sign_signature, 3, 1)
1464         layout.setRowStretch(3,1)
1465
1466         def do_sign():
1467             if self.wallet.use_encryption:
1468                 password = self.password_dialog()
1469                 if not password:
1470                     return
1471             else:
1472                 password = None
1473
1474             try:
1475                 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1476                 sign_signature.setText(signature)
1477             except BaseException, e:
1478                 self.show_message(str(e))
1479                 return
1480
1481         hbox = QHBoxLayout()
1482         b = QPushButton(_("Sign"))
1483         hbox.addWidget(b)
1484         b.clicked.connect(do_sign)
1485         b = QPushButton(_("Close"))
1486         b.clicked.connect(d.accept)
1487         hbox.addWidget(b)
1488         layout.addLayout(hbox, 4, 1)
1489         tab_widget.addTab(tab, "Sign")
1490
1491
1492         tab = QWidget()
1493         layout = QGridLayout(tab)
1494
1495         verify_address = QLineEdit()
1496         layout.addWidget(QLabel(_('Address')), 1, 0)
1497         layout.addWidget(verify_address, 1, 1)
1498
1499         verify_message = QTextEdit()
1500         layout.addWidget(QLabel(_('Message')), 2, 0)
1501         layout.addWidget(verify_message, 2, 1)
1502         layout.setRowStretch(2,3)
1503
1504         verify_signature = QTextEdit()
1505         layout.addWidget(QLabel(_('Signature')), 3, 0)
1506         layout.addWidget(verify_signature, 3, 1)
1507         layout.setRowStretch(3,1)
1508
1509         def do_verify():
1510             try:
1511                 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1512                 self.show_message("Signature verified")
1513             except BaseException, e:
1514                 self.show_message(str(e))
1515                 return
1516
1517         hbox = QHBoxLayout()
1518         b = QPushButton(_("Verify"))
1519         b.clicked.connect(do_verify)
1520         hbox.addWidget(b)
1521         b = QPushButton(_("Close"))
1522         b.clicked.connect(d.accept)
1523         hbox.addWidget(b)
1524         layout.addLayout(hbox, 4, 1)
1525         tab_widget.addTab(tab, "Verify")
1526
1527         vbox = QVBoxLayout()
1528         vbox.addWidget(tab_widget)
1529         d.setLayout(vbox)
1530         d.exec_()
1531
1532         
1533     def toggle_QR_window(self, show):
1534         if show and not self.qr_window:
1535             self.qr_window = QR_Window(self.exchanger)
1536             self.qr_window.setVisible(True)
1537             self.qr_window_geometry = self.qr_window.geometry()
1538             item = self.receive_list.currentItem()
1539             if item:
1540                 address = str(item.text(1))
1541                 label = self.wallet.labels.get(address)
1542                 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1543                 self.qr_window.set_content( address, label, amount, currency )
1544
1545         elif show and self.qr_window and not self.qr_window.isVisible():
1546             self.qr_window.setVisible(True)
1547             self.qr_window.setGeometry(self.qr_window_geometry)
1548
1549         elif not show and self.qr_window and self.qr_window.isVisible():
1550             self.qr_window_geometry = self.qr_window.geometry()
1551             self.qr_window.setVisible(False)
1552
1553         #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1554         self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1555         self.receive_list.setColumnWidth(2, 200)
1556
1557
1558     def question(self, msg):
1559         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1560
1561     def show_message(self, msg):
1562         QMessageBox.information(self, _('Message'), msg, _('OK'))
1563
1564     def password_dialog(self ):
1565         d = QDialog(self)
1566         d.setModal(1)
1567
1568         pw = QLineEdit()
1569         pw.setEchoMode(2)
1570
1571         vbox = QVBoxLayout()
1572         msg = _('Please enter your password')
1573         vbox.addWidget(QLabel(msg))
1574
1575         grid = QGridLayout()
1576         grid.setSpacing(8)
1577         grid.addWidget(QLabel(_('Password')), 1, 0)
1578         grid.addWidget(pw, 1, 1)
1579         vbox.addLayout(grid)
1580
1581         vbox.addLayout(ok_cancel_buttons(d))
1582         d.setLayout(vbox) 
1583
1584         if not d.exec_(): return
1585         return unicode(pw.text())
1586
1587
1588
1589
1590
1591     @staticmethod
1592     def change_password_dialog( wallet, parent=None ):
1593
1594         if not wallet.seed:
1595             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1596             return
1597
1598         d = QDialog(parent)
1599         d.setModal(1)
1600
1601         pw = QLineEdit()
1602         pw.setEchoMode(2)
1603         new_pw = QLineEdit()
1604         new_pw.setEchoMode(2)
1605         conf_pw = QLineEdit()
1606         conf_pw.setEchoMode(2)
1607
1608         vbox = QVBoxLayout()
1609         if parent:
1610             msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1611                    +_('To disable wallet encryption, enter an empty new password.')) \
1612                    if wallet.use_encryption else _('Your wallet keys are not encrypted')
1613         else:
1614             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1615                   +_("Leave these fields empty if you want to disable encryption.")
1616         vbox.addWidget(QLabel(msg))
1617
1618         grid = QGridLayout()
1619         grid.setSpacing(8)
1620
1621         if wallet.use_encryption:
1622             grid.addWidget(QLabel(_('Password')), 1, 0)
1623             grid.addWidget(pw, 1, 1)
1624
1625         grid.addWidget(QLabel(_('New Password')), 2, 0)
1626         grid.addWidget(new_pw, 2, 1)
1627
1628         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1629         grid.addWidget(conf_pw, 3, 1)
1630         vbox.addLayout(grid)
1631
1632         vbox.addLayout(ok_cancel_buttons(d))
1633         d.setLayout(vbox) 
1634
1635         if not d.exec_(): return
1636
1637         password = unicode(pw.text()) if wallet.use_encryption else None
1638         new_password = unicode(new_pw.text())
1639         new_password2 = unicode(conf_pw.text())
1640
1641         try:
1642             seed = wallet.decode_seed(password)
1643         except:
1644             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1645             return
1646
1647         if new_password != new_password2:
1648             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1649             return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1650
1651         wallet.update_password(seed, password, new_password)
1652
1653     @staticmethod
1654     def seed_dialog(wallet, parent=None):
1655         d = QDialog(parent)
1656         d.setModal(1)
1657
1658         vbox = QVBoxLayout()
1659         msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1660         vbox.addWidget(QLabel(msg))
1661
1662         grid = QGridLayout()
1663         grid.setSpacing(8)
1664
1665         seed_e = QLineEdit()
1666         grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1667         grid.addWidget(seed_e, 1, 1)
1668
1669         gap_e = QLineEdit()
1670         gap_e.setText("5")
1671         grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1672         grid.addWidget(gap_e, 2, 1)
1673         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1674         vbox.addLayout(grid)
1675
1676         vbox.addLayout(ok_cancel_buttons(d))
1677         d.setLayout(vbox) 
1678
1679         if not d.exec_(): return
1680
1681         try:
1682             gap = int(unicode(gap_e.text()))
1683         except:
1684             QMessageBox.warning(None, _('Error'), 'error', 'OK')
1685             return
1686
1687         try:
1688             seed = str(seed_e.text())
1689             seed.decode('hex')
1690         except:
1691             print_error("Warning: Not hex, trying decode")
1692             try:
1693                 seed = mnemonic.mn_decode( seed.split(' ') )
1694             except:
1695                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1696                 return
1697
1698         if not seed:
1699             QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1700             return
1701
1702         return seed, gap
1703
1704
1705     def do_import_labels(self):
1706         labelsFile = QFileDialog.getOpenFileName(QWidget(), "Open text file", util.user_dir(), self.tr("Text Files (labels.dat)"))
1707         if not labelsFile: return
1708         try:
1709             f = open(labelsFile, 'r')
1710             data = f.read()
1711             f.close()
1712             for key, value in json.loads(data).items():
1713                 self.wallet.labels[key] = value
1714             self.wallet.save()
1715             QMessageBox.information(None, "Labels imported", "Your labels where imported from '%s'" % str(labelsFile))
1716         except (IOError, os.error), reason:
1717             QMessageBox.critical(None, "Unable to import labels", "Electrum was unable to import your labels.\n" + str(reason))
1718         
1719
1720
1721     def do_export_labels(self):
1722         labels = self.wallet.labels
1723         try:
1724             labelsFile = util.user_dir() + '/labels.dat'
1725             f = open(labelsFile, 'w+')
1726             json.dump(labels, f)
1727             f.close()
1728             QMessageBox.information(None, "Labels exported", "Your labels where exported to '%s'" % str(labelsFile))
1729         except (IOError, os.error), reason:
1730             QMessageBox.critical(None, "Unable to export labels", "Electrum was unable to export your labels.\n" + str(reason))
1731
1732     def do_export_history(self):
1733         from gui_lite import csv_transaction
1734         csv_transaction(self.wallet)
1735
1736     def do_import_privkey(self):
1737         if not self.wallet.imported_keys:
1738             r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1739                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1740                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1741             if r == 4: return
1742
1743         text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1744         if not ok: return
1745         sec = str(text).strip()
1746         if self.wallet.use_encryption:
1747             password = self.password_dialog()
1748             if not password:
1749                 return
1750         else:
1751             password = None
1752         try:
1753             addr = self.wallet.import_key(sec, password)
1754             if not addr:
1755                 QMessageBox.critical(None, "Unable to import key", "error")
1756             else:
1757                 QMessageBox.information(None, "Key imported", addr)
1758                 self.update_receive_tab()
1759                 self.update_history_tab()
1760         except BaseException as e:
1761             QMessageBox.critical(None, "Unable to import key", str(e))
1762
1763     def settings_dialog(self):
1764         d = QDialog(self)
1765         d.setWindowTitle(_('Electrum Settings'))
1766         d.setModal(1)
1767         vbox = QVBoxLayout()
1768
1769         tabs = QTabWidget(self)
1770         vbox.addWidget(tabs)
1771
1772         tab1 = QWidget()
1773         grid_ui = QGridLayout(tab1)
1774         grid_ui.setColumnStretch(0,1)
1775         tabs.addTab(tab1, _('Display') )
1776
1777         nz_label = QLabel(_('Display zeros'))
1778         grid_ui.addWidget(nz_label, 3, 0)
1779         nz_e = QLineEdit()
1780         nz_e.setText("%d"% self.wallet.num_zeros)
1781         grid_ui.addWidget(nz_e, 3, 1)
1782         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1783         grid_ui.addWidget(HelpButton(msg), 3, 2)
1784         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1785         if not self.config.is_modifiable('num_zeros'):
1786             for w in [nz_e, nz_label]: w.setEnabled(False)
1787         
1788         lang_label=QLabel(_('Language') + ':')
1789         grid_ui.addWidget(lang_label , 8, 0)
1790         lang_combo = QComboBox()
1791         from i18n import languages
1792         lang_combo.addItems(languages.values())
1793         try:
1794             index = languages.keys().index(self.config.get("language",''))
1795         except:
1796             index = 0
1797         lang_combo.setCurrentIndex(index)
1798         grid_ui.addWidget(lang_combo, 8, 1)
1799         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1800         if not self.config.is_modifiable('language'):
1801             for w in [lang_combo, lang_label]: w.setEnabled(False)
1802
1803         currencies = self.exchanger.get_currencies()
1804         currencies.insert(0, "None")
1805
1806         cur_label=QLabel(_('Currency') + ':')
1807         grid_ui.addWidget(cur_label , 9, 0)
1808         cur_combo = QComboBox()
1809         cur_combo.addItems(currencies)
1810         try:
1811             index = currencies.index(self.config.get('currency', "None"))
1812         except:
1813             index = 0
1814         cur_combo.setCurrentIndex(index)
1815         grid_ui.addWidget(cur_combo, 9, 1)
1816         grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1817         
1818         view_label=QLabel(_('Receive Tab') + ':')
1819         grid_ui.addWidget(view_label , 10, 0)
1820         view_combo = QComboBox()
1821         view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1822         view_combo.setCurrentIndex(self.receive_tab_mode)
1823         grid_ui.addWidget(view_combo, 10, 1)
1824         hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1825              + _('Simple') +   ': ' + _('Show only addresses and labels.') + '\n\n' \
1826              + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1827              + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n' 
1828         
1829         grid_ui.addWidget(HelpButton(hh), 10, 2)
1830
1831         # wallet tab
1832         tab2 = QWidget()
1833         grid_wallet = QGridLayout(tab2)
1834         grid_wallet.setColumnStretch(0,1)
1835         tabs.addTab(tab2, _('Wallet') )
1836         
1837         fee_label = QLabel(_('Transaction fee'))
1838         grid_wallet.addWidget(fee_label, 0, 0)
1839         fee_e = QLineEdit()
1840         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1841         grid_wallet.addWidget(fee_e, 0, 1)
1842         msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1843             + _('Recommended value') + ': 0.001'
1844         grid_wallet.addWidget(HelpButton(msg), 0, 2)
1845         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1846         if not self.config.is_modifiable('fee'):
1847             for w in [fee_e, fee_label]: w.setEnabled(False)
1848
1849         usechange_label = QLabel(_('Use change addresses'))
1850         grid_wallet.addWidget(usechange_label, 1, 0)
1851         usechange_combo = QComboBox()
1852         usechange_combo.addItems([_('Yes'), _('No')])
1853         usechange_combo.setCurrentIndex(not self.wallet.use_change)
1854         grid_wallet.addWidget(usechange_combo, 1, 1)
1855         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1856         if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1857
1858         gap_label = QLabel(_('Gap limit'))
1859         grid_wallet.addWidget(gap_label, 2, 0)
1860         gap_e = QLineEdit()
1861         gap_e.setText("%d"% self.wallet.gap_limit)
1862         grid_wallet.addWidget(gap_e, 2, 1)
1863         msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1864               + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1865               + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1866               + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1867               + _('Warning') + ': ' \
1868               + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1869               + _('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' 
1870         grid_wallet.addWidget(HelpButton(msg), 2, 2)
1871         gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1872         if not self.config.is_modifiable('gap_limit'):
1873             for w in [gap_e, gap_label]: w.setEnabled(False)
1874
1875         grid_wallet.setRowStretch(3,1)
1876
1877
1878         # wallet tab
1879         tab3 = QWidget()
1880         grid_io = QGridLayout(tab3)
1881         grid_io.setColumnStretch(0,1)
1882         tabs.addTab(tab3, _('Import/Export') )
1883         
1884         grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1885         grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1886         grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1887         grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1888
1889         grid_io.addWidget(QLabel(_('History')), 2, 0)
1890         grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1891         grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1892
1893         grid_io.addWidget(QLabel(_('Private key')), 3, 0)
1894         grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1895         grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1896
1897         grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
1898         grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1899         grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1900                               + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1901                               + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1902
1903         grid_io.setRowStretch(4,1)
1904         vbox.addLayout(ok_cancel_buttons(d))
1905         d.setLayout(vbox) 
1906
1907         # run the dialog
1908         if not d.exec_(): return
1909
1910         fee = unicode(fee_e.text())
1911         try:
1912             fee = int( 100000000 * Decimal(fee) )
1913         except:
1914             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1915             return
1916
1917         if self.wallet.fee != fee:
1918             self.wallet.fee = fee
1919             self.wallet.save()
1920         
1921         nz = unicode(nz_e.text())
1922         try:
1923             nz = int( nz )
1924             if nz>8: nz=8
1925         except:
1926             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1927             return
1928
1929         if self.wallet.num_zeros != nz:
1930             self.wallet.num_zeros = nz
1931             self.config.set_key('num_zeros', nz, True)
1932             self.update_history_tab()
1933             self.update_receive_tab()
1934
1935         usechange_result = usechange_combo.currentIndex() == 0
1936         if self.wallet.use_change != usechange_result:
1937             self.wallet.use_change = usechange_result
1938             self.config.set_key('use_change', self.wallet.use_change, True)
1939         
1940         try:
1941             n = int(gap_e.text())
1942         except:
1943             QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1944             return
1945
1946         if self.wallet.gap_limit != n:
1947             r = self.wallet.change_gap_limit(n)
1948             if r:
1949                 self.update_receive_tab()
1950                 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1951             else:
1952                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1953
1954         need_restart = False
1955
1956         lang_request = languages.keys()[lang_combo.currentIndex()]
1957         if lang_request != self.config.get('language'):
1958             self.config.set_key("language", lang_request, True)
1959             need_restart = True
1960             
1961         cur_request = str(currencies[cur_combo.currentIndex()])
1962         if cur_request != self.config.get('currency', "None"):
1963             self.config.set_key('currency', cur_request, True)
1964             self.update_wallet()
1965
1966         if need_restart:
1967             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1968
1969         self.receive_tab_set_mode(view_combo.currentIndex())
1970
1971
1972     @staticmethod 
1973     def network_dialog(wallet, parent=None):
1974         interface = wallet.interface
1975         if parent:
1976             if interface.is_connected:
1977                 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
1978             else:
1979                 status = _("Not connected")
1980             server = interface.server
1981         else:
1982             import random
1983             status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1984             server = interface.server
1985
1986         plist, servers_list = interface.get_servers_list()
1987
1988         d = QDialog(parent)
1989         d.setModal(1)
1990         d.setWindowTitle(_('Server'))
1991         d.setMinimumSize(375, 20)
1992
1993         vbox = QVBoxLayout()
1994         vbox.setSpacing(30)
1995
1996         hbox = QHBoxLayout()
1997         l = QLabel()
1998         l.setPixmap(QPixmap(":icons/network.png"))
1999         hbox.addStretch(10)
2000         hbox.addWidget(l)
2001         hbox.addWidget(QLabel(status))
2002         hbox.addStretch(50)
2003         vbox.addLayout(hbox)
2004
2005
2006         # grid layout
2007         grid = QGridLayout()
2008         grid.setSpacing(8)
2009         vbox.addLayout(grid)
2010
2011         # server
2012         server_protocol = QComboBox()
2013         server_host = QLineEdit()
2014         server_host.setFixedWidth(200)
2015         server_port = QLineEdit()
2016         server_port.setFixedWidth(60)
2017
2018         protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2019         protocol_letters = 'thsg'
2020         DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2021         server_protocol.addItems(protocol_names)
2022
2023         grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2024         grid.addWidget(server_protocol, 0, 1)
2025         grid.addWidget(server_host, 0, 2)
2026         grid.addWidget(server_port, 0, 3)
2027
2028         def change_protocol(p):
2029             protocol = protocol_letters[p]
2030             host = unicode(server_host.text())
2031             pp = plist.get(host,DEFAULT_PORTS)
2032             if protocol not in pp.keys():
2033                 protocol = pp.keys()[0]
2034             port = pp[protocol]
2035             server_host.setText( host )
2036             server_port.setText( port )
2037
2038         server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2039         
2040         label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2041         servers_list_widget = QTreeWidget(parent)
2042         servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2043         servers_list_widget.setMaximumHeight(150)
2044         servers_list_widget.setColumnWidth(0, 240)
2045         for _host in servers_list.keys():
2046             _type = 'P' if servers_list[_host].get('pruning') else 'F'
2047             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2048
2049         def change_server(host, protocol=None):
2050             pp = plist.get(host,DEFAULT_PORTS)
2051             if protocol:
2052                 port = pp.get(protocol)
2053                 if not port: protocol = None
2054                     
2055             if not protocol:
2056                 if 't' in pp.keys():
2057                     protocol = 't'
2058                     port = pp.get(protocol)
2059                 else:
2060                     protocol = pp.keys()[0]
2061                     port = pp.get(protocol)
2062             
2063             server_host.setText( host )
2064             server_port.setText( port )
2065             server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2066
2067             if not plist: return
2068             for p in protocol_letters:
2069                 i = protocol_letters.index(p)
2070                 j = server_protocol.model().index(i,0)
2071                 if p not in pp.keys():
2072                     server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2073                 else:
2074                     server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2075
2076
2077         if server:
2078             host, port, protocol = server.split(':')
2079             change_server(host,protocol)
2080
2081         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2082         grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2083
2084         if not wallet.config.is_modifiable('server'):
2085             for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2086
2087         # auto cycle
2088         autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2089         autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2090         grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2091         if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2092
2093         # proxy setting
2094         proxy_mode = QComboBox()
2095         proxy_host = QLineEdit()
2096         proxy_host.setFixedWidth(200)
2097         proxy_port = QLineEdit()
2098         proxy_port.setFixedWidth(60)
2099         proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2100
2101         def check_for_disable(index = False):
2102             if proxy_mode.currentText() != 'NONE':
2103                 proxy_host.setEnabled(True)
2104                 proxy_port.setEnabled(True)
2105             else:
2106                 proxy_host.setEnabled(False)
2107                 proxy_port.setEnabled(False)
2108
2109         check_for_disable()
2110         proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2111
2112         if not wallet.config.is_modifiable('proxy'):
2113             for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2114
2115         proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2116         proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2117         proxy_host.setText(proxy_config.get("host"))
2118         proxy_port.setText(proxy_config.get("port"))
2119
2120         grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2121         grid.addWidget(proxy_mode, 2, 1)
2122         grid.addWidget(proxy_host, 2, 2)
2123         grid.addWidget(proxy_port, 2, 3)
2124
2125         # buttons
2126         vbox.addLayout(ok_cancel_buttons(d))
2127         d.setLayout(vbox) 
2128
2129         if not d.exec_(): return
2130
2131         server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2132         if proxy_mode.currentText() != 'NONE':
2133             proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2134         else:
2135             proxy = None
2136
2137         wallet.config.set_key("proxy", proxy, True)
2138         wallet.config.set_key("server", server, True)
2139         interface.set_server(server, proxy)
2140         wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2141         return True
2142
2143     def closeEvent(self, event):
2144         g = self.geometry()
2145         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2146         self.save_column_widths()
2147         self.config.set_key("column-widths", self.column_widths, True)
2148         event.accept()
2149
2150
2151 class ElectrumGui:
2152
2153     def __init__(self, wallet, config, app=None):
2154         self.wallet = wallet
2155         self.config = config
2156         if app is None:
2157             self.app = QApplication(sys.argv)
2158
2159
2160     def restore_or_create(self):
2161         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2162         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2163         if r==2: return None
2164         return 'restore' if r==1 else 'create'
2165
2166     def seed_dialog(self):
2167         return ElectrumWindow.seed_dialog( self.wallet )
2168
2169     def network_dialog(self):
2170         return ElectrumWindow.network_dialog( self.wallet, parent=None )
2171         
2172
2173     def show_seed(self):
2174         ElectrumWindow.show_seed_dialog(self.wallet)
2175
2176
2177     def password_dialog(self):
2178         ElectrumWindow.change_password_dialog(self.wallet)
2179
2180
2181     def restore_wallet(self):
2182         wallet = self.wallet
2183         # wait until we are connected, because the user might have selected another server
2184         if not wallet.interface.is_connected:
2185             waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
2186             waiting_dialog(waiting)
2187
2188         waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
2189             %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
2190
2191         wallet.set_up_to_date(False)
2192         wallet.interface.poke('synchronizer')
2193         waiting_dialog(waiting)
2194         if wallet.is_found():
2195             print_error( "Recovery successful" )
2196         else:
2197             QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2198
2199         return True
2200
2201     def main(self,url):
2202         s = Timer()
2203         s.start()
2204         w = ElectrumWindow(self.wallet, self.config)
2205         if url: w.set_url(url)
2206         w.app = self.app
2207         w.connect_slots(s)
2208         w.update_wallet()
2209         w.show()
2210
2211         self.app.exec_()