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