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