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