add confirmations to history checkmark tooltips
[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 import exchange_rate
42
43 from decimal import Decimal
44
45 import platform
46
47 if platform.system() == 'Windows':
48     MONOSPACE_FONT = 'Lucida Console'
49 elif platform.system() == 'Darwin':
50     MONOSPACE_FONT = 'Monaco'
51 else:
52     MONOSPACE_FONT = 'monospace'
53
54 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
55
56 def numbify(entry, is_int = False):
57     text = unicode(entry.text()).strip()
58     pos = entry.cursorPosition()
59     chars = '0123456789'
60     if not is_int: chars +='.'
61     s = ''.join([i for i in text if i in chars])
62     if not is_int:
63         if '.' in s:
64             p = s.find('.')
65             s = s.replace('.','')
66             s = s[:p] + '.' + s[p:p+8]
67         try:
68             amount = int( Decimal(s) * 100000000 )
69         except:
70             amount = None
71     else:
72         try:
73             amount = int( s )
74         except:
75             amount = None
76     entry.setText(s)
77     entry.setCursorPosition(pos)
78     return amount
79
80
81 class Timer(QtCore.QThread):
82     def run(self):
83         while True:
84             self.emit(QtCore.SIGNAL('timersignal'))
85             time.sleep(0.5)
86
87 class HelpButton(QPushButton):
88     def __init__(self, text):
89         QPushButton.__init__(self, '?')
90         self.setFocusPolicy(Qt.NoFocus)
91         self.setFixedWidth(20)
92         self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
93
94
95 class EnterButton(QPushButton):
96     def __init__(self, text, func):
97         QPushButton.__init__(self, text)
98         self.func = func
99         self.clicked.connect(func)
100
101     def keyPressEvent(self, e):
102         if e.key() == QtCore.Qt.Key_Return:
103             apply(self.func,())
104
105 class MyTreeWidget(QTreeWidget):
106     def __init__(self, parent):
107         QTreeWidget.__init__(self, parent)
108         def ddfr(item):
109             if not item: return
110             for i in range(0,self.viewport().height()/5):
111                 if self.itemAt(QPoint(0,i*5)) == item:
112                     break
113             else:
114                 return
115             for j in range(0,30):
116                 if self.itemAt(QPoint(0,i*5 + j)) != item:
117                     break
118             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
119
120         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
121         
122
123
124
125 class StatusBarButton(QPushButton):
126     def __init__(self, icon, tooltip, func):
127         QPushButton.__init__(self, icon, '')
128         self.setToolTip(tooltip)
129         self.setFlat(True)
130         self.setMaximumWidth(25)
131         self.clicked.connect(func)
132         self.func = func
133
134     def keyPressEvent(self, e):
135         if e.key() == QtCore.Qt.Key_Return:
136             apply(self.func,())
137
138
139 class QRCodeWidget(QWidget):
140
141     def __init__(self, data = None):
142         QWidget.__init__(self)
143         self.setMinimumSize(210, 210)
144         self.addr = None
145         self.qr = None
146         if data:
147             self.set_addr(data)
148             self.update_qr()
149
150     def set_addr(self, addr):
151         if self.addr != addr:
152             self.addr = addr
153             self.qr = None
154             self.update()
155
156     def update_qr(self):
157         if self.addr and not self.qr:
158             self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
159             self.qr.addData(self.addr)
160             self.qr.make()
161             self.update()
162
163     def paintEvent(self, e):
164
165         if not self.addr:
166             return
167
168         black = QColor(0, 0, 0, 255)
169         white = QColor(255, 255, 255, 255)
170
171         if not self.qr:
172             qp = QtGui.QPainter()
173             qp.begin(self)
174             qp.setBrush(white)
175             qp.setPen(white)
176             qp.drawRect(0, 0, 198, 198)
177             qp.end()
178             return
179  
180         k = self.qr.getModuleCount()
181         qp = QtGui.QPainter()
182         qp.begin(self)
183         r = qp.viewport()
184         boxsize = min(r.width(), r.height())*0.8/k
185         size = k*boxsize
186         left = (r.width() - size)/2
187         top = (r.height() - size)/2         
188
189         for r in range(k):
190             for c in range(k):
191                 if self.qr.isDark(r, c):
192                     qp.setBrush(black)
193                     qp.setPen(black)
194                 else:
195                     qp.setBrush(white)
196                     qp.setPen(white)
197                 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
198         qp.end()
199         
200
201
202 class QR_Window(QWidget):
203
204     def __init__(self):
205         QWidget.__init__(self)
206         self.setWindowTitle('Electrum - Invoice')
207         self.setMinimumSize(800, 250)
208         self.address = ''
209         self.labe = ''
210         self.amount = 0
211         self.setFocusPolicy(QtCore.Qt.NoFocus)
212
213         main_box = QHBoxLayout()
214         
215         self.qrw = QRCodeWidget()
216         main_box.addWidget(self.qrw, 1)
217
218         vbox = QVBoxLayout()
219         main_box.addLayout(vbox)
220
221         self.address_label = QLabel("")
222         self.address_label.setFont(QFont(MONOSPACE_FONT))
223         vbox.addWidget(self.address_label)
224
225         self.label_label = QLabel("")
226         vbox.addWidget(self.label_label)
227
228         self.amount_label = QLabel("")
229         vbox.addWidget(self.amount_label)
230
231         vbox.addStretch(1)
232         self.setLayout(main_box)
233
234
235     def set_content(self, addr, label, amount):
236         self.address = addr
237         address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
238         self.address_label.setText(address_text)
239
240         self.amount = amount
241         amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
242         self.amount_label.setText(amount_text)
243
244         self.label = label
245         label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
246         self.label_label.setText(label_text)
247
248         msg = 'bitcoin:'+self.address
249         if self.amount is not None:
250             msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
251             if self.label is not None:
252                 msg += '&label=%s'%(self.label)
253         elif self.label is not None:
254             msg += '?label=%s'%(self.label)
255             
256         self.qrw.set_addr( msg )
257
258             
259
260
261 def waiting_dialog(f):
262
263     s = Timer()
264     s.start()
265     w = QDialog()
266     w.resize(200, 70)
267     w.setWindowTitle('Electrum')
268     l = QLabel('')
269     vbox = QVBoxLayout()
270     vbox.addWidget(l)
271     w.setLayout(vbox)
272     w.show()
273     def ff():
274         s = f()
275         if s: l.setText(s)
276         else: w.close()
277     w.connect(s, QtCore.SIGNAL('timersignal'), ff)
278     w.exec_()
279     w.destroy()
280
281
282 def ok_cancel_buttons(dialog):
283     hbox = QHBoxLayout()
284     hbox.addStretch(1)
285     b = QPushButton("OK")
286     hbox.addWidget(b)
287     b.clicked.connect(dialog.accept)
288     b = QPushButton("Cancel")
289     hbox.addWidget(b)
290     b.clicked.connect(dialog.reject)
291     return hbox
292
293
294 class ElectrumWindow(QMainWindow):
295
296     def __init__(self, wallet, config):
297         QMainWindow.__init__(self)
298         self.wallet = wallet
299         self.config = config
300         self.wallet.interface.register_callback('updated', self.update_callback)
301         self.wallet.interface.register_callback('connected', self.update_callback)
302         self.wallet.interface.register_callback('disconnected', self.update_callback)
303         self.wallet.interface.register_callback('disconnecting', self.update_callback)
304
305         self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
306         self.merchant_name = config.get('merchant_name', 'Invoice')
307
308         self.qr_window = None
309         self.funds_error = False
310         self.completions = QStringListModel()
311
312         self.tabs = tabs = QTabWidget(self)
313         tabs.addTab(self.create_history_tab(), _('History') )
314         tabs.addTab(self.create_send_tab(), _('Send') )
315         tabs.addTab(self.create_receive_tab(), _('Receive') )
316         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
317         tabs.addTab(self.create_wall_tab(), _('Wall') )
318         tabs.setMinimumSize(600, 400)
319         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
320         self.setCentralWidget(tabs)
321         self.create_status_bar()
322         self.toggle_QR_window(self.receive_tab_mode == 2)
323
324         g = self.config.get("winpos-qt",[100, 100, 840, 400])
325         self.setGeometry(g[0], g[1], g[2], g[3])
326         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
327         if not self.wallet.seed: title += ' [seedless]'
328         self.setWindowTitle( title )
329
330         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
331         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
332         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
333         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
334         
335         self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
336         #self.connect(self, SIGNAL('editamount'), self.edit_amount)
337         self.history_list.setFocus(True)
338         
339         self.exchanger = exchange_rate.Exchanger(self)
340         self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
341
342         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
343         if platform.system() == 'Windows':
344             n = 3 if self.wallet.seed else 2
345             tabs.setCurrentIndex (n)
346             tabs.setCurrentIndex (0)
347
348     def close(self):
349         QMainWindow.close(self)
350         if self.qr_window: 
351             self.qr_window.close()
352             self.qr_window = None
353
354     def connect_slots(self, sender):
355         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
356         self.previous_payto_e=''
357
358     def timer_actions(self):
359         if self.qr_window:
360             self.qr_window.qrw.update_qr()
361             
362         if self.payto_e.hasFocus():
363             return
364         r = unicode( self.payto_e.text() )
365         if r != self.previous_payto_e:
366             self.previous_payto_e = r
367             r = r.strip()
368             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
369                 try:
370                     to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
371                 except:
372                     return
373                 if to_address:
374                     s = r + '  <' + to_address + '>'
375                     self.payto_e.setText(s)
376
377
378     def update_callback(self):
379         self.emit(QtCore.SIGNAL('updatesignal'))
380
381     def update_wallet(self):
382         if self.wallet.interface and self.wallet.interface.is_connected:
383             if not self.wallet.up_to_date:
384                 text = _( "Synchronizing..." )
385                 icon = QIcon(":icons/status_waiting.png")
386             else:
387                 c, u = self.wallet.get_balance()
388                 text =  _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
389                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
390                 text += self.create_quote_text(Decimal(c+u)/100000000)
391                 icon = QIcon(":icons/status_connected.png")
392         else:
393             text = _( "Not connected" )
394             icon = QIcon(":icons/status_disconnected.png")
395
396         if self.funds_error:
397             text = _( "Not enough funds" )
398
399         self.statusBar().showMessage(text)
400         self.status_button.setIcon( icon )
401
402         if self.wallet.up_to_date or not self.wallet.interface.is_connected:
403             self.textbox.setText( self.wallet.banner )
404             self.update_history_tab()
405             self.update_receive_tab()
406             self.update_contacts_tab()
407             self.update_completions()
408
409     def create_quote_text(self, btc_balance):
410         quote_currency = self.config.get("currency", "None")
411         quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
412         if quote_balance is None:
413             quote_text = ""
414         else:
415             quote_text = "  (%.2f %s)" % (quote_balance, quote_currency)
416         return quote_text
417         
418     def create_history_tab(self):
419         self.history_list = l = MyTreeWidget(self)
420         l.setColumnCount(5)
421         l.setColumnWidth(0, 40) 
422         l.setColumnWidth(1, 140) 
423         l.setColumnWidth(2, 350) 
424         l.setColumnWidth(3, 140) 
425         l.setColumnWidth(4, 140) 
426         l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
427         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
428         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
429
430         l.setContextMenuPolicy(Qt.CustomContextMenu)
431         l.customContextMenuRequested.connect(self.create_history_menu)
432         return l
433
434
435     def create_history_menu(self, position):
436         self.history_list.selectedIndexes() 
437         item = self.history_list.currentItem()
438         if not item: return
439         tx_hash = str(item.data(0, Qt.UserRole).toString())
440         if not tx_hash: return
441         menu = QMenu()
442         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
443         menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
444         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
445         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
446
447
448     def tx_details(self, tx_hash):
449         tx_details = self.wallet.get_tx_details(tx_hash)
450         QMessageBox.information(self, 'Details', tx_details, 'OK')
451
452
453     def tx_label_clicked(self, item, column):
454         if column==2 and item.isSelected():
455             tx_hash = str(item.toolTip(0))
456             self.is_edit=True
457             #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
458             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
459             self.history_list.editItem( item, column )
460             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
461             self.is_edit=False
462
463     def tx_label_changed(self, item, column):
464         if self.is_edit: 
465             return
466         self.is_edit=True
467         tx_hash = str(item.toolTip(0))
468         tx = self.wallet.transactions.get(tx_hash)
469         s = self.wallet.labels.get(tx_hash)
470         text = unicode( item.text(2) )
471         if text: 
472             self.wallet.labels[tx_hash] = text
473             item.setForeground(2, QBrush(QColor('black')))
474         else:
475             if s: self.wallet.labels.pop(tx_hash)
476             text = self.wallet.get_default_label(tx_hash)
477             item.setText(2, text)
478             item.setForeground(2, QBrush(QColor('gray')))
479         self.is_edit=False
480
481
482     def edit_label(self, is_recv):
483         l = self.receive_list if is_recv else self.contacts_list
484         c = 2 if is_recv else 1
485         item = l.currentItem()
486         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
487         l.editItem( item, c )
488         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
489
490     def edit_amount(self):
491         l = self.receive_list
492         item = l.currentItem()
493         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
494         l.editItem( item, 3 )
495         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
496
497
498     def address_label_clicked(self, item, column, l, column_addr, column_label):
499         if column == column_label and item.isSelected():
500             addr = unicode( item.text(column_addr) )
501             label = unicode( item.text(column_label) )
502             if label in self.wallet.aliases.keys():
503                 return
504             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
505             l.editItem( item, column )
506             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
507
508
509     def address_label_changed(self, item, column, l, column_addr, column_label):
510
511         if column == column_label:
512             addr = unicode( item.text(column_addr) )
513             text = unicode( item.text(column_label) )
514             changed = False
515
516             if text:
517                 if text not in self.wallet.aliases.keys():
518                     old_addr = self.wallet.labels.get(text)
519                     if old_addr != addr:
520                         self.wallet.labels[addr] = text
521                         changed = True
522                 else:
523                     print_error("Error: This is one of your aliases")
524                     label = self.wallet.labels.get(addr,'')
525                     item.setText(column_label, QString(label))
526             else:
527                 s = self.wallet.labels.get(addr)
528                 if s: 
529                     self.wallet.labels.pop(addr)
530                     changed = True
531
532             if changed:
533                 self.update_history_tab()
534                 self.update_completions()
535                 
536             self.recv_changed(item)
537
538         if column == 3:
539             address = unicode( item.text(column_addr) )
540             text = unicode( item.text(3) )
541             try:
542                 index = self.wallet.addresses.index(address)
543             except:
544                 return
545
546             try:
547                 amount = int( Decimal(text) * 100000000 )
548                 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
549             except:
550                 amount = self.wallet.requested_amounts.get(address)
551                 if amount: 
552                     item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
553                 else:
554                     item.setText(3,"")
555                 return
556
557             self.wallet.requested_amounts[address] = amount
558
559             label = self.wallet.labels.get(address)
560             if label is None:
561                 label = self.merchant_name + ' - %04d'%(index+1)
562                 self.wallet.labels[address] = label
563
564             self.update_receive_item(self.receive_list.currentItem())
565             if self.qr_window:
566                 self.qr_window.set_content( address, label, amount )
567
568
569     def recv_changed(self, a):
570         "current item changed"
571         if a is not None and self.qr_window and self.qr_window.isVisible():
572             address = str(a.text(1))
573             label = self.wallet.labels.get(address)
574             amount = self.wallet.requested_amounts.get(address)
575             self.qr_window.set_content( address, label, amount )
576
577
578     def update_history_tab(self):
579
580         self.history_list.clear()
581         for item in self.wallet.get_tx_history():
582             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
583             if conf:
584                 try:
585                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
586                 except:
587                     time_str = "unknown"
588                 if conf == -1:
589                     icon = None
590                 if conf == 0:
591                     icon = QIcon(":icons/unconfirmed.png")
592                 elif conf < 6:
593                     icon = QIcon(":icons/clock%d.png"%conf)
594                 else:
595                     icon = QIcon(":icons/confirmed.png")
596             else:
597                 time_str = 'pending'
598                 icon = QIcon(":icons/unconfirmed.png")
599
600             if value is not None:
601                 v_str = format_satoshis(value, True, self.wallet.num_zeros)
602             else:
603                 v_str = '--'
604
605             balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
606             
607             if tx_hash:
608                 label, is_default_label = self.wallet.get_label(tx_hash)
609             else:
610                 label = _('Pruned transaction outputs')
611                 is_default_label = False
612
613             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
614             item.setFont(2, QFont(MONOSPACE_FONT))
615             item.setFont(3, QFont(MONOSPACE_FONT))
616             item.setFont(4, QFont(MONOSPACE_FONT))
617             if value < 0:
618                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
619             if tx_hash:
620                 item.setData(0, Qt.UserRole, tx_hash)
621                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
622             if is_default_label:
623                 item.setForeground(2, QBrush(QColor('grey')))
624
625             item.setIcon(0, icon)
626             self.history_list.insertTopLevelItem(0,item)
627             
628
629         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
630
631
632     def create_send_tab(self):
633         w = QWidget()
634
635         grid = QGridLayout()
636         grid.setSpacing(8)
637         grid.setColumnMinimumWidth(3,300)
638         grid.setColumnStretch(5,1)
639
640         self.payto_e = QLineEdit()
641         grid.addWidget(QLabel(_('Pay to')), 1, 0)
642         grid.addWidget(self.payto_e, 1, 1, 1, 3)
643         
644         def fill_from_qr():
645             qrcode = qrscanner.scan_qr()
646             if 'address' in qrcode:
647                 self.payto_e.setText(qrcode['address'])
648             if 'amount' in qrcode:
649                 self.amount_e.setText(str(qrcode['amount']))
650             if 'label' in qrcode:
651                 self.message_e.setText(qrcode['label'])
652             if 'message' in qrcode:
653                 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
654                 
655
656         if qrscanner.is_available():
657             b = QPushButton(_("Scan QR code"))
658             b.clicked.connect(fill_from_qr)
659             grid.addWidget(b, 1, 5)
660     
661         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)
662
663         completer = QCompleter()
664         completer.setCaseSensitivity(False)
665         self.payto_e.setCompleter(completer)
666         completer.setModel(self.completions)
667
668         self.message_e = QLineEdit()
669         grid.addWidget(QLabel(_('Description')), 2, 0)
670         grid.addWidget(self.message_e, 2, 1, 1, 3)
671         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)
672
673         self.amount_e = QLineEdit()
674         grid.addWidget(QLabel(_('Amount')), 3, 0)
675         grid.addWidget(self.amount_e, 3, 1, 1, 2)
676         grid.addWidget(HelpButton(
677                 _('Amount to be sent.') + '\n\n' \
678                     + _('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)
679         
680         self.fee_e = QLineEdit()
681         grid.addWidget(QLabel(_('Fee')), 4, 0)
682         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
683         grid.addWidget(HelpButton(
684                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
685                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
686                     + _('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)
687         
688         b = EnterButton(_("Send"), self.do_send)
689         grid.addWidget(b, 6, 1)
690
691         b = EnterButton(_("Clear"),self.do_clear)
692         grid.addWidget(b, 6, 2)
693
694         self.payto_sig = QLabel('')
695         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
696
697         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
698         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
699         w.setLayout(grid) 
700
701         w2 = QWidget()
702         vbox = QVBoxLayout()
703         vbox.addWidget(w)
704         vbox.addStretch(1)
705         w2.setLayout(vbox)
706
707         def entry_changed( is_fee ):
708             self.funds_error = False
709             amount = numbify(self.amount_e)
710             fee = numbify(self.fee_e)
711             if not is_fee: fee = None
712             if amount is None:
713                 return
714             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
715             if not is_fee:
716                 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
717             if inputs:
718                 palette = QPalette()
719                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
720             else:
721                 palette = QPalette()
722                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
723                 self.funds_error = True
724             self.amount_e.setPalette(palette)
725             self.fee_e.setPalette(palette)
726             self.update_wallet()
727
728         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
729         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
730
731         return w2
732
733
734     def update_completions(self):
735         l = []
736         for addr,label in self.wallet.labels.items():
737             if addr in self.wallet.addressbook:
738                 l.append( label + '  <' + addr + '>')
739         l = l + self.wallet.aliases.keys()
740
741         self.completions.setStringList(l)
742
743
744
745     def do_send(self):
746
747         label = unicode( self.message_e.text() )
748         r = unicode( self.payto_e.text() )
749         r = r.strip()
750
751         # alias
752         m1 = re.match(ALIAS_REGEXP, r)
753         # label or alias, with address in brackets
754         m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
755         
756         if m1:
757             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
758             if not to_address:
759                 return
760         elif m2:
761             to_address = m2.group(2)
762         else:
763             to_address = r
764
765         if not self.wallet.is_valid(to_address):
766             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
767             return
768
769         try:
770             amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
771         except:
772             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
773             return
774         try:
775             fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
776         except:
777             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
778             return
779
780         if self.wallet.use_encryption:
781             password = self.password_dialog()
782             if not password:
783                 return
784         else:
785             password = None
786
787         try:
788             tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
789         except BaseException, e:
790             self.show_message(str(e))
791             return
792
793         if self.wallet.seed:
794             h = self.wallet.send_tx(tx)
795             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
796             status, msg = self.wallet.receive_tx( h )
797             if status:
798                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
799                 self.do_clear()
800                 self.update_contacts_tab()
801             else:
802                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
803         else:
804             filename = 'unsigned_tx'
805             f = open(filename,'w')
806             f.write(tx)
807             f.close()
808             QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
809
810
811     def set_url(self, url):
812         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
813         self.tabs.setCurrentIndex(1)
814         label = self.wallet.labels.get(payto)
815         m_addr = label + '  <'+ payto+'>' if label else payto
816         self.payto_e.setText(m_addr)
817
818         self.message_e.setText(message)
819         self.amount_e.setText(amount)
820         if identity:
821             self.set_frozen(self.payto_e,True)
822             self.set_frozen(self.amount_e,True)
823             self.set_frozen(self.message_e,True)
824             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
825         else:
826             self.payto_sig.setVisible(False)
827
828     def do_clear(self):
829         self.payto_sig.setVisible(False)
830         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
831             e.setText('')
832             self.set_frozen(e,False)
833
834     def set_frozen(self,entry,frozen):
835         if frozen:
836             entry.setReadOnly(True)
837             entry.setFrame(False)
838             palette = QPalette()
839             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
840             entry.setPalette(palette)
841         else:
842             entry.setReadOnly(False)
843             entry.setFrame(True)
844             palette = QPalette()
845             palette.setColor(entry.backgroundRole(), QColor('white'))
846             entry.setPalette(palette)
847
848
849     def toggle_freeze(self,addr):
850         if not addr: return
851         if addr in self.wallet.frozen_addresses:
852             self.wallet.unfreeze(addr)
853         else:
854             self.wallet.freeze(addr)
855         self.update_receive_tab()
856
857     def toggle_priority(self,addr):
858         if not addr: return
859         if addr in self.wallet.prioritized_addresses:
860             self.wallet.unprioritize(addr)
861         else:
862             self.wallet.prioritize(addr)
863         self.update_receive_tab()
864
865
866     def create_list_tab(self, headers):
867         "generic tab creation method"
868         l = MyTreeWidget(self)
869         l.setColumnCount( len(headers) )
870         l.setHeaderLabels( headers )
871
872         w = QWidget()
873         vbox = QVBoxLayout()
874         w.setLayout(vbox)
875
876         vbox.setMargin(0)
877         vbox.setSpacing(0)
878         vbox.addWidget(l)
879         buttons = QWidget()
880         vbox.addWidget(buttons)
881
882         hbox = QHBoxLayout()
883         hbox.setMargin(0)
884         hbox.setSpacing(0)
885         buttons.setLayout(hbox)
886
887         return l,w,hbox
888
889
890     def create_receive_tab(self):
891         l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
892         l.setContextMenuPolicy(Qt.CustomContextMenu)
893         l.customContextMenuRequested.connect(self.create_receive_menu)
894         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
895         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
896         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
897         self.receive_list = l
898         self.receive_buttons_hbox = hbox
899         hbox.addStretch(1)
900         return w
901
902
903
904     def receive_tab_set_mode(self, i):
905         self.receive_tab_mode = i
906         self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
907         self.wallet.save()
908         self.update_receive_tab()
909         self.toggle_QR_window(self.receive_tab_mode == 2)
910
911
912     def create_contacts_tab(self):
913         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
914         l.setContextMenuPolicy(Qt.CustomContextMenu)
915         l.customContextMenuRequested.connect(self.create_contact_menu)
916         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
917         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
918         self.contacts_list = l
919         self.contacts_buttons_hbox = hbox
920         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
921         hbox.addStretch(1)
922         return w
923
924
925     def create_receive_menu(self, position):
926         # fixme: this function apparently has a side effect.
927         # if it is not called the menu pops up several times
928         #self.receive_list.selectedIndexes() 
929
930         item = self.receive_list.itemAt(position)
931         if not item: return
932         addr = unicode(item.text(1))
933         menu = QMenu()
934         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
935         if self.receive_tab_mode == 2:
936             menu.addAction(_("Request amount"), lambda: self.edit_amount())
937         menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
938         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
939         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
940
941         if self.receive_tab_mode == 1:
942             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
943             menu.addAction(t, lambda: self.toggle_freeze(addr))
944             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
945             menu.addAction(t, lambda: self.toggle_priority(addr))
946             
947         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
948
949
950     def payto(self, x, is_alias):
951         if not x: return
952         if is_alias:
953             label = x
954             m_addr = label
955         else:
956             addr = x
957             label = self.wallet.labels.get(addr)
958             m_addr = label + '  <' + addr + '>' if label else addr
959         self.tabs.setCurrentIndex(1)
960         self.payto_e.setText(m_addr)
961         self.amount_e.setFocus()
962
963     def delete_contact(self, x, is_alias):
964         if self.question("Do you want to remove %s from your list of contacts?"%x):
965             if not is_alias and x in self.wallet.addressbook:
966                 self.wallet.addressbook.remove(x)
967                 if x in self.wallet.labels.keys():
968                     self.wallet.labels.pop(x)
969             elif is_alias and x in self.wallet.aliases:
970                 self.wallet.aliases.pop(x)
971             self.update_history_tab()
972             self.update_contacts_tab()
973             self.update_completions()
974
975     def create_contact_menu(self, position):
976         # fixme: this function apparently has a side effect.
977         # if it is not called the menu pops up several times
978         #self.contacts_list.selectedIndexes() 
979
980         item = self.contacts_list.itemAt(position)
981         if not item: return
982         addr = unicode(item.text(0))
983         label = unicode(item.text(1))
984         is_alias = label in self.wallet.aliases.keys()
985         x = label if is_alias else addr
986         menu = QMenu()
987         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
988         menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
989         menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
990         if not is_alias:
991             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
992         else:
993             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
994         menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
995         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
996
997
998     def update_receive_item(self, item):
999         address = str( item.data(1,0).toString() )
1000
1001         flags = self.wallet.get_address_flags(address)
1002         item.setData(0,0,flags)
1003
1004         label = self.wallet.labels.get(address,'')
1005         item.setData(2,0,label)
1006
1007         amount = self.wallet.requested_amounts.get(address,None)
1008         amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None  else ""
1009         item.setData(3,0,amount_str)
1010         
1011         c, u = self.wallet.get_addr_balance(address)
1012         balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1013         item.setData(4,0,balance)
1014
1015         if self.receive_tab_mode == 1:
1016             if address in self.wallet.frozen_addresses: 
1017                 item.setBackgroundColor(1, QColor('lightblue'))
1018             elif address in self.wallet.prioritized_addresses: 
1019                 item.setBackgroundColor(1, QColor('lightgreen'))
1020         
1021
1022     def update_receive_tab(self):
1023         l = self.receive_list
1024         
1025         l.clear()
1026         l.setColumnHidden(0, not self.receive_tab_mode == 1)
1027         l.setColumnHidden(3, not self.receive_tab_mode == 2)
1028         l.setColumnHidden(4, self.receive_tab_mode == 0)
1029         l.setColumnHidden(5, not self.receive_tab_mode == 1)
1030         l.setColumnWidth(0, 50)
1031         l.setColumnWidth(1, 310) 
1032         l.setColumnWidth(2, 200)
1033         l.setColumnWidth(3, 130)
1034         l.setColumnWidth(4, 130)
1035         l.setColumnWidth(5, 10)
1036
1037         gap = 0
1038         is_red = False
1039         for address in self.wallet.all_addresses():
1040
1041             if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1042                 continue
1043
1044             n = 0 
1045             h = self.wallet.history.get(address,[])
1046
1047             if h != ['*']: 
1048                 for tx_hash, tx_height in h:
1049                     tx = self.wallet.transactions.get(tx_hash)
1050                     if tx: n += 1
1051                 num_tx = "%d "%n
1052             else:
1053                 n = -1
1054                 num_tx = "*"
1055
1056             if n==0:
1057                 if address in self.wallet.addresses:
1058                     gap += 1
1059                     if gap > self.wallet.gap_limit:
1060                         is_red = True
1061             else:
1062                 if address in self.wallet.addresses:
1063                     gap = 0
1064
1065             item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1066             item.setFont(0, QFont(MONOSPACE_FONT))
1067             item.setFont(1, QFont(MONOSPACE_FONT))
1068             item.setFont(3, QFont(MONOSPACE_FONT))
1069             self.update_receive_item(item)
1070             if is_red and address in self.wallet.addresses:
1071                 item.setBackgroundColor(1, QColor('red'))
1072             l.addTopLevelItem(item)
1073
1074         # we use column 1 because column 0 may be hidden
1075         l.setCurrentItem(l.topLevelItem(0),1)
1076
1077     def show_contact_details(self, m):
1078         a = self.wallet.aliases.get(m)
1079         if a:
1080             if a[0] in self.wallet.authorities.keys():
1081                 s = self.wallet.authorities.get(a[0])
1082             else:
1083                 s = "self-signed"
1084             msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1085             QMessageBox.information(self, 'Alias', msg, 'OK')
1086
1087     def update_contacts_tab(self):
1088
1089         l = self.contacts_list
1090         l.clear()
1091         l.setColumnWidth(0, 350) 
1092         l.setColumnWidth(1, 330)
1093         l.setColumnWidth(2, 100) 
1094
1095         alias_targets = []
1096         for alias, v in self.wallet.aliases.items():
1097             s, target = v
1098             alias_targets.append(target)
1099             item = QTreeWidgetItem( [ target, alias, '-'] )
1100             item.setBackgroundColor(0, QColor('lightgray'))
1101             l.addTopLevelItem(item)
1102             
1103         for address in self.wallet.addressbook:
1104             if address in alias_targets: continue
1105             label = self.wallet.labels.get(address,'')
1106             n = 0 
1107             for item in self.wallet.transactions.values():
1108                 if address in item['outputs'] : n=n+1
1109             tx = "%d"%n
1110             item = QTreeWidgetItem( [ address, label, tx] )
1111             item.setFont(0, QFont(MONOSPACE_FONT))
1112             l.addTopLevelItem(item)
1113
1114         l.setCurrentItem(l.topLevelItem(0))
1115
1116     def create_wall_tab(self):
1117         self.textbox = textbox = QTextEdit(self)
1118         textbox.setFont(QFont(MONOSPACE_FONT))
1119         textbox.setReadOnly(True)
1120         return textbox
1121
1122     def create_status_bar(self):
1123         sb = QStatusBar()
1124         sb.setFixedHeight(35)
1125         if self.wallet.seed:
1126             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1127         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1128         if self.wallet.seed:
1129             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1130         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) 
1131         sb.addPermanentWidget( self.status_button )
1132         self.setStatusBar(sb)
1133
1134     def new_contact_dialog(self):
1135         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1136         address = unicode(text)
1137         if ok:
1138             if self.wallet.is_valid(address):
1139                 self.wallet.addressbook.append(address)
1140                 self.wallet.save()
1141                 self.update_contacts_tab()
1142                 self.update_history_tab()
1143                 self.update_completions()
1144             else:
1145                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1146
1147     @staticmethod
1148     def show_seed_dialog(wallet, parent=None):
1149         if not wallet.seed:
1150             QMessageBox.information(parent, _('Message'),
1151                                     _('No seed'), _('OK'))
1152             return
1153
1154         if wallet.use_encryption:
1155             password = parent.password_dialog()
1156             if not password:
1157                 return
1158         else:
1159             password = None
1160             
1161         try:
1162             seed = wallet.pw_decode(wallet.seed, password)
1163         except:
1164             QMessageBox.warning(parent, _('Error'),
1165                                 _('Incorrect Password'), _('OK'))
1166             return
1167
1168         dialog = QDialog(None)
1169         dialog.setModal(1)
1170         dialog.setWindowTitle("Electrum")
1171
1172         brainwallet = ' '.join(mnemonic.mn_encode(seed))
1173
1174         msg =   _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1175               + _("Please write down or memorize these 12 words (order is important).") + " " \
1176               + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1177               + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1178
1179         main_text = QLabel(msg)
1180         main_text.setWordWrap(True)
1181
1182         logo = QLabel()
1183         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1184
1185         if parent:
1186             app = parent.app
1187         else:
1188             app = QApplication
1189
1190         copy_function = lambda: app.clipboard().setText(brainwallet)
1191         copy_button = QPushButton(_("Copy to Clipboard"))
1192         copy_button.clicked.connect(copy_function)
1193
1194         show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1195         qr_button = QPushButton(_("View as QR Code"))
1196         qr_button.clicked.connect(show_qr_function)
1197
1198         ok_button = QPushButton(_("OK"))
1199         ok_button.setDefault(True)
1200         ok_button.clicked.connect(dialog.accept)
1201
1202         main_layout = QGridLayout()
1203         main_layout.addWidget(logo, 0, 0)
1204         main_layout.addWidget(main_text, 0, 1, 1, -1)
1205         main_layout.addWidget(copy_button, 1, 1)
1206         main_layout.addWidget(qr_button, 1, 2)
1207         main_layout.addWidget(ok_button, 1, 3)
1208         dialog.setLayout(main_layout)
1209
1210         dialog.exec_()
1211
1212     @staticmethod
1213     def show_qrcode(title, data):
1214         if not data: return
1215         d = QDialog(None)
1216         d.setModal(1)
1217         d.setWindowTitle(title)
1218         d.setMinimumSize(270, 300)
1219         vbox = QVBoxLayout()
1220         qrw = QRCodeWidget(data)
1221         vbox.addWidget(qrw, 1)
1222         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1223         hbox = QHBoxLayout()
1224         hbox.addStretch(1)
1225
1226         def print_qr(self):
1227             filename = "qrcode.bmp"
1228             bmp.save_qrcode(qrw.qr, filename)
1229             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1230
1231         b = QPushButton(_("Print"))
1232         hbox.addWidget(b)
1233         b.clicked.connect(print_qr)
1234
1235         b = QPushButton(_("Close"))
1236         hbox.addWidget(b)
1237         b.clicked.connect(d.accept)
1238
1239         vbox.addLayout(hbox)
1240         d.setLayout(vbox)
1241         d.exec_()
1242
1243     def sign_message(self,address):
1244         if not address: return
1245         d = QDialog(self)
1246         d.setModal(1)
1247         d.setWindowTitle('Sign Message')
1248         d.setMinimumSize(270, 350)
1249
1250         tab_widget = QTabWidget()
1251         tab = QWidget()
1252         layout = QGridLayout(tab)
1253
1254         sign_address = QLineEdit()
1255         sign_address.setText(address)
1256         layout.addWidget(QLabel(_('Address')), 1, 0)
1257         layout.addWidget(sign_address, 1, 1)
1258
1259         sign_message = QTextEdit()
1260         layout.addWidget(QLabel(_('Message')), 2, 0)
1261         layout.addWidget(sign_message, 2, 1, 2, 1)
1262
1263         sign_signature = QLineEdit()
1264         layout.addWidget(QLabel(_('Signature')), 3, 0)
1265         layout.addWidget(sign_signature, 3, 1)
1266
1267         def do_sign():
1268             if self.wallet.use_encryption:
1269                 password = self.password_dialog()
1270                 if not password:
1271                     return
1272             else:
1273                 password = None
1274
1275             try:
1276                 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1277                 sign_signature.setText(signature)
1278             except BaseException, e:
1279                 self.show_message(str(e))
1280                 return
1281
1282         hbox = QHBoxLayout()
1283         b = QPushButton(_("Sign"))
1284         hbox.addWidget(b)
1285         b.clicked.connect(do_sign)
1286         b = QPushButton(_("Close"))
1287         b.clicked.connect(d.accept)
1288         hbox.addWidget(b)
1289         layout.addLayout(hbox, 4, 1)
1290         tab_widget.addTab(tab, "Sign")
1291
1292
1293         tab = QWidget()
1294         layout = QGridLayout(tab)
1295
1296         verify_address = QLineEdit()
1297         layout.addWidget(QLabel(_('Address')), 1, 0)
1298         layout.addWidget(verify_address, 1, 1)
1299
1300         verify_message = QTextEdit()
1301         layout.addWidget(QLabel(_('Message')), 2, 0)
1302         layout.addWidget(verify_message, 2, 1, 2, 1)
1303
1304         verify_signature = QLineEdit()
1305         layout.addWidget(QLabel(_('Signature')), 3, 0)
1306         layout.addWidget(verify_signature, 3, 1)
1307
1308         def do_verify():
1309             try:
1310                 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1311                 self.show_message("Signature verified")
1312             except BaseException, e:
1313                 self.show_message(str(e))
1314                 return
1315
1316         hbox = QHBoxLayout()
1317         b = QPushButton(_("Verify"))
1318         b.clicked.connect(do_verify)
1319         hbox.addWidget(b)
1320         b = QPushButton(_("Close"))
1321         b.clicked.connect(d.accept)
1322         hbox.addWidget(b)
1323         layout.addLayout(hbox, 4, 1)
1324         tab_widget.addTab(tab, "Verify")
1325
1326         vbox = QVBoxLayout()
1327         vbox.addWidget(tab_widget)
1328         d.setLayout(vbox)
1329         d.exec_()
1330
1331         
1332     def toggle_QR_window(self, show):
1333         if show and not self.qr_window:
1334             self.qr_window = QR_Window()
1335             self.qr_window.setVisible(True)
1336             self.qr_window_geometry = self.qr_window.geometry()
1337             item = self.receive_list.currentItem()
1338             if item:
1339                 address = str(item.text(1))
1340                 label = self.wallet.labels.get(address)
1341                 amount = self.wallet.requested_amounts.get(address)
1342                 self.qr_window.set_content( address, label, amount )
1343
1344         elif show and self.qr_window and not self.qr_window.isVisible():
1345             self.qr_window.setVisible(True)
1346             self.qr_window.setGeometry(self.qr_window_geometry)
1347
1348         elif not show and self.qr_window and self.qr_window.isVisible():
1349             self.qr_window_geometry = self.qr_window.geometry()
1350             self.qr_window.setVisible(False)
1351
1352         #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1353         self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1354         self.receive_list.setColumnWidth(2, 200)
1355
1356
1357     def question(self, msg):
1358         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1359
1360     def show_message(self, msg):
1361         QMessageBox.information(self, _('Message'), msg, _('OK'))
1362
1363     def password_dialog(self ):
1364         d = QDialog(self)
1365         d.setModal(1)
1366
1367         pw = QLineEdit()
1368         pw.setEchoMode(2)
1369
1370         vbox = QVBoxLayout()
1371         msg = _('Please enter your password')
1372         vbox.addWidget(QLabel(msg))
1373
1374         grid = QGridLayout()
1375         grid.setSpacing(8)
1376         grid.addWidget(QLabel(_('Password')), 1, 0)
1377         grid.addWidget(pw, 1, 1)
1378         vbox.addLayout(grid)
1379
1380         vbox.addLayout(ok_cancel_buttons(d))
1381         d.setLayout(vbox) 
1382
1383         if not d.exec_(): return
1384         return unicode(pw.text())
1385
1386
1387
1388
1389
1390     @staticmethod
1391     def change_password_dialog( wallet, parent=None ):
1392
1393         if not wallet.seed:
1394             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1395             return
1396
1397         d = QDialog(parent)
1398         d.setModal(1)
1399
1400         pw = QLineEdit()
1401         pw.setEchoMode(2)
1402         new_pw = QLineEdit()
1403         new_pw.setEchoMode(2)
1404         conf_pw = QLineEdit()
1405         conf_pw.setEchoMode(2)
1406
1407         vbox = QVBoxLayout()
1408         if parent:
1409             msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1410                    +_('To disable wallet encryption, enter an empty new password.')) \
1411                    if wallet.use_encryption else _('Your wallet keys are not encrypted')
1412         else:
1413             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1414                   +_("Leave these fields empty if you want to disable encryption.")
1415         vbox.addWidget(QLabel(msg))
1416
1417         grid = QGridLayout()
1418         grid.setSpacing(8)
1419
1420         if wallet.use_encryption:
1421             grid.addWidget(QLabel(_('Password')), 1, 0)
1422             grid.addWidget(pw, 1, 1)
1423
1424         grid.addWidget(QLabel(_('New Password')), 2, 0)
1425         grid.addWidget(new_pw, 2, 1)
1426
1427         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1428         grid.addWidget(conf_pw, 3, 1)
1429         vbox.addLayout(grid)
1430
1431         vbox.addLayout(ok_cancel_buttons(d))
1432         d.setLayout(vbox) 
1433
1434         if not d.exec_(): return
1435
1436         password = unicode(pw.text()) if wallet.use_encryption else None
1437         new_password = unicode(new_pw.text())
1438         new_password2 = unicode(conf_pw.text())
1439
1440         try:
1441             seed = wallet.pw_decode( wallet.seed, password)
1442         except:
1443             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1444             return
1445
1446         if new_password != new_password2:
1447             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1448             return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1449
1450         wallet.update_password(seed, password, new_password)
1451
1452     @staticmethod
1453     def seed_dialog(wallet, parent=None):
1454         d = QDialog(parent)
1455         d.setModal(1)
1456
1457         vbox = QVBoxLayout()
1458         msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1459         vbox.addWidget(QLabel(msg))
1460
1461         grid = QGridLayout()
1462         grid.setSpacing(8)
1463
1464         seed_e = QLineEdit()
1465         grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1466         grid.addWidget(seed_e, 1, 1)
1467
1468         gap_e = QLineEdit()
1469         gap_e.setText("5")
1470         grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1471         grid.addWidget(gap_e, 2, 1)
1472         gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1473         vbox.addLayout(grid)
1474
1475         vbox.addLayout(ok_cancel_buttons(d))
1476         d.setLayout(vbox) 
1477
1478         if not d.exec_(): return
1479
1480         try:
1481             gap = int(unicode(gap_e.text()))
1482         except:
1483             QMessageBox.warning(None, _('Error'), 'error', 'OK')
1484             sys.exit(0)
1485
1486         try:
1487             seed = unicode(seed_e.text())
1488             seed.decode('hex')
1489         except:
1490             print_error("Warning: Not hex, trying decode")
1491             try:
1492                 seed = mnemonic.mn_decode( seed.split(' ') )
1493             except:
1494                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1495                 sys.exit(0)
1496         if not seed:
1497             QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1498             sys.exit(0)
1499         
1500         wallet.seed = str(seed)
1501         #print repr(wallet.seed)
1502         wallet.gap_limit = gap
1503         return True
1504
1505
1506
1507     def settings_dialog(self):
1508         d = QDialog(self)
1509         d.setWindowTitle(_('Electrum Settings'))
1510         d.setModal(1)
1511         vbox = QVBoxLayout()
1512
1513         tabs = QTabWidget(self)
1514         vbox.addWidget(tabs)
1515
1516         tab2 = QWidget()
1517         grid_ui = QGridLayout(tab2)
1518         grid_ui.setColumnStretch(0,1)
1519         tabs.addTab(tab2, _('Display') )
1520
1521         tab = QWidget()
1522         grid_wallet = QGridLayout(tab)
1523         grid_wallet.setColumnStretch(0,1)
1524         tabs.addTab(tab, _('Wallet') )
1525         
1526         fee_label = QLabel(_('Transaction fee'))
1527         grid_wallet.addWidget(fee_label, 2, 0)
1528         fee_e = QLineEdit()
1529         fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1530         grid_wallet.addWidget(fee_e, 2, 1)
1531         msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1532             + _('Recommended value') + ': 0.001'
1533         grid_wallet.addWidget(HelpButton(msg), 2, 2)
1534         fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1535         if not self.config.is_modifiable('fee'):
1536             for w in [fee_e, fee_label]: w.setEnabled(False)
1537
1538         nz_label = QLabel(_('Display zeros'))
1539         grid_ui.addWidget(nz_label, 3, 0)
1540         nz_e = QLineEdit()
1541         nz_e.setText("%d"% self.wallet.num_zeros)
1542         grid_ui.addWidget(nz_e, 3, 1)
1543         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1544         grid_ui.addWidget(HelpButton(msg), 3, 2)
1545         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1546         if not self.config.is_modifiable('num_zeros'):
1547             for w in [nz_e, nz_label]: w.setEnabled(False)
1548
1549
1550         usechange_label = QLabel(_('Use change addresses'))
1551         grid_wallet.addWidget(usechange_label, 5, 0)
1552         usechange_combo = QComboBox()
1553         usechange_combo.addItems(['Yes', 'No'])
1554         usechange_combo.setCurrentIndex(not self.wallet.use_change)
1555         grid_wallet.addWidget(usechange_combo, 5, 1)
1556         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1557         if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1558
1559         gap_label = QLabel(_('Gap limit'))
1560         grid_wallet.addWidget(gap_label, 6, 0)
1561         gap_e = QLineEdit()
1562         gap_e.setText("%d"% self.wallet.gap_limit)
1563         grid_wallet.addWidget(gap_e, 6, 1)
1564         msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1565               + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1566               + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1567               + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1568               + _('Warning') + ': ' \
1569               + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1570               + _('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' 
1571         grid_wallet.addWidget(HelpButton(msg), 6, 2)
1572         gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1573         if not self.config.is_modifiable('gap_limit'):
1574             for w in [gap_e, gap_label]: w.setEnabled(False)
1575         
1576         gui_label=QLabel(_('Default GUI') + ':')
1577         grid_ui.addWidget(gui_label , 7, 0)
1578         gui_combo = QComboBox()
1579         gui_combo.addItems(['Lite', 'Classic'])
1580         index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1581         if index==-1: index = 1
1582         gui_combo.setCurrentIndex(index)
1583         grid_ui.addWidget(gui_combo, 7, 1)
1584         grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up.'+'\n'+'Note: use the command line to access the "text" and "gtk" GUIs')), 7, 2)
1585         if not self.config.is_modifiable('gui'):
1586             for w in [gui_combo, gui_label]: w.setEnabled(False)
1587
1588         lang_label=QLabel(_('Language') + ':')
1589         grid_ui.addWidget(lang_label , 8, 0)
1590         lang_combo = QComboBox()
1591         from i18n import languages
1592         lang_combo.addItems(languages.values())
1593         try:
1594             index = languages.keys().index(self.config.get("language",''))
1595         except:
1596             index = 0
1597         lang_combo.setCurrentIndex(index)
1598         grid_ui.addWidget(lang_combo, 8, 1)
1599         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1600         if not self.config.is_modifiable('language'):
1601             for w in [lang_combo, lang_label]: w.setEnabled(False)
1602
1603         currencies = self.exchanger.get_currencies()
1604         currencies.insert(0, "None")
1605
1606         cur_label=QLabel(_('Currency') + ':')
1607         grid_ui.addWidget(cur_label , 9, 0)
1608         cur_combo = QComboBox()
1609         cur_combo.addItems(currencies)
1610         try:
1611             index = currencies.index(self.config.get('currency', "None"))
1612         except:
1613             index = 0
1614         cur_combo.setCurrentIndex(index)
1615         grid_ui.addWidget(cur_combo, 9, 1)
1616         grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes. ')), 9, 2)
1617         
1618         view_label=QLabel(_('Receive Tab') + ':')
1619         grid_ui.addWidget(view_label , 10, 0)
1620         view_combo = QComboBox()
1621         view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1622         view_combo.setCurrentIndex(self.receive_tab_mode)
1623         grid_ui.addWidget(view_combo, 10, 1)
1624         hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
1625              + _('Simple') +   ': ' + _('Show only addresses and labels.') + '\n\n' \
1626              + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1627              + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n' 
1628         
1629         grid_ui.addWidget(HelpButton(hh), 10, 2)
1630         
1631         vbox.addLayout(ok_cancel_buttons(d))
1632         d.setLayout(vbox) 
1633
1634         # run the dialog
1635         if not d.exec_(): return
1636
1637         fee = unicode(fee_e.text())
1638         try:
1639             fee = int( 100000000 * Decimal(fee) )
1640         except:
1641             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1642             return
1643
1644         if self.wallet.fee != fee:
1645             self.wallet.fee = fee
1646             self.wallet.save()
1647         
1648         nz = unicode(nz_e.text())
1649         try:
1650             nz = int( nz )
1651             if nz>8: nz=8
1652         except:
1653             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1654             return
1655
1656         if self.wallet.num_zeros != nz:
1657             self.wallet.num_zeros = nz
1658             self.config.set_key('num_zeros', nz, True)
1659             self.update_history_tab()
1660             self.update_receive_tab()
1661
1662         usechange_result = usechange_combo.currentIndex() == 0
1663         if self.wallet.use_change != usechange_result:
1664             self.wallet.use_change = usechange_result
1665             self.config.set_key('use_change', self.wallet.use_change, True)
1666         
1667         try:
1668             n = int(gap_e.text())
1669         except:
1670             QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1671             return
1672
1673         if self.wallet.gap_limit != n:
1674             r = self.wallet.change_gap_limit(n)
1675             if r:
1676                 self.update_receive_tab()
1677                 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1678             else:
1679                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1680
1681         need_restart = False
1682
1683         gui_request = str(gui_combo.currentText()).lower()
1684         if gui_request != self.config.get('gui'):
1685             self.config.set_key('gui', gui_request, True)
1686             need_restart = True
1687             
1688         lang_request = languages.keys()[lang_combo.currentIndex()]
1689         if lang_request != self.config.get('language'):
1690             self.config.set_key("language", lang_request, True)
1691             need_restart = True
1692             
1693         cur_request = str(currencies[cur_combo.currentIndex()])
1694         if cur_request != self.config.get('currency', "None"):
1695             self.config.set_key('currency', cur_request, True)
1696             self.update_wallet()
1697
1698         if need_restart:
1699             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1700
1701         self.receive_tab_set_mode(view_combo.currentIndex())
1702
1703
1704     @staticmethod 
1705     def network_dialog(wallet, parent=None):
1706         interface = wallet.interface
1707         if parent:
1708             if interface.is_connected:
1709                 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1710             else:
1711                 status = _("Not connected")
1712             server = interface.server
1713         else:
1714             import random
1715             status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1716             server = interface.server
1717
1718         plist, servers_list = interface.get_servers_list()
1719
1720         d = QDialog(parent)
1721         d.setModal(1)
1722         d.setWindowTitle(_('Server'))
1723         d.setMinimumSize(375, 20)
1724
1725         vbox = QVBoxLayout()
1726         vbox.setSpacing(30)
1727
1728         hbox = QHBoxLayout()
1729         l = QLabel()
1730         l.setPixmap(QPixmap(":icons/network.png"))
1731         hbox.addStretch(10)
1732         hbox.addWidget(l)
1733         hbox.addWidget(QLabel(status))
1734         hbox.addStretch(50)
1735         vbox.addLayout(hbox)
1736
1737
1738         # grid layout
1739         grid = QGridLayout()
1740         grid.setSpacing(8)
1741         vbox.addLayout(grid)
1742
1743         # server
1744         server_protocol = QComboBox()
1745         server_host = QLineEdit()
1746         server_host.setFixedWidth(200)
1747         server_port = QLineEdit()
1748         server_port.setFixedWidth(60)
1749
1750         protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1751         protocol_letters = 'thsg'
1752         DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1753         server_protocol.addItems(protocol_names)
1754
1755         grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1756         grid.addWidget(server_protocol, 0, 1)
1757         grid.addWidget(server_host, 0, 2)
1758         grid.addWidget(server_port, 0, 3)
1759
1760         def change_protocol(p):
1761             protocol = protocol_letters[p]
1762             host = unicode(server_host.text())
1763             pp = plist.get(host,DEFAULT_PORTS)
1764             if protocol not in pp.keys():
1765                 protocol = pp.keys()[0]
1766             port = pp[protocol]
1767             server_host.setText( host )
1768             server_port.setText( port )
1769
1770         server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1771         
1772         label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1773         servers_list_widget = QTreeWidget(parent)
1774         servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1775         servers_list_widget.setMaximumHeight(150)
1776         servers_list_widget.setColumnWidth(0, 240)
1777         for _host in servers_list.keys():
1778             _type = 'P' if servers_list[_host].get('pruning') else 'F'
1779             servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1780
1781         def change_server(host, protocol=None):
1782             pp = plist.get(host,DEFAULT_PORTS)
1783             if protocol:
1784                 port = pp.get(protocol)
1785                 if not port: protocol = None
1786                     
1787             if not protocol:
1788                 if 't' in pp.keys():
1789                     protocol = 't'
1790                     port = pp.get(protocol)
1791                 else:
1792                     protocol = pp.keys()[0]
1793                     port = pp.get(protocol)
1794             
1795             server_host.setText( host )
1796             server_port.setText( port )
1797             server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1798
1799             if not plist: return
1800             for p in protocol_letters:
1801                 i = protocol_letters.index(p)
1802                 j = server_protocol.model().index(i,0)
1803                 if p not in pp.keys():
1804                     server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1805                 else:
1806                     server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1807
1808
1809         if server:
1810             host, port, protocol = server.split(':')
1811             change_server(host,protocol)
1812
1813         servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1814         grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1815
1816         if not wallet.config.is_modifiable('server'):
1817             for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1818
1819         # auto cycle
1820         autocycle_cb = QCheckBox('Try random servers if disconnected')
1821         autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
1822         grid.addWidget(autocycle_cb, 3, 1, 3, 2)
1823         if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
1824
1825         # proxy setting
1826         proxy_mode = QComboBox()
1827         proxy_host = QLineEdit()
1828         proxy_host.setFixedWidth(200)
1829         proxy_port = QLineEdit()
1830         proxy_port.setFixedWidth(60)
1831         proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1832
1833         def check_for_disable(index = False):
1834             if proxy_mode.currentText() != 'NONE':
1835                 proxy_host.setEnabled(True)
1836                 proxy_port.setEnabled(True)
1837             else:
1838                 proxy_host.setEnabled(False)
1839                 proxy_port.setEnabled(False)
1840
1841         check_for_disable()
1842         proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1843
1844         if not wallet.config.is_modifiable('proxy'):
1845             for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1846
1847         proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1848         proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1849         proxy_host.setText(proxy_config.get("host"))
1850         proxy_port.setText(proxy_config.get("port"))
1851
1852         grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1853         grid.addWidget(proxy_mode, 2, 1)
1854         grid.addWidget(proxy_host, 2, 2)
1855         grid.addWidget(proxy_port, 2, 3)
1856
1857         # buttons
1858         vbox.addLayout(ok_cancel_buttons(d))
1859         d.setLayout(vbox) 
1860
1861         if not d.exec_(): return
1862
1863         server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1864         if proxy_mode.currentText() != 'NONE':
1865             proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1866         else:
1867             proxy = None
1868
1869         wallet.config.set_key("proxy", proxy, True)
1870         wallet.config.set_key("server", server, True)
1871         interface.set_server(server, proxy)
1872         wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
1873         return True
1874
1875     def closeEvent(self, event):
1876         g = self.geometry()
1877         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1878         event.accept()
1879
1880
1881 class ElectrumGui:
1882
1883     def __init__(self, wallet, config, app=None):
1884         self.wallet = wallet
1885         self.config = config
1886         if app is None:
1887             self.app = QApplication(sys.argv)
1888
1889
1890     def restore_or_create(self):
1891         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1892         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1893         if r==2: return None
1894         return 'restore' if r==1 else 'create'
1895
1896     def seed_dialog(self):
1897         return ElectrumWindow.seed_dialog( self.wallet )
1898
1899     def network_dialog(self):
1900         return ElectrumWindow.network_dialog( self.wallet, parent=None )
1901         
1902
1903     def show_seed(self):
1904         ElectrumWindow.show_seed_dialog(self.wallet)
1905
1906
1907     def password_dialog(self):
1908         ElectrumWindow.change_password_dialog(self.wallet)
1909
1910
1911     def restore_wallet(self):
1912         wallet = self.wallet
1913         # wait until we are connected, because the user might have selected another server
1914         if not wallet.interface.is_connected:
1915             waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1916             waiting_dialog(waiting)
1917
1918         waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1919             %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1920
1921         wallet.set_up_to_date(False)
1922         wallet.interface.poke('synchronizer')
1923         waiting_dialog(waiting)
1924         if wallet.is_found():
1925             print_error( "Recovery successful" )
1926         else:
1927             QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1928
1929         return True
1930
1931     def main(self,url):
1932         s = Timer()
1933         s.start()
1934         w = ElectrumWindow(self.wallet, self.config)
1935         if url: w.set_url(url)
1936         w.app = self.app
1937         w.connect_slots(s)
1938         w.update_wallet()
1939         w.show()
1940
1941         self.app.exec_()