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