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