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