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