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