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