1be3f86b1214e5ee035da442691781a1315a0cf1
[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         msg = _('Here are the settings of your wallet.') + '\n'\
1494               + _('For more explanations, click on the help buttons next to each field.')
1495
1496         label = QLabel(msg)
1497         label.setFixedWidth(250)
1498         label.setWordWrap(True)
1499         label.setAlignment(Qt.AlignJustify)
1500
1501         tabs = QTabWidget(self)
1502         vbox.addWidget(tabs)
1503
1504         vbox.addWidget(label)
1505
1506         tab = QWidget()
1507         grid_wallet = QGridLayout(tab)
1508         grid_wallet.setColumnStretch(0,1)
1509         tabs.addTab(tab, _('Wallet') )
1510         
1511         tab2 = QWidget()
1512         grid_ui = QGridLayout(tab2)
1513         grid_ui.setColumnStretch(0,1)
1514         tabs.addTab(tab2, _('Display') )
1515
1516         fee_label = QLabel(_('Transaction fee'))
1517         grid_wallet.addWidget(fee_label, 2, 0)
1518         fee_e = QLineEdit()
1519         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1520         grid_wallet.addWidget(fee_e, 2, 1)
1521         msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1522             + _('Recommended value') + ': 0.001'
1523         grid_wallet.addWidget(HelpButton(msg), 2, 2)
1524         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1525         if not self.config.is_modifiable('fee'):
1526             for w in [fee_e, fee_label]: w.setEnabled(False)
1527
1528         nz_label = QLabel(_('Display zeros'))
1529         grid_ui.addWidget(nz_label, 3, 0)
1530         nz_e = QLineEdit()
1531         nz_e.setText("%d"% self.wallet.num_zeros)
1532         grid_ui.addWidget(nz_e, 3, 1)
1533         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1534         grid_ui.addWidget(HelpButton(msg), 3, 2)
1535         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1536         if not self.config.is_modifiable('num_zeros'):
1537             for w in [nz_e, nz_label]: w.setEnabled(False)
1538
1539
1540         usechange_label = QLabel(_('Use change addresses'))
1541         grid_wallet.addWidget(usechange_label, 5, 0)
1542         usechange_combo = QComboBox()
1543         usechange_combo.addItems(['Yes', 'No'])
1544         usechange_combo.setCurrentIndex(not self.wallet.use_change)
1545         grid_wallet.addWidget(usechange_combo, 5, 1)
1546         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1547         if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1548
1549         gap_label = QLabel(_('Gap limit'))
1550         grid_wallet.addWidget(gap_label, 6, 0)
1551         gap_e = QLineEdit()
1552         gap_e.setText("%d"% self.wallet.gap_limit)
1553         grid_wallet.addWidget(gap_e, 6, 1)
1554         msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1555               + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1556               + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1557               + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1558               + _('Warning') + ': ' \
1559               + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1560               + _('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' 
1561         grid_wallet.addWidget(HelpButton(msg), 6, 2)
1562         gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1563         if not self.config.is_modifiable('gap_limit'):
1564             for w in [gap_e, gap_label]: w.setEnabled(False)
1565         
1566         gui_label=QLabel(_('Default GUI') + ':')
1567         grid_ui.addWidget(gui_label , 7, 0)
1568         gui_combo = QComboBox()
1569         gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1570         index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1571         if index==-1: index = 1
1572         gui_combo.setCurrentIndex(index)
1573         grid_ui.addWidget(gui_combo, 7, 1)
1574         grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1575         if not self.config.is_modifiable('gui'):
1576             for w in [gui_combo, gui_label]: w.setEnabled(False)
1577
1578         lang_label=QLabel(_('Language') + ':')
1579         grid_ui.addWidget(lang_label , 8, 0)
1580         lang_combo = QComboBox()
1581         languages = {'':_('Default'), 'br':_('Brasilian'), 'cs':_('Czech'), 'de':_('German'),
1582                      'eo':_('Esperanto'), 'en':_('English'), 'es':_('Spanish'), 'fr':_('French'),
1583                      'it':_('Italian'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'),
1584                      'sl':_('Slovenian'), 'vi':_('Vietnamese'), 'zh':_('Chinese')
1585                      }
1586         lang_combo.addItems(languages.values())
1587         try:
1588             index = languages.keys().index(self.config.get("language",''))
1589         except:
1590             index = 0
1591         lang_combo.setCurrentIndex(index)
1592         grid_ui.addWidget(lang_combo, 8, 1)
1593         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1594         if not self.config.is_modifiable('language'):
1595             for w in [lang_combo, lang_label]: w.setEnabled(False)
1596
1597
1598         view_label=QLabel(_('Receive Tab') + ':')
1599         grid_ui.addWidget(view_label , 9, 0)
1600         view_combo = QComboBox()
1601         view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1602         view_combo.setCurrentIndex(self.receive_tab_mode)
1603         grid_ui.addWidget(view_combo, 9, 1)
1604         hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
1605              + _('Simple') +   ': ' + _('Show only addresses and labels.') + '\n\n' \
1606              + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1607              + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n' 
1608         
1609         grid_ui.addWidget(HelpButton(hh), 9, 2)
1610         
1611         vbox.addLayout(ok_cancel_buttons(d))
1612         d.setLayout(vbox) 
1613
1614         # run the dialog
1615         if not d.exec_(): return
1616
1617         fee = unicode(fee_e.text())
1618         try:
1619             fee = int( 100000000 * Decimal(fee) )
1620         except:
1621             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1622             return
1623
1624         if self.wallet.fee != fee:
1625             self.wallet.fee = fee
1626             self.wallet.save()
1627         
1628         nz = unicode(nz_e.text())
1629         try:
1630             nz = int( nz )
1631             if nz>8: nz=8
1632         except:
1633             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1634             return
1635
1636         if self.wallet.num_zeros != nz:
1637             self.wallet.num_zeros = nz
1638             self.config.set_key('num_zeros', nz, True)
1639             self.update_history_tab()
1640             self.update_receive_tab()
1641
1642         usechange_result = usechange_combo.currentIndex() == 0
1643         if self.wallet.use_change != usechange_result:
1644             self.wallet.use_change = usechange_result
1645             self.config.set_key('use_change', self.wallet.use_change, True)
1646         
1647         try:
1648             n = int(gap_e.text())
1649         except:
1650             QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1651             return
1652
1653         if self.wallet.gap_limit != n:
1654             r = self.wallet.change_gap_limit(n)
1655             if r:
1656                 self.update_receive_tab()
1657                 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1658             else:
1659                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1660
1661         need_restart = False
1662
1663         gui_request = str(gui_combo.currentText()).lower()
1664         if gui_request != self.config.get('gui'):
1665             self.config.set_key('gui', gui_request, True)
1666             need_restart = True
1667             
1668         lang_request = languages.keys()[lang_combo.currentIndex()]
1669         if lang_request != self.config.get('language'):
1670             self.config.set_key("language", lang_request, True)
1671             need_restart = True
1672
1673         if need_restart:
1674             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1675
1676         self.receive_tab_set_mode(view_combo.currentIndex())
1677
1678
1679     @staticmethod 
1680     def network_dialog(wallet, parent=None):
1681         interface = wallet.interface
1682         if parent:
1683             if interface.is_connected:
1684                 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1685             else:
1686                 status = _("Not connected")
1687             server = interface.server
1688         else:
1689             import random
1690             status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1691             server = interface.server
1692
1693         plist, servers_list = interface.get_servers_list()
1694
1695         d = QDialog(parent)
1696         d.setModal(1)
1697         d.setWindowTitle(_('Server'))
1698         d.setMinimumSize(375, 20)
1699
1700         vbox = QVBoxLayout()
1701         vbox.setSpacing(30)
1702
1703         hbox = QHBoxLayout()
1704         l = QLabel()
1705         l.setPixmap(QPixmap(":icons/network.png"))
1706         hbox.addStretch(10)
1707         hbox.addWidget(l)
1708         hbox.addWidget(QLabel(status))
1709         hbox.addStretch(50)
1710         vbox.addLayout(hbox)
1711
1712
1713         # grid layout
1714         grid = QGridLayout()
1715         grid.setSpacing(8)
1716         vbox.addLayout(grid)
1717
1718         # server
1719         server_protocol = QComboBox()
1720         server_host = QLineEdit()
1721         server_host.setFixedWidth(200)
1722         server_port = QLineEdit()
1723         server_port.setFixedWidth(60)
1724
1725         protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1726         protocol_letters = 'thsg'
1727         DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1728         server_protocol.addItems(protocol_names)
1729
1730         grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1731         grid.addWidget(server_protocol, 0, 1)
1732         grid.addWidget(server_host, 0, 2)
1733         grid.addWidget(server_port, 0, 3)
1734
1735         def change_protocol(p):
1736             protocol = protocol_letters[p]
1737             host = unicode(server_host.text())
1738             pp = plist.get(host,DEFAULT_PORTS)
1739             if protocol not in pp.keys():
1740                 protocol = pp.keys()[0]
1741             port = pp[protocol]
1742             server_host.setText( host )
1743             server_port.setText( port )
1744
1745         server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1746         
1747         label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1748         servers_list_widget = QTreeWidget(parent)
1749         servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1750         servers_list_widget.setMaximumHeight(150)
1751         servers_list_widget.setColumnWidth(0, 240)
1752         for _host in servers_list.keys():
1753             _type = 'P' if servers_list[_host].get('pruning') else 'F'
1754             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1755
1756         def change_server(host, protocol=None):
1757             pp = plist.get(host,DEFAULT_PORTS)
1758             if protocol:
1759                 port = pp.get(protocol)
1760                 if not port: protocol = None
1761                     
1762             if not protocol:
1763                 if 't' in pp.keys():
1764                     protocol = 't'
1765                     port = pp.get(protocol)
1766                 else:
1767                     protocol = pp.keys()[0]
1768                     port = pp.get(protocol)
1769             
1770             server_host.setText( host )
1771             server_port.setText( port )
1772             server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1773
1774             if not plist: return
1775             for p in protocol_letters:
1776                 i = protocol_letters.index(p)
1777                 j = server_protocol.model().index(i,0)
1778                 if p not in pp.keys():
1779                     server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1780                 else:
1781                     server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1782
1783
1784         if server:
1785             host, port, protocol = server.split(':')
1786             change_server(host,protocol)
1787
1788         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1789         grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1790
1791         if not wallet.config.is_modifiable('server'):
1792             for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1793
1794         # proxy setting
1795         proxy_mode = QComboBox()
1796         proxy_host = QLineEdit()
1797         proxy_host.setFixedWidth(200)
1798         proxy_port = QLineEdit()
1799         proxy_port.setFixedWidth(60)
1800         proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1801
1802         def check_for_disable(index = False):
1803             if proxy_mode.currentText() != 'NONE':
1804                 proxy_host.setEnabled(True)
1805                 proxy_port.setEnabled(True)
1806             else:
1807                 proxy_host.setEnabled(False)
1808                 proxy_port.setEnabled(False)
1809
1810         check_for_disable()
1811         proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1812
1813         if not wallet.config.is_modifiable('proxy'):
1814             for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1815
1816         proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1817         proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1818         proxy_host.setText(proxy_config.get("host"))
1819         proxy_port.setText(proxy_config.get("port"))
1820
1821         grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1822         grid.addWidget(proxy_mode, 2, 1)
1823         grid.addWidget(proxy_host, 2, 2)
1824         grid.addWidget(proxy_port, 2, 3)
1825
1826         # buttons
1827         vbox.addLayout(ok_cancel_buttons(d))
1828         d.setLayout(vbox) 
1829
1830         if not d.exec_(): return
1831
1832         server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1833         if proxy_mode.currentText() != 'NONE':
1834             proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1835         else:
1836             proxy = None
1837
1838         wallet.config.set_key("proxy", proxy, True)
1839         wallet.config.set_key("server", server, True)
1840         interface.set_server(server, proxy)
1841                 
1842         return True
1843
1844     def closeEvent(self, event):
1845         g = self.geometry()
1846         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1847         event.accept()
1848
1849
1850 class ElectrumGui:
1851
1852     def __init__(self, wallet, config, app=None):
1853         self.wallet = wallet
1854         self.config = config
1855         if app is None:
1856             self.app = QApplication(sys.argv)
1857
1858
1859     def restore_or_create(self):
1860         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1861         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1862         if r==2: return None
1863         return 'restore' if r==1 else 'create'
1864
1865     def seed_dialog(self):
1866         return ElectrumWindow.seed_dialog( self.wallet )
1867
1868     def network_dialog(self):
1869         return ElectrumWindow.network_dialog( self.wallet, parent=None )
1870         
1871
1872     def show_seed(self):
1873         ElectrumWindow.show_seed_dialog(self.wallet)
1874
1875
1876     def password_dialog(self):
1877         ElectrumWindow.change_password_dialog(self.wallet)
1878
1879
1880     def restore_wallet(self):
1881         wallet = self.wallet
1882         # wait until we are connected, because the user might have selected another server
1883         if not wallet.interface.is_connected:
1884             waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1885             waiting_dialog(waiting)
1886
1887         waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1888             %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1889
1890         wallet.set_up_to_date(False)
1891         wallet.interface.poke('synchronizer')
1892         waiting_dialog(waiting)
1893         if wallet.is_found():
1894             print_error( "Recovery successful" )
1895         else:
1896             QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1897
1898         return True
1899
1900     def main(self,url):
1901         s = Timer()
1902         s.start()
1903         w = ElectrumWindow(self.wallet, self.config)
1904         if url: w.set_url(url)
1905         w.app = self.app
1906         w.connect_slots(s)
1907         w.update_wallet()
1908         w.show()
1909
1910         self.app.exec_()