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