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