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