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