Merge branch 'master' into qt-switch-gui
[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
23 try:
24     import PyQt4
25 except:
26     sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
27
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31 import PyQt4.QtGui as QtGui
32 from interface import DEFAULT_SERVERS
33
34 try:
35     import icons_rc
36 except:
37     sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
38
39 from wallet import format_satoshis
40 import bmp, mnemonic, pyqrnative, qrscanner
41 import exchange_rate
42
43 from decimal import Decimal
44
45 import platform
46
47 if platform.system() == 'Windows':
48     MONOSPACE_FONT = 'Lucida Console'
49 elif platform.system() == 'Darwin':
50     MONOSPACE_FONT = 'Monaco'
51 else:
52     MONOSPACE_FONT = 'monospace'
53
54 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
55
56 def numbify(entry, is_int = False):
57     text = unicode(entry.text()).strip()
58     pos = entry.cursorPosition()
59     chars = '0123456789'
60     if not is_int: chars +='.'
61     s = ''.join([i for i in text if i in chars])
62     if not is_int:
63         if '.' in s:
64             p = s.find('.')
65             s = s.replace('.','')
66             s = s[:p] + '.' + s[p:p+8]
67         try:
68             amount = int( Decimal(s) * 100000000 )
69         except:
70             amount = None
71     else:
72         try:
73             amount = int( s )
74         except:
75             amount = None
76     entry.setText(s)
77     entry.setCursorPosition(pos)
78     return amount
79
80
81 class Timer(QtCore.QThread):
82     def run(self):
83         while True:
84             self.emit(QtCore.SIGNAL('timersignal'))
85             time.sleep(0.5)
86
87 class HelpButton(QPushButton):
88     def __init__(self, text):
89         QPushButton.__init__(self, '?')
90         self.setFocusPolicy(Qt.NoFocus)
91         self.setFixedWidth(20)
92         self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
93
94
95 class EnterButton(QPushButton):
96     def __init__(self, text, func):
97         QPushButton.__init__(self, text)
98         self.func = func
99         self.clicked.connect(func)
100
101     def keyPressEvent(self, e):
102         if e.key() == QtCore.Qt.Key_Return:
103             apply(self.func,())
104
105 class MyTreeWidget(QTreeWidget):
106     def __init__(self, parent):
107         QTreeWidget.__init__(self, parent)
108         def ddfr(item):
109             if not item: return
110             for i in range(0,self.viewport().height()/5):
111                 if self.itemAt(QPoint(0,i*5)) == item:
112                     break
113             else:
114                 return
115             for j in range(0,30):
116                 if self.itemAt(QPoint(0,i*5 + j)) != item:
117                     break
118             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
119
120         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
121         
122
123
124
125 class StatusBarButton(QPushButton):
126     def __init__(self, icon, tooltip, func):
127         QPushButton.__init__(self, icon, '')
128         self.setToolTip(tooltip)
129         self.setFlat(True)
130         self.setMaximumWidth(25)
131         self.clicked.connect(func)
132         self.func = func
133
134     def keyPressEvent(self, e):
135         if e.key() == QtCore.Qt.Key_Return:
136             apply(self.func,())
137
138
139 class QRCodeWidget(QWidget):
140
141     def __init__(self, data = None):
142         QWidget.__init__(self)
143         self.setMinimumSize(210, 210)
144         self.addr = None
145         self.qr = None
146         if data:
147             self.set_addr(data)
148             self.update_qr()
149
150     def set_addr(self, addr):
151         if self.addr != addr:
152             self.addr = addr
153             self.qr = None
154             self.update()
155
156     def update_qr(self):
157         if self.addr and not self.qr:
158             self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
159             self.qr.addData(self.addr)
160             self.qr.make()
161             self.update()
162
163     def paintEvent(self, e):
164
165         if not self.addr:
166             return
167
168         black = QColor(0, 0, 0, 255)
169         white = QColor(255, 255, 255, 255)
170
171         if not self.qr:
172             qp = QtGui.QPainter()
173             qp.begin(self)
174             qp.setBrush(white)
175             qp.setPen(white)
176             qp.drawRect(0, 0, 198, 198)
177             qp.end()
178             return
179  
180         k = self.qr.getModuleCount()
181         qp = QtGui.QPainter()
182         qp.begin(self)
183         r = qp.viewport()
184         boxsize = min(r.width(), r.height())*0.8/k
185         size = k*boxsize
186         left = (r.width() - size)/2
187         top = (r.height() - size)/2         
188
189         for r in range(k):
190             for c in range(k):
191                 if self.qr.isDark(r, c):
192                     qp.setBrush(black)
193                     qp.setPen(black)
194                 else:
195                     qp.setBrush(white)
196                     qp.setPen(white)
197                 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
198         qp.end()
199         
200
201
202 class QR_Window(QWidget):
203
204     def __init__(self):
205         QWidget.__init__(self)
206         self.setWindowTitle('Electrum - Invoice')
207         self.setMinimumSize(800, 250)
208         self.address = ''
209         self.labe = ''
210         self.amount = 0
211         self.setFocusPolicy(QtCore.Qt.NoFocus)
212
213         main_box = QHBoxLayout()
214         
215         self.qrw = QRCodeWidget()
216         main_box.addWidget(self.qrw, 1)
217
218         vbox = QVBoxLayout()
219         main_box.addLayout(vbox)
220
221         self.address_label = QLabel("")
222         self.address_label.setFont(QFont(MONOSPACE_FONT))
223         vbox.addWidget(self.address_label)
224
225         self.label_label = QLabel("")
226         vbox.addWidget(self.label_label)
227
228         self.amount_label = QLabel("")
229         vbox.addWidget(self.amount_label)
230
231         vbox.addStretch(1)
232         self.setLayout(main_box)
233
234
235     def set_content(self, addr, label, amount):
236         self.address = addr
237         address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
238         self.address_label.setText(address_text)
239
240         self.amount = amount
241         amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
242         self.amount_label.setText(amount_text)
243
244         self.label = label
245         label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
246         self.label_label.setText(label_text)
247
248         msg = 'bitcoin:'+self.address
249         if self.amount is not None:
250             msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
251             if self.label is not None:
252                 msg += '&label=%s'%(self.label)
253         elif self.label is not None:
254             msg += '?label=%s'%(self.label)
255             
256         self.qrw.set_addr( msg )
257
258             
259
260
261 def waiting_dialog(f):
262
263     s = Timer()
264     s.start()
265     w = QDialog()
266     w.resize(200, 70)
267     w.setWindowTitle('Electrum')
268     l = QLabel('')
269     vbox = QVBoxLayout()
270     vbox.addWidget(l)
271     w.setLayout(vbox)
272     w.show()
273     def ff():
274         s = f()
275         if s: l.setText(s)
276         else: w.close()
277     w.connect(s, QtCore.SIGNAL('timersignal'), ff)
278     w.exec_()
279     w.destroy()
280
281
282 def ok_cancel_buttons(dialog):
283     hbox = QHBoxLayout()
284     hbox.addStretch(1)
285     b = QPushButton("OK")
286     hbox.addWidget(b)
287     b.clicked.connect(dialog.accept)
288     b = QPushButton("Cancel")
289     hbox.addWidget(b)
290     b.clicked.connect(dialog.reject)
291     return hbox
292
293
294 class ElectrumWindow(QMainWindow):
295
296     def __init__(self, wallet, config):
297         QMainWindow.__init__(self)
298         self.lite = None
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.toolTip(0))
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.setToolTip(0, 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             else:
721                 palette = QPalette()
722                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
723                 self.funds_error = True
724             self.amount_e.setPalette(palette)
725             self.fee_e.setPalette(palette)
726             self.update_wallet()
727
728         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
729         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
730
731         return w2
732
733
734     def update_completions(self):
735         l = []
736         for addr,label in self.wallet.labels.items():
737             if addr in self.wallet.addressbook:
738                 l.append( label + '  <' + addr + '>')
739         l = l + self.wallet.aliases.keys()
740
741         self.completions.setStringList(l)
742
743
744
745     def do_send(self):
746
747         label = unicode( self.message_e.text() )
748         r = unicode( self.payto_e.text() )
749         r = r.strip()
750
751         # alias
752         m1 = re.match(ALIAS_REGEXP, r)
753         # label or alias, with address in brackets
754         m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
755         
756         if m1:
757             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
758             if not to_address:
759                 return
760         elif m2:
761             to_address = m2.group(2)
762         else:
763             to_address = r
764
765         if not self.wallet.is_valid(to_address):
766             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
767             return
768
769         try:
770             amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
771         except:
772             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
773             return
774         try:
775             fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
776         except:
777             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
778             return
779
780         if self.wallet.use_encryption:
781             password = self.password_dialog()
782             if not password:
783                 return
784         else:
785             password = None
786
787         try:
788             tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
789         except BaseException, e:
790             self.show_message(str(e))
791             return
792
793         if self.wallet.seed:
794             h = self.wallet.send_tx(tx)
795             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
796             status, msg = self.wallet.receive_tx( h )
797             if status:
798                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
799                 self.do_clear()
800                 self.update_contacts_tab()
801             else:
802                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
803         else:
804             filename = 'unsigned_tx'
805             f = open(filename,'w')
806             f.write(tx)
807             f.close()
808             QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
809
810
811     def set_url(self, url):
812         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
813         self.tabs.setCurrentIndex(1)
814         label = self.wallet.labels.get(payto)
815         m_addr = label + '  <'+ payto+'>' if label else payto
816         self.payto_e.setText(m_addr)
817
818         self.message_e.setText(message)
819         self.amount_e.setText(amount)
820         if identity:
821             self.set_frozen(self.payto_e,True)
822             self.set_frozen(self.amount_e,True)
823             self.set_frozen(self.message_e,True)
824             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
825         else:
826             self.payto_sig.setVisible(False)
827
828     def do_clear(self):
829         self.payto_sig.setVisible(False)
830         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
831             e.setText('')
832             self.set_frozen(e,False)
833
834     def set_frozen(self,entry,frozen):
835         if frozen:
836             entry.setReadOnly(True)
837             entry.setFrame(False)
838             palette = QPalette()
839             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
840             entry.setPalette(palette)
841         else:
842             entry.setReadOnly(False)
843             entry.setFrame(True)
844             palette = QPalette()
845             palette.setColor(entry.backgroundRole(), QColor('white'))
846             entry.setPalette(palette)
847
848
849     def toggle_freeze(self,addr):
850         if not addr: return
851         if addr in self.wallet.frozen_addresses:
852             self.wallet.unfreeze(addr)
853         else:
854             self.wallet.freeze(addr)
855         self.update_receive_tab()
856
857     def toggle_priority(self,addr):
858         if not addr: return
859         if addr in self.wallet.prioritized_addresses:
860             self.wallet.unprioritize(addr)
861         else:
862             self.wallet.prioritize(addr)
863         self.update_receive_tab()
864
865
866     def create_list_tab(self, headers):
867         "generic tab creation method"
868         l = MyTreeWidget(self)
869         l.setColumnCount( len(headers) )
870         l.setHeaderLabels( headers )
871
872         w = QWidget()
873         vbox = QVBoxLayout()
874         w.setLayout(vbox)
875
876         vbox.setMargin(0)
877         vbox.setSpacing(0)
878         vbox.addWidget(l)
879         buttons = QWidget()
880         vbox.addWidget(buttons)
881
882         hbox = QHBoxLayout()
883         hbox.setMargin(0)
884         hbox.setSpacing(0)
885         buttons.setLayout(hbox)
886
887         return l,w,hbox
888
889
890     def create_receive_tab(self):
891         l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
892         l.setContextMenuPolicy(Qt.CustomContextMenu)
893         l.customContextMenuRequested.connect(self.create_receive_menu)
894         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
895         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
896         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
897         self.receive_list = l
898         self.receive_buttons_hbox = hbox
899         hbox.addStretch(1)
900         return w
901
902
903
904     def receive_tab_set_mode(self, i):
905         self.receive_tab_mode = i
906         self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
907         self.wallet.save()
908         self.update_receive_tab()
909         self.toggle_QR_window(self.receive_tab_mode == 2)
910
911
912     def create_contacts_tab(self):
913         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
914         l.setContextMenuPolicy(Qt.CustomContextMenu)
915         l.customContextMenuRequested.connect(self.create_contact_menu)
916         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
917         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
918         self.contacts_list = l
919         self.contacts_buttons_hbox = hbox
920         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
921         hbox.addStretch(1)
922         return w
923
924
925     def create_receive_menu(self, position):
926         # fixme: this function apparently has a side effect.
927         # if it is not called the menu pops up several times
928         #self.receive_list.selectedIndexes() 
929
930         item = self.receive_list.itemAt(position)
931         if not item: return
932         addr = unicode(item.text(1))
933         menu = QMenu()
934         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
935         if self.receive_tab_mode == 2:
936             menu.addAction(_("Request amount"), lambda: self.edit_amount())
937         menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
938         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
939         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
940
941         if self.receive_tab_mode == 1:
942             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
943             menu.addAction(t, lambda: self.toggle_freeze(addr))
944             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
945             menu.addAction(t, lambda: self.toggle_priority(addr))
946             
947         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
948
949
950     def payto(self, x, is_alias):
951         if not x: return
952         if is_alias:
953             label = x
954             m_addr = label
955         else:
956             addr = x
957             label = self.wallet.labels.get(addr)
958             m_addr = label + '  <' + addr + '>' if label else addr
959         self.tabs.setCurrentIndex(1)
960         self.payto_e.setText(m_addr)
961         self.amount_e.setFocus()
962
963     def delete_contact(self, x, is_alias):
964         if self.question("Do you want to remove %s from your list of contacts?"%x):
965             if not is_alias and x in self.wallet.addressbook:
966                 self.wallet.addressbook.remove(x)
967                 if x in self.wallet.labels.keys():
968                     self.wallet.labels.pop(x)
969             elif is_alias and x in self.wallet.aliases:
970                 self.wallet.aliases.pop(x)
971             self.update_history_tab()
972             self.update_contacts_tab()
973             self.update_completions()
974
975     def create_contact_menu(self, position):
976         # fixme: this function apparently has a side effect.
977         # if it is not called the menu pops up several times
978         #self.contacts_list.selectedIndexes() 
979
980         item = self.contacts_list.itemAt(position)
981         if not item: return
982         addr = unicode(item.text(0))
983         label = unicode(item.text(1))
984         is_alias = label in self.wallet.aliases.keys()
985         x = label if is_alias else addr
986         menu = QMenu()
987         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
988         menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
989         menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
990         if not is_alias:
991             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
992         else:
993             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
994         menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
995         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
996
997
998     def update_receive_item(self, item):
999         address = str( item.data(1,0).toString() )
1000
1001         flags = self.wallet.get_address_flags(address)
1002         item.setData(0,0,flags)
1003
1004         label = self.wallet.labels.get(address,'')
1005         item.setData(2,0,label)
1006
1007         amount = self.wallet.requested_amounts.get(address,None)
1008         amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None  else ""
1009         item.setData(3,0,amount_str)
1010         
1011         c, u = self.wallet.get_addr_balance(address)
1012         balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1013         item.setData(4,0,balance)
1014
1015         if self.receive_tab_mode == 1:
1016             if address in self.wallet.frozen_addresses: 
1017                 item.setBackgroundColor(1, QColor('lightblue'))
1018             elif address in self.wallet.prioritized_addresses: 
1019                 item.setBackgroundColor(1, QColor('lightgreen'))
1020         
1021
1022     def update_receive_tab(self):
1023         l = self.receive_list
1024         
1025         l.clear()
1026         l.setColumnHidden(0, not self.receive_tab_mode == 1)
1027         l.setColumnHidden(3, not self.receive_tab_mode == 2)
1028         l.setColumnHidden(4, self.receive_tab_mode == 0)
1029         l.setColumnHidden(5, not self.receive_tab_mode == 1)
1030         l.setColumnWidth(0, 50)
1031         l.setColumnWidth(1, 310) 
1032         l.setColumnWidth(2, 200)
1033         l.setColumnWidth(3, 130)
1034         l.setColumnWidth(4, 130)
1035         l.setColumnWidth(5, 10)
1036
1037         gap = 0
1038         is_red = False
1039         for address in self.wallet.all_addresses():
1040
1041             if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1042                 continue
1043
1044             n = 0 
1045             h = self.wallet.history.get(address,[])
1046
1047             if h != ['*']: 
1048                 for tx_hash, tx_height in h:
1049                     tx = self.wallet.transactions.get(tx_hash)
1050                     if tx: n += 1
1051                 num_tx = "%d "%n
1052             else:
1053                 n = -1
1054                 num_tx = "*"
1055
1056             if n==0:
1057                 if address in self.wallet.addresses:
1058                     gap += 1
1059                     if gap > self.wallet.gap_limit:
1060                         is_red = True
1061             else:
1062                 if address in self.wallet.addresses:
1063                     gap = 0
1064
1065             item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1066             item.setFont(0, QFont(MONOSPACE_FONT))
1067             item.setFont(1, QFont(MONOSPACE_FONT))
1068             item.setFont(3, QFont(MONOSPACE_FONT))
1069             self.update_receive_item(item)
1070             if is_red and address in self.wallet.addresses:
1071                 item.setBackgroundColor(1, QColor('red'))
1072             l.addTopLevelItem(item)
1073
1074         # we use column 1 because column 0 may be hidden
1075         l.setCurrentItem(l.topLevelItem(0),1)
1076
1077     def show_contact_details(self, m):
1078         a = self.wallet.aliases.get(m)
1079         if a:
1080             if a[0] in self.wallet.authorities.keys():
1081                 s = self.wallet.authorities.get(a[0])
1082             else:
1083                 s = "self-signed"
1084             msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1085             QMessageBox.information(self, 'Alias', msg, 'OK')
1086
1087     def update_contacts_tab(self):
1088
1089         l = self.contacts_list
1090         l.clear()
1091         l.setColumnWidth(0, 350) 
1092         l.setColumnWidth(1, 330)
1093         l.setColumnWidth(2, 100) 
1094
1095         alias_targets = []
1096         for alias, v in self.wallet.aliases.items():
1097             s, target = v
1098             alias_targets.append(target)
1099             item = QTreeWidgetItem( [ target, alias, '-'] )
1100             item.setBackgroundColor(0, QColor('lightgray'))
1101             l.addTopLevelItem(item)
1102             
1103         for address in self.wallet.addressbook:
1104             if address in alias_targets: continue
1105             label = self.wallet.labels.get(address,'')
1106             n = 0 
1107             for item in self.wallet.transactions.values():
1108                 if address in item['outputs'] : n=n+1
1109             tx = "%d"%n
1110             item = QTreeWidgetItem( [ address, label, tx] )
1111             item.setFont(0, QFont(MONOSPACE_FONT))
1112             l.addTopLevelItem(item)
1113
1114         l.setCurrentItem(l.topLevelItem(0))
1115
1116     def create_wall_tab(self):
1117         self.textbox = textbox = QTextEdit(self)
1118         textbox.setFont(QFont(MONOSPACE_FONT))
1119         textbox.setReadOnly(True)
1120         return textbox
1121
1122     def create_status_bar(self):
1123         sb = QStatusBar()
1124         sb.setFixedHeight(35)
1125         qtVersion = qVersion()
1126         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1127             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), "Switch to Lite Mode", self.go_lite ) )
1128         if self.wallet.seed:
1129             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1130         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1131         if self.wallet.seed:
1132             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1133         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) 
1134         sb.addPermanentWidget( self.status_button )
1135         self.setStatusBar(sb)
1136         
1137     def go_lite(self):
1138         import gui_lite
1139         self.hide()
1140         if self.lite:
1141             self.lite.mini.show()
1142         else:
1143             self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1144             self.lite.main(None)
1145
1146     def new_contact_dialog(self):
1147         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1148         address = unicode(text)
1149         if ok:
1150             if self.wallet.is_valid(address):
1151                 self.wallet.addressbook.append(address)
1152                 self.wallet.save()
1153                 self.update_contacts_tab()
1154                 self.update_history_tab()
1155                 self.update_completions()
1156             else:
1157                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1158
1159     @staticmethod
1160     def show_seed_dialog(wallet, parent=None):
1161         if not wallet.seed:
1162             QMessageBox.information(parent, _('Message'),
1163                                     _('No seed'), _('OK'))
1164             return
1165
1166         if wallet.use_encryption:
1167             password = parent.password_dialog()
1168             if not password:
1169                 return
1170         else:
1171             password = None
1172             
1173         try:
1174             seed = wallet.pw_decode(wallet.seed, password)
1175         except:
1176             QMessageBox.warning(parent, _('Error'),
1177                                 _('Incorrect Password'), _('OK'))
1178             return
1179
1180         dialog = QDialog(None)
1181         dialog.setModal(1)
1182         dialog.setWindowTitle("Electrum")
1183
1184         brainwallet = ' '.join(mnemonic.mn_encode(seed))
1185
1186         msg =   _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1187               + _("Please write down or memorize these 12 words (order is important).") + " " \
1188               + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1189               + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1190
1191         main_text = QLabel(msg)
1192         main_text.setWordWrap(True)
1193
1194         logo = QLabel()
1195         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1196
1197         if parent:
1198             app = parent.app
1199         else:
1200             app = QApplication
1201
1202         copy_function = lambda: app.clipboard().setText(brainwallet)
1203         copy_button = QPushButton(_("Copy to Clipboard"))
1204         copy_button.clicked.connect(copy_function)
1205
1206         show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1207         qr_button = QPushButton(_("View as QR Code"))
1208         qr_button.clicked.connect(show_qr_function)
1209
1210         ok_button = QPushButton(_("OK"))
1211         ok_button.setDefault(True)
1212         ok_button.clicked.connect(dialog.accept)
1213
1214         main_layout = QGridLayout()
1215         main_layout.addWidget(logo, 0, 0)
1216         main_layout.addWidget(main_text, 0, 1, 1, -1)
1217         main_layout.addWidget(copy_button, 1, 1)
1218         main_layout.addWidget(qr_button, 1, 2)
1219         main_layout.addWidget(ok_button, 1, 3)
1220         dialog.setLayout(main_layout)
1221
1222         dialog.exec_()
1223
1224     @staticmethod
1225     def show_qrcode(title, data):
1226         if not data: return
1227         d = QDialog(None)
1228         d.setModal(1)
1229         d.setWindowTitle(title)
1230         d.setMinimumSize(270, 300)
1231         vbox = QVBoxLayout()
1232         qrw = QRCodeWidget(data)
1233         vbox.addWidget(qrw, 1)
1234         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1235         hbox = QHBoxLayout()
1236         hbox.addStretch(1)
1237
1238         def print_qr(self):
1239             filename = "qrcode.bmp"
1240             bmp.save_qrcode(qrw.qr, filename)
1241             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1242
1243         b = QPushButton(_("Print"))
1244         hbox.addWidget(b)
1245         b.clicked.connect(print_qr)
1246
1247         b = QPushButton(_("Close"))
1248         hbox.addWidget(b)
1249         b.clicked.connect(d.accept)
1250
1251         vbox.addLayout(hbox)
1252         d.setLayout(vbox)
1253         d.exec_()
1254
1255     def sign_message(self,address):
1256         if not address: return
1257         d = QDialog(self)
1258         d.setModal(1)
1259         d.setWindowTitle('Sign Message')
1260         d.setMinimumSize(270, 350)
1261
1262         tab_widget = QTabWidget()
1263         tab = QWidget()
1264         layout = QGridLayout(tab)
1265
1266         sign_address = QLineEdit()
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, 2, 1)
1274
1275         sign_signature = QLineEdit()
1276         layout.addWidget(QLabel(_('Signature')), 3, 0)
1277         layout.addWidget(sign_signature, 3, 1)
1278
1279         def do_sign():
1280             if self.wallet.use_encryption:
1281                 password = self.password_dialog()
1282                 if not password:
1283                     return
1284             else:
1285                 password = None
1286
1287             try:
1288                 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1289                 sign_signature.setText(signature)
1290             except BaseException, e:
1291                 self.show_message(str(e))
1292                 return
1293
1294         hbox = QHBoxLayout()
1295         b = QPushButton(_("Sign"))
1296         hbox.addWidget(b)
1297         b.clicked.connect(do_sign)
1298         b = QPushButton(_("Close"))
1299         b.clicked.connect(d.accept)
1300         hbox.addWidget(b)
1301         layout.addLayout(hbox, 4, 1)
1302         tab_widget.addTab(tab, "Sign")
1303
1304
1305         tab = QWidget()
1306         layout = QGridLayout(tab)
1307
1308         verify_address = QLineEdit()
1309         layout.addWidget(QLabel(_('Address')), 1, 0)
1310         layout.addWidget(verify_address, 1, 1)
1311
1312         verify_message = QTextEdit()
1313         layout.addWidget(QLabel(_('Message')), 2, 0)
1314         layout.addWidget(verify_message, 2, 1, 2, 1)
1315
1316         verify_signature = QLineEdit()
1317         layout.addWidget(QLabel(_('Signature')), 3, 0)
1318         layout.addWidget(verify_signature, 3, 1)
1319
1320         def do_verify():
1321             try:
1322                 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1323                 self.show_message("Signature verified")
1324             except BaseException, e:
1325                 self.show_message(str(e))
1326                 return
1327
1328         hbox = QHBoxLayout()
1329         b = QPushButton(_("Verify"))
1330         b.clicked.connect(do_verify)
1331         hbox.addWidget(b)
1332         b = QPushButton(_("Close"))
1333         b.clicked.connect(d.accept)
1334         hbox.addWidget(b)
1335         layout.addLayout(hbox, 4, 1)
1336         tab_widget.addTab(tab, "Verify")
1337
1338         vbox = QVBoxLayout()
1339         vbox.addWidget(tab_widget)
1340         d.setLayout(vbox)
1341         d.exec_()
1342
1343         
1344     def toggle_QR_window(self, show):
1345         if show and not self.qr_window:
1346             self.qr_window = QR_Window()
1347             self.qr_window.setVisible(True)
1348             self.qr_window_geometry = self.qr_window.geometry()
1349             item = self.receive_list.currentItem()
1350             if item:
1351                 address = str(item.text(1))
1352                 label = self.wallet.labels.get(address)
1353                 amount = self.wallet.requested_amounts.get(address)
1354                 self.qr_window.set_content( address, label, amount )
1355
1356         elif show and self.qr_window and not self.qr_window.isVisible():
1357             self.qr_window.setVisible(True)
1358             self.qr_window.setGeometry(self.qr_window_geometry)
1359
1360         elif not show and self.qr_window and self.qr_window.isVisible():
1361             self.qr_window_geometry = self.qr_window.geometry()
1362             self.qr_window.setVisible(False)
1363
1364         #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1365         self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1366         self.receive_list.setColumnWidth(2, 200)
1367
1368
1369     def question(self, msg):
1370         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1371
1372     def show_message(self, msg):
1373         QMessageBox.information(self, _('Message'), msg, _('OK'))
1374
1375     def password_dialog(self ):
1376         d = QDialog(self)
1377         d.setModal(1)
1378
1379         pw = QLineEdit()
1380         pw.setEchoMode(2)
1381
1382         vbox = QVBoxLayout()
1383         msg = _('Please enter your password')
1384         vbox.addWidget(QLabel(msg))
1385
1386         grid = QGridLayout()
1387         grid.setSpacing(8)
1388         grid.addWidget(QLabel(_('Password')), 1, 0)
1389         grid.addWidget(pw, 1, 1)
1390         vbox.addLayout(grid)
1391
1392         vbox.addLayout(ok_cancel_buttons(d))
1393         d.setLayout(vbox) 
1394
1395         if not d.exec_(): return
1396         return unicode(pw.text())
1397
1398
1399
1400
1401
1402     @staticmethod
1403     def change_password_dialog( wallet, parent=None ):
1404
1405         if not wallet.seed:
1406             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1407             return
1408
1409         d = QDialog(parent)
1410         d.setModal(1)
1411
1412         pw = QLineEdit()
1413         pw.setEchoMode(2)
1414         new_pw = QLineEdit()
1415         new_pw.setEchoMode(2)
1416         conf_pw = QLineEdit()
1417         conf_pw.setEchoMode(2)
1418
1419         vbox = QVBoxLayout()
1420         if parent:
1421             msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1422                    +_('To disable wallet encryption, enter an empty new password.')) \
1423                    if wallet.use_encryption else _('Your wallet keys are not encrypted')
1424         else:
1425             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1426                   +_("Leave these fields empty if you want to disable encryption.")
1427         vbox.addWidget(QLabel(msg))
1428
1429         grid = QGridLayout()
1430         grid.setSpacing(8)
1431
1432         if wallet.use_encryption:
1433             grid.addWidget(QLabel(_('Password')), 1, 0)
1434             grid.addWidget(pw, 1, 1)
1435
1436         grid.addWidget(QLabel(_('New Password')), 2, 0)
1437         grid.addWidget(new_pw, 2, 1)
1438
1439         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1440         grid.addWidget(conf_pw, 3, 1)
1441         vbox.addLayout(grid)
1442
1443         vbox.addLayout(ok_cancel_buttons(d))
1444         d.setLayout(vbox) 
1445
1446         if not d.exec_(): return
1447
1448         password = unicode(pw.text()) if wallet.use_encryption else None
1449         new_password = unicode(new_pw.text())
1450         new_password2 = unicode(conf_pw.text())
1451
1452         try:
1453             seed = wallet.pw_decode( wallet.seed, password)
1454         except:
1455             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1456             return
1457
1458         if new_password != new_password2:
1459             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1460             return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1461
1462         wallet.update_password(seed, password, new_password)
1463
1464     @staticmethod
1465     def seed_dialog(wallet, parent=None):
1466         d = QDialog(parent)
1467         d.setModal(1)
1468
1469         vbox = QVBoxLayout()
1470         msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1471         vbox.addWidget(QLabel(msg))
1472
1473         grid = QGridLayout()
1474         grid.setSpacing(8)
1475
1476         seed_e = QLineEdit()
1477         grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1478         grid.addWidget(seed_e, 1, 1)
1479
1480         gap_e = QLineEdit()
1481         gap_e.setText("5")
1482         grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1483         grid.addWidget(gap_e, 2, 1)
1484         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1485         vbox.addLayout(grid)
1486
1487         vbox.addLayout(ok_cancel_buttons(d))
1488         d.setLayout(vbox) 
1489
1490         if not d.exec_(): return
1491
1492         try:
1493             gap = int(unicode(gap_e.text()))
1494         except:
1495             QMessageBox.warning(None, _('Error'), 'error', 'OK')
1496             sys.exit(0)
1497
1498         try:
1499             seed = unicode(seed_e.text())
1500             seed.decode('hex')
1501         except:
1502             print_error("Warning: Not hex, trying decode")
1503             try:
1504                 seed = mnemonic.mn_decode( seed.split(' ') )
1505             except:
1506                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1507                 sys.exit(0)
1508         if not seed:
1509             QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1510             sys.exit(0)
1511         
1512         wallet.seed = str(seed)
1513         #print repr(wallet.seed)
1514         wallet.gap_limit = gap
1515         return True
1516
1517
1518
1519     def settings_dialog(self):
1520         d = QDialog(self)
1521         d.setWindowTitle(_('Electrum Settings'))
1522         d.setModal(1)
1523         vbox = QVBoxLayout()
1524
1525         tabs = QTabWidget(self)
1526         vbox.addWidget(tabs)
1527
1528         tab2 = QWidget()
1529         grid_ui = QGridLayout(tab2)
1530         grid_ui.setColumnStretch(0,1)
1531         tabs.addTab(tab2, _('Display') )
1532
1533         tab = QWidget()
1534         grid_wallet = QGridLayout(tab)
1535         grid_wallet.setColumnStretch(0,1)
1536         tabs.addTab(tab, _('Wallet') )
1537         
1538         fee_label = QLabel(_('Transaction fee'))
1539         grid_wallet.addWidget(fee_label, 2, 0)
1540         fee_e = QLineEdit()
1541         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1542         grid_wallet.addWidget(fee_e, 2, 1)
1543         msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1544             + _('Recommended value') + ': 0.001'
1545         grid_wallet.addWidget(HelpButton(msg), 2, 2)
1546         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1547         if not self.config.is_modifiable('fee'):
1548             for w in [fee_e, fee_label]: w.setEnabled(False)
1549
1550         nz_label = QLabel(_('Display zeros'))
1551         grid_ui.addWidget(nz_label, 3, 0)
1552         nz_e = QLineEdit()
1553         nz_e.setText("%d"% self.wallet.num_zeros)
1554         grid_ui.addWidget(nz_e, 3, 1)
1555         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1556         grid_ui.addWidget(HelpButton(msg), 3, 2)
1557         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1558         if not self.config.is_modifiable('num_zeros'):
1559             for w in [nz_e, nz_label]: w.setEnabled(False)
1560
1561
1562         usechange_label = QLabel(_('Use change addresses'))
1563         grid_wallet.addWidget(usechange_label, 5, 0)
1564         usechange_combo = QComboBox()
1565         usechange_combo.addItems(['Yes', 'No'])
1566         usechange_combo.setCurrentIndex(not self.wallet.use_change)
1567         grid_wallet.addWidget(usechange_combo, 5, 1)
1568         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1569         if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1570
1571         gap_label = QLabel(_('Gap limit'))
1572         grid_wallet.addWidget(gap_label, 6, 0)
1573         gap_e = QLineEdit()
1574         gap_e.setText("%d"% self.wallet.gap_limit)
1575         grid_wallet.addWidget(gap_e, 6, 1)
1576         msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1577               + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1578               + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1579               + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1580               + _('Warning') + ': ' \
1581               + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1582               + _('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' 
1583         grid_wallet.addWidget(HelpButton(msg), 6, 2)
1584         gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1585         if not self.config.is_modifiable('gap_limit'):
1586             for w in [gap_e, gap_label]: w.setEnabled(False)
1587         
1588         gui_label=QLabel(_('Default GUI') + ':')
1589         grid_ui.addWidget(gui_label , 7, 0)
1590         gui_combo = QComboBox()
1591         gui_combo.addItems(['Lite', 'Classic'])
1592         index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1593         if index==-1: index = 1
1594         gui_combo.setCurrentIndex(index)
1595         grid_ui.addWidget(gui_combo, 7, 1)
1596         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)
1597         if not self.config.is_modifiable('gui'):
1598             for w in [gui_combo, gui_label]: w.setEnabled(False)
1599
1600         lang_label=QLabel(_('Language') + ':')
1601         grid_ui.addWidget(lang_label , 8, 0)
1602         lang_combo = QComboBox()
1603         from i18n import languages
1604         lang_combo.addItems(languages.values())
1605         try:
1606             index = languages.keys().index(self.config.get("language",''))
1607         except:
1608             index = 0
1609         lang_combo.setCurrentIndex(index)
1610         grid_ui.addWidget(lang_combo, 8, 1)
1611         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1612         if not self.config.is_modifiable('language'):
1613             for w in [lang_combo, lang_label]: w.setEnabled(False)
1614
1615         currencies = self.exchanger.get_currencies()
1616         currencies.insert(0, "None")
1617
1618         cur_label=QLabel(_('Currency') + ':')
1619         grid_ui.addWidget(cur_label , 9, 0)
1620         cur_combo = QComboBox()
1621         cur_combo.addItems(currencies)
1622         try:
1623             index = currencies.index(self.config.get('currency', "None"))
1624         except:
1625             index = 0
1626         cur_combo.setCurrentIndex(index)
1627         grid_ui.addWidget(cur_combo, 9, 1)
1628         grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes. ')), 9, 2)
1629         
1630         view_label=QLabel(_('Receive Tab') + ':')
1631         grid_ui.addWidget(view_label , 10, 0)
1632         view_combo = QComboBox()
1633         view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1634         view_combo.setCurrentIndex(self.receive_tab_mode)
1635         grid_ui.addWidget(view_combo, 10, 1)
1636         hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
1637              + _('Simple') +   ': ' + _('Show only addresses and labels.') + '\n\n' \
1638              + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1639              + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n' 
1640         
1641         grid_ui.addWidget(HelpButton(hh), 10, 2)
1642         
1643         vbox.addLayout(ok_cancel_buttons(d))
1644         d.setLayout(vbox) 
1645
1646         # run the dialog
1647         if not d.exec_(): return
1648
1649         fee = unicode(fee_e.text())
1650         try:
1651             fee = int( 100000000 * Decimal(fee) )
1652         except:
1653             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1654             return
1655
1656         if self.wallet.fee != fee:
1657             self.wallet.fee = fee
1658             self.wallet.save()
1659         
1660         nz = unicode(nz_e.text())
1661         try:
1662             nz = int( nz )
1663             if nz>8: nz=8
1664         except:
1665             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1666             return
1667
1668         if self.wallet.num_zeros != nz:
1669             self.wallet.num_zeros = nz
1670             self.config.set_key('num_zeros', nz, True)
1671             self.update_history_tab()
1672             self.update_receive_tab()
1673
1674         usechange_result = usechange_combo.currentIndex() == 0
1675         if self.wallet.use_change != usechange_result:
1676             self.wallet.use_change = usechange_result
1677             self.config.set_key('use_change', self.wallet.use_change, True)
1678         
1679         try:
1680             n = int(gap_e.text())
1681         except:
1682             QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1683             return
1684
1685         if self.wallet.gap_limit != n:
1686             r = self.wallet.change_gap_limit(n)
1687             if r:
1688                 self.update_receive_tab()
1689                 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1690             else:
1691                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1692
1693         need_restart = False
1694
1695         gui_request = str(gui_combo.currentText()).lower()
1696         if gui_request != self.config.get('gui'):
1697             self.config.set_key('gui', gui_request, True)
1698             need_restart = True
1699             
1700         lang_request = languages.keys()[lang_combo.currentIndex()]
1701         if lang_request != self.config.get('language'):
1702             self.config.set_key("language", lang_request, True)
1703             need_restart = True
1704             
1705         cur_request = str(currencies[cur_combo.currentIndex()])
1706         if cur_request != self.config.get('currency', "None"):
1707             self.config.set_key('currency', cur_request, True)
1708             self.update_wallet()
1709
1710         if need_restart:
1711             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1712
1713         self.receive_tab_set_mode(view_combo.currentIndex())
1714
1715
1716     @staticmethod 
1717     def network_dialog(wallet, parent=None):
1718         interface = wallet.interface
1719         if parent:
1720             if interface.is_connected:
1721                 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1722             else:
1723                 status = _("Not connected")
1724             server = interface.server
1725         else:
1726             import random
1727             status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1728             server = interface.server
1729
1730         plist, servers_list = interface.get_servers_list()
1731
1732         d = QDialog(parent)
1733         d.setModal(1)
1734         d.setWindowTitle(_('Server'))
1735         d.setMinimumSize(375, 20)
1736
1737         vbox = QVBoxLayout()
1738         vbox.setSpacing(30)
1739
1740         hbox = QHBoxLayout()
1741         l = QLabel()
1742         l.setPixmap(QPixmap(":icons/network.png"))
1743         hbox.addStretch(10)
1744         hbox.addWidget(l)
1745         hbox.addWidget(QLabel(status))
1746         hbox.addStretch(50)
1747         vbox.addLayout(hbox)
1748
1749
1750         # grid layout
1751         grid = QGridLayout()
1752         grid.setSpacing(8)
1753         vbox.addLayout(grid)
1754
1755         # server
1756         server_protocol = QComboBox()
1757         server_host = QLineEdit()
1758         server_host.setFixedWidth(200)
1759         server_port = QLineEdit()
1760         server_port.setFixedWidth(60)
1761
1762         protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1763         protocol_letters = 'thsg'
1764         DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1765         server_protocol.addItems(protocol_names)
1766
1767         grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1768         grid.addWidget(server_protocol, 0, 1)
1769         grid.addWidget(server_host, 0, 2)
1770         grid.addWidget(server_port, 0, 3)
1771
1772         def change_protocol(p):
1773             protocol = protocol_letters[p]
1774             host = unicode(server_host.text())
1775             pp = plist.get(host,DEFAULT_PORTS)
1776             if protocol not in pp.keys():
1777                 protocol = pp.keys()[0]
1778             port = pp[protocol]
1779             server_host.setText( host )
1780             server_port.setText( port )
1781
1782         server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1783         
1784         label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1785         servers_list_widget = QTreeWidget(parent)
1786         servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1787         servers_list_widget.setMaximumHeight(150)
1788         servers_list_widget.setColumnWidth(0, 240)
1789         for _host in servers_list.keys():
1790             _type = 'P' if servers_list[_host].get('pruning') else 'F'
1791             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1792
1793         def change_server(host, protocol=None):
1794             pp = plist.get(host,DEFAULT_PORTS)
1795             if protocol:
1796                 port = pp.get(protocol)
1797                 if not port: protocol = None
1798                     
1799             if not protocol:
1800                 if 't' in pp.keys():
1801                     protocol = 't'
1802                     port = pp.get(protocol)
1803                 else:
1804                     protocol = pp.keys()[0]
1805                     port = pp.get(protocol)
1806             
1807             server_host.setText( host )
1808             server_port.setText( port )
1809             server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1810
1811             if not plist: return
1812             for p in protocol_letters:
1813                 i = protocol_letters.index(p)
1814                 j = server_protocol.model().index(i,0)
1815                 if p not in pp.keys():
1816                     server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1817                 else:
1818                     server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1819
1820
1821         if server:
1822             host, port, protocol = server.split(':')
1823             change_server(host,protocol)
1824
1825         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1826         grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1827
1828         if not wallet.config.is_modifiable('server'):
1829             for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1830
1831         # auto cycle
1832         autocycle_cb = QCheckBox('Try random servers if disconnected')
1833         autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
1834         grid.addWidget(autocycle_cb, 3, 1, 3, 2)
1835         if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
1836
1837         # proxy setting
1838         proxy_mode = QComboBox()
1839         proxy_host = QLineEdit()
1840         proxy_host.setFixedWidth(200)
1841         proxy_port = QLineEdit()
1842         proxy_port.setFixedWidth(60)
1843         proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1844
1845         def check_for_disable(index = False):
1846             if proxy_mode.currentText() != 'NONE':
1847                 proxy_host.setEnabled(True)
1848                 proxy_port.setEnabled(True)
1849             else:
1850                 proxy_host.setEnabled(False)
1851                 proxy_port.setEnabled(False)
1852
1853         check_for_disable()
1854         proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1855
1856         if not wallet.config.is_modifiable('proxy'):
1857             for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1858
1859         proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1860         proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1861         proxy_host.setText(proxy_config.get("host"))
1862         proxy_port.setText(proxy_config.get("port"))
1863
1864         grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1865         grid.addWidget(proxy_mode, 2, 1)
1866         grid.addWidget(proxy_host, 2, 2)
1867         grid.addWidget(proxy_port, 2, 3)
1868
1869         # buttons
1870         vbox.addLayout(ok_cancel_buttons(d))
1871         d.setLayout(vbox) 
1872
1873         if not d.exec_(): return
1874
1875         server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1876         if proxy_mode.currentText() != 'NONE':
1877             proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1878         else:
1879             proxy = None
1880
1881         wallet.config.set_key("proxy", proxy, True)
1882         wallet.config.set_key("server", server, True)
1883         interface.set_server(server, proxy)
1884         wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
1885         return True
1886
1887     def closeEvent(self, event):
1888         g = self.geometry()
1889         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1890         event.accept()
1891
1892
1893 class ElectrumGui:
1894
1895     def __init__(self, wallet, config, app=None):
1896         self.wallet = wallet
1897         self.config = config
1898         if app is None:
1899             self.app = QApplication(sys.argv)
1900
1901
1902     def restore_or_create(self):
1903         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1904         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1905         if r==2: return None
1906         return 'restore' if r==1 else 'create'
1907
1908     def seed_dialog(self):
1909         return ElectrumWindow.seed_dialog( self.wallet )
1910
1911     def network_dialog(self):
1912         return ElectrumWindow.network_dialog( self.wallet, parent=None )
1913         
1914
1915     def show_seed(self):
1916         ElectrumWindow.show_seed_dialog(self.wallet)
1917
1918
1919     def password_dialog(self):
1920         ElectrumWindow.change_password_dialog(self.wallet)
1921
1922
1923     def restore_wallet(self):
1924         wallet = self.wallet
1925         # wait until we are connected, because the user might have selected another server
1926         if not wallet.interface.is_connected:
1927             waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1928             waiting_dialog(waiting)
1929
1930         waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1931             %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1932
1933         wallet.set_up_to_date(False)
1934         wallet.interface.poke('synchronizer')
1935         waiting_dialog(waiting)
1936         if wallet.is_found():
1937             print_error( "Recovery successful" )
1938         else:
1939             QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1940
1941         return True
1942
1943     def main(self,url):
1944         s = Timer()
1945         s.start()
1946         w = ElectrumWindow(self.wallet, self.config)
1947         if url: w.set_url(url)
1948         w.app = self.app
1949         w.connect_slots(s)
1950         w.update_wallet()
1951         w.show()
1952
1953         self.app.exec_()