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