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