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