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