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