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