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