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