nofocus for help buttons
[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 HelpButton(QPushButton):
81     def __init__(self, text):
82         QPushButton.__init__(self, '?')
83         self.setFocusPolicy(Qt.NoFocus)
84         self.setFixedWidth(20)
85         self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
86
87
88 class EnterButton(QPushButton):
89     def __init__(self, text, func):
90         QPushButton.__init__(self, text)
91         self.func = func
92         self.clicked.connect(func)
93
94     def keyPressEvent(self, e):
95         if e.key() == QtCore.Qt.Key_Return:
96             apply(self.func,())
97
98 class MyTreeWidget(QTreeWidget):
99     def __init__(self, parent):
100         QTreeWidget.__init__(self, parent)
101         def ddfr(item):
102             if not item: return
103             for i in range(0,self.viewport().height()/5):
104                 if self.itemAt(QPoint(0,i*5)) == item:
105                     break
106             else:
107                 return
108             for j in range(0,30):
109                 if self.itemAt(QPoint(0,i*5 + j)) != item:
110                     break
111             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
112
113         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
114         
115
116
117
118 class StatusBarButton(QPushButton):
119     def __init__(self, icon, tooltip, func):
120         QPushButton.__init__(self, icon, '')
121         self.setToolTip(tooltip)
122         self.setFlat(True)
123         self.setMaximumWidth(25)
124         self.clicked.connect(func)
125         self.func = func
126
127     def keyPressEvent(self, e):
128         if e.key() == QtCore.Qt.Key_Return:
129             apply(self.func,())
130
131
132 class QRCodeWidget(QWidget):
133
134     def __init__(self, addr):
135         super(QRCodeWidget, self).__init__()
136         self.setGeometry(300, 300, 350, 350)
137         self.set_addr(addr)
138
139     def set_addr(self, addr):
140         self.addr = addr
141         self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
142         self.qr.addData(addr)
143         self.qr.make()
144         
145     def paintEvent(self, e):
146         qp = QtGui.QPainter()
147         qp.begin(self)
148         boxsize = 7
149         size = self.qr.getModuleCount()*boxsize
150         k = self.qr.getModuleCount()
151         black = QColor(0, 0, 0, 255)
152         white = QColor(255, 255, 255, 255)
153         for r in range(k):
154             for c in range(k):
155                 if self.qr.isDark(r, c):
156                     qp.setBrush(black)
157                     qp.setPen(black)
158                 else:
159                     qp.setBrush(white)
160                     qp.setPen(white)
161                 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
162         qp.end()
163         
164
165
166 def ok_cancel_buttons(dialog):
167     hbox = QHBoxLayout()
168     hbox.addStretch(1)
169     b = QPushButton("OK")
170     hbox.addWidget(b)
171     b.clicked.connect(dialog.accept)
172     b = QPushButton("Cancel")
173     hbox.addWidget(b)
174     b.clicked.connect(dialog.reject)
175     return hbox
176
177
178 class ElectrumWindow(QMainWindow):
179
180     def __init__(self, wallet):
181         QMainWindow.__init__(self)
182         self.wallet = wallet
183         self.wallet.gui_callback = self.update_callback
184
185         self.funds_error = False
186         self.completions = QStringListModel()
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         if not item: return
292         tx_hash = str(item.toolTip(0))
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(5,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         grid.addWidget(HelpButton('Recipient of the funds.\n\nYou may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)'), 1, 4)
434
435         completer = QCompleter()
436         completer.setCaseSensitivity(False)
437         self.payto_e.setCompleter(completer)
438         completer.setModel(self.completions)
439
440         self.message_e = QLineEdit()
441         grid.addWidget(QLabel(_('Description')), 2, 0)
442         grid.addWidget(self.message_e, 2, 1, 1, 3)
443         grid.addWidget(HelpButton('Description of the transaction (not mandatory).\n\nThe description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.'), 2, 4)
444
445         self.amount_e = QLineEdit()
446         grid.addWidget(QLabel(_('Amount')), 3, 0)
447         grid.addWidget(self.amount_e, 3, 1, 1, 2)
448         grid.addWidget(HelpButton('Amount to be sent.\n\nThe amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.'), 3, 3)
449         
450         self.nochange_cb = QCheckBox('Do not create change address')
451         grid.addWidget(self.nochange_cb,3,3)
452         self.nochange_cb.setChecked(False)
453         self.nochange_cb.setHidden(not self.wallet.expert_mode)
454
455         self.fee_e = QLineEdit()
456         grid.addWidget(QLabel(_('Fee')), 4, 0)
457         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
458         grid.addWidget(HelpButton('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.\n\nThe amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.\n\nThe fee suggested by this software increases with the size of the transaction.'), 4, 3)
459        
460         b = EnterButton(_("Send"), self.do_send)
461         grid.addWidget(b, 5, 1)
462
463         b = EnterButton(_("Clear"),self.do_clear)
464         grid.addWidget(b, 5, 2)
465
466         self.payto_sig = QLabel('')
467         grid.addWidget(self.payto_sig, 6, 0, 1, 4)
468
469         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
470         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
471         w.setLayout(grid) 
472         w.show()
473
474         w2 = QWidget()
475         vbox = QVBoxLayout()
476         vbox.addWidget(w)
477         vbox.addStretch(1)
478         w2.setLayout(vbox)
479
480         def entry_changed( is_fee ):
481             self.funds_error = False
482             amount = numbify(self.amount_e)
483             fee = numbify(self.fee_e)
484             if not is_fee: fee = None
485             if amount is None:
486                 return
487             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
488             if not is_fee:
489                 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
490             if inputs:
491                 palette = QPalette()
492                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
493             else:
494                 palette = QPalette()
495                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
496                 self.funds_error = True
497             self.amount_e.setPalette(palette)
498             self.fee_e.setPalette(palette)
499
500         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
501         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
502
503         return w2
504
505
506     def update_completions(self):
507         l = []
508         for addr,label in self.wallet.labels.items():
509             if addr in self.wallet.addressbook:
510                 l.append( label + '  <' + addr + '>')
511         l = l + self.wallet.aliases.keys()
512
513         self.completions.setStringList(l)
514
515
516
517     def do_send(self):
518
519         label = unicode( self.message_e.text() )
520         r = unicode( self.payto_e.text() )
521         r = r.strip()
522
523         # alias
524         m1 = re.match(ALIAS_REGEXP, r)
525         # label or alias, with address in brackets
526         m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
527         
528         if m1:
529             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
530             if not to_address:
531                 return
532         elif m2:
533             to_address = m2.group(2)
534         else:
535             to_address = r
536
537         if not self.wallet.is_valid(to_address):
538             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
539             return
540
541         try:
542             amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
543         except:
544             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
545             return
546         try:
547             fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
548         except:
549             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
550             return
551
552         if self.wallet.use_encryption:
553             password = self.password_dialog()
554             if not password:
555                 return
556         else:
557             password = None
558
559         if self.nochange_cb.isChecked():
560             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
561             change_addr = inputs[0][0]
562             print "sending change to", change_addr
563         else:
564             change_addr = None
565
566         try:
567             tx = self.wallet.mktx( to_address, amount, label, password, fee, change_addr )
568         except BaseException, e:
569             self.show_message(str(e))
570             return
571             
572         status, msg = self.wallet.sendtx( tx )
573         if status:
574             QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
575             self.do_clear()
576             self.update_contacts_tab()
577         else:
578             QMessageBox.warning(self, _('Error'), msg, _('OK'))
579
580
581     def set_url(self, url):
582         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
583         self.tabs.setCurrentIndex(1)
584         label = self.wallet.labels.get(payto)
585         m_addr = label + '  <'+ payto+'>' if label else payto
586         self.payto_e.setText(m_addr)
587
588         self.message_e.setText(message)
589         self.amount_e.setText(amount)
590         if identity:
591             self.set_frozen(self.payto_e,True)
592             self.set_frozen(self.amount_e,True)
593             self.set_frozen(self.message_e,True)
594             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
595         else:
596             self.payto_sig.setVisible(False)
597
598     def do_clear(self):
599         self.payto_sig.setVisible(False)
600         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
601             e.setText('')
602             self.set_frozen(e,False)
603
604     def set_frozen(self,entry,frozen):
605         if frozen:
606             entry.setReadOnly(True)
607             entry.setFrame(False)
608             palette = QPalette()
609             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
610             entry.setPalette(palette)
611         else:
612             entry.setReadOnly(False)
613             entry.setFrame(True)
614             palette = QPalette()
615             palette.setColor(entry.backgroundRole(), QColor('white'))
616             entry.setPalette(palette)
617
618
619     def toggle_freeze(self,addr):
620         if not addr: return
621         if addr in self.wallet.frozen_addresses:
622             self.wallet.unfreeze(addr)
623         else:
624             self.wallet.freeze(addr)
625         self.update_receive_tab()
626
627     def toggle_priority(self,addr):
628         if not addr: return
629         if addr in self.wallet.prioritized_addresses:
630             self.wallet.unprioritize(addr)
631         else:
632             self.wallet.prioritize(addr)
633         self.update_receive_tab()
634
635
636     def create_list_tab(self, headers):
637         "generic tab creation method"
638         l = MyTreeWidget(self)
639         l.setColumnCount( len(headers) )
640         l.setHeaderLabels( headers )
641
642         w = QWidget()
643         vbox = QVBoxLayout()
644         w.setLayout(vbox)
645
646         vbox.setMargin(0)
647         vbox.setSpacing(0)
648         vbox.addWidget(l)
649         buttons = QWidget()
650         vbox.addWidget(buttons)
651
652         hbox = QHBoxLayout()
653         hbox.setMargin(0)
654         hbox.setSpacing(0)
655         buttons.setLayout(hbox)
656
657         return l,w,hbox
658
659
660     def create_receive_tab(self):
661         l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Balance'), _('Tx')])
662         l.setContextMenuPolicy(Qt.CustomContextMenu)
663         l.customContextMenuRequested.connect(self.create_receive_menu)
664         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
665         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
666         self.receive_list = l
667         self.receive_buttons_hbox = hbox
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         if not item: return
691         addr = unicode(item.text(1))
692         menu = QMenu()
693         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
694         menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
695         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
696         if self.wallet.expert_mode:
697             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
698             menu.addAction(t, lambda: self.toggle_freeze(addr))
699             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
700             menu.addAction(t, lambda: self.toggle_priority(addr))
701         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
702
703
704     def payto(self, x, is_alias):
705         if not x: return
706         if is_alias:
707             label = x
708             m_addr = label
709         else:
710             addr = x
711             label = self.wallet.labels.get(addr)
712             m_addr = label + '  <' + addr + '>' if label else addr
713         self.tabs.setCurrentIndex(1)
714         self.payto_e.setText(m_addr)
715         self.amount_e.setFocus()
716
717     def delete_contact(self, x, is_alias):
718         if self.question("Do you want to remove %s from your list of contacts?"%x):
719             if not is_alias and x in self.wallet.addressbook:
720                 self.wallet.addressbook.remove(x)
721                 if x in self.wallet.labels.keys():
722                     self.wallet.labels.pop(x)
723             elif is_alias and x in self.wallet.aliases:
724                 self.wallet.aliases.pop(x)
725             self.update_history_tab()
726             self.update_contacts_tab()
727             self.update_completions()
728
729     def create_contact_menu(self, position):
730         # fixme: this function apparently has a side effect.
731         # if it is not called the menu pops up several times
732         #self.contacts_list.selectedIndexes() 
733
734         item = self.contacts_list.itemAt(position)
735         if not item: return
736         addr = unicode(item.text(0))
737         label = unicode(item.text(1))
738         is_alias = label in self.wallet.aliases.keys()
739         x = label if is_alias else addr
740         menu = QMenu()
741         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
742         menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
743         menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
744         if not is_alias:
745             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
746         else:
747             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
748         menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
749         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
750
751
752     def update_receive_tab(self):
753         l = self.receive_list
754         l.clear()
755         l.setColumnHidden(0,not self.wallet.expert_mode)
756         l.setColumnHidden(3,not self.wallet.expert_mode)
757         l.setColumnHidden(4,not self.wallet.expert_mode)
758         l.setColumnWidth(0, 50) 
759         l.setColumnWidth(1, 310) 
760         l.setColumnWidth(2, 250)
761         l.setColumnWidth(3, 130) 
762         l.setColumnWidth(4, 10)
763
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(0, 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
1042
1043
1044     @staticmethod
1045     def change_password_dialog( wallet, parent=None ):
1046
1047         if not wallet.seed:
1048             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1049             return
1050
1051         d = QDialog(parent)
1052         d.setModal(1)
1053
1054         pw = QLineEdit()
1055         pw.setEchoMode(2)
1056         new_pw = QLineEdit()
1057         new_pw.setEchoMode(2)
1058         conf_pw = QLineEdit()
1059         conf_pw.setEchoMode(2)
1060
1061         vbox = QVBoxLayout()
1062         if parent:
1063             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')
1064         else:
1065             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1066         vbox.addWidget(QLabel(msg))
1067
1068         grid = QGridLayout()
1069         grid.setSpacing(8)
1070
1071         if wallet.use_encryption:
1072             grid.addWidget(QLabel(_('Password')), 1, 0)
1073             grid.addWidget(pw, 1, 1)
1074
1075         grid.addWidget(QLabel(_('New Password')), 2, 0)
1076         grid.addWidget(new_pw, 2, 1)
1077
1078         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1079         grid.addWidget(conf_pw, 3, 1)
1080         vbox.addLayout(grid)
1081
1082         vbox.addLayout(ok_cancel_buttons(d))
1083         d.setLayout(vbox) 
1084
1085         if not d.exec_(): return
1086
1087         password = unicode(pw.text()) if wallet.use_encryption else None
1088         new_password = unicode(new_pw.text())
1089         new_password2 = unicode(conf_pw.text())
1090
1091         try:
1092             seed = wallet.pw_decode( wallet.seed, password)
1093         except:
1094             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1095             return
1096
1097         if new_password != new_password2:
1098             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1099             return
1100
1101         wallet.update_password(seed, password, new_password)
1102
1103     @staticmethod
1104     def seed_dialog(wallet, parent=None):
1105         d = QDialog(parent)
1106         d.setModal(1)
1107
1108         vbox = QVBoxLayout()
1109         msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1110         vbox.addWidget(QLabel(msg))
1111
1112         grid = QGridLayout()
1113         grid.setSpacing(8)
1114
1115         seed_e = QLineEdit()
1116         grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1117         grid.addWidget(seed_e, 1, 1)
1118
1119         gap_e = QLineEdit()
1120         gap_e.setText("5")
1121         grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1122         grid.addWidget(gap_e, 2, 1)
1123         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1124         vbox.addLayout(grid)
1125
1126         vbox.addLayout(ok_cancel_buttons(d))
1127         d.setLayout(vbox) 
1128
1129         if not d.exec_(): return
1130
1131         try:
1132             gap = int(unicode(gap_e.text()))
1133         except:
1134             QMessageBox.warning(None, _('Error'), 'error', 'OK')
1135             sys.exit(0)
1136
1137         try:
1138             seed = unicode(seed_e.text())
1139             seed.decode('hex')
1140         except:
1141             print "not hex, trying decode"
1142             try:
1143                 seed = mnemonic.mn_decode( seed.split(' ') )
1144             except:
1145                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1146                 sys.exit(0)
1147         if not seed:
1148             QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1149             sys.exit(0)
1150         
1151         wallet.seed = str(seed)
1152         #print repr(wallet.seed)
1153         wallet.gap_limit = gap
1154         return True
1155
1156
1157     def set_expert_mode(self, b):
1158         self.wallet.expert_mode = b
1159         self.wallet.save()
1160         self.update_receive_tab()
1161         self.update_contacts_tab()
1162         self.nochange_cb.setHidden(not self.wallet.expert_mode)
1163         
1164
1165     def settings_dialog(self):
1166         d = QDialog(self)
1167         d.setModal(1)
1168         vbox = QVBoxLayout()
1169         msg = _('Here are the settings of your wallet.') + '\n'\
1170               + _('For more explanations, click on the help buttons next to each field.')
1171
1172         label = QLabel(msg)
1173         label.setFixedWidth(250)
1174         label.setWordWrap(True)
1175         label.setAlignment(Qt.AlignJustify)
1176         vbox.addWidget(label)
1177
1178         grid = QGridLayout()
1179         grid.setSpacing(8)
1180         vbox.addLayout(grid)
1181
1182         fee_e = QLineEdit()
1183         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1184         grid.addWidget(QLabel(_('Transaction fee')), 2, 0)
1185         grid.addWidget(fee_e, 2, 1)
1186         grid.addWidget(HelpButton('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee. Recommended value: 0.001'), 2, 2)
1187         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1188
1189         nz_e = QLineEdit()
1190         nz_e.setText("%d"% self.wallet.num_zeros)
1191         grid.addWidget(QLabel(_('Display zeros')), 3, 0)
1192         grid.addWidget(HelpButton('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"'), 3, 2)
1193         grid.addWidget(nz_e, 3, 1)
1194         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1195
1196         if self.wallet.expert_mode:
1197             msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1198                   + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1199                   + _('Your current gap limit is: ') + '%d'%self.wallet.gap_limit + '\n' \
1200                   + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1201                   + _('Warning:') + ' ' \
1202                   + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1203                   + _('Do not modify it if you do not understand what you are doing, or if you expect to recover your wallet without knowing it!') + '\n\n' 
1204             gap_e = QLineEdit()
1205             gap_e.setText("%d"% self.wallet.gap_limit)
1206             grid.addWidget(QLabel(_('Gap limit')), 4, 0)
1207             grid.addWidget(gap_e, 4, 1)
1208             grid.addWidget(HelpButton(msg), 4, 2)
1209             gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1210
1211         cb = QCheckBox('Expert mode')
1212         grid.addWidget(cb, 5, 0)
1213         cb.setChecked(self.wallet.expert_mode)
1214         
1215         vbox.addLayout(ok_cancel_buttons(d))
1216         d.setLayout(vbox) 
1217
1218         # run the dialog
1219         if not d.exec_(): return
1220
1221         fee = unicode(fee_e.text())
1222         try:
1223             fee = int( 100000000 * Decimal(fee) )
1224         except:
1225             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1226             return
1227
1228         if self.wallet.fee != fee:
1229             self.wallet.fee = fee
1230             self.wallet.save()
1231         
1232         nz = unicode(nz_e.text())
1233         try:
1234             nz = int( nz )
1235             if nz>8: nz=8
1236         except:
1237             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1238             return
1239
1240         if self.wallet.num_zeros != nz:
1241             self.wallet.num_zeros = nz
1242             self.update_history_tab()
1243             self.update_receive_tab()
1244             self.wallet.save()
1245
1246         if self.wallet.expert_mode:
1247             try:
1248                 n = int(gap_e.text())
1249             except:
1250                 QMessageBox.warning(self, _('Error'), _('Invalid Value'), _('OK'))
1251                 return
1252             if self.wallet.gap_limit != n:
1253                 r = self.wallet.change_gap_limit(n)
1254                 if r:
1255                     self.update_receive_tab()
1256                 else:
1257                     QMessageBox.warning(self, _('Error'), _('Invalid Value'), _('OK'))
1258
1259         self.set_expert_mode(cb.isChecked())
1260
1261
1262     @staticmethod 
1263     def network_dialog(wallet, parent=None):
1264         interface = wallet.interface
1265         if parent:
1266             if interface.is_connected:
1267                 status = _("Connected to")+" %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks)
1268             else:
1269                 status = _("Not connected")
1270             server = wallet.server
1271         else:
1272             import random
1273             status = _("Please choose a server.")
1274             server = random.choice( DEFAULT_SERVERS )
1275
1276         if not wallet.interface.servers:
1277             servers_list = []
1278             for x in DEFAULT_SERVERS:
1279                 h,port,protocol = x.split(':')
1280                 servers_list.append( (h,[(protocol,port)] ) )
1281         else:
1282             servers_list = wallet.interface.servers
1283             
1284         plist = {}
1285         for item in servers_list:
1286             host, pp = item
1287             z = {}
1288             for item2 in pp:
1289                 protocol, port = item2
1290                 z[protocol] = port
1291             plist[host] = z
1292
1293         d = QDialog(parent)
1294         d.setModal(1)
1295         d.setWindowTitle(_('Server'))
1296         d.setMinimumSize(375, 20)
1297
1298         vbox = QVBoxLayout()
1299         vbox.setSpacing(20)
1300
1301         hbox = QHBoxLayout()
1302         l = QLabel()
1303         l.setPixmap(QPixmap(":icons/network.png"))
1304         hbox.addWidget(l)        
1305         hbox.addWidget(QLabel(status))
1306
1307         vbox.addLayout(hbox)
1308
1309         hbox = QHBoxLayout()
1310         host_line = QLineEdit()
1311         host_line.setText(server)
1312         hbox.addWidget(QLabel(_('Connect to') + ':'))
1313         hbox.addWidget(host_line)
1314         vbox.addLayout(hbox)
1315
1316         hbox = QHBoxLayout()
1317
1318         buttonGroup = QGroupBox(_("Protocol"))
1319         radio1 = QRadioButton("tcp", buttonGroup)
1320         radio2 = QRadioButton("http", buttonGroup)
1321
1322         def current_line():
1323             return unicode(host_line.text()).split(':')
1324             
1325         def set_button(protocol):
1326             if protocol == 't':
1327                 radio1.setChecked(1)
1328             elif protocol == 'h':
1329                 radio2.setChecked(1)
1330
1331         def set_protocol(protocol):
1332             host = current_line()[0]
1333             pp = plist[host]
1334             if protocol not in pp.keys():
1335                 protocol = pp.keys()[0]
1336                 set_button(protocol)
1337             port = pp[protocol]
1338             host_line.setText( host + ':' + port + ':' + protocol)
1339
1340         radio1.clicked.connect(lambda x: set_protocol('t') )
1341         radio2.clicked.connect(lambda x: set_protocol('h') )
1342
1343         set_button(current_line()[2])
1344
1345         hbox.addWidget(QLabel(_('Protocol')+':'))
1346         hbox.addWidget(radio1)
1347         hbox.addWidget(radio2)
1348
1349         vbox.addLayout(hbox)
1350
1351         if wallet.interface.servers:
1352             label = _('Active Servers')
1353         else:
1354             label = _('Default Servers')
1355         
1356         servers_list_widget = QTreeWidget(parent)
1357         servers_list_widget.setHeaderLabels( [ label ] )
1358         servers_list_widget.setMaximumHeight(150)
1359         for host in plist.keys():
1360             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] ))
1361
1362         def do_set_line(x):
1363             host = unicode(x.text(0))
1364             pp = plist[host]
1365             if 't' in pp.keys():
1366                 protocol = 't'
1367             else:
1368                 protocol = pp.keys()[0]
1369             port = pp[protocol]
1370             host_line.setText( host + ':' + port + ':' + protocol)
1371             set_button(protocol)
1372
1373         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line)
1374         vbox.addWidget(servers_list_widget)
1375
1376         vbox.addLayout(ok_cancel_buttons(d))
1377         d.setLayout(vbox) 
1378
1379         if not d.exec_(): return
1380         server = unicode( host_line.text() )
1381
1382         try:
1383             wallet.set_server(server)
1384         except:
1385             QMessageBox.information(None, _('Error'), 'error', _('OK'))
1386             if parent == None:
1387                 sys.exit(1)
1388             else:
1389                 return
1390
1391         return True
1392
1393
1394
1395 class ElectrumGui():
1396
1397     def __init__(self, wallet):
1398         self.wallet = wallet
1399         self.app = QApplication(sys.argv)
1400
1401     def waiting_dialog(self):
1402
1403         s = Timer()
1404         s.start()
1405         w = QDialog()
1406         w.resize(200, 70)
1407         w.setWindowTitle('Electrum')
1408         l = QLabel('')
1409         vbox = QVBoxLayout()
1410         vbox.addWidget(l)
1411         w.setLayout(vbox)
1412         w.show()
1413         def f():
1414             if self.wallet.up_to_date: 
1415                 w.close()
1416             else:
1417                 l.setText("Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1418                               %(len(self.wallet.all_addresses()), self.wallet.interface.bytes_received/1024.))
1419
1420         w.connect(s, QtCore.SIGNAL('timersignal'), f)
1421         self.wallet.interface.poke()
1422         w.exec_()
1423         w.destroy()
1424
1425
1426     def restore_or_create(self):
1427
1428         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1429         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1430         if r==2: return False
1431         
1432         is_recovery = (r==1)
1433         wallet = self.wallet
1434         # ask for the server.
1435         if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1436
1437         if not is_recovery:
1438             wallet.new_seed(None)
1439             wallet.init_mpk( wallet.seed )
1440             wallet.up_to_date_event.clear()
1441             wallet.up_to_date = False
1442             self.waiting_dialog()
1443             # run a dialog indicating the seed, ask the user to remember it
1444             ElectrumWindow.show_seed_dialog(wallet)
1445             #ask for password
1446             ElectrumWindow.change_password_dialog(wallet)
1447         else:
1448             # ask for seed and gap.
1449             if not ElectrumWindow.seed_dialog( wallet ): return False
1450             wallet.init_mpk( wallet.seed )
1451             wallet.up_to_date_event.clear()
1452             wallet.up_to_date = False
1453             self.waiting_dialog()
1454             if wallet.is_found():
1455                 # history and addressbook
1456                 wallet.update_tx_history()
1457                 wallet.fill_addressbook()
1458                 print "recovery successful"
1459                 wallet.save()
1460             else:
1461                 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1462
1463         wallet.save()
1464         return True
1465
1466     def main(self,url):
1467         s = Timer()
1468         s.start()
1469         w = ElectrumWindow(self.wallet)
1470         if url: w.set_url(url)
1471         w.app = self.app
1472         w.connect_slots(s)
1473         w.update_wallet()
1474
1475         self.app.exec_()