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