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