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