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