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