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