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