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