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