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