slightly smarter qr code widget that does not need size parameter
[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 from electrum import util, bitcoin, commands
45
46 import bmp, pyqrnative
47 import exchange_rate
48
49 from decimal import Decimal
50
51 import platform
52 import httplib
53 import socket
54 import webbrowser
55 import csv
56
57 if platform.system() == 'Windows':
58     MONOSPACE_FONT = 'Lucida Console'
59 elif platform.system() == 'Darwin':
60     MONOSPACE_FONT = 'Monaco'
61 else:
62     MONOSPACE_FONT = 'monospace'
63
64 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
65
66 from electrum import ELECTRUM_VERSION
67 import re
68
69 class UpdateLabel(QtGui.QLabel):
70     def __init__(self, config, parent=None):
71         QtGui.QLabel.__init__(self, parent)
72         self.new_version = False
73
74         try:
75             con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
76             con.request("GET", "/version")
77             res = con.getresponse()
78         except socket.error as msg:
79             print_error("Could not retrieve version information")
80             return
81             
82         if res.status == 200:
83             self.latest_version = res.read()
84             self.latest_version = self.latest_version.replace("\n","")
85             if(re.match('^\d+(\.\d+)*$', self.latest_version)):
86                 self.config = config
87                 self.current_version = ELECTRUM_VERSION
88                 if(self.compare_versions(self.latest_version, self.current_version) == 1):
89                     latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
90                     if(self.compare_versions(self.latest_version, latest_seen) == 1):
91                         self.new_version = True
92                         self.setText(_("New version available") + ": " + self.latest_version)
93
94
95     def compare_versions(self, version1, version2):
96         def normalize(v):
97             return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
98         return cmp(normalize(version1), normalize(version2))
99
100     def ignore_this_version(self):
101         self.setText("")
102         self.config.set_key("last_seen_version", self.latest_version, True)
103         QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
104         self.dialog.done(0)
105
106     def ignore_all_version(self):
107         self.setText("")
108         self.config.set_key("last_seen_version", "9.9.9", True)
109         QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
110         self.dialog.done(0)
111   
112     def open_website(self):
113         webbrowser.open("http://electrum.org/download.html")
114         self.dialog.done(0)
115
116     def mouseReleaseEvent(self, event):
117         dialog = QDialog(self)
118         dialog.setWindowTitle(_('Electrum update'))
119         dialog.setModal(1)
120
121         main_layout = QGridLayout()
122         main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
123         
124         ignore_version = QPushButton(_("Ignore this version"))
125         ignore_version.clicked.connect(self.ignore_this_version)
126
127         ignore_all_versions = QPushButton(_("Ignore all versions"))
128         ignore_all_versions.clicked.connect(self.ignore_all_version)
129
130         open_website = QPushButton(_("Goto download page"))
131         open_website.clicked.connect(self.open_website)
132
133         main_layout.addWidget(ignore_version, 1, 0)
134         main_layout.addWidget(ignore_all_versions, 1, 1)
135         main_layout.addWidget(open_website, 1, 2)
136
137         dialog.setLayout(main_layout)
138
139         self.dialog = dialog
140         
141         if not dialog.exec_(): return
142
143 def numbify(entry, is_int = False):
144     text = unicode(entry.text()).strip()
145     pos = entry.cursorPosition()
146     chars = '0123456789'
147     if not is_int: chars +='.'
148     s = ''.join([i for i in text if i in chars])
149     if not is_int:
150         if '.' in s:
151             p = s.find('.')
152             s = s.replace('.','')
153             s = s[:p] + '.' + s[p:p+8]
154         try:
155             amount = int( Decimal(s) * 100000000 )
156         except:
157             amount = None
158     else:
159         try:
160             amount = int( s )
161         except:
162             amount = None
163     entry.setText(s)
164     entry.setCursorPosition(pos)
165     return amount
166
167
168 class Timer(QtCore.QThread):
169     def run(self):
170         while True:
171             self.emit(QtCore.SIGNAL('timersignal'))
172             time.sleep(0.5)
173
174 class HelpButton(QPushButton):
175     def __init__(self, text):
176         QPushButton.__init__(self, '?')
177         self.setFocusPolicy(Qt.NoFocus)
178         self.setFixedWidth(20)
179         self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
180
181
182 class EnterButton(QPushButton):
183     def __init__(self, text, func):
184         QPushButton.__init__(self, text)
185         self.func = func
186         self.clicked.connect(func)
187
188     def keyPressEvent(self, e):
189         if e.key() == QtCore.Qt.Key_Return:
190             apply(self.func,())
191
192 class MyTreeWidget(QTreeWidget):
193     def __init__(self, parent):
194         QTreeWidget.__init__(self, parent)
195         def ddfr(item):
196             if not item: return
197             for i in range(0,self.viewport().height()/5):
198                 if self.itemAt(QPoint(0,i*5)) == item:
199                     break
200             else:
201                 return
202             for j in range(0,30):
203                 if self.itemAt(QPoint(0,i*5 + j)) != item:
204                     break
205             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
206
207         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
208         
209
210
211
212 class StatusBarButton(QPushButton):
213     def __init__(self, icon, tooltip, func):
214         QPushButton.__init__(self, icon, '')
215         self.setToolTip(tooltip)
216         self.setFlat(True)
217         self.setMaximumWidth(25)
218         self.clicked.connect(func)
219         self.func = func
220
221     def keyPressEvent(self, e):
222         if e.key() == QtCore.Qt.Key_Return:
223             apply(self.func,())
224
225
226
227
228
229
230 def waiting_dialog(f):
231
232     s = Timer()
233     s.start()
234     w = QDialog()
235     w.resize(200, 70)
236     w.setWindowTitle('Electrum')
237     l = QLabel('')
238     vbox = QVBoxLayout()
239     vbox.addWidget(l)
240     w.setLayout(vbox)
241     w.show()
242     def ff():
243         s = f()
244         if s: l.setText(s)
245         else: w.close()
246     w.connect(s, QtCore.SIGNAL('timersignal'), ff)
247     w.exec_()
248     w.destroy()
249
250
251 def ok_cancel_buttons(dialog):
252     hbox = QHBoxLayout()
253     hbox.addStretch(1)
254     b = QPushButton("OK")
255     hbox.addWidget(b)
256     b.clicked.connect(dialog.accept)
257     b = QPushButton("Cancel")
258     hbox.addWidget(b)
259     b.clicked.connect(dialog.reject)
260     return hbox
261
262
263 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], 
264         "receive":[[370],[370,200,130]] }
265
266 class ElectrumWindow(QMainWindow):
267
268     def __init__(self, wallet, config):
269         QMainWindow.__init__(self)
270         self.lite = None
271         self.wallet = wallet
272         self.config = config
273         self.init_plugins()
274
275         self.wallet.interface.register_callback('updated', self.update_callback)
276         self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
277         self.wallet.interface.register_callback('disconnected', self.update_callback)
278         self.wallet.interface.register_callback('disconnecting', self.update_callback)
279
280         self.expert_mode = config.get('classic_expert_mode', False)
281         self.merchant_name = config.get('merchant_name', 'Invoice')
282
283         set_language(config.get('language'))
284
285         self.funds_error = False
286         self.completions = QStringListModel()
287
288         self.tabs = tabs = QTabWidget(self)
289         self.column_widths = self.config.get("column-widths", default_column_widths )
290         tabs.addTab(self.create_history_tab(), _('History') )
291         tabs.addTab(self.create_send_tab(), _('Send') )
292         tabs.addTab(self.create_receive_tab(), _('Receive') )
293         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
294         tabs.addTab(self.create_console_tab(), _('Console') )
295         tabs.setMinimumSize(600, 400)
296         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
297         self.setCentralWidget(tabs)
298         self.create_status_bar()
299
300         g = self.config.get("winpos-qt",[100, 100, 840, 400])
301         self.setGeometry(g[0], g[1], g[2], g[3])
302         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
303         if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
304         self.setWindowTitle( title )
305
306         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
307         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
308         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
309         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
310         
311         self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
312         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
313         self.history_list.setFocus(True)
314         
315         self.exchanger = exchange_rate.Exchanger(self)
316         self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
317
318         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
319         if platform.system() == 'Windows':
320             n = 3 if self.wallet.seed else 2
321             tabs.setCurrentIndex (n)
322             tabs.setCurrentIndex (0)
323
324         # set initial message
325         self.console.showMessage(self.wallet.banner)
326
327
328     # plugins
329     def init_plugins(self):
330         import imp, pkgutil
331         if os.path.exists("plugins"):
332             fp, pathname, description = imp.find_module('plugins')
333             imp.load_module('electrum_plugins', fp, pathname, description)
334             plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
335             self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
336         else:
337             import electrum_plugins
338             plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
339             self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
340
341         self.plugin_hooks = {}
342         for p in self.plugins:
343             try:
344                 p.init(self)
345             except:
346                 print_msg("Error:cannot initialize plugin",p)
347                 traceback.print_exc(file=sys.stdout)
348
349     def set_hook(self, name, callback):
350         h = self.plugin_hooks.get(name, [])
351         h.append(callback)
352         self.plugin_hooks[name] = h
353
354     def unset_hook(self, name, callback):
355         h = self.plugin_hooks.get(name,[])
356         if callback in h: h.remove(callback)
357         self.plugin_hooks[name] = h
358
359     def run_hook(self, name, args):
360         for cb in self.plugin_hooks.get(name,[]):
361             apply(cb, args)
362
363
364     def close(self):
365         QMainWindow.close(self)
366         self.run_hook('close_main_window', (self,))
367
368     def connect_slots(self, sender):
369         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
370         self.previous_payto_e=''
371
372     def timer_actions(self):
373         self.run_hook('timer_actions', (self,))
374             
375         if self.payto_e.hasFocus():
376             return
377         r = unicode( self.payto_e.text() )
378         if r != self.previous_payto_e:
379             self.previous_payto_e = r
380             r = r.strip()
381             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
382                 try:
383                     to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
384                 except:
385                     return
386                 if to_address:
387                     s = r + '  <' + to_address + '>'
388                     self.payto_e.setText(s)
389
390
391     def update_callback(self):
392         self.emit(QtCore.SIGNAL('updatesignal'))
393
394     def update_wallet(self):
395         if self.wallet.interface and self.wallet.interface.is_connected:
396             if not self.wallet.up_to_date:
397                 text = _("Synchronizing...")
398                 icon = QIcon(":icons/status_waiting.png")
399             else:
400                 c, u = self.wallet.get_balance()
401                 text =  _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
402                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
403                 text += self.create_quote_text(Decimal(c+u)/100000000)
404                 icon = QIcon(":icons/status_connected.png")
405         else:
406             text = _("Not connected")
407             icon = QIcon(":icons/status_disconnected.png")
408
409         self.status_text = text
410         self.statusBar().showMessage(text)
411         self.status_button.setIcon( icon )
412
413         if self.wallet.up_to_date or not self.wallet.interface.is_connected:
414             self.update_history_tab()
415             self.update_receive_tab()
416             self.update_contacts_tab()
417             self.update_completions()
418
419
420     def create_quote_text(self, btc_balance):
421         quote_currency = self.config.get("currency", "None")
422         quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
423         if quote_balance is None:
424             quote_text = ""
425         else:
426             quote_text = "  (%.2f %s)" % (quote_balance, quote_currency)
427         return quote_text
428         
429     def create_history_tab(self):
430         self.history_list = l = MyTreeWidget(self)
431         l.setColumnCount(5)
432         for i,width in enumerate(self.column_widths['history']):
433             l.setColumnWidth(i, width)
434         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
435         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
436         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
437
438         l.setContextMenuPolicy(Qt.CustomContextMenu)
439         l.customContextMenuRequested.connect(self.create_history_menu)
440         return l
441
442
443     def create_history_menu(self, position):
444         self.history_list.selectedIndexes() 
445         item = self.history_list.currentItem()
446         if not item: return
447         tx_hash = str(item.data(0, Qt.UserRole).toString())
448         if not tx_hash: return
449         menu = QMenu()
450         #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
451         menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
452         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
453         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
454
455
456     def show_tx_details(self, tx):
457         dialog = QDialog(None)
458         dialog.setModal(1)
459         dialog.setWindowTitle(_("Transaction Details"))
460         vbox = QVBoxLayout()
461         dialog.setLayout(vbox)
462         dialog.setMinimumSize(600,300)
463
464         tx_hash = tx.hash()
465         if tx_hash in self.wallet.transactions.keys():
466             is_mine, v, fee = self.wallet.get_tx_value(tx)
467             conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
468             if timestamp:
469                 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
470             else:
471                 time_str = 'pending'
472         else:
473             is_mine = False
474
475         vbox.addWidget(QLabel("Transaction ID:"))
476         e  = QLineEdit(tx_hash)
477         e.setReadOnly(True)
478         vbox.addWidget(e)
479
480         vbox.addWidget(QLabel("Date: %s"%time_str))
481         vbox.addWidget(QLabel("Status: %d confirmations"%conf))
482         if is_mine:
483             if fee: 
484                 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
485                 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
486             else:
487                 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
488                 vbox.addWidget(QLabel("Transaction fee: unknown"))
489         else:
490             vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
491
492         vbox.addWidget( self.generate_transaction_information_widget(tx) )
493
494         ok_button = QPushButton(_("Close"))
495         ok_button.setDefault(True)
496         ok_button.clicked.connect(dialog.accept)
497         
498         hbox = QHBoxLayout()
499         hbox.addStretch(1)
500         hbox.addWidget(ok_button)
501         vbox.addLayout(hbox)
502         dialog.exec_()
503
504     def tx_label_clicked(self, item, column):
505         if column==2 and item.isSelected():
506             self.is_edit=True
507             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
508             self.history_list.editItem( item, column )
509             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
510             self.is_edit=False
511
512     def tx_label_changed(self, item, column):
513         if self.is_edit: 
514             return
515         self.is_edit=True
516         tx_hash = str(item.data(0, Qt.UserRole).toString())
517         tx = self.wallet.transactions.get(tx_hash)
518         s = self.wallet.labels.get(tx_hash)
519         text = unicode( item.text(2) )
520         if text: 
521             self.wallet.labels[tx_hash] = text
522             item.setForeground(2, QBrush(QColor('black')))
523         else:
524             if s: self.wallet.labels.pop(tx_hash)
525             text = self.wallet.get_default_label(tx_hash)
526             item.setText(2, text)
527             item.setForeground(2, QBrush(QColor('gray')))
528         self.is_edit=False
529
530
531     def edit_label(self, is_recv):
532         l = self.receive_list if is_recv else self.contacts_list
533         item = l.currentItem()
534         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
535         l.editItem( item, 1 )
536         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
537
538
539
540     def address_label_clicked(self, item, column, l, column_addr, column_label):
541         if column == column_label and item.isSelected():
542             addr = unicode( item.text(column_addr) )
543             label = unicode( item.text(column_label) )
544             if label in self.wallet.aliases.keys():
545                 return
546             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
547             l.editItem( item, column )
548             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
549
550
551     def address_label_changed(self, item, column, l, column_addr, column_label):
552
553         if column == column_label:
554             addr = unicode( item.text(column_addr) )
555             text = unicode( item.text(column_label) )
556             changed = False
557
558             if text:
559                 if text not in self.wallet.aliases.keys():
560                     old_addr = self.wallet.labels.get(text)
561                     if old_addr != addr:
562                         self.wallet.labels[addr] = text
563                         changed = True
564                 else:
565                     print_error("Error: This is one of your aliases")
566                     label = self.wallet.labels.get(addr,'')
567                     item.setText(column_label, QString(label))
568             else:
569                 s = self.wallet.labels.get(addr)
570                 if s: 
571                     self.wallet.labels.pop(addr)
572                     changed = True
573
574             if changed:
575                 self.update_history_tab()
576                 self.update_completions()
577                 
578             self.current_item_changed(item)
579
580         self.run_hook('item_changed',(self, item, column))
581
582
583     def current_item_changed(self, a):
584         self.run_hook('current_item_changed',(self, a))
585
586
587
588     def update_history_tab(self):
589
590         self.history_list.clear()
591         for item in self.wallet.get_tx_history():
592             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
593             if conf:
594                 try:
595                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
596                 except:
597                     time_str = "unknown"
598                 if conf == -1:
599                     icon = None
600                 if conf == 0:
601                     icon = QIcon(":icons/unconfirmed.png")
602                 elif conf < 6:
603                     icon = QIcon(":icons/clock%d.png"%conf)
604                 else:
605                     icon = QIcon(":icons/confirmed.png")
606             else:
607                 time_str = 'pending'
608                 icon = QIcon(":icons/unconfirmed.png")
609
610             if value is not None:
611                 v_str = format_satoshis(value, True, self.wallet.num_zeros)
612             else:
613                 v_str = '--'
614
615             balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
616             
617             if tx_hash:
618                 label, is_default_label = self.wallet.get_label(tx_hash)
619             else:
620                 label = _('Pruned transaction outputs')
621                 is_default_label = False
622
623             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
624             item.setFont(2, QFont(MONOSPACE_FONT))
625             item.setFont(3, QFont(MONOSPACE_FONT))
626             item.setFont(4, QFont(MONOSPACE_FONT))
627             if value < 0:
628                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
629             if tx_hash:
630                 item.setData(0, Qt.UserRole, tx_hash)
631                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
632             if is_default_label:
633                 item.setForeground(2, QBrush(QColor('grey')))
634
635             item.setIcon(0, icon)
636             self.history_list.insertTopLevelItem(0,item)
637             
638
639         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
640
641
642     def create_send_tab(self):
643         w = QWidget()
644
645         grid = QGridLayout()
646         grid.setSpacing(8)
647         grid.setColumnMinimumWidth(3,300)
648         grid.setColumnStretch(5,1)
649
650         self.payto_e = QLineEdit()
651         grid.addWidget(QLabel(_('Pay to')), 1, 0)
652         grid.addWidget(self.payto_e, 1, 1, 1, 3)
653             
654         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)
655
656         completer = QCompleter()
657         completer.setCaseSensitivity(False)
658         self.payto_e.setCompleter(completer)
659         completer.setModel(self.completions)
660
661         self.message_e = QLineEdit()
662         grid.addWidget(QLabel(_('Description')), 2, 0)
663         grid.addWidget(self.message_e, 2, 1, 1, 3)
664         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)
665
666         self.amount_e = QLineEdit()
667         grid.addWidget(QLabel(_('Amount')), 3, 0)
668         grid.addWidget(self.amount_e, 3, 1, 1, 2)
669         grid.addWidget(HelpButton(
670                 _('Amount to be sent.') + '\n\n' \
671                     + _('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)
672         
673         self.fee_e = QLineEdit()
674         grid.addWidget(QLabel(_('Fee')), 4, 0)
675         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
676         grid.addWidget(HelpButton(
677                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
678                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
679                     + _('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)
680         b = ''
681         if self.wallet.seed: 
682             b = EnterButton(_("Send"), self.do_send)
683         else:
684             b = EnterButton(_("Create unsigned transaction"), self.do_send)
685         grid.addWidget(b, 6, 1)
686
687         b = EnterButton(_("Clear"),self.do_clear)
688         grid.addWidget(b, 6, 2)
689
690         self.payto_sig = QLabel('')
691         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
692
693         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
694         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
695         w.setLayout(grid) 
696
697         w2 = QWidget()
698         vbox = QVBoxLayout()
699         vbox.addWidget(w)
700         vbox.addStretch(1)
701         w2.setLayout(vbox)
702
703         def entry_changed( is_fee ):
704             self.funds_error = False
705             amount = numbify(self.amount_e)
706             fee = numbify(self.fee_e)
707             if not is_fee: fee = None
708             if amount is None:
709                 return
710             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
711             if not is_fee:
712                 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
713             if inputs:
714                 palette = QPalette()
715                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
716                 text = self.status_text
717             else:
718                 palette = QPalette()
719                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
720                 self.funds_error = True
721                 text = _( "Not enough funds" )
722
723             self.statusBar().showMessage(text)
724             self.amount_e.setPalette(palette)
725             self.fee_e.setPalette(palette)
726
727         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
728         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
729
730         self.run_hook('create_send_tab',(self,grid))
731         return w2
732
733
734     def update_completions(self):
735         l = []
736         for addr,label in self.wallet.labels.items():
737             if addr in self.wallet.addressbook:
738                 l.append( label + '  <' + addr + '>')
739         l = l + self.wallet.aliases.keys()
740
741         self.completions.setStringList(l)
742
743
744
745     def do_send(self):
746
747         label = unicode( self.message_e.text() )
748         r = unicode( self.payto_e.text() )
749         r = r.strip()
750
751         # alias
752         m1 = re.match(ALIAS_REGEXP, r)
753         # label or alias, with address in brackets
754         m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
755         
756         if m1:
757             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
758             if not to_address:
759                 return
760         elif m2:
761             to_address = m2.group(2)
762         else:
763             to_address = r
764
765         if not is_valid(to_address):
766             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
767             return
768
769         try:
770             amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
771         except:
772             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
773             return
774         try:
775             fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
776         except:
777             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
778             return
779
780         if self.wallet.use_encryption:
781             password = self.password_dialog()
782             if not password:
783                 return
784         else:
785             password = None
786
787         try:
788             tx = self.wallet.mktx( [(to_address, amount)], password, fee)
789         except BaseException, e:
790             self.show_message(str(e))
791             return
792
793         self.run_hook('send_tx', (wallet, self, tx))
794
795         if label: 
796             self.wallet.labels[tx.hash()] = label
797
798         if tx.is_complete:
799             h = self.wallet.send_tx(tx)
800             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
801             status, msg = self.wallet.receive_tx( h )
802             if status:
803                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
804                 self.do_clear()
805                 self.update_contacts_tab()
806             else:
807                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
808         else:
809             filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
810             try:
811                 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
812                 with open(fileName,'w') as f:
813                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
814                 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
815             except:
816                 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
817
818
819
820
821     def set_url(self, url):
822         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
823         self.tabs.setCurrentIndex(1)
824         label = self.wallet.labels.get(payto)
825         m_addr = label + '  <'+ payto+'>' if label else payto
826         self.payto_e.setText(m_addr)
827
828         self.message_e.setText(message)
829         self.amount_e.setText(amount)
830         if identity:
831             self.set_frozen(self.payto_e,True)
832             self.set_frozen(self.amount_e,True)
833             self.set_frozen(self.message_e,True)
834             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
835         else:
836             self.payto_sig.setVisible(False)
837
838     def do_clear(self):
839         self.payto_sig.setVisible(False)
840         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
841             e.setText('')
842             self.set_frozen(e,False)
843
844     def set_frozen(self,entry,frozen):
845         if frozen:
846             entry.setReadOnly(True)
847             entry.setFrame(False)
848             palette = QPalette()
849             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
850             entry.setPalette(palette)
851         else:
852             entry.setReadOnly(False)
853             entry.setFrame(True)
854             palette = QPalette()
855             palette.setColor(entry.backgroundRole(), QColor('white'))
856             entry.setPalette(palette)
857
858
859     def toggle_freeze(self,addr):
860         if not addr: return
861         if addr in self.wallet.frozen_addresses:
862             self.wallet.unfreeze(addr)
863         else:
864             self.wallet.freeze(addr)
865         self.update_receive_tab()
866
867     def toggle_priority(self,addr):
868         if not addr: return
869         if addr in self.wallet.prioritized_addresses:
870             self.wallet.unprioritize(addr)
871         else:
872             self.wallet.prioritize(addr)
873         self.update_receive_tab()
874
875
876     def create_list_tab(self, headers):
877         "generic tab creation method"
878         l = MyTreeWidget(self)
879         l.setColumnCount( len(headers) )
880         l.setHeaderLabels( headers )
881
882         w = QWidget()
883         vbox = QVBoxLayout()
884         w.setLayout(vbox)
885
886         vbox.setMargin(0)
887         vbox.setSpacing(0)
888         vbox.addWidget(l)
889         buttons = QWidget()
890         vbox.addWidget(buttons)
891
892         hbox = QHBoxLayout()
893         hbox.setMargin(0)
894         hbox.setSpacing(0)
895         buttons.setLayout(hbox)
896
897         return l,w,hbox
898
899
900     def create_receive_tab(self):
901         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
902         l.setContextMenuPolicy(Qt.CustomContextMenu)
903         l.customContextMenuRequested.connect(self.create_receive_menu)
904         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
905         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
906         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
907         self.receive_list = l
908         self.receive_buttons_hbox = hbox
909         hbox.addStretch(1)
910         return w
911
912
913     def receive_tab_set_mode(self, i):
914         self.save_column_widths()
915         self.expert_mode = (i == 1)
916         self.config.set_key('classic_expert_mode', self.expert_mode, True)
917         self.wallet.save()
918         self.update_receive_tab()
919
920
921     def save_column_widths(self):
922         if not self.expert_mode:
923             widths = [ self.receive_list.columnWidth(0) ]
924         else:
925             widths = []
926             for i in range(self.receive_list.columnCount() -1):
927                 widths.append(self.receive_list.columnWidth(i))
928         self.column_widths["receive"][self.expert_mode] = widths
929         
930         self.column_widths["history"] = []
931         for i in range(self.history_list.columnCount() - 1):
932             self.column_widths["history"].append(self.history_list.columnWidth(i))
933
934         self.column_widths["contacts"] = []
935         for i in range(self.contacts_list.columnCount() - 1):
936             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
937
938
939     def create_contacts_tab(self):
940         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
941         l.setContextMenuPolicy(Qt.CustomContextMenu)
942         l.customContextMenuRequested.connect(self.create_contact_menu)
943         for i,width in enumerate(self.column_widths['contacts']):
944             l.setColumnWidth(i, width)
945
946         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
947         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
948         self.contacts_list = l
949         self.contacts_buttons_hbox = hbox
950         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
951         hbox.addStretch(1)
952         return w
953
954
955     def delete_imported_key(self, addr):
956         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
957             self.wallet.imported_keys.pop(addr)
958             self.update_receive_tab()
959             self.update_history_tab()
960             self.wallet.save()
961
962
963     def create_receive_menu(self, position):
964         # fixme: this function apparently has a side effect.
965         # if it is not called the menu pops up several times
966         #self.receive_list.selectedIndexes() 
967
968         item = self.receive_list.itemAt(position)
969         if not item: return
970         addr = unicode(item.text(0))
971         if not is_valid(addr): 
972             item.setExpanded(not item.isExpanded())
973             return 
974         menu = QMenu()
975         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
976         menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
977         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
978         menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
979         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
980         if addr in self.wallet.imported_keys:
981             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
982
983         if self.expert_mode:
984             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
985             menu.addAction(t, lambda: self.toggle_freeze(addr))
986             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
987             menu.addAction(t, lambda: self.toggle_priority(addr))
988             
989         self.run_hook('receive_menu', (self, menu,))
990         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
991
992
993     def payto(self, x, is_alias):
994         if not x: return
995         if is_alias:
996             label = x
997             m_addr = label
998         else:
999             addr = x
1000             label = self.wallet.labels.get(addr)
1001             m_addr = label + '  <' + addr + '>' if label else addr
1002         self.tabs.setCurrentIndex(1)
1003         self.payto_e.setText(m_addr)
1004         self.amount_e.setFocus()
1005
1006     def delete_contact(self, x, is_alias):
1007         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1008             if not is_alias and x in self.wallet.addressbook:
1009                 self.wallet.addressbook.remove(x)
1010                 if x in self.wallet.labels.keys():
1011                     self.wallet.labels.pop(x)
1012             elif is_alias and x in self.wallet.aliases:
1013                 self.wallet.aliases.pop(x)
1014             self.update_history_tab()
1015             self.update_contacts_tab()
1016             self.update_completions()
1017
1018     def create_contact_menu(self, position):
1019         # fixme: this function apparently has a side effect.
1020         # if it is not called the menu pops up several times
1021         #self.contacts_list.selectedIndexes() 
1022
1023         item = self.contacts_list.itemAt(position)
1024         if not item: return
1025         addr = unicode(item.text(0))
1026         label = unicode(item.text(1))
1027         is_alias = label in self.wallet.aliases.keys()
1028         x = label if is_alias else addr
1029         menu = QMenu()
1030         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1031         menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1032         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1033         if not is_alias:
1034             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1035         else:
1036             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1037         menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1038         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1039
1040
1041     def update_receive_item(self, item):
1042         item.setFont(0, QFont(MONOSPACE_FONT))
1043         address = str(item.data(0,0).toString())
1044         label = self.wallet.labels.get(address,'')
1045         item.setData(1,0,label)
1046
1047         self.run_hook('update_receive_item', (self, address, item))
1048                 
1049         c, u = self.wallet.get_addr_balance(address)
1050         balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1051         item.setData(2,0,balance)
1052
1053         if self.expert_mode:
1054             if address in self.wallet.frozen_addresses: 
1055                 item.setBackgroundColor(0, QColor('lightblue'))
1056             elif address in self.wallet.prioritized_addresses: 
1057                 item.setBackgroundColor(0, QColor('lightgreen'))
1058         
1059
1060     def update_receive_tab(self):
1061         l = self.receive_list
1062         
1063         l.clear()
1064         l.setColumnHidden(2, not self.expert_mode)
1065         l.setColumnHidden(3, not self.expert_mode)
1066         if not self.expert_mode:
1067             width = self.column_widths['receive'][0][0]
1068             l.setColumnWidth(0, width)
1069         else:
1070             for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1071                 l.setColumnWidth(i, width)        
1072
1073
1074         for k, account in self.wallet.accounts.items():
1075             name = account.get('name',str(k))
1076             c,u = self.wallet.get_account_balance(k)
1077             account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1078             l.addTopLevelItem(account_item)
1079             account_item.setExpanded(True)
1080             
1081
1082             for is_change in [0,1]:
1083                 name = "Receiving" if not is_change else "Change"
1084                 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1085                 account_item.addChild(seq_item)
1086                 if not is_change: seq_item.setExpanded(True)
1087                 is_red = False
1088                 gap = 0
1089
1090                 for address in account[is_change]:
1091                     h = self.wallet.history.get(address,[])
1092             
1093                     if not is_change:
1094                         if h == []:
1095                             gap += 1
1096                             if gap > self.wallet.gap_limit:
1097                                 is_red = True
1098                         else:
1099                             gap = 0
1100
1101                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1102                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1103                     self.update_receive_item(item)
1104                     if is_red:
1105                         item.setBackgroundColor(1, QColor('red'))
1106                     seq_item.addChild(item)
1107
1108         if self.wallet.imported_keys:
1109             c,u = self.wallet.get_imported_balance()
1110             account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1111             l.addTopLevelItem(account_item)
1112             account_item.setExpanded(True)
1113             for address in self.wallet.imported_keys.keys():
1114                 item = QTreeWidgetItem( [ address, '', '', ''] )
1115                 self.update_receive_item(item)
1116                 account_item.addChild(item)
1117                 
1118
1119         # we use column 1 because column 0 may be hidden
1120         l.setCurrentItem(l.topLevelItem(0),1)
1121
1122     def show_contact_details(self, m):
1123         a = self.wallet.aliases.get(m)
1124         if a:
1125             if a[0] in self.wallet.authorities.keys():
1126                 s = self.wallet.authorities.get(a[0])
1127             else:
1128                 s = "self-signed"
1129             msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1130             QMessageBox.information(self, 'Alias', msg, 'OK')
1131
1132     def update_contacts_tab(self):
1133
1134         l = self.contacts_list
1135         l.clear()
1136
1137         alias_targets = []
1138         for alias, v in self.wallet.aliases.items():
1139             s, target = v
1140             alias_targets.append(target)
1141             item = QTreeWidgetItem( [ target, alias, '-'] )
1142             item.setBackgroundColor(0, QColor('lightgray'))
1143             l.addTopLevelItem(item)
1144             
1145         for address in self.wallet.addressbook:
1146             if address in alias_targets: continue
1147             label = self.wallet.labels.get(address,'')
1148             n = 0 
1149             for tx in self.wallet.transactions.values():
1150                 if address in map(lambda x: x[0], tx.outputs): n += 1
1151             
1152             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1153             item.setFont(0, QFont(MONOSPACE_FONT))
1154             l.addTopLevelItem(item)
1155
1156         l.setCurrentItem(l.topLevelItem(0))
1157
1158
1159     def create_console_tab(self):
1160         from qt_console import Console
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())
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