Merge branch 'startup-gui-option' into merged
[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 = _('<p>"%s"</p>'
930                 "<p>If you memorise or write down these 12 words, you will always be able to recover your wallet.</p>"
931                 "<p>This is called a 'BrainWallet'. The order of words is important. Case does not matter (capitals or lowercase).</p>") % brainwallet
932         main_text = QLabel(msg)
933         main_text.setWordWrap(True)
934
935         logo = QLabel()
936         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
937
938         if parent:
939             app = parent.app
940         else:
941             app = QApplication
942
943         copy_function = lambda: app.clipboard().setText(brainwallet)
944         copy_button = QPushButton(_("Copy to Clipboard"))
945         copy_button.clicked.connect(copy_function)
946
947         show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
948         qr_button = QPushButton(_("View as QR Code"))
949         qr_button.clicked.connect(show_qr_function)
950
951         ok_button = QPushButton(_("OK"))
952         ok_button.clicked.connect(dialog.accept)
953
954         main_layout = QGridLayout()
955         main_layout.addWidget(logo, 0, 0)
956         main_layout.addWidget(main_text, 0, 1, 1, -1)
957         main_layout.addWidget(copy_button, 1, 1)
958         main_layout.addWidget(qr_button, 1, 2)
959         main_layout.addWidget(ok_button, 1, 3)
960         dialog.setLayout(main_layout)
961
962         dialog.exec_()
963
964     @staticmethod
965     def show_seed_qrcode(seed):
966         if not seed: return
967         d = QDialog(None)
968         d.setModal(1)
969         d.setWindowTitle(_("Seed"))
970         d.setMinimumSize(270, 300)
971         vbox = QVBoxLayout()
972         vbox.addWidget(QRCodeWidget(seed))
973         hbox = QHBoxLayout()
974         hbox.addStretch(1)
975         b = QPushButton(_("OK"))
976         hbox.addWidget(b)
977         b.clicked.connect(d.accept)
978
979         vbox.addLayout(hbox)
980         d.setLayout(vbox)
981         d.exec_()
982
983
984     def show_address_qrcode(self,address):
985         if not address: return
986         d = QDialog(self)
987         d.setModal(1)
988         d.setWindowTitle(address)
989         d.setMinimumSize(270, 350)
990         vbox = QVBoxLayout()
991         qrw = QRCodeWidget(address)
992         vbox.addWidget(qrw)
993
994         hbox = QHBoxLayout()
995         amount_e = QLineEdit()
996         hbox.addWidget(QLabel(_('Amount')))
997         hbox.addWidget(amount_e)
998         vbox.addLayout(hbox)
999
1000         #hbox = QHBoxLayout()
1001         #label_e = QLineEdit()
1002         #hbox.addWidget(QLabel('Label'))
1003         #hbox.addWidget(label_e)
1004         #vbox.addLayout(hbox)
1005
1006         def amount_changed():
1007             amount = numbify(amount_e)
1008             #label = str( label_e.getText() )
1009             if amount is not None:
1010                 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1011             else:
1012                 qrw.set_addr( address )
1013             qrw.repaint()
1014
1015         def do_save():
1016             bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1017             self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1018             
1019         amount_e.textChanged.connect( amount_changed )
1020
1021         hbox = QHBoxLayout()
1022         hbox.addStretch(1)
1023         b = QPushButton(_("Save"))
1024         b.clicked.connect(do_save)
1025         hbox.addWidget(b)
1026         b = QPushButton(_("Close"))
1027         hbox.addWidget(b)
1028         b.clicked.connect(d.accept)
1029
1030         vbox.addLayout(hbox)
1031         d.setLayout(vbox)
1032         d.exec_()
1033
1034     def question(self, msg):
1035         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1036
1037     def show_message(self, msg):
1038         QMessageBox.information(self, _('Message'), msg, _('OK'))
1039
1040     def password_dialog(self ):
1041         d = QDialog(self)
1042         d.setModal(1)
1043
1044         pw = QLineEdit()
1045         pw.setEchoMode(2)
1046
1047         vbox = QVBoxLayout()
1048         msg = _('Please enter your password')
1049         vbox.addWidget(QLabel(msg))
1050
1051         grid = QGridLayout()
1052         grid.setSpacing(8)
1053         grid.addWidget(QLabel(_('Password')), 1, 0)
1054         grid.addWidget(pw, 1, 1)
1055         vbox.addLayout(grid)
1056
1057         vbox.addLayout(ok_cancel_buttons(d))
1058         d.setLayout(vbox) 
1059
1060         if not d.exec_(): return
1061         return unicode(pw.text())
1062
1063
1064
1065
1066
1067     @staticmethod
1068     def change_password_dialog( wallet, parent=None ):
1069
1070         if not wallet.seed:
1071             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1072             return
1073
1074         d = QDialog(parent)
1075         d.setModal(1)
1076
1077         pw = QLineEdit()
1078         pw.setEchoMode(2)
1079         new_pw = QLineEdit()
1080         new_pw.setEchoMode(2)
1081         conf_pw = QLineEdit()
1082         conf_pw.setEchoMode(2)
1083
1084         vbox = QVBoxLayout()
1085         if parent:
1086             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')
1087         else:
1088             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1089         vbox.addWidget(QLabel(msg))
1090
1091         grid = QGridLayout()
1092         grid.setSpacing(8)
1093
1094         if wallet.use_encryption:
1095             grid.addWidget(QLabel(_('Password')), 1, 0)
1096             grid.addWidget(pw, 1, 1)
1097
1098         grid.addWidget(QLabel(_('New Password')), 2, 0)
1099         grid.addWidget(new_pw, 2, 1)
1100
1101         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1102         grid.addWidget(conf_pw, 3, 1)
1103         vbox.addLayout(grid)
1104
1105         vbox.addLayout(ok_cancel_buttons(d))
1106         d.setLayout(vbox) 
1107
1108         if not d.exec_(): return
1109
1110         password = unicode(pw.text()) if wallet.use_encryption else None
1111         new_password = unicode(new_pw.text())
1112         new_password2 = unicode(conf_pw.text())
1113
1114         try:
1115             seed = wallet.pw_decode( wallet.seed, password)
1116         except:
1117             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1118             return
1119
1120         if new_password != new_password2:
1121             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1122             return
1123
1124         wallet.update_password(seed, password, new_password)
1125
1126     @staticmethod
1127     def seed_dialog(wallet, parent=None):
1128         d = QDialog(parent)
1129         d.setModal(1)
1130
1131         vbox = QVBoxLayout()
1132         msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1133         vbox.addWidget(QLabel(msg))
1134
1135         grid = QGridLayout()
1136         grid.setSpacing(8)
1137
1138         seed_e = QLineEdit()
1139         grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1140         grid.addWidget(seed_e, 1, 1)
1141
1142         gap_e = QLineEdit()
1143         gap_e.setText("5")
1144         grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1145         grid.addWidget(gap_e, 2, 1)
1146         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1147         vbox.addLayout(grid)
1148
1149         vbox.addLayout(ok_cancel_buttons(d))
1150         d.setLayout(vbox) 
1151
1152         if not d.exec_(): return
1153
1154         try:
1155             gap = int(unicode(gap_e.text()))
1156         except:
1157             QMessageBox.warning(None, _('Error'), 'error', 'OK')
1158             sys.exit(0)
1159
1160         try:
1161             seed = unicode(seed_e.text())
1162             seed.decode('hex')
1163         except:
1164             print_error("Warning: Not hex, trying decode")
1165             try:
1166                 seed = mnemonic.mn_decode( seed.split(' ') )
1167             except:
1168                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1169                 sys.exit(0)
1170         if not seed:
1171             QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1172             sys.exit(0)
1173         
1174         wallet.seed = str(seed)
1175         #print repr(wallet.seed)
1176         wallet.gap_limit = gap
1177         return True
1178
1179
1180     def set_expert_mode(self, b):
1181         self.wallet.expert_mode = b
1182         self.wallet.save()
1183         self.update_receive_tab()
1184         self.update_contacts_tab()
1185         # if self.wallet.seed:
1186         # self.nochange_cb.setHidden(not self.wallet.expert_mode)
1187         
1188
1189     def settings_dialog(self):
1190         d = QDialog(self)
1191         d.setModal(1)
1192         vbox = QVBoxLayout()
1193         msg = _('Here are the settings of your wallet.') + '\n'\
1194               + _('For more explanations, click on the help buttons next to each field.')
1195
1196         label = QLabel(msg)
1197         label.setFixedWidth(250)
1198         label.setWordWrap(True)
1199         label.setAlignment(Qt.AlignJustify)
1200         vbox.addWidget(label)
1201
1202         grid = QGridLayout()
1203         grid.setSpacing(8)
1204         vbox.addLayout(grid)
1205
1206         fee_e = QLineEdit()
1207         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1208         grid.addWidget(QLabel(_('Transaction fee')), 2, 0)
1209         grid.addWidget(fee_e, 2, 1)
1210         msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1211             + _('Recommended value') + ': 0.001'
1212         grid.addWidget(HelpButton(msg), 2, 2)
1213         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1214
1215         nz_e = QLineEdit()
1216         nz_e.setText("%d"% self.wallet.num_zeros)
1217         grid.addWidget(QLabel(_('Display zeros')), 3, 0)
1218         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1219         grid.addWidget(HelpButton(msg), 3, 2)
1220         grid.addWidget(nz_e, 3, 1)
1221         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1222
1223         cb = QCheckBox(_('Expert mode'))
1224         grid.addWidget(cb, 4, 0)
1225         cb.setChecked(self.wallet.expert_mode)
1226
1227         if self.wallet.expert_mode:
1228
1229             usechange_cb = QCheckBox(_('Use change addresses'))
1230             grid.addWidget(usechange_cb, 5, 0)
1231             usechange_cb.setChecked(self.wallet.use_change)
1232             grid.addWidget(HelpButton(_('Using a change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1233
1234             msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1235                   + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1236                   + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1237                   + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1238                   + _('Warning') + ': ' \
1239                   + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1240                   + _('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' 
1241             gap_e = QLineEdit()
1242             gap_e.setText("%d"% self.wallet.gap_limit)
1243             grid.addWidget(QLabel(_('Gap limit')), 6, 0)
1244             grid.addWidget(gap_e, 6, 1)
1245             grid.addWidget(HelpButton(msg), 6, 2)
1246             gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1247
1248             gui = QComboBox()
1249             gui.addItems(['Lite', 'Qt'])
1250             cfg = SimpleConfig()
1251             gui.setCurrentIndex(gui.findText(cfg.config["gui"].capitalize()))
1252             grid.addWidget(QLabel(_('Default GUI') + ':'), 7, 0)
1253             grid.addWidget(gui, 7, 1)
1254             grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1255         
1256         vbox.addLayout(ok_cancel_buttons(d))
1257         d.setLayout(vbox) 
1258
1259         # run the dialog
1260         if not d.exec_(): return
1261
1262         fee = unicode(fee_e.text())
1263         try:
1264             fee = int( 100000000 * Decimal(fee) )
1265         except:
1266             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1267             return
1268
1269         if self.wallet.fee != fee:
1270             self.wallet.fee = fee
1271             self.wallet.save()
1272         
1273         nz = unicode(nz_e.text())
1274         try:
1275             nz = int( nz )
1276             if nz>8: nz=8
1277         except:
1278             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1279             return
1280
1281         if self.wallet.num_zeros != nz:
1282             self.wallet.num_zeros = nz
1283             self.update_history_tab()
1284             self.update_receive_tab()
1285             self.wallet.save()
1286
1287         if self.wallet.expert_mode:
1288
1289             self.wallet.use_change = usechange_cb.isChecked()
1290
1291             try:
1292                 n = int(gap_e.text())
1293             except:
1294                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1295                 return
1296             if self.wallet.gap_limit != n:
1297                 r = self.wallet.change_gap_limit(n)
1298                 if r:
1299                     self.update_receive_tab()
1300                 else:
1301                     QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1302                     
1303             cfg = SimpleConfig()
1304             cfg.set_key("gui", str(gui.currentText()).lower())
1305             cfg.save_config()
1306
1307         self.set_expert_mode(cb.isChecked())
1308
1309
1310     @staticmethod 
1311     def network_dialog(wallet, parent=None):
1312         interface = wallet.interface
1313         if parent:
1314             if interface.is_connected:
1315                 status = _("Connected to")+" %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks)
1316             else:
1317                 status = _("Not connected")
1318             server = wallet.server
1319         else:
1320             import random
1321             status = _("Please choose a server.")
1322             server = random.choice( DEFAULT_SERVERS )
1323
1324         if not wallet.interface.servers:
1325             servers_list = []
1326             for x in DEFAULT_SERVERS:
1327                 h,port,protocol = x.split(':')
1328                 servers_list.append( (h,[(protocol,port)] ) )
1329         else:
1330             servers_list = wallet.interface.servers
1331             
1332         plist = {}
1333         for item in servers_list:
1334             host, pp = item
1335             z = {}
1336             for item2 in pp:
1337                 protocol, port = item2
1338                 z[protocol] = port
1339             plist[host] = z
1340
1341         d = QDialog(parent)
1342         d.setModal(1)
1343         d.setWindowTitle(_('Server'))
1344         d.setMinimumSize(375, 20)
1345
1346         vbox = QVBoxLayout()
1347         vbox.setSpacing(20)
1348
1349         hbox = QHBoxLayout()
1350         l = QLabel()
1351         l.setPixmap(QPixmap(":icons/network.png"))
1352         hbox.addWidget(l)        
1353         hbox.addWidget(QLabel(status))
1354
1355         vbox.addLayout(hbox)
1356
1357         hbox = QHBoxLayout()
1358         host_line = QLineEdit()
1359         host_line.setText(server)
1360         hbox.addWidget(QLabel(_('Connect to') + ':'))
1361         hbox.addWidget(host_line)
1362         vbox.addLayout(hbox)
1363
1364         hbox = QHBoxLayout()
1365
1366         buttonGroup = QGroupBox(_("Protocol"))
1367         radio1 = QRadioButton("tcp", buttonGroup)
1368         radio2 = QRadioButton("http", buttonGroup)
1369
1370         def current_line():
1371             return unicode(host_line.text()).split(':')
1372             
1373         def set_button(protocol):
1374             if protocol == 't':
1375                 radio1.setChecked(1)
1376             elif protocol == 'h':
1377                 radio2.setChecked(1)
1378
1379         def set_protocol(protocol):
1380             host = current_line()[0]
1381             pp = plist[host]
1382             if protocol not in pp.keys():
1383                 protocol = pp.keys()[0]
1384                 set_button(protocol)
1385             port = pp[protocol]
1386             host_line.setText( host + ':' + port + ':' + protocol)
1387
1388         radio1.clicked.connect(lambda x: set_protocol('t') )
1389         radio2.clicked.connect(lambda x: set_protocol('h') )
1390
1391         set_button(current_line()[2])
1392
1393         hbox.addWidget(QLabel(_('Protocol')+':'))
1394         hbox.addWidget(radio1)
1395         hbox.addWidget(radio2)
1396
1397         vbox.addLayout(hbox)
1398         
1399         hbox = QHBoxLayout()
1400         proxy_mode = QComboBox()
1401         proxy_host = QLineEdit()
1402         proxy_host.setFixedWidth(200)
1403         proxy_port = QLineEdit()
1404         proxy_port.setFixedWidth(50)
1405         proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1406         proxy_mode.setCurrentIndex(proxy_mode.findText(str(interface.proxy["mode"]).upper()))
1407         proxy_host.setText(interface.proxy["host"])
1408         proxy_port.setText(interface.proxy["port"])
1409         hbox.addWidget(QLabel(_('Proxy') + ':'))
1410         hbox.addWidget(proxy_mode)
1411         hbox.addWidget(proxy_host)
1412         hbox.addWidget(proxy_port)
1413         vbox.addLayout(hbox)
1414
1415         hbox = QHBoxLayout()
1416
1417         if wallet.interface.servers:
1418             label = _('Active Servers')
1419         else:
1420             label = _('Default Servers')
1421         
1422         servers_list_widget = QTreeWidget(parent)
1423         servers_list_widget.setHeaderLabels( [ label ] )
1424         servers_list_widget.setMaximumHeight(150)
1425         for host in plist.keys():
1426             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] ))
1427
1428         def do_set_line(x):
1429             host = unicode(x.text(0))
1430             pp = plist[host]
1431             if 't' in pp.keys():
1432                 protocol = 't'
1433             else:
1434                 protocol = pp.keys()[0]
1435             port = pp[protocol]
1436             host_line.setText( host + ':' + port + ':' + protocol)
1437             set_button(protocol)
1438
1439         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line)
1440         vbox.addWidget(servers_list_widget)
1441
1442         vbox.addLayout(ok_cancel_buttons(d))
1443         d.setLayout(vbox) 
1444
1445         if not d.exec_(): return
1446         server = unicode( host_line.text() )
1447
1448         try:
1449             cfg = SimpleConfig()
1450             cfg.config["proxy"] = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1451             cfg.save_config()
1452             wallet.set_server(server, cfg.config["proxy"])
1453         except Exception as err:
1454             QMessageBox.information(None, _('Error'), str(err), _('OK'))
1455             if parent == None:
1456                 sys.exit(1)
1457             else:
1458                 return
1459
1460         return True
1461
1462     def closeEvent(self, event):
1463         cfg = SimpleConfig()
1464         g = self.geometry()
1465         cfg.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
1466         cfg.save_config()
1467         event.accept()
1468
1469
1470 class ElectrumGui:
1471
1472     def __init__(self, wallet, app=None):
1473         self.wallet = wallet
1474         if app is None:
1475             self.app = QApplication(sys.argv)
1476
1477     def server_list_changed(self):
1478         pass
1479
1480     def waiting_dialog(self):
1481
1482         s = Timer()
1483         s.start()
1484         w = QDialog()
1485         w.resize(200, 70)
1486         w.setWindowTitle('Electrum')
1487         l = QLabel('')
1488         vbox = QVBoxLayout()
1489         vbox.addWidget(l)
1490         w.setLayout(vbox)
1491         w.show()
1492         def f():
1493             if self.wallet.up_to_date: 
1494                 w.close()
1495             else:
1496                 l.setText("Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1497                               %(len(self.wallet.all_addresses()), self.wallet.interface.bytes_received/1024.))
1498
1499         w.connect(s, QtCore.SIGNAL('timersignal'), f)
1500         self.wallet.interface.poke()
1501         w.exec_()
1502         w.destroy()
1503
1504
1505     def restore_or_create(self):
1506
1507         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1508         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1509         if r==2: return False
1510         
1511         is_recovery = (r==1)
1512         wallet = self.wallet
1513         # ask for the server.
1514         if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1515
1516         if not is_recovery:
1517             wallet.new_seed(None)
1518             wallet.init_mpk( wallet.seed )
1519             wallet.up_to_date_event.clear()
1520             wallet.up_to_date = False
1521             self.waiting_dialog()
1522             # run a dialog indicating the seed, ask the user to remember it
1523             ElectrumWindow.show_seed_dialog(wallet)
1524             #ask for password
1525             ElectrumWindow.change_password_dialog(wallet)
1526         else:
1527             # ask for seed and gap.
1528             if not ElectrumWindow.seed_dialog( wallet ): return False
1529             wallet.init_mpk( wallet.seed )
1530             wallet.up_to_date_event.clear()
1531             wallet.up_to_date = False
1532             self.waiting_dialog()
1533             if wallet.is_found():
1534                 # history and addressbook
1535                 wallet.update_tx_history()
1536                 wallet.fill_addressbook()
1537                 print "Recovery successful"
1538                 wallet.save()
1539             else:
1540                 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1541
1542         wallet.save()
1543         return True
1544
1545     def main(self,url):
1546         s = Timer()
1547         s.start()
1548         w = ElectrumWindow(self.wallet)
1549         if url: w.set_url(url)
1550         w.app = self.app
1551         w.connect_slots(s)
1552         w.update_wallet()
1553         w.show()
1554
1555         self.app.exec_()