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