corrected instructions for generating icons data file.
[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_rc.py"
39     print "Please generate it with: 'pyrcc4 icons.qrc -o lib/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
49 if platform.system() == 'Windows':
50     MONOSPACE_FONT = 'Lucida Console'
51 elif platform.system() == 'Darwin':
52     MONOSPACE_FONT = 'Monaco'
53 else:
54     MONOSPACE_FONT = 'monospace'
55
56 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
57
58 def numbify(entry, is_int = False):
59     text = unicode(entry.text()).strip()
60     chars = '0123456789'
61     if not is_int: chars +='.'
62     s = ''.join([i for i in text if i in chars])
63     if not is_int:
64         if '.' in s:
65             p = s.find('.')
66             s = s.replace('.','')
67             s = s[:p] + '.' + s[p:p+8]
68         try:
69             amount = int( Decimal(s) * 100000000 )
70         except:
71             amount = None
72     else:
73         try:
74             amount = int( s )
75         except:
76             amount = None
77     entry.setText(s)
78     return amount
79
80
81 class Timer(QtCore.QThread):
82     def run(self):
83         while True:
84             self.emit(QtCore.SIGNAL('timersignal'))
85             time.sleep(0.5)
86
87 class HelpButton(QPushButton):
88     def __init__(self, text):
89         QPushButton.__init__(self, '?')
90         self.setFocusPolicy(Qt.NoFocus)
91         self.setFixedWidth(20)
92         self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
93
94
95 class EnterButton(QPushButton):
96     def __init__(self, text, func):
97         QPushButton.__init__(self, text)
98         self.func = func
99         self.clicked.connect(func)
100
101     def keyPressEvent(self, e):
102         if e.key() == QtCore.Qt.Key_Return:
103             apply(self.func,())
104
105 class MyTreeWidget(QTreeWidget):
106     def __init__(self, parent):
107         QTreeWidget.__init__(self, parent)
108         def ddfr(item):
109             if not item: return
110             for i in range(0,self.viewport().height()/5):
111                 if self.itemAt(QPoint(0,i*5)) == item:
112                     break
113             else:
114                 return
115             for j in range(0,30):
116                 if self.itemAt(QPoint(0,i*5 + j)) != item:
117                     break
118             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
119
120         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
121         
122
123
124
125 class StatusBarButton(QPushButton):
126     def __init__(self, icon, tooltip, func):
127         QPushButton.__init__(self, icon, '')
128         self.setToolTip(tooltip)
129         self.setFlat(True)
130         self.setMaximumWidth(25)
131         self.clicked.connect(func)
132         self.func = func
133
134     def keyPressEvent(self, e):
135         if e.key() == QtCore.Qt.Key_Return:
136             apply(self.func,())
137
138
139 class QRCodeWidget(QWidget):
140
141     def __init__(self, addr):
142         super(QRCodeWidget, self).__init__()
143         self.setGeometry(300, 300, 350, 350)
144         self.set_addr(addr)
145
146     def set_addr(self, addr):
147         self.addr = addr
148         self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
149         self.qr.addData(addr)
150         self.qr.make()
151         
152     def paintEvent(self, e):
153         qp = QtGui.QPainter()
154         qp.begin(self)
155         boxsize = 7
156         size = self.qr.getModuleCount()*boxsize
157         k = self.qr.getModuleCount()
158         black = QColor(0, 0, 0, 255)
159         white = QColor(255, 255, 255, 255)
160         for r in range(k):
161             for c in range(k):
162                 if self.qr.isDark(r, c):
163                     qp.setBrush(black)
164                     qp.setPen(black)
165                 else:
166                     qp.setBrush(white)
167                     qp.setPen(white)
168                 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
169         qp.end()
170         
171
172
173 def ok_cancel_buttons(dialog):
174     hbox = QHBoxLayout()
175     hbox.addStretch(1)
176     b = QPushButton("OK")
177     hbox.addWidget(b)
178     b.clicked.connect(dialog.accept)
179     b = QPushButton("Cancel")
180     hbox.addWidget(b)
181     b.clicked.connect(dialog.reject)
182     return hbox
183
184
185 class ElectrumWindow(QMainWindow):
186
187     def __init__(self, wallet):
188         QMainWindow.__init__(self)
189         self.wallet = wallet
190         self.wallet.register_callback(self.update_callback)
191
192         self.funds_error = False
193         self.completions = QStringListModel()
194
195         self.tabs = tabs = QTabWidget(self)
196         tabs.addTab(self.create_history_tab(), _('History') )
197         if self.wallet.seed:
198             tabs.addTab(self.create_send_tab(), _('Send') )
199         tabs.addTab(self.create_receive_tab(), _('Receive') )
200         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
201         tabs.addTab(self.create_wall_tab(), _('Wall') )
202         tabs.setMinimumSize(600, 400)
203         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
204         self.setCentralWidget(tabs)
205         self.create_status_bar()
206         self.setGeometry(100,100,840,400)
207         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.path
208         if not self.wallet.seed: title += ' [seedless]'
209         self.setWindowTitle( title )
210
211         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
212         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
213         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
214         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
215         
216         self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
217         self.history_list.setFocus(True)
218
219         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
220         if platform.system() == 'Windows':
221             n = 3 if self.wallet.seed else 2
222             tabs.setCurrentIndex (n)
223             tabs.setCurrentIndex (0)
224
225
226     def connect_slots(self, sender):
227         if self.wallet.seed:
228             self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
229             self.previous_payto_e=''
230
231     def check_recipient(self):
232         if self.payto_e.hasFocus():
233             return
234         r = unicode( self.payto_e.text() )
235         if r != self.previous_payto_e:
236             self.previous_payto_e = r
237             r = r.strip()
238             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
239                 try:
240                     to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
241                 except:
242                     return
243                 if to_address:
244                     s = r + '  <' + to_address + '>'
245                     self.payto_e.setText(s)
246
247
248     def update_callback(self):
249         self.emit(QtCore.SIGNAL('updatesignal'))
250
251     def update_wallet(self):
252         if self.wallet.interface and self.wallet.interface.is_connected:
253             if self.wallet.blocks == -1:
254                 text = _( "Connecting..." )
255                 icon = QIcon(":icons/status_disconnected.png")
256             elif self.wallet.blocks == 0:
257                 text = _( "Server not ready" )
258                 icon = QIcon(":icons/status_disconnected.png")
259             elif not self.wallet.up_to_date:
260                 text = _( "Synchronizing..." )
261                 icon = QIcon(":icons/status_waiting.png")
262             else:
263                 c, u = self.wallet.get_balance()
264                 text =  _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
265                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
266                 icon = QIcon(":icons/status_connected.png")
267         else:
268             text = _( "Not connected" )
269             icon = QIcon(":icons/status_disconnected.png")
270
271         if self.funds_error:
272             text = _( "Not enough funds" )
273
274         self.statusBar().showMessage(text)
275         self.status_button.setIcon( icon )
276
277         if self.wallet.up_to_date:
278             self.textbox.setText( self.wallet.banner )
279             self.update_history_tab()
280             self.update_receive_tab()
281             self.update_contacts_tab()
282             self.update_completions()
283
284
285     def create_history_tab(self):
286         self.history_list = l = MyTreeWidget(self)
287         l.setColumnCount(5)
288         l.setColumnWidth(0, 40) 
289         l.setColumnWidth(1, 140) 
290         l.setColumnWidth(2, 350) 
291         l.setColumnWidth(3, 140) 
292         l.setColumnWidth(4, 140) 
293         l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
294         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
295         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
296         l.setContextMenuPolicy(Qt.CustomContextMenu)
297         l.customContextMenuRequested.connect(self.create_history_menu)
298         return l
299
300     def create_history_menu(self, position):
301         self.history_list.selectedIndexes() 
302         item = self.history_list.currentItem()
303         if not item: return
304         tx_hash = str(item.toolTip(0))
305         menu = QMenu()
306         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
307         menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
308         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
309         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
310
311     def tx_details(self, tx_hash):
312         tx = self.wallet.tx_history.get(tx_hash)
313
314         if tx['height']:
315             conf = self.wallet.blocks - tx['height'] + 1
316             time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
317         else:
318             conf = 0
319             time_str = 'pending'
320
321         tx_details = _("Transaction Details") +"\n\n" \
322             + "Transaction ID:\n" + tx_hash + "\n\n" \
323             + "Status: %d confirmations\n\n"%conf  \
324             + "Date: %s\n\n"%time_str \
325             + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
326             + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
327
328         r = self.wallet.receipts.get(tx_hash)
329         if r:
330             tx_details += "\n_______________________________________" \
331                 + '\n\nSigned URI: ' + r[2] \
332                 + "\n\nSigned by: " + r[0] \
333                 + '\n\nSignature: ' + r[1]
334
335         QMessageBox.information(self, 'Details', tx_details, 'OK')
336
337
338     def tx_label_clicked(self, item, column):
339         if column==2 and item.isSelected():
340             tx_hash = str(item.toolTip(0))
341             self.is_edit=True
342             #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
343             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
344             self.history_list.editItem( item, column )
345             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
346             self.is_edit=False
347
348     def tx_label_changed(self, item, column):
349         if self.is_edit: 
350             return
351         self.is_edit=True
352         tx_hash = str(item.toolTip(0))
353         tx = self.wallet.tx_history.get(tx_hash)
354         s = self.wallet.labels.get(tx_hash)
355         text = unicode( item.text(2) )
356         if text: 
357             self.wallet.labels[tx_hash] = text
358             item.setForeground(2, QBrush(QColor('black')))
359         else:
360             if s: self.wallet.labels.pop(tx_hash)
361             text = tx['default_label']
362             item.setText(2, text)
363             item.setForeground(2, QBrush(QColor('gray')))
364         self.is_edit=False
365
366     def edit_label(self, is_recv):
367         l = self.receive_list if is_recv else self.contacts_list
368         c = 2 if is_recv else 1
369         item = l.currentItem()
370         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
371         l.editItem( item, c )
372         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
373
374     def address_label_clicked(self, item, column, l, column_addr, column_label):
375         if column==column_label and item.isSelected():
376             addr = unicode( item.text(column_addr) )
377             label = unicode( item.text(column_label) )
378             if label in self.wallet.aliases.keys():
379                 return
380             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
381             l.editItem( item, column )
382             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
383
384     def address_label_changed(self, item, column, l, column_addr, column_label):
385         addr = unicode( item.text(column_addr) )
386         text = unicode( item.text(column_label) )
387         if text:
388             if text not in self.wallet.aliases.keys():
389                 self.wallet.labels[addr] = text
390             else:
391                 print "error: this is one of your aliases"
392                 label = self.wallet.labels.get(addr,'')
393                 item.setText(column_label, QString(label))
394         else:
395             s = self.wallet.labels.get(addr)
396             if s: self.wallet.labels.pop(addr)
397
398         self.update_history_tab()
399         self.update_completions()
400
401     def update_history_tab(self):
402         self.history_list.clear()
403         balance = 0
404         for tx in self.wallet.get_tx_history():
405             tx_hash = tx['tx_hash']
406             if tx['height']:
407                 conf = self.wallet.blocks - tx['height'] + 1
408                 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
409                 icon = QIcon(":icons/confirmed.png")
410             else:
411                 conf = 0
412                 time_str = 'pending'
413                 icon = QIcon(":icons/unconfirmed.png")
414             v = tx['value']
415             balance += v 
416             label = self.wallet.labels.get(tx_hash)
417             is_default_label = (label == '') or (label is None)
418             if is_default_label: label = tx['default_label']
419
420             item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
421             item.setFont(2, QFont(MONOSPACE_FONT))
422             item.setFont(3, QFont(MONOSPACE_FONT))
423             item.setFont(4, QFont(MONOSPACE_FONT))
424             item.setToolTip(0, tx_hash)
425             if is_default_label:
426                 item.setForeground(2, QBrush(QColor('grey')))
427
428             item.setIcon(0, icon)
429             self.history_list.insertTopLevelItem(0,item)
430
431         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
432
433
434     def create_send_tab(self):
435         w = QWidget()
436
437         grid = QGridLayout()
438         grid.setSpacing(8)
439         grid.setColumnMinimumWidth(3,300)
440         grid.setColumnStretch(5,1)
441
442         self.payto_e = QLineEdit()
443         grid.addWidget(QLabel(_('Pay to')), 1, 0)
444         grid.addWidget(self.payto_e, 1, 1, 1, 3)
445         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)
446
447         completer = QCompleter()
448         completer.setCaseSensitivity(False)
449         self.payto_e.setCompleter(completer)
450         completer.setModel(self.completions)
451
452         self.message_e = QLineEdit()
453         grid.addWidget(QLabel(_('Description')), 2, 0)
454         grid.addWidget(self.message_e, 2, 1, 1, 3)
455         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)
456
457         self.amount_e = QLineEdit()
458         grid.addWidget(QLabel(_('Amount')), 3, 0)
459         grid.addWidget(self.amount_e, 3, 1, 1, 2)
460         grid.addWidget(HelpButton(
461                 _('Amount to be sent.') + '\n\n' \
462                     + _('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)
463         
464         self.fee_e = QLineEdit()
465         grid.addWidget(QLabel(_('Fee')), 4, 0)
466         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
467         grid.addWidget(HelpButton(
468                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
469                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
470                     + _('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)
471         
472         b = EnterButton(_("Send"), self.do_send)
473         grid.addWidget(b, 6, 1)
474
475         b = EnterButton(_("Clear"),self.do_clear)
476         grid.addWidget(b, 6, 2)
477
478         self.payto_sig = QLabel('')
479         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
480
481         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
482         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
483         w.setLayout(grid) 
484         w.show()
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 "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_()