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