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