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