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