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