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