highlight current item only when we have the focus
[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
22 try:
23     import PyQt4
24 except:
25     print "could not import PyQt4"
26     print "on Linux systems, you may try 'sudo apt-get install python-qt4'"
27     sys.exit(1)
28
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
32 import PyQt4.QtGui as QtGui
33 from interface import DEFAULT_SERVERS
34
35 try:
36     import icons_rc
37 except:
38     print "Could not import icons_rp.py"
39     print "Please generate it with: 'pyrcc4 icons.qrc -o icons_rc.py'"
40     sys.exit(1)
41
42 from wallet import format_satoshis
43 import bmp, mnemonic, pyqrnative
44
45 from decimal import Decimal
46
47 import platform
48 MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
49 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
50
51 def numbify(entry, is_int = False):
52     text = unicode(entry.text()).strip()
53     chars = '0123456789'
54     if not is_int: chars +='.'
55     s = ''.join([i for i in text if i in chars])
56     if not is_int:
57         if '.' in s:
58             p = s.find('.')
59             s = s.replace('.','')
60             s = s[:p] + '.' + s[p:p+8]
61         try:
62             amount = int( Decimal(s) * 100000000 )
63         except:
64             amount = None
65     else:
66         try:
67             amount = int( s )
68         except:
69             amount = None
70     entry.setText(s)
71     return amount
72
73
74 class Timer(QtCore.QThread):
75     def run(self):
76         while True:
77             self.emit(QtCore.SIGNAL('timersignal'))
78             time.sleep(0.5)
79
80 class EnterButton(QPushButton):
81     def __init__(self, text, func):
82         QPushButton.__init__(self, text)
83         self.func = func
84         self.clicked.connect(func)
85
86     def keyPressEvent(self, e):
87         if e.key() == QtCore.Qt.Key_Return:
88             apply(self.func,())
89
90 class MyTreeWidget(QTreeWidget):
91     def __init__(self, parent):
92         QTreeWidget.__init__(self, parent)
93         def ddfr(item):
94             if not item: return
95             for i in range(0,100):
96                 if self.itemAt(QPoint(0,i*5)) == item:
97                     break
98             else:
99                 return
100             for j in range(0,30):
101                 if self.itemAt(QPoint(0,i*5 + j)) != item:
102                     break
103             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
104
105         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
106         self.my_item = None
107
108     def focusInEvent(self, e):
109         # we use column 1 because column 0 may be hidden
110         self.setCurrentItem(self.my_item,1)
111
112     def focusOutEvent(self, e):
113         self.my_item = self.currentItem()
114         self.setCurrentItem(None)
115         
116
117
118
119 class StatusBarButton(QPushButton):
120     def __init__(self, icon, tooltip, func):
121         QPushButton.__init__(self, icon, '')
122         self.setToolTip(tooltip)
123         self.setFlat(True)
124         self.setMaximumWidth(25)
125         self.clicked.connect(func)
126         self.func = func
127
128     def keyPressEvent(self, e):
129         if e.key() == QtCore.Qt.Key_Return:
130             apply(self.func,())
131
132
133 class QRCodeWidget(QWidget):
134
135     def __init__(self, addr):
136         super(QRCodeWidget, self).__init__()
137         self.setGeometry(300, 300, 350, 350)
138         self.set_addr(addr)
139
140     def set_addr(self, addr):
141         self.addr = addr
142         self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
143         self.qr.addData(addr)
144         self.qr.make()
145         
146     def paintEvent(self, e):
147         qp = QtGui.QPainter()
148         qp.begin(self)
149         boxsize = 7
150         size = self.qr.getModuleCount()*boxsize
151         k = self.qr.getModuleCount()
152         black = QColor(0, 0, 0, 255)
153         white = QColor(255, 255, 255, 255)
154         for r in range(k):
155             for c in range(k):
156                 if self.qr.isDark(r, c):
157                     qp.setBrush(black)
158                     qp.setPen(black)
159                 else:
160                     qp.setBrush(white)
161                     qp.setPen(white)
162                 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
163         qp.end()
164         
165
166
167 def ok_cancel_buttons(dialog):
168     hbox = QHBoxLayout()
169     hbox.addStretch(1)
170     b = QPushButton("OK")
171     hbox.addWidget(b)
172     b.clicked.connect(dialog.accept)
173     b = QPushButton("Cancel")
174     hbox.addWidget(b)
175     b.clicked.connect(dialog.reject)
176     return hbox
177
178
179 class ElectrumWindow(QMainWindow):
180
181     def __init__(self, wallet):
182         QMainWindow.__init__(self)
183         self.wallet = wallet
184         self.wallet.gui_callback = self.update_callback
185
186         self.funds_error = False
187
188         self.tabs = tabs = QTabWidget(self)
189         tabs.addTab(self.create_history_tab(), _('History') )
190         if self.wallet.seed:
191             tabs.addTab(self.create_send_tab(), _('Send') )
192         tabs.addTab(self.create_receive_tab(), _('Receive') )
193         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
194         tabs.addTab(self.create_wall_tab(), _('Wall') )
195         tabs.setMinimumSize(600, 400)
196         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
197         self.setCentralWidget(tabs)
198         self.create_status_bar()
199         self.setGeometry(100,100,840,400)
200         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.path
201         if not self.wallet.seed: title += ' [seedless]'
202         self.setWindowTitle( title )
203         self.show()
204
205         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
206         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
207         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
208         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
209         
210         self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
211         self.history_list.setFocus(True)
212
213
214     def connect_slots(self, sender):
215         if self.wallet.seed:
216             self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
217             self.previous_payto_e=''
218
219     def check_recipient(self):
220         if self.payto_e.hasFocus():
221             return
222         r = unicode( self.payto_e.text() )
223         if r != self.previous_payto_e:
224             self.previous_payto_e = r
225             r = r.strip()
226             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
227                 try:
228                     to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
229                 except:
230                     return
231                 if to_address:
232                     s = r + '  <' + to_address + '>'
233                     self.payto_e.setText(s)
234
235
236     def update_callback(self):
237         self.emit(QtCore.SIGNAL('updatesignal'))
238
239     def update_wallet(self):
240         if self.wallet.interface and self.wallet.interface.is_connected:
241             if self.wallet.blocks == -1:
242                 text = _( "Connecting..." )
243                 icon = QIcon(":icons/status_disconnected.png")
244             elif self.wallet.blocks == 0:
245                 text = _( "Server not ready" )
246                 icon = QIcon(":icons/status_disconnected.png")
247             elif not self.wallet.up_to_date:
248                 text = _( "Synchronizing..." )
249                 icon = QIcon(":icons/status_waiting.png")
250             else:
251                 c, u = self.wallet.get_balance()
252                 text =  _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
253                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
254                 icon = QIcon(":icons/status_connected.png")
255         else:
256             text = _( "Not connected" )
257             icon = QIcon(":icons/status_disconnected.png")
258
259         if self.funds_error:
260             text = _( "Not enough funds" )
261
262         self.statusBar().showMessage(text)
263         self.status_button.setIcon( icon )
264
265         if self.wallet.up_to_date:
266             self.textbox.setText( self.wallet.banner )
267             self.update_history_tab()
268             self.update_receive_tab()
269             self.update_contacts_tab()
270             self.update_completions()
271
272
273     def create_history_tab(self):
274         self.history_list = l = MyTreeWidget(self)
275         l.setColumnCount(5)
276         l.setColumnWidth(0, 40) 
277         l.setColumnWidth(1, 140) 
278         l.setColumnWidth(2, 350) 
279         l.setColumnWidth(3, 140) 
280         l.setColumnWidth(4, 140) 
281         l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
282         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
283         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
284         l.setContextMenuPolicy(Qt.CustomContextMenu)
285         l.customContextMenuRequested.connect(self.create_history_menu)
286         return l
287
288     def create_history_menu(self, position):
289         self.history_list.selectedIndexes() 
290         item = self.history_list.currentItem()
291         tx_hash = str(item.toolTip(0))
292         if not item: return
293         menu = QMenu()
294         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
295         menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
296         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
297         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
298
299     def tx_details(self, tx_hash):
300         tx = self.wallet.tx_history.get(tx_hash)
301
302         if tx['height']:
303             conf = self.wallet.blocks - tx['height'] + 1
304             time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
305         else:
306             conf = 0
307             time_str = 'pending'
308
309         tx_details = _("Transaction Details") +"\n\n" \
310             + "Transaction ID:\n" + tx_hash + "\n\n" \
311             + "Status: %d confirmations\n\n"%conf  \
312             + "Date: %s\n\n"%time_str \
313             + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
314             + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
315
316         r = self.wallet.receipts.get(tx_hash)
317         if r:
318             tx_details += "\n_______________________________________" \
319                 + '\n\nSigned URI: ' + r[2] \
320                 + "\n\nSigned by: " + r[0] \
321                 + '\n\nSignature: ' + r[1]
322
323         QMessageBox.information(self, 'Details', tx_details, 'OK')
324
325
326     def tx_label_clicked(self, item, column):
327         if column==2 and item.isSelected():
328             tx_hash = str(item.toolTip(0))
329             self.is_edit=True
330             #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
331             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
332             self.history_list.editItem( item, column )
333             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
334             self.is_edit=False
335
336     def tx_label_changed(self, item, column):
337         if self.is_edit: 
338             return
339         self.is_edit=True
340         tx_hash = str(item.toolTip(0))
341         tx = self.wallet.tx_history.get(tx_hash)
342         s = self.wallet.labels.get(tx_hash)
343         text = unicode( item.text(2) )
344         if text: 
345             self.wallet.labels[tx_hash] = text
346             item.setForeground(2, QBrush(QColor('black')))
347         else:
348             if s: self.wallet.labels.pop(tx_hash)
349             text = tx['default_label']
350             item.setText(2, text)
351             item.setForeground(2, QBrush(QColor('gray')))
352         self.is_edit=False
353
354     def edit_label(self, is_recv):
355         l = self.receive_list if is_recv else self.contacts_list
356         c = 2 if is_recv else 1
357         item = l.currentItem()
358         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
359         l.editItem( item, c )
360         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
361
362     def address_label_clicked(self, item, column, l, column_addr, column_label):
363         if column==column_label and item.isSelected():
364             addr = unicode( item.text(column_addr) )
365             label = unicode( item.text(column_label) )
366             if label in self.wallet.aliases.keys():
367                 return
368             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
369             l.editItem( item, column )
370             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
371
372     def address_label_changed(self, item, column, l, column_addr, column_label):
373         addr = unicode( item.text(column_addr) )
374         text = unicode( item.text(column_label) )
375         if text:
376             if text not in self.wallet.aliases.keys():
377                 self.wallet.labels[addr] = text
378             else:
379                 print "error: this is one of your aliases"
380                 label = self.wallet.labels.get(addr,'')
381                 item.setText(column_label, QString(label))
382         else:
383             s = self.wallet.labels.get(addr)
384             if s: self.wallet.labels.pop(addr)
385
386         self.update_history_tab()
387         self.update_completions()
388
389     def update_history_tab(self):
390         self.history_list.clear()
391         balance = 0
392         for tx in self.wallet.get_tx_history():
393             tx_hash = tx['tx_hash']
394             if tx['height']:
395                 conf = self.wallet.blocks - tx['height'] + 1
396                 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
397                 icon = QIcon(":icons/confirmed.png")
398             else:
399                 conf = 0
400                 time_str = 'pending'
401                 icon = QIcon(":icons/unconfirmed.png")
402             v = tx['value']
403             balance += v 
404             label = self.wallet.labels.get(tx_hash)
405             is_default_label = (label == '') or (label is None)
406             if is_default_label: label = tx['default_label']
407
408             item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
409             item.setFont(2, QFont(MONOSPACE_FONT))
410             item.setFont(3, QFont(MONOSPACE_FONT))
411             item.setFont(4, QFont(MONOSPACE_FONT))
412             item.setToolTip(0, tx_hash)
413             if is_default_label:
414                 item.setForeground(2, QBrush(QColor('grey')))
415
416             item.setIcon(0, icon)
417             self.history_list.insertTopLevelItem(0,item)
418
419         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
420
421
422     def create_send_tab(self):
423         w = QWidget()
424
425         grid = QGridLayout()
426         grid.setSpacing(8)
427         grid.setColumnMinimumWidth(3,300)
428         grid.setColumnStretch(4,1)
429
430         self.payto_e = QLineEdit()
431         grid.addWidget(QLabel(_('Pay to')), 1, 0)
432         grid.addWidget(self.payto_e, 1, 1, 1, 3)
433
434         completer = QCompleter()
435         self.payto_e.setCompleter(completer)
436         self.completions = QStringListModel()
437         completer.setModel(self.completions)
438
439         self.message_e = QLineEdit()
440         grid.addWidget(QLabel(_('Description')), 2, 0)
441         grid.addWidget(self.message_e, 2, 1, 1, 3)
442
443         self.amount_e = QLineEdit()
444         grid.addWidget(QLabel(_('Amount')), 3, 0)
445         grid.addWidget(self.amount_e, 3, 1, 1, 2)
446         
447         self.nochange_cb = QCheckBox('Do not create change address')
448         grid.addWidget(self.nochange_cb,3,3)
449         self.nochange_cb.setChecked(False)
450         self.nochange_cb.setHidden(not self.wallet.expert_mode)
451
452         self.fee_e = QLineEdit()
453         grid.addWidget(QLabel(_('Fee')), 4, 0)
454         grid.addWidget(self.fee_e, 4, 1, 1, 2)
455         
456         b = EnterButton(_("Send"), self.do_send)
457         grid.addWidget(b, 5, 1)
458
459         b = EnterButton(_("Clear"),self.do_clear)
460         grid.addWidget(b, 5, 2)
461
462         self.payto_sig = QLabel('')
463         grid.addWidget(self.payto_sig, 6, 0, 1, 4)
464
465         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
466         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
467         w.setLayout(grid) 
468         w.show()
469
470         w2 = QWidget()
471         vbox = QVBoxLayout()
472         vbox.addWidget(w)
473         vbox.addStretch(1)
474         w2.setLayout(vbox)
475
476         def entry_changed( is_fee ):
477             self.funds_error = False
478             amount = numbify(self.amount_e)
479             fee = numbify(self.fee_e)
480             if not is_fee: fee = None
481             if amount is None:
482                 return
483             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
484             if not is_fee:
485                 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
486             if inputs:
487                 palette = QPalette()
488                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
489             else:
490                 palette = QPalette()
491                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
492                 self.funds_error = True
493             self.amount_e.setPalette(palette)
494             self.fee_e.setPalette(palette)
495
496         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
497         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
498
499         return w2
500
501
502     def update_completions(self):
503         l = []
504         for addr,label in self.wallet.labels.items():
505             if addr in self.wallet.addressbook:
506                 l.append( label + '  <' + addr + '>')
507         l = l + self.wallet.aliases.keys()
508
509         self.completions.setStringList(l)
510
511
512
513     def do_send(self):
514
515         label = unicode( self.message_e.text() )
516         r = unicode( self.payto_e.text() )
517         r = r.strip()
518
519         # alias
520         m1 = re.match(ALIAS_REGEXP, r)
521         # label or alias, with address in brackets
522         m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
523         
524         if m1:
525             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
526             if not to_address:
527                 return
528         elif m2:
529             to_address = m2.group(2)
530         else:
531             to_address = r
532
533         if not self.wallet.is_valid(to_address):
534             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
535             return
536
537         try:
538             amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
539         except:
540             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
541             return
542         try:
543             fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
544         except:
545             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
546             return
547
548         if self.wallet.use_encryption:
549             password = self.password_dialog()
550             if not password:
551                 return
552         else:
553             password = None
554
555         if self.nochange_cb.isChecked():
556             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
557             change_addr = inputs[0][0]
558             print "sending change to", change_addr
559         else:
560             change_addr = None
561
562         try:
563             tx = self.wallet.mktx( to_address, amount, label, password, fee, change_addr )
564         except BaseException, e:
565             self.show_message(str(e))
566             return
567             
568         status, msg = self.wallet.sendtx( tx )
569         if status:
570             QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
571             self.do_clear()
572             self.update_contacts_tab()
573         else:
574             QMessageBox.warning(self, _('Error'), msg, _('OK'))
575
576
577     def set_url(self, url):
578         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
579         self.tabs.setCurrentIndex(1)
580         label = self.wallet.labels.get(payto)
581         m_addr = label + '  <'+ payto+'>' if label else payto
582         self.payto_e.setText(m_addr)
583
584         self.message_e.setText(message)
585         self.amount_e.setText(amount)
586         if identity:
587             self.set_frozen(self.payto_e,True)
588             self.set_frozen(self.amount_e,True)
589             self.set_frozen(self.message_e,True)
590             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
591         else:
592             self.payto_sig.setVisible(False)
593
594     def do_clear(self):
595         self.payto_sig.setVisible(False)
596         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
597             e.setText('')
598             self.set_frozen(e,False)
599
600     def set_frozen(self,entry,frozen):
601         if frozen:
602             entry.setReadOnly(True)
603             entry.setFrame(False)
604             palette = QPalette()
605             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
606             entry.setPalette(palette)
607         else:
608             entry.setReadOnly(False)
609             entry.setFrame(True)
610             palette = QPalette()
611             palette.setColor(entry.backgroundRole(), QColor('white'))
612             entry.setPalette(palette)
613
614
615     def toggle_freeze(self,addr):
616         if not addr: return
617         if addr in self.wallet.frozen_addresses:
618             self.wallet.unfreeze(addr)
619         else:
620             self.wallet.freeze(addr)
621         self.update_receive_tab()
622
623     def toggle_priority(self,addr):
624         if not addr: return
625         if addr in self.wallet.prioritized_addresses:
626             self.wallet.unprioritize(addr)
627         else:
628             self.wallet.prioritize(addr)
629         self.update_receive_tab()
630
631
632     def create_list_tab(self, headers):
633         "generic tab creation method"
634         l = MyTreeWidget(self)
635         l.setColumnCount( len(headers) )
636         l.setHeaderLabels( headers )
637
638         w = QWidget()
639         vbox = QVBoxLayout()
640         w.setLayout(vbox)
641
642         vbox.setMargin(0)
643         vbox.setSpacing(0)
644         vbox.addWidget(l)
645         buttons = QWidget()
646         vbox.addWidget(buttons)
647
648         hbox = QHBoxLayout()
649         hbox.setMargin(0)
650         hbox.setSpacing(0)
651         buttons.setLayout(hbox)
652
653         return l,w,hbox
654
655
656     def create_receive_tab(self):
657         l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Balance'), _('Tx')])
658         l.setContextMenuPolicy(Qt.CustomContextMenu)
659         l.customContextMenuRequested.connect(self.create_receive_menu)
660         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
661         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
662         self.receive_list = l
663         self.receive_buttons_hbox = hbox
664         self.new_address_button = EnterButton(_("New"), self.change_gap_limit_dialog)
665         self.new_address_button.setHidden(not self.wallet.expert_mode)
666         hbox.addWidget(self.new_address_button)
667         hbox.addStretch(1)
668         return w
669
670
671     def create_contacts_tab(self):
672         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
673         l.setContextMenuPolicy(Qt.CustomContextMenu)
674         l.customContextMenuRequested.connect(self.create_contact_menu)
675         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
676         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
677         self.contacts_list = l
678         self.contacts_buttons_hbox = hbox
679         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
680         hbox.addStretch(1)
681         return w
682
683
684     def create_receive_menu(self, position):
685         # fixme: this function apparently has a side effect.
686         # if it is not called the menu pops up several times
687         #self.receive_list.selectedIndexes() 
688
689         item = self.receive_list.itemAt(position)
690         addr = unicode(item.text(1))
691         menu = QMenu()
692         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
693         menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
694         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
695         if self.wallet.expert_mode:
696             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
697             menu.addAction(t, lambda: self.toggle_freeze(addr))
698             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
699             menu.addAction(t, lambda: self.toggle_priority(addr))
700         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
701
702
703     def payto(self, x, is_alias):
704         if not x: return
705         if is_alias:
706             label = x
707             s, addr = self.wallet.aliases.get(x)
708         else:
709             addr = x
710             label = self.wallet.labels.get(addr)
711         m_addr = label + '  <' + addr + '>' if label else addr
712         self.tabs.setCurrentIndex(1)
713         self.payto_e.setText(m_addr)
714         self.amount_e.setFocus()
715
716     def delete_contact(self, x, is_alias):
717         if self.question("Do you want to remove %s from your list of contacts?"%x):
718             if not is_alias and x in self.wallet.addressbook:
719                 self.wallet.addressbook.remove(x)
720                 if x in self.wallet.labels.keys():
721                     self.wallet.labels.pop(x)
722             elif is_alias and x in self.wallet.aliases:
723                 self.wallet.aliases.pop(x)
724             self.update_history_tab()
725             self.update_contacts_tab()
726             self.update_completions()
727
728     def create_contact_menu(self, position):
729         # fixme: this function apparently has a side effect.
730         # if it is not called the menu pops up several times
731         #self.contacts_list.selectedIndexes() 
732
733         item = self.contacts_list.itemAt(position)
734         if not item: return
735         addr = unicode(item.text(0))
736         label = unicode(item.text(1))
737         is_alias = label in self.wallet.aliases.keys()
738         x = label if is_alias else addr
739         menu = QMenu()
740         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
741         menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
742         menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
743         if not is_alias:
744             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
745         else:
746             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
747         menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
748         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
749
750
751     def update_receive_tab(self):
752         l = self.receive_list
753         l.clear()
754         l.setColumnHidden(0,not self.wallet.expert_mode)
755         l.setColumnHidden(3,not self.wallet.expert_mode)
756         l.setColumnHidden(4,not self.wallet.expert_mode)
757         l.setColumnWidth(0, 50) 
758         l.setColumnWidth(1, 310) 
759         l.setColumnWidth(2, 250)
760         l.setColumnWidth(3, 130) 
761         l.setColumnWidth(4, 10)
762
763         self.new_address_button.setHidden(not self.wallet.expert_mode)
764         gap = 0
765         is_red = False
766         for address in self.wallet.all_addresses():
767
768             if self.wallet.is_change(address) and not self.wallet.expert_mode:
769                 continue
770
771             label = self.wallet.labels.get(address,'')
772             n = 0 
773             h = self.wallet.history.get(address,[])
774             for item in h:
775                 if not item['is_input'] : n=n+1
776
777             if n==0:
778                 tx = "None"
779                 if address in self.wallet.addresses:
780                     gap += 1
781                     if gap > self.wallet.gap_limit:
782                         is_red = True
783             else:
784                 tx = "%d"%n
785                 if address in self.wallet.addresses:
786                     gap = 0
787
788             c, u = self.wallet.get_addr_balance(address)
789             balance = format_satoshis( c + u, False, self.wallet.num_zeros )
790             flags = self.wallet.get_address_flags(address)
791             item = QTreeWidgetItem( [ flags, address, label, balance, tx] )
792
793             item.setFont(0, QFont(MONOSPACE_FONT))
794             item.setFont(1, QFont(MONOSPACE_FONT))
795             item.setFont(3, QFont(MONOSPACE_FONT))
796             if address in self.wallet.frozen_addresses: 
797                 item.setBackgroundColor(1, QColor('lightblue'))
798             elif address in self.wallet.prioritized_addresses: 
799                 item.setBackgroundColor(1, QColor('lightgreen'))
800             if is_red and address in self.wallet.addresses:
801                 item.setBackgroundColor(1, QColor('red'))
802             l.addTopLevelItem(item)
803
804         # we use column 1 because column 0 may be hidden
805         l.setCurrentItem(l.topLevelItem(0),1)
806
807     def show_contact_details(self, m):
808         a = self.wallet.aliases.get(m)
809         if a:
810             if a[0] in self.wallet.authorities.keys():
811                 s = self.wallet.authorities.get(a[0])
812             else:
813                 s = "self-signed"
814             msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
815             QMessageBox.information(self, 'Alias', msg, 'OK')
816
817     def update_contacts_tab(self):
818
819         l = self.contacts_list
820         l.clear()
821         l.setColumnHidden(2, not self.wallet.expert_mode)
822         l.setColumnWidth(0, 350) 
823         l.setColumnWidth(1, 330)
824         l.setColumnWidth(2, 100) 
825
826         alias_targets = []
827         for alias, v in self.wallet.aliases.items():
828             s, target = v
829             alias_targets.append(target)
830             item = QTreeWidgetItem( [ target, alias, '-'] )
831             item.setBackgroundColor(1, QColor('lightgray'))
832             l.addTopLevelItem(item)
833             
834         for address in self.wallet.addressbook:
835             if address in alias_targets: continue
836             label = self.wallet.labels.get(address,'')
837             n = 0 
838             for item in self.wallet.tx_history.values():
839                 if address in item['outputs'] : n=n+1
840             tx = "None" if n==0 else "%d"%n
841             item = QTreeWidgetItem( [ address, label, tx] )
842             item.setFont(0, QFont(MONOSPACE_FONT))
843             l.addTopLevelItem(item)
844
845         l.setCurrentItem(l.topLevelItem(0))
846
847     def create_wall_tab(self):
848         self.textbox = textbox = QTextEdit(self)
849         textbox.setFont(QFont(MONOSPACE_FONT))
850         textbox.setReadOnly(True)
851         return textbox
852
853     def create_status_bar(self):
854         sb = QStatusBar()
855         sb.setFixedHeight(35)
856         if self.wallet.seed:
857             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
858         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
859         if self.wallet.seed:
860             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
861         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) 
862         sb.addPermanentWidget( self.status_button )
863         self.setStatusBar(sb)
864
865     def new_contact_dialog(self):
866         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
867         address = unicode(text)
868         if ok:
869             if self.wallet.is_valid(address):
870                 self.wallet.addressbook.append(address)
871                 self.wallet.save()
872                 self.update_contacts_tab()
873                 self.update_history_tab()
874                 self.update_completions()
875             else:
876                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
877
878     @staticmethod
879     def show_seed_dialog(wallet, parent=None):
880
881         if not wallet.seed:
882             QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
883             return
884
885         if wallet.use_encryption:
886             password = parent.password_dialog()
887             if not password: return
888         else:
889             password = None
890             
891         try:
892             seed = wallet.pw_decode( wallet.seed, password)
893         except:
894             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
895             return
896
897         msg = _("Your wallet generation seed is") + ":\n\n" + seed + "\n\n"\
898               + _("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.") + "\n\n" \
899               + _("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code") + ":\n\n\"" \
900               + ' '.join(mnemonic.mn_encode(seed)) + "\"\n\n\n"
901
902         d = QDialog(None)
903         d.setModal(1)
904         d.setWindowTitle(_("Seed"))
905         d.setMinimumSize(400, 270)
906
907         vbox = QVBoxLayout()
908         hbox = QHBoxLayout()
909         vbox2 = QVBoxLayout()
910         l = QLabel()
911         l.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
912         vbox2.addWidget(l)
913         vbox2.addStretch(1)
914         hbox.addLayout(vbox2)
915         hbox.addWidget(QLabel(msg))
916         vbox.addLayout(hbox)
917
918         hbox = QHBoxLayout()
919         hbox.addStretch(1)
920
921
922         if parent:
923             app = parent.app
924         else:
925             app = QApplication
926
927         b = QPushButton(_("Copy to Clipboard"))
928         b.clicked.connect(lambda: app.clipboard().setText(seed + ' "' + ' '.join(mnemonic.mn_encode(seed))+'"'))
929         hbox.addWidget(b)
930         b = QPushButton(_("View as QR Code"))
931         b.clicked.connect(lambda: ElectrumWindow.show_seed_qrcode(seed))
932         hbox.addWidget(b)
933
934         b = QPushButton(_("OK"))
935         b.clicked.connect(d.accept)
936         hbox.addWidget(b)
937         vbox.addLayout(hbox)
938         d.setLayout(vbox)
939         d.exec_()
940
941     @staticmethod
942     def show_seed_qrcode(seed):
943         if not seed: return
944         d = QDialog(None)
945         d.setModal(1)
946         d.setWindowTitle(_("Seed"))
947         d.setMinimumSize(270, 300)
948         vbox = QVBoxLayout()
949         vbox.addWidget(QRCodeWidget(seed))
950         hbox = QHBoxLayout()
951         hbox.addStretch(1)
952         b = QPushButton(_("OK"))
953         hbox.addWidget(b)
954         b.clicked.connect(d.accept)
955
956         vbox.addLayout(hbox)
957         d.setLayout(vbox)
958         d.exec_()
959
960
961     def show_address_qrcode(self,address):
962         if not address: return
963         d = QDialog(self)
964         d.setModal(1)
965         d.setWindowTitle(address)
966         d.setMinimumSize(270, 350)
967         vbox = QVBoxLayout()
968         qrw = QRCodeWidget(address)
969         vbox.addWidget(qrw)
970
971         hbox = QHBoxLayout()
972         amount_e = QLineEdit()
973         hbox.addWidget(QLabel(_('Amount')))
974         hbox.addWidget(amount_e)
975         vbox.addLayout(hbox)
976
977         #hbox = QHBoxLayout()
978         #label_e = QLineEdit()
979         #hbox.addWidget(QLabel('Label'))
980         #hbox.addWidget(label_e)
981         #vbox.addLayout(hbox)
982
983         def amount_changed():
984             amount = numbify(amount_e)
985             #label = str( label_e.getText() )
986             if amount is not None:
987                 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
988             else:
989                 qrw.set_addr( address )
990             qrw.repaint()
991
992         def do_save():
993             bmp.save_qrcode(qrw.qr, "qrcode.bmp")
994             self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
995             
996         amount_e.textChanged.connect( amount_changed )
997
998         hbox = QHBoxLayout()
999         hbox.addStretch(1)
1000         b = QPushButton(_("Save"))
1001         b.clicked.connect(do_save)
1002         hbox.addWidget(b)
1003         b = QPushButton(_("Close"))
1004         hbox.addWidget(b)
1005         b.clicked.connect(d.accept)
1006
1007         vbox.addLayout(hbox)
1008         d.setLayout(vbox)
1009         d.exec_()
1010
1011     def question(self, msg):
1012         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1013
1014     def show_message(self, msg):
1015         QMessageBox.information(self, _('Message'), msg, _('OK'))
1016
1017     def password_dialog(self ):
1018         d = QDialog(self)
1019         d.setModal(1)
1020
1021         pw = QLineEdit()
1022         pw.setEchoMode(2)
1023
1024         vbox = QVBoxLayout()
1025         msg = _('Please enter your password')
1026         vbox.addWidget(QLabel(msg))
1027
1028         grid = QGridLayout()
1029         grid.setSpacing(8)
1030         grid.addWidget(QLabel(_('Password')), 1, 0)
1031         grid.addWidget(pw, 1, 1)
1032         vbox.addLayout(grid)
1033
1034         vbox.addLayout(ok_cancel_buttons(d))
1035         d.setLayout(vbox) 
1036
1037         if not d.exec_(): return
1038         return unicode(pw.text())
1039
1040
1041     def change_gap_limit_dialog(self):
1042         d = QDialog(self)
1043         d.setModal(1)
1044
1045         vbox = QVBoxLayout()
1046         
1047         msg = _('In order to create more addresses, you need to raise your gap limit.') + '\n' \
1048             + _('The gap limit is the maximal number of contiguous unused addresses in your wallet.') + '\n\n' \
1049             + _('Warning:') + '\n' \
1050             + _('This parameter must be provided in order to recover your wallet from seed.') + '\n' \
1051             + _('Do not modify it if you do not understand what you are doing!!!') + '\n\n' \
1052             + _('Your current gap limit is: ') + '%d'%self.wallet.gap_limit + '\n' \
1053             + _('The minimum for this wallet is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n' 
1054
1055         vbox.addWidget(QLabel(msg))
1056
1057         grid = QGridLayout()
1058         grid.setSpacing(8)
1059         grid.addWidget(QLabel(_('New gap limit: ')), 1, 0)
1060
1061         e = QLineEdit()
1062         grid.addWidget(e, 1, 1)
1063         vbox.addLayout(grid)
1064
1065         vbox.addLayout(ok_cancel_buttons(d))
1066
1067         d.setLayout(vbox) 
1068
1069         if not d.exec_(): return
1070         try:
1071             n = int(e.text())
1072         except:
1073             QMessageBox.warning(self, _('Error'), _('Invalid Value'), _('OK'))
1074             return
1075
1076         r = self.wallet.change_gap_limit(n)
1077         if r:
1078             self.update_receive_tab()
1079         else:
1080             QMessageBox.warning(self, _('Error'), _('Invalid Value'), _('OK'))
1081
1082
1083
1084
1085
1086     @staticmethod
1087     def change_password_dialog( wallet, parent=None ):
1088
1089         if not wallet.seed:
1090             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1091             return
1092
1093         d = QDialog(parent)
1094         d.setModal(1)
1095
1096         pw = QLineEdit()
1097         pw.setEchoMode(2)
1098         new_pw = QLineEdit()
1099         new_pw.setEchoMode(2)
1100         conf_pw = QLineEdit()
1101         conf_pw.setEchoMode(2)
1102
1103         vbox = QVBoxLayout()
1104         if parent:
1105             msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'+_('To disable wallet encryption, enter an empty new password.')) if wallet.use_encryption else _('Your wallet keys are not encrypted')
1106         else:
1107             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1108         vbox.addWidget(QLabel(msg))
1109
1110         grid = QGridLayout()
1111         grid.setSpacing(8)
1112
1113         if wallet.use_encryption:
1114             grid.addWidget(QLabel(_('Password')), 1, 0)
1115             grid.addWidget(pw, 1, 1)
1116
1117         grid.addWidget(QLabel(_('New Password')), 2, 0)
1118         grid.addWidget(new_pw, 2, 1)
1119
1120         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1121         grid.addWidget(conf_pw, 3, 1)
1122         vbox.addLayout(grid)
1123
1124         vbox.addLayout(ok_cancel_buttons(d))
1125         d.setLayout(vbox) 
1126
1127         if not d.exec_(): return
1128
1129         password = unicode(pw.text()) if wallet.use_encryption else None
1130         new_password = unicode(new_pw.text())
1131         new_password2 = unicode(conf_pw.text())
1132
1133         try:
1134             seed = wallet.pw_decode( wallet.seed, password)
1135         except:
1136             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1137             return
1138
1139         if new_password != new_password2:
1140             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1141             return
1142
1143         wallet.update_password(seed, password, new_password)
1144
1145     @staticmethod
1146     def seed_dialog(wallet, parent=None):
1147         d = QDialog(parent)
1148         d.setModal(1)
1149
1150         vbox = QVBoxLayout()
1151         msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1152         vbox.addWidget(QLabel(msg))
1153
1154         grid = QGridLayout()
1155         grid.setSpacing(8)
1156
1157         seed_e = QLineEdit()
1158         grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1159         grid.addWidget(seed_e, 1, 1)
1160
1161         gap_e = QLineEdit()
1162         gap_e.setText("5")
1163         grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1164         grid.addWidget(gap_e, 2, 1)
1165         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1166         vbox.addLayout(grid)
1167
1168         vbox.addLayout(ok_cancel_buttons(d))
1169         d.setLayout(vbox) 
1170
1171         if not d.exec_(): return
1172
1173         try:
1174             gap = int(unicode(gap_e.text()))
1175         except:
1176             QMessageBox.warning(None, _('Error'), 'error', 'OK')
1177             sys.exit(0)
1178
1179         try:
1180             seed = unicode(seed_e.text())
1181             seed.decode('hex')
1182         except:
1183             print "not hex, trying decode"
1184             try:
1185                 seed = mnemonic.mn_decode( seed.split(' ') )
1186             except:
1187                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1188                 sys.exit(0)
1189         if not seed:
1190             QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1191             sys.exit(0)
1192         
1193         wallet.seed = str(seed)
1194         #print repr(wallet.seed)
1195         wallet.gap_limit = gap
1196         return True
1197
1198
1199     def set_expert_mode(self, b):
1200         self.wallet.expert_mode = b
1201         self.wallet.save()
1202         self.update_receive_tab()
1203         self.update_contacts_tab()
1204         self.nochange_cb.setHidden(not self.wallet.expert_mode)
1205         
1206
1207
1208     def settings_dialog(self):
1209         d = QDialog(self)
1210         d.setModal(1)
1211
1212         vbox = QVBoxLayout()
1213
1214         msg = _('Here are the settings of your wallet.')
1215         vbox.addWidget(QLabel(msg))
1216
1217         grid = QGridLayout()
1218         grid.setSpacing(8)
1219         vbox.addLayout(grid)
1220
1221         fee_e = QLineEdit()
1222         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1223         grid.addWidget(QLabel(_('Fee per tx. input')), 2, 0)
1224         grid.addWidget(fee_e, 2, 1)
1225         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1226
1227         nz_e = QLineEdit()
1228         nz_e.setText("%d"% self.wallet.num_zeros)
1229         grid.addWidget(QLabel(_('Zeros displayed after decimal point')), 3, 0)
1230         grid.addWidget(nz_e, 3, 1)
1231         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1232
1233         cb = QCheckBox('Expert mode')
1234         grid.addWidget(cb,4,0)
1235         cb.setChecked(self.wallet.expert_mode)
1236
1237         vbox.addLayout(ok_cancel_buttons(d))
1238         d.setLayout(vbox) 
1239
1240         if not d.exec_(): return
1241
1242         self.set_expert_mode(cb.isChecked())
1243
1244         fee = unicode(fee_e.text())
1245         try:
1246             fee = int( 100000000 * Decimal(fee) )
1247         except:
1248             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1249             return
1250
1251         if self.wallet.fee != fee:
1252             self.wallet.fee = fee
1253             self.wallet.save()
1254         
1255         nz = unicode(nz_e.text())
1256         try:
1257             nz = int( nz )
1258             if nz>8: nz=8
1259         except:
1260             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1261             return
1262
1263         if self.wallet.num_zeros != nz:
1264             self.wallet.num_zeros = nz
1265             self.update_history_tab()
1266             self.update_receive_tab()
1267             self.wallet.save()
1268
1269     @staticmethod 
1270     def network_dialog(wallet, parent=None):
1271         interface = wallet.interface
1272         if parent:
1273             if interface.is_connected:
1274                 status = _("Connected to")+" %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks)
1275             else:
1276                 status = _("Not connected")
1277             server = wallet.server
1278         else:
1279             import random
1280             status = _("Please choose a server.")
1281             server = random.choice( DEFAULT_SERVERS )
1282
1283         if not wallet.interface.servers:
1284             servers_list = []
1285             for x in DEFAULT_SERVERS:
1286                 h,port,protocol = x.split(':')
1287                 servers_list.append( (h,[(protocol,port)] ) )
1288         else:
1289             servers_list = wallet.interface.servers
1290             
1291         plist = {}
1292         for item in servers_list:
1293             host, pp = item
1294             z = {}
1295             for item2 in pp:
1296                 protocol, port = item2
1297                 z[protocol] = port
1298             plist[host] = z
1299
1300         d = QDialog(parent)
1301         d.setModal(1)
1302         d.setWindowTitle(_('Server'))
1303         d.setMinimumSize(375, 20)
1304
1305         vbox = QVBoxLayout()
1306         vbox.setSpacing(20)
1307
1308         hbox = QHBoxLayout()
1309         l = QLabel()
1310         l.setPixmap(QPixmap(":icons/network.png"))
1311         hbox.addWidget(l)        
1312         hbox.addWidget(QLabel(status))
1313
1314         vbox.addLayout(hbox)
1315
1316         hbox = QHBoxLayout()
1317         host_line = QLineEdit()
1318         host_line.setText(server)
1319         hbox.addWidget(QLabel(_('Connect to') + ':'))
1320         hbox.addWidget(host_line)
1321         vbox.addLayout(hbox)
1322
1323         hbox = QHBoxLayout()
1324
1325         buttonGroup = QGroupBox(_("Protocol"))
1326         radio1 = QRadioButton("tcp", buttonGroup)
1327         radio2 = QRadioButton("http", buttonGroup)
1328
1329         def current_line():
1330             return unicode(host_line.text()).split(':')
1331             
1332         def set_button(protocol):
1333             if protocol == 't':
1334                 radio1.setChecked(1)
1335             elif protocol == 'h':
1336                 radio2.setChecked(1)
1337
1338         def set_protocol(protocol):
1339             host = current_line()[0]
1340             pp = plist[host]
1341             if protocol not in pp.keys():
1342                 protocol = pp.keys()[0]
1343                 set_button(protocol)
1344             port = pp[protocol]
1345             host_line.setText( host + ':' + port + ':' + protocol)
1346
1347         radio1.clicked.connect(lambda x: set_protocol('t') )
1348         radio2.clicked.connect(lambda x: set_protocol('h') )
1349
1350         set_button(current_line()[2])
1351
1352         hbox.addWidget(QLabel(_('Protocol')+':'))
1353         hbox.addWidget(radio1)
1354         hbox.addWidget(radio2)
1355
1356         vbox.addLayout(hbox)
1357
1358         if wallet.interface.servers:
1359             label = _('Active Servers')
1360         else:
1361             label = _('Default Servers')
1362         
1363         servers_list_widget = QTreeWidget(parent)
1364         servers_list_widget.setHeaderLabels( [ label ] )
1365         servers_list_widget.setMaximumHeight(150)
1366         for host in plist.keys():
1367             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] ))
1368
1369         def do_set_line(x):
1370             host = unicode(x.text(0))
1371             pp = plist[host]
1372             if 't' in pp.keys():
1373                 protocol = 't'
1374             else:
1375                 protocol = pp.keys()[0]
1376             port = pp[protocol]
1377             host_line.setText( host + ':' + port + ':' + protocol)
1378             set_button(protocol)
1379
1380         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line)
1381         vbox.addWidget(servers_list_widget)
1382
1383         vbox.addLayout(ok_cancel_buttons(d))
1384         d.setLayout(vbox) 
1385
1386         if not d.exec_(): return
1387         server = unicode( host_line.text() )
1388
1389         try:
1390             wallet.set_server(server)
1391         except:
1392             QMessageBox.information(None, _('Error'), 'error', _('OK'))
1393             if parent == None:
1394                 sys.exit(1)
1395             else:
1396                 return
1397
1398         return True
1399
1400
1401
1402 class ElectrumGui():
1403
1404     def __init__(self, wallet):
1405         self.wallet = wallet
1406         self.app = QApplication(sys.argv)
1407
1408     def waiting_dialog(self):
1409
1410         s = Timer()
1411         s.start()
1412         w = QDialog()
1413         w.resize(200, 70)
1414         w.setWindowTitle('Electrum')
1415         l = QLabel('')
1416         vbox = QVBoxLayout()
1417         vbox.addWidget(l)
1418         w.setLayout(vbox)
1419         w.show()
1420         def f():
1421             if self.wallet.up_to_date: 
1422                 w.close()
1423             else:
1424                 l.setText("Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1425                               %(len(self.wallet.all_addresses()), self.wallet.interface.bytes_received/1024.))
1426
1427         w.connect(s, QtCore.SIGNAL('timersignal'), f)
1428         self.wallet.interface.poke()
1429         w.exec_()
1430         w.destroy()
1431
1432
1433     def restore_or_create(self):
1434
1435         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1436         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1437         if r==2: return False
1438         
1439         is_recovery = (r==1)
1440         wallet = self.wallet
1441         # ask for the server.
1442         if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1443
1444         if not is_recovery:
1445             wallet.new_seed(None)
1446             wallet.init_mpk( wallet.seed )
1447             wallet.up_to_date_event.clear()
1448             wallet.up_to_date = False
1449             self.waiting_dialog()
1450             # run a dialog indicating the seed, ask the user to remember it
1451             ElectrumWindow.show_seed_dialog(wallet)
1452             #ask for password
1453             ElectrumWindow.change_password_dialog(wallet)
1454         else:
1455             # ask for seed and gap.
1456             if not ElectrumWindow.seed_dialog( wallet ): return False
1457             wallet.init_mpk( wallet.seed )
1458             wallet.up_to_date_event.clear()
1459             wallet.up_to_date = False
1460             self.waiting_dialog()
1461             if wallet.is_found():
1462                 # history and addressbook
1463                 wallet.update_tx_history()
1464                 wallet.fill_addressbook()
1465                 print "recovery successful"
1466                 wallet.save()
1467             else:
1468                 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1469
1470         wallet.save()
1471         return True
1472
1473     def main(self,url):
1474         s = Timer()
1475         s.start()
1476         w = ElectrumWindow(self.wallet)
1477         if url: w.set_url(url)
1478         w.app = self.app
1479         w.connect_slots(s)
1480         w.update_wallet()
1481
1482         self.app.exec_()