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