fix for watching-only wallets
[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.fee_e = QLineEdit()
451         grid.addWidget(QLabel(_('Fee')), 4, 0)
452         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
453         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\nA suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
454        
455         self.nochange_cb = QCheckBox(_('Do not create change address'))
456         grid.addWidget(self.nochange_cb,5,1,1,4)
457         self.nochange_cb.setChecked(False)
458         self.nochange_cb.setHidden(not self.wallet.expert_mode)
459
460         b = EnterButton(_("Send"), self.do_send)
461         grid.addWidget(b, 6, 1)
462
463         b = EnterButton(_("Clear"),self.do_clear)
464         grid.addWidget(b, 6, 2)
465
466         self.payto_sig = QLabel('')
467         grid.addWidget(self.payto_sig, 7, 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         if self.wallet.seed:
1163             self.nochange_cb.setHidden(not self.wallet.expert_mode)
1164         
1165
1166     def settings_dialog(self):
1167         d = QDialog(self)
1168         d.setModal(1)
1169         vbox = QVBoxLayout()
1170         msg = _('Here are the settings of your wallet.') + '\n'\
1171               + _('For more explanations, click on the help buttons next to each field.')
1172
1173         label = QLabel(msg)
1174         label.setFixedWidth(250)
1175         label.setWordWrap(True)
1176         label.setAlignment(Qt.AlignJustify)
1177         vbox.addWidget(label)
1178
1179         grid = QGridLayout()
1180         grid.setSpacing(8)
1181         vbox.addLayout(grid)
1182
1183         fee_e = QLineEdit()
1184         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1185         grid.addWidget(QLabel(_('Transaction fee')), 2, 0)
1186         grid.addWidget(fee_e, 2, 1)
1187         grid.addWidget(HelpButton(_('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee. Recommended value: 0.001')), 2, 2)
1188         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1189
1190         nz_e = QLineEdit()
1191         nz_e.setText("%d"% self.wallet.num_zeros)
1192         grid.addWidget(QLabel(_('Display zeros')), 3, 0)
1193         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)
1194         grid.addWidget(nz_e, 3, 1)
1195         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1196
1197         if self.wallet.expert_mode:
1198             msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1199                   + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1200                   + _('Your current gap limit is: ') + '%d'%self.wallet.gap_limit + '\n' \
1201                   + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1202                   + _('Warning:') + ' ' \
1203                   + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1204                   + _('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' 
1205             gap_e = QLineEdit()
1206             gap_e.setText("%d"% self.wallet.gap_limit)
1207             grid.addWidget(QLabel(_('Gap limit')), 4, 0)
1208             grid.addWidget(gap_e, 4, 1)
1209             grid.addWidget(HelpButton(msg), 4, 2)
1210             gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1211
1212         cb = QCheckBox(_('Expert mode'))
1213         grid.addWidget(cb, 5, 0)
1214         cb.setChecked(self.wallet.expert_mode)
1215         
1216         vbox.addLayout(ok_cancel_buttons(d))
1217         d.setLayout(vbox) 
1218
1219         # run the dialog
1220         if not d.exec_(): return
1221
1222         fee = unicode(fee_e.text())
1223         try:
1224             fee = int( 100000000 * Decimal(fee) )
1225         except:
1226             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1227             return
1228
1229         if self.wallet.fee != fee:
1230             self.wallet.fee = fee
1231             self.wallet.save()
1232         
1233         nz = unicode(nz_e.text())
1234         try:
1235             nz = int( nz )
1236             if nz>8: nz=8
1237         except:
1238             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1239             return
1240
1241         if self.wallet.num_zeros != nz:
1242             self.wallet.num_zeros = nz
1243             self.update_history_tab()
1244             self.update_receive_tab()
1245             self.wallet.save()
1246
1247         if self.wallet.expert_mode:
1248             try:
1249                 n = int(gap_e.text())
1250             except:
1251                 QMessageBox.warning(self, _('Error'), _('Invalid Value'), _('OK'))
1252                 return
1253             if self.wallet.gap_limit != n:
1254                 r = self.wallet.change_gap_limit(n)
1255                 if r:
1256                     self.update_receive_tab()
1257                 else:
1258                     QMessageBox.warning(self, _('Error'), _('Invalid Value'), _('OK'))
1259
1260         self.set_expert_mode(cb.isChecked())
1261
1262
1263     @staticmethod 
1264     def network_dialog(wallet, parent=None):
1265         interface = wallet.interface
1266         if parent:
1267             if interface.is_connected:
1268                 status = _("Connected to")+" %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks)
1269             else:
1270                 status = _("Not connected")
1271             server = wallet.server
1272         else:
1273             import random
1274             status = _("Please choose a server.")
1275             server = random.choice( DEFAULT_SERVERS )
1276
1277         if not wallet.interface.servers:
1278             servers_list = []
1279             for x in DEFAULT_SERVERS:
1280                 h,port,protocol = x.split(':')
1281                 servers_list.append( (h,[(protocol,port)] ) )
1282         else:
1283             servers_list = wallet.interface.servers
1284             
1285         plist = {}
1286         for item in servers_list:
1287             host, pp = item
1288             z = {}
1289             for item2 in pp:
1290                 protocol, port = item2
1291                 z[protocol] = port
1292             plist[host] = z
1293
1294         d = QDialog(parent)
1295         d.setModal(1)
1296         d.setWindowTitle(_('Server'))
1297         d.setMinimumSize(375, 20)
1298
1299         vbox = QVBoxLayout()
1300         vbox.setSpacing(20)
1301
1302         hbox = QHBoxLayout()
1303         l = QLabel()
1304         l.setPixmap(QPixmap(":icons/network.png"))
1305         hbox.addWidget(l)        
1306         hbox.addWidget(QLabel(status))
1307
1308         vbox.addLayout(hbox)
1309
1310         hbox = QHBoxLayout()
1311         host_line = QLineEdit()
1312         host_line.setText(server)
1313         hbox.addWidget(QLabel(_('Connect to') + ':'))
1314         hbox.addWidget(host_line)
1315         vbox.addLayout(hbox)
1316
1317         hbox = QHBoxLayout()
1318
1319         buttonGroup = QGroupBox(_("Protocol"))
1320         radio1 = QRadioButton("tcp", buttonGroup)
1321         radio2 = QRadioButton("http", buttonGroup)
1322
1323         def current_line():
1324             return unicode(host_line.text()).split(':')
1325             
1326         def set_button(protocol):
1327             if protocol == 't':
1328                 radio1.setChecked(1)
1329             elif protocol == 'h':
1330                 radio2.setChecked(1)
1331
1332         def set_protocol(protocol):
1333             host = current_line()[0]
1334             pp = plist[host]
1335             if protocol not in pp.keys():
1336                 protocol = pp.keys()[0]
1337                 set_button(protocol)
1338             port = pp[protocol]
1339             host_line.setText( host + ':' + port + ':' + protocol)
1340
1341         radio1.clicked.connect(lambda x: set_protocol('t') )
1342         radio2.clicked.connect(lambda x: set_protocol('h') )
1343
1344         set_button(current_line()[2])
1345
1346         hbox.addWidget(QLabel(_('Protocol')+':'))
1347         hbox.addWidget(radio1)
1348         hbox.addWidget(radio2)
1349
1350         vbox.addLayout(hbox)
1351
1352         if wallet.interface.servers:
1353             label = _('Active Servers')
1354         else:
1355             label = _('Default Servers')
1356         
1357         servers_list_widget = QTreeWidget(parent)
1358         servers_list_widget.setHeaderLabels( [ label ] )
1359         servers_list_widget.setMaximumHeight(150)
1360         for host in plist.keys():
1361             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] ))
1362
1363         def do_set_line(x):
1364             host = unicode(x.text(0))
1365             pp = plist[host]
1366             if 't' in pp.keys():
1367                 protocol = 't'
1368             else:
1369                 protocol = pp.keys()[0]
1370             port = pp[protocol]
1371             host_line.setText( host + ':' + port + ':' + protocol)
1372             set_button(protocol)
1373
1374         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line)
1375         vbox.addWidget(servers_list_widget)
1376
1377         vbox.addLayout(ok_cancel_buttons(d))
1378         d.setLayout(vbox) 
1379
1380         if not d.exec_(): return
1381         server = unicode( host_line.text() )
1382
1383         try:
1384             wallet.set_server(server)
1385         except:
1386             QMessageBox.information(None, _('Error'), 'error', _('OK'))
1387             if parent == None:
1388                 sys.exit(1)
1389             else:
1390                 return
1391
1392         return True
1393
1394
1395
1396 class ElectrumGui():
1397
1398     def __init__(self, wallet):
1399         self.wallet = wallet
1400         self.app = QApplication(sys.argv)
1401
1402     def waiting_dialog(self):
1403
1404         s = Timer()
1405         s.start()
1406         w = QDialog()
1407         w.resize(200, 70)
1408         w.setWindowTitle('Electrum')
1409         l = QLabel('')
1410         vbox = QVBoxLayout()
1411         vbox.addWidget(l)
1412         w.setLayout(vbox)
1413         w.show()
1414         def f():
1415             if self.wallet.up_to_date: 
1416                 w.close()
1417             else:
1418                 l.setText("Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1419                               %(len(self.wallet.all_addresses()), self.wallet.interface.bytes_received/1024.))
1420
1421         w.connect(s, QtCore.SIGNAL('timersignal'), f)
1422         self.wallet.interface.poke()
1423         w.exec_()
1424         w.destroy()
1425
1426
1427     def restore_or_create(self):
1428
1429         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1430         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1431         if r==2: return False
1432         
1433         is_recovery = (r==1)
1434         wallet = self.wallet
1435         # ask for the server.
1436         if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1437
1438         if not is_recovery:
1439             wallet.new_seed(None)
1440             wallet.init_mpk( wallet.seed )
1441             wallet.up_to_date_event.clear()
1442             wallet.up_to_date = False
1443             self.waiting_dialog()
1444             # run a dialog indicating the seed, ask the user to remember it
1445             ElectrumWindow.show_seed_dialog(wallet)
1446             #ask for password
1447             ElectrumWindow.change_password_dialog(wallet)
1448         else:
1449             # ask for seed and gap.
1450             if not ElectrumWindow.seed_dialog( wallet ): return False
1451             wallet.init_mpk( wallet.seed )
1452             wallet.up_to_date_event.clear()
1453             wallet.up_to_date = False
1454             self.waiting_dialog()
1455             if wallet.is_found():
1456                 # history and addressbook
1457                 wallet.update_tx_history()
1458                 wallet.fill_addressbook()
1459                 print "recovery successful"
1460                 wallet.save()
1461             else:
1462                 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1463
1464         wallet.save()
1465         return True
1466
1467     def main(self,url):
1468         s = Timer()
1469         s.start()
1470         w = ElectrumWindow(self.wallet)
1471         if url: w.set_url(url)
1472         w.app = self.app
1473         w.connect_slots(s)
1474         w.update_wallet()
1475
1476         self.app.exec_()