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