3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
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.
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.
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/>.
19 import sys, time, datetime, re
20 from i18n import _, set_language
21 from electrum.util import print_error
22 import os.path, json, ast
28 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
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
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
45 import bmp, pyqrnative, qrscanner
48 from decimal import Decimal
56 if platform.system() == 'Windows':
57 MONOSPACE_FONT = 'Lucida Console'
58 elif platform.system() == 'Darwin':
59 MONOSPACE_FONT = 'Monaco'
61 MONOSPACE_FONT = 'monospace'
63 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
65 from electrum import ELECTRUM_VERSION
68 class UpdateLabel(QtGui.QLabel):
69 def __init__(self, config, parent=None):
70 QtGui.QLabel.__init__(self, parent)
71 self.new_version = False
74 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
75 con.request("GET", "/version")
76 res = con.getresponse()
77 except socket.error as msg:
78 print_error("Could not retrieve version information")
82 self.latest_version = res.read()
83 self.latest_version = self.latest_version.replace("\n","")
84 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
86 self.current_version = ELECTRUM_VERSION
87 if(self.compare_versions(self.latest_version, self.current_version) == 1):
88 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
89 if(self.compare_versions(self.latest_version, latest_seen) == 1):
90 self.new_version = True
91 self.setText(_("New version available") + ": " + self.latest_version)
94 def compare_versions(self, version1, version2):
96 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
97 return cmp(normalize(version1), normalize(version2))
99 def ignore_this_version(self):
101 self.config.set_key("last_seen_version", self.latest_version, True)
102 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
105 def ignore_all_version(self):
107 self.config.set_key("last_seen_version", "9.9.9", True)
108 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
111 def open_website(self):
112 webbrowser.open("http://electrum.org/download.html")
115 def mouseReleaseEvent(self, event):
116 dialog = QDialog(self)
117 dialog.setWindowTitle(_('Electrum update'))
120 main_layout = QGridLayout()
121 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
123 ignore_version = QPushButton(_("Ignore this version"))
124 ignore_version.clicked.connect(self.ignore_this_version)
126 ignore_all_versions = QPushButton(_("Ignore all versions"))
127 ignore_all_versions.clicked.connect(self.ignore_all_version)
129 open_website = QPushButton(_("Goto download page"))
130 open_website.clicked.connect(self.open_website)
132 main_layout.addWidget(ignore_version, 1, 0)
133 main_layout.addWidget(ignore_all_versions, 1, 1)
134 main_layout.addWidget(open_website, 1, 2)
136 dialog.setLayout(main_layout)
140 if not dialog.exec_(): return
142 def numbify(entry, is_int = False):
143 text = unicode(entry.text()).strip()
144 pos = entry.cursorPosition()
146 if not is_int: chars +='.'
147 s = ''.join([i for i in text if i in chars])
151 s = s.replace('.','')
152 s = s[:p] + '.' + s[p:p+8]
154 amount = int( Decimal(s) * 100000000 )
163 entry.setCursorPosition(pos)
167 class Timer(QtCore.QThread):
170 self.emit(QtCore.SIGNAL('timersignal'))
173 class HelpButton(QPushButton):
174 def __init__(self, text):
175 QPushButton.__init__(self, '?')
176 self.setFocusPolicy(Qt.NoFocus)
177 self.setFixedWidth(20)
178 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
181 class EnterButton(QPushButton):
182 def __init__(self, text, func):
183 QPushButton.__init__(self, text)
185 self.clicked.connect(func)
187 def keyPressEvent(self, e):
188 if e.key() == QtCore.Qt.Key_Return:
191 class MyTreeWidget(QTreeWidget):
192 def __init__(self, parent):
193 QTreeWidget.__init__(self, parent)
196 for i in range(0,self.viewport().height()/5):
197 if self.itemAt(QPoint(0,i*5)) == item:
201 for j in range(0,30):
202 if self.itemAt(QPoint(0,i*5 + j)) != item:
204 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
206 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
211 class StatusBarButton(QPushButton):
212 def __init__(self, icon, tooltip, func):
213 QPushButton.__init__(self, icon, '')
214 self.setToolTip(tooltip)
216 self.setMaximumWidth(25)
217 self.clicked.connect(func)
220 def keyPressEvent(self, e):
221 if e.key() == QtCore.Qt.Key_Return:
225 class QRCodeWidget(QWidget):
227 def __init__(self, data = None, size=4):
228 QWidget.__init__(self)
229 self.setMinimumSize(210, 210)
237 def set_addr(self, addr):
238 if self.addr != addr:
244 if self.addr and not self.qr:
245 self.qr = pyqrnative.QRCode(self.size, pyqrnative.QRErrorCorrectLevel.L)
246 self.qr.addData(self.addr)
250 def paintEvent(self, e):
255 black = QColor(0, 0, 0, 255)
256 white = QColor(255, 255, 255, 255)
259 qp = QtGui.QPainter()
263 qp.drawRect(0, 0, 198, 198)
267 k = self.qr.getModuleCount()
268 qp = QtGui.QPainter()
271 boxsize = min(r.width(), r.height())*0.8/k
273 left = (r.width() - size)/2
274 top = (r.height() - size)/2
278 if self.qr.isDark(r, c):
284 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
289 class QR_Window(QWidget):
291 def __init__(self, exchanger):
292 QWidget.__init__(self)
293 self.exchanger = exchanger
294 self.setWindowTitle('Electrum - '+_('Invoice'))
295 self.setMinimumSize(800, 250)
299 self.setFocusPolicy(QtCore.Qt.NoFocus)
301 main_box = QHBoxLayout()
303 self.qrw = QRCodeWidget()
304 main_box.addWidget(self.qrw, 1)
307 main_box.addLayout(vbox)
309 self.address_label = QLabel("")
310 self.address_label.setFont(QFont(MONOSPACE_FONT))
311 vbox.addWidget(self.address_label)
313 self.label_label = QLabel("")
314 vbox.addWidget(self.label_label)
316 self.amount_label = QLabel("")
317 vbox.addWidget(self.amount_label)
320 self.setLayout(main_box)
323 def set_content(self, addr, label, amount, currency):
325 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
326 self.address_label.setText(address_text)
328 if currency == 'BTC': currency = None
332 self.amount = Decimal(amount) / self.exchanger.exchange(1, currency) if currency else amount
334 self.amount = Decimal(amount)
335 self.amount = self.amount.quantize(Decimal('1.0000'))
338 amount_text += "<span style='font-size: 18pt'>%s %s</span><br/>" % (amount, currency)
339 amount_text += "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % str(self.amount)
340 self.amount_label.setText(amount_text)
343 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
344 self.label_label.setText(label_text)
346 msg = 'bitcoin:'+self.address
347 if self.amount is not None:
348 msg += '?amount=%s'%(str( self.amount))
349 if self.label is not None:
350 msg += '&label=%s'%(self.label)
351 elif self.label is not None:
352 msg += '?label=%s'%(self.label)
354 self.qrw.set_addr( msg )
359 def waiting_dialog(f):
365 w.setWindowTitle('Electrum')
375 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
380 def ok_cancel_buttons(dialog):
383 b = QPushButton("OK")
385 b.clicked.connect(dialog.accept)
386 b = QPushButton("Cancel")
388 b.clicked.connect(dialog.reject)
392 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
393 "receive":[[370],[370,200,130,130],[370,200,130,130]] }
395 class ElectrumWindow(QMainWindow):
397 def __init__(self, wallet, config):
398 QMainWindow.__init__(self)
402 self.wallet.interface.register_callback('updated', self.update_callback)
403 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
404 self.wallet.interface.register_callback('disconnected', self.update_callback)
405 self.wallet.interface.register_callback('disconnecting', self.update_callback)
407 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
408 self.merchant_name = config.get('merchant_name', 'Invoice')
410 set_language(config.get('language'))
412 self.qr_window = None
413 self.funds_error = False
414 self.completions = QStringListModel()
416 self.tabs = tabs = QTabWidget(self)
417 self.column_widths = self.config.get("column-widths", default_column_widths )
418 tabs.addTab(self.create_history_tab(), _('History') )
419 tabs.addTab(self.create_send_tab(), _('Send') )
420 tabs.addTab(self.create_receive_tab(), _('Receive') )
421 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
422 tabs.addTab(self.create_console_tab(), _('Console') )
423 tabs.setMinimumSize(600, 400)
424 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
425 self.setCentralWidget(tabs)
426 self.create_status_bar()
428 g = self.config.get("winpos-qt",[100, 100, 840, 400])
429 self.setGeometry(g[0], g[1], g[2], g[3])
430 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
431 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
432 self.setWindowTitle( title )
434 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
435 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
436 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
437 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
439 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
440 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
441 self.history_list.setFocus(True)
443 self.exchanger = exchange_rate.Exchanger(self)
444 self.toggle_QR_window(self.receive_tab_mode == 2)
445 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
447 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
448 if platform.system() == 'Windows':
449 n = 3 if self.wallet.seed else 2
450 tabs.setCurrentIndex (n)
451 tabs.setCurrentIndex (0)
453 # set initial message
454 self.console.showMessage(self.wallet.banner)
457 QMainWindow.close(self)
459 self.qr_window.close()
460 self.qr_window = None
462 def connect_slots(self, sender):
463 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
464 self.previous_payto_e=''
466 def timer_actions(self):
468 self.qr_window.qrw.update_qr()
470 if self.payto_e.hasFocus():
472 r = unicode( self.payto_e.text() )
473 if r != self.previous_payto_e:
474 self.previous_payto_e = r
476 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
478 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
482 s = r + ' <' + to_address + '>'
483 self.payto_e.setText(s)
486 def update_callback(self):
487 self.emit(QtCore.SIGNAL('updatesignal'))
489 def update_wallet(self):
490 if self.wallet.interface and self.wallet.interface.is_connected:
491 if not self.wallet.up_to_date:
492 text = _("Synchronizing...")
493 icon = QIcon(":icons/status_waiting.png")
495 c, u = self.wallet.get_balance()
496 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
497 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
498 text += self.create_quote_text(Decimal(c+u)/100000000)
499 icon = QIcon(":icons/status_connected.png")
501 text = _("Not connected")
502 icon = QIcon(":icons/status_disconnected.png")
504 self.status_text = text
505 self.statusBar().showMessage(text)
506 self.status_button.setIcon( icon )
508 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
509 self.update_history_tab()
510 self.update_receive_tab()
511 self.update_contacts_tab()
512 self.update_completions()
515 def create_quote_text(self, btc_balance):
516 quote_currency = self.config.get("currency", "None")
517 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
518 if quote_balance is None:
521 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
524 def create_history_tab(self):
525 self.history_list = l = MyTreeWidget(self)
527 for i,width in enumerate(self.column_widths['history']):
528 l.setColumnWidth(i, width)
529 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
530 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
531 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
533 l.setContextMenuPolicy(Qt.CustomContextMenu)
534 l.customContextMenuRequested.connect(self.create_history_menu)
538 def create_history_menu(self, position):
539 self.history_list.selectedIndexes()
540 item = self.history_list.currentItem()
542 tx_hash = str(item.data(0, Qt.UserRole).toString())
543 if not tx_hash: return
545 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
546 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
547 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
548 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
551 def show_tx_details(self, tx):
552 dialog = QDialog(None)
554 dialog.setWindowTitle(_("Transaction Details"))
556 dialog.setLayout(vbox)
557 dialog.setMinimumSize(600,300)
560 if tx_hash in self.wallet.transactions.keys():
561 is_mine, v, fee = self.wallet.get_tx_value(tx)
562 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
564 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
570 vbox.addWidget(QLabel("Transaction ID:"))
571 e = QLineEdit(tx_hash)
575 vbox.addWidget(QLabel("Date: %s"%time_str))
576 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
579 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
580 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
582 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
583 vbox.addWidget(QLabel("Transaction fee: unknown"))
585 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
587 vbox.addWidget( self.generate_transaction_information_widget(tx) )
589 ok_button = QPushButton(_("Close"))
590 ok_button.setDefault(True)
591 ok_button.clicked.connect(dialog.accept)
595 hbox.addWidget(ok_button)
599 def tx_label_clicked(self, item, column):
600 if column==2 and item.isSelected():
602 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
603 self.history_list.editItem( item, column )
604 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
607 def tx_label_changed(self, item, column):
611 tx_hash = str(item.data(0, Qt.UserRole).toString())
612 tx = self.wallet.transactions.get(tx_hash)
613 s = self.wallet.labels.get(tx_hash)
614 text = unicode( item.text(2) )
616 self.wallet.labels[tx_hash] = text
617 item.setForeground(2, QBrush(QColor('black')))
619 if s: self.wallet.labels.pop(tx_hash)
620 text = self.wallet.get_default_label(tx_hash)
621 item.setText(2, text)
622 item.setForeground(2, QBrush(QColor('gray')))
626 def edit_label(self, is_recv):
627 l = self.receive_list if is_recv else self.contacts_list
628 c = 2 if is_recv else 1
629 item = l.currentItem()
630 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
631 l.editItem( item, c )
632 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
634 def edit_amount(self):
635 l = self.receive_list
636 item = l.currentItem()
637 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
638 l.editItem( item, 3 )
639 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
642 def address_label_clicked(self, item, column, l, column_addr, column_label):
643 if column == column_label and item.isSelected():
644 addr = unicode( item.text(column_addr) )
645 label = unicode( item.text(column_label) )
646 if label in self.wallet.aliases.keys():
648 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
649 l.editItem( item, column )
650 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
653 def address_label_changed(self, item, column, l, column_addr, column_label):
655 if column == column_label:
656 addr = unicode( item.text(column_addr) )
657 text = unicode( item.text(column_label) )
661 if text not in self.wallet.aliases.keys():
662 old_addr = self.wallet.labels.get(text)
664 self.wallet.labels[addr] = text
667 print_error("Error: This is one of your aliases")
668 label = self.wallet.labels.get(addr,'')
669 item.setText(column_label, QString(label))
671 s = self.wallet.labels.get(addr)
673 self.wallet.labels.pop(addr)
677 self.update_history_tab()
678 self.update_completions()
680 self.recv_changed(item)
683 address = str( item.text(column_addr) )
684 text = str( item.text(3) )
686 index = self.wallet.addresses.index(address)
690 text = text.strip().upper()
691 m = re.match('^(\d+(|\.\d*))\s*(|BTC|EUR|USD|GBP|CNY|JPY|RUB|BRL)$', text)
694 currency = m.group(3)
698 currency = currency.upper()
699 self.wallet.requested_amounts[address] = (amount, currency)
701 label = self.wallet.labels.get(address)
703 label = self.merchant_name + ' - %04d'%(index+1)
704 self.wallet.labels[address] = label
707 self.qr_window.set_content( address, label, amount, currency )
711 if address in self.wallet.requested_amounts:
712 self.wallet.requested_amounts.pop(address)
714 self.update_receive_item(self.receive_list.currentItem())
717 def recv_changed(self, a):
718 "current item changed"
719 if a is not None and self.qr_window and self.qr_window.isVisible():
720 address = str(a.text(1))
721 label = self.wallet.labels.get(address)
723 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
725 amount, currency = None, None
726 self.qr_window.set_content( address, label, amount, currency )
729 def update_history_tab(self):
731 self.history_list.clear()
732 for item in self.wallet.get_tx_history():
733 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
736 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
742 icon = QIcon(":icons/unconfirmed.png")
744 icon = QIcon(":icons/clock%d.png"%conf)
746 icon = QIcon(":icons/confirmed.png")
749 icon = QIcon(":icons/unconfirmed.png")
751 if value is not None:
752 v_str = format_satoshis(value, True, self.wallet.num_zeros)
756 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
759 label, is_default_label = self.wallet.get_label(tx_hash)
761 label = _('Pruned transaction outputs')
762 is_default_label = False
764 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
765 item.setFont(2, QFont(MONOSPACE_FONT))
766 item.setFont(3, QFont(MONOSPACE_FONT))
767 item.setFont(4, QFont(MONOSPACE_FONT))
769 item.setForeground(3, QBrush(QColor("#BC1E1E")))
771 item.setData(0, Qt.UserRole, tx_hash)
772 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
774 item.setForeground(2, QBrush(QColor('grey')))
776 item.setIcon(0, icon)
777 self.history_list.insertTopLevelItem(0,item)
780 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
783 def create_send_tab(self):
788 grid.setColumnMinimumWidth(3,300)
789 grid.setColumnStretch(5,1)
791 self.payto_e = QLineEdit()
792 grid.addWidget(QLabel(_('Pay to')), 1, 0)
793 grid.addWidget(self.payto_e, 1, 1, 1, 3)
796 qrcode = qrscanner.scan_qr()
797 if 'address' in qrcode:
798 self.payto_e.setText(qrcode['address'])
799 if 'amount' in qrcode:
800 self.amount_e.setText(str(qrcode['amount']))
801 if 'label' in qrcode:
802 self.message_e.setText(qrcode['label'])
803 if 'message' in qrcode:
804 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
807 if qrscanner.is_available():
808 b = QPushButton(_("Scan QR code"))
809 b.clicked.connect(fill_from_qr)
810 grid.addWidget(b, 1, 5)
812 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)
814 completer = QCompleter()
815 completer.setCaseSensitivity(False)
816 self.payto_e.setCompleter(completer)
817 completer.setModel(self.completions)
819 self.message_e = QLineEdit()
820 grid.addWidget(QLabel(_('Description')), 2, 0)
821 grid.addWidget(self.message_e, 2, 1, 1, 3)
822 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)
824 self.amount_e = QLineEdit()
825 grid.addWidget(QLabel(_('Amount')), 3, 0)
826 grid.addWidget(self.amount_e, 3, 1, 1, 2)
827 grid.addWidget(HelpButton(
828 _('Amount to be sent.') + '\n\n' \
829 + _('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)
831 self.fee_e = QLineEdit()
832 grid.addWidget(QLabel(_('Fee')), 4, 0)
833 grid.addWidget(self.fee_e, 4, 1, 1, 2)
834 grid.addWidget(HelpButton(
835 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
836 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
837 + _('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)
840 b = EnterButton(_("Send"), self.do_send)
842 b = EnterButton(_("Create unsigned transaction"), self.do_send)
843 grid.addWidget(b, 6, 1)
845 b = EnterButton(_("Clear"),self.do_clear)
846 grid.addWidget(b, 6, 2)
848 self.payto_sig = QLabel('')
849 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
851 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
852 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
861 def entry_changed( is_fee ):
862 self.funds_error = False
863 amount = numbify(self.amount_e)
864 fee = numbify(self.fee_e)
865 if not is_fee: fee = None
868 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
870 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
873 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
874 text = self.status_text
877 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
878 self.funds_error = True
879 text = _( "Not enough funds" )
881 self.statusBar().showMessage(text)
882 self.amount_e.setPalette(palette)
883 self.fee_e.setPalette(palette)
885 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
886 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
891 def update_completions(self):
893 for addr,label in self.wallet.labels.items():
894 if addr in self.wallet.addressbook:
895 l.append( label + ' <' + addr + '>')
896 l = l + self.wallet.aliases.keys()
898 self.completions.setStringList(l)
904 label = unicode( self.message_e.text() )
905 r = unicode( self.payto_e.text() )
909 m1 = re.match(ALIAS_REGEXP, r)
910 # label or alias, with address in brackets
911 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
914 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
918 to_address = m2.group(2)
922 if not is_valid(to_address):
923 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
927 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
929 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
932 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
934 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
937 if self.wallet.use_encryption:
938 password = self.password_dialog()
945 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
946 except BaseException, e:
947 self.show_message(str(e))
951 for cb in self.wallet.plugin_hooks.get('send_tx'):
952 apply(cb, (wallet, self, tx))
956 self.wallet.labels[tx.hash()] = label
959 h = self.wallet.send_tx(tx)
960 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
961 status, msg = self.wallet.receive_tx( h )
963 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
965 self.update_contacts_tab()
967 QMessageBox.warning(self, _('Error'), msg, _('OK'))
969 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
971 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
972 with open(fileName,'w') as f:
973 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
974 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
976 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
981 def set_url(self, url):
982 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
983 self.tabs.setCurrentIndex(1)
984 label = self.wallet.labels.get(payto)
985 m_addr = label + ' <'+ payto+'>' if label else payto
986 self.payto_e.setText(m_addr)
988 self.message_e.setText(message)
989 self.amount_e.setText(amount)
991 self.set_frozen(self.payto_e,True)
992 self.set_frozen(self.amount_e,True)
993 self.set_frozen(self.message_e,True)
994 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
996 self.payto_sig.setVisible(False)
999 self.payto_sig.setVisible(False)
1000 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1002 self.set_frozen(e,False)
1004 def set_frozen(self,entry,frozen):
1006 entry.setReadOnly(True)
1007 entry.setFrame(False)
1008 palette = QPalette()
1009 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1010 entry.setPalette(palette)
1012 entry.setReadOnly(False)
1013 entry.setFrame(True)
1014 palette = QPalette()
1015 palette.setColor(entry.backgroundRole(), QColor('white'))
1016 entry.setPalette(palette)
1019 def toggle_freeze(self,addr):
1021 if addr in self.wallet.frozen_addresses:
1022 self.wallet.unfreeze(addr)
1024 self.wallet.freeze(addr)
1025 self.update_receive_tab()
1027 def toggle_priority(self,addr):
1029 if addr in self.wallet.prioritized_addresses:
1030 self.wallet.unprioritize(addr)
1032 self.wallet.prioritize(addr)
1033 self.update_receive_tab()
1036 def create_list_tab(self, headers):
1037 "generic tab creation method"
1038 l = MyTreeWidget(self)
1039 l.setColumnCount( len(headers) )
1040 l.setHeaderLabels( headers )
1043 vbox = QVBoxLayout()
1050 vbox.addWidget(buttons)
1052 hbox = QHBoxLayout()
1055 buttons.setLayout(hbox)
1060 def create_receive_tab(self):
1061 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
1062 l.setContextMenuPolicy(Qt.CustomContextMenu)
1063 l.customContextMenuRequested.connect(self.create_receive_menu)
1064 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1065 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1066 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
1067 self.receive_list = l
1068 self.receive_buttons_hbox = hbox
1073 def receive_tab_set_mode(self, i):
1074 self.save_column_widths()
1075 self.receive_tab_mode = i
1076 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
1078 self.update_receive_tab()
1079 self.toggle_QR_window(self.receive_tab_mode == 2)
1082 def save_column_widths(self):
1083 if self.receive_tab_mode == 0:
1084 widths = [ self.receive_list.columnWidth(0) ]
1087 for i in range(self.receive_list.columnCount() -1):
1088 widths.append(self.receive_list.columnWidth(i))
1089 self.column_widths["receive"][self.receive_tab_mode] = widths
1091 self.column_widths["history"] = []
1092 for i in range(self.history_list.columnCount() - 1):
1093 self.column_widths["history"].append(self.history_list.columnWidth(i))
1095 self.column_widths["contacts"] = []
1096 for i in range(self.contacts_list.columnCount() - 1):
1097 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1100 def create_contacts_tab(self):
1101 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1102 l.setContextMenuPolicy(Qt.CustomContextMenu)
1103 l.customContextMenuRequested.connect(self.create_contact_menu)
1104 for i,width in enumerate(self.column_widths['contacts']):
1105 l.setColumnWidth(i, width)
1107 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1108 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1109 self.contacts_list = l
1110 self.contacts_buttons_hbox = hbox
1111 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1116 def delete_imported_key(self, addr):
1117 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1118 self.wallet.imported_keys.pop(addr)
1119 self.update_receive_tab()
1120 self.update_history_tab()
1124 def create_receive_menu(self, position):
1125 # fixme: this function apparently has a side effect.
1126 # if it is not called the menu pops up several times
1127 #self.receive_list.selectedIndexes()
1129 item = self.receive_list.itemAt(position)
1131 addr = unicode(item.text(0))
1133 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1134 if self.receive_tab_mode == 2:
1135 menu.addAction(_("Request amount"), lambda: self.edit_amount())
1136 menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
1137 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1138 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
1139 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1140 if addr in self.wallet.imported_keys:
1141 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1143 if self.receive_tab_mode == 1:
1144 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1145 menu.addAction(t, lambda: self.toggle_freeze(addr))
1146 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1147 menu.addAction(t, lambda: self.toggle_priority(addr))
1149 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1152 def payto(self, x, is_alias):
1159 label = self.wallet.labels.get(addr)
1160 m_addr = label + ' <' + addr + '>' if label else addr
1161 self.tabs.setCurrentIndex(1)
1162 self.payto_e.setText(m_addr)
1163 self.amount_e.setFocus()
1165 def delete_contact(self, x, is_alias):
1166 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1167 if not is_alias and x in self.wallet.addressbook:
1168 self.wallet.addressbook.remove(x)
1169 if x in self.wallet.labels.keys():
1170 self.wallet.labels.pop(x)
1171 elif is_alias and x in self.wallet.aliases:
1172 self.wallet.aliases.pop(x)
1173 self.update_history_tab()
1174 self.update_contacts_tab()
1175 self.update_completions()
1177 def create_contact_menu(self, position):
1178 # fixme: this function apparently has a side effect.
1179 # if it is not called the menu pops up several times
1180 #self.contacts_list.selectedIndexes()
1182 item = self.contacts_list.itemAt(position)
1184 addr = unicode(item.text(0))
1185 label = unicode(item.text(1))
1186 is_alias = label in self.wallet.aliases.keys()
1187 x = label if is_alias else addr
1189 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1190 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1191 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1193 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1195 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1196 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1197 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1200 def update_receive_item(self, item):
1201 address = str(item.data(0,0).toString())
1202 label = self.wallet.labels.get(address,'')
1203 item.setData(1,0,label)
1206 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1208 amount, currency = None, None
1210 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1211 item.setData(2,0,amount_str)
1213 c, u = self.wallet.get_addr_balance(address)
1214 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1215 item.setData(3,0,balance)
1217 if self.receive_tab_mode == 1:
1218 if address in self.wallet.frozen_addresses:
1219 item.setBackgroundColor(0, QColor('lightblue'))
1220 elif address in self.wallet.prioritized_addresses:
1221 item.setBackgroundColor(0, QColor('lightgreen'))
1224 def update_receive_tab(self):
1225 l = self.receive_list
1228 l.setColumnHidden(2, not self.receive_tab_mode == 2)
1229 l.setColumnHidden(3, self.receive_tab_mode == 0)
1230 l.setColumnHidden(4, not self.receive_tab_mode == 1)
1231 if self.receive_tab_mode == 0:
1232 width = self.column_widths['receive'][0][0]
1233 l.setColumnWidth(0, width)
1235 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1236 l.setColumnWidth(i, width)
1239 for k, account in self.wallet.accounts.items():
1240 name = account.get('name',str(k))
1241 c,u = self.wallet.get_account_balance(k)
1242 account_item = QTreeWidgetItem( [ name, '', '', format_satoshis(c+u), ''] )
1243 l.addTopLevelItem(account_item)
1244 account_item.setExpanded(True)
1247 for is_change in [0,1]:
1248 name = "Receiving" if not is_change else "Change"
1249 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1250 account_item.addChild(seq_item)
1251 if not is_change: seq_item.setExpanded(True)
1255 for address in account[is_change]:
1256 h = self.wallet.history.get(address,[])
1261 if gap > self.wallet.gap_limit:
1266 num_tx = '*' if h == ['*'] else "%d"%len(h)
1267 item = QTreeWidgetItem( [ address, '', '', '', num_tx] )
1268 item.setFont(0, QFont(MONOSPACE_FONT))
1269 item.setFont(2, QFont(MONOSPACE_FONT))
1270 self.update_receive_item(item)
1272 item.setBackgroundColor(1, QColor('red'))
1273 seq_item.addChild(item)
1275 if self.wallet.imported_keys:
1276 c,u = self.wallet.get_imported_balance()
1277 account_item = QTreeWidgetItem( [ _('Imported'), '', '', format_satoshis(c+u), ''] )
1278 l.addTopLevelItem(account_item)
1279 account_item.setExpanded(True)
1280 for address in self.wallet.imported_keys.keys():
1281 item = QTreeWidgetItem( [ address, '', '', '', ''] )
1282 item.setFont(0, QFont(MONOSPACE_FONT))
1283 item.setFont(2, QFont(MONOSPACE_FONT))
1284 self.update_receive_item(item)
1285 account_item.addChild(item)
1288 # we use column 1 because column 0 may be hidden
1289 l.setCurrentItem(l.topLevelItem(0),1)
1291 def show_contact_details(self, m):
1292 a = self.wallet.aliases.get(m)
1294 if a[0] in self.wallet.authorities.keys():
1295 s = self.wallet.authorities.get(a[0])
1298 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1299 QMessageBox.information(self, 'Alias', msg, 'OK')
1301 def update_contacts_tab(self):
1303 l = self.contacts_list
1307 for alias, v in self.wallet.aliases.items():
1309 alias_targets.append(target)
1310 item = QTreeWidgetItem( [ target, alias, '-'] )
1311 item.setBackgroundColor(0, QColor('lightgray'))
1312 l.addTopLevelItem(item)
1314 for address in self.wallet.addressbook:
1315 if address in alias_targets: continue
1316 label = self.wallet.labels.get(address,'')
1318 for tx in self.wallet.transactions.values():
1319 if address in map(lambda x: x[0], tx.outputs): n += 1
1321 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1322 item.setFont(0, QFont(MONOSPACE_FONT))
1323 l.addTopLevelItem(item)
1325 l.setCurrentItem(l.topLevelItem(0))
1328 def create_console_tab(self):
1329 from qt_console import Console
1330 from electrum import util, bitcoin, commands
1331 self.console = console = Console()
1332 self.console.history = self.config.get("console-history",[])
1333 self.console.history_index = len(self.console.history)
1336 for p in self.wallet.plugins:
1338 p.init_console(self.console, self)
1341 print_msg("Error:cannot initialize plugin",p)
1342 traceback.print_exc(file=sys.stdout)
1345 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1346 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1348 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1350 def mkfunc(f, method):
1351 return lambda *args: apply( f, (method, args, self.password_dialog ))
1353 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1354 methods[m] = mkfunc(c._run, m)
1356 console.updateNamespace(methods)
1360 def create_status_bar(self):
1361 self.status_text = ""
1363 sb.setFixedHeight(35)
1364 qtVersion = qVersion()
1366 update_notification = UpdateLabel(self.config)
1367 if(update_notification.new_version):
1368 sb.addPermanentWidget(update_notification)
1370 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1371 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1372 if self.wallet.seed:
1373 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1374 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1375 if self.wallet.seed:
1376 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1377 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1378 sb.addPermanentWidget( self.status_button )
1380 self.setStatusBar(sb)
1384 self.config.set_key('gui', 'lite', True)
1387 self.lite.mini.show()
1389 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1390 self.lite.main(None)
1392 def new_contact_dialog(self):
1393 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1394 address = unicode(text)
1396 if is_valid(address):
1397 self.wallet.addressbook.append(address)
1399 self.update_contacts_tab()
1400 self.update_history_tab()
1401 self.update_completions()
1403 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1405 def show_master_public_key(self):
1406 dialog = QDialog(None)
1408 dialog.setWindowTitle(_("Master Public Key"))
1410 main_text = QTextEdit()
1411 main_text.setText(self.wallet.get_master_public_key())
1412 main_text.setReadOnly(True)
1413 main_text.setMaximumHeight(170)
1414 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1416 ok_button = QPushButton(_("OK"))
1417 ok_button.setDefault(True)
1418 ok_button.clicked.connect(dialog.accept)
1420 main_layout = QGridLayout()
1421 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1423 main_layout.addWidget(main_text, 1, 0)
1424 main_layout.addWidget(qrw, 1, 1 )
1426 vbox = QVBoxLayout()
1427 vbox.addLayout(main_layout)
1428 hbox = QHBoxLayout()
1430 hbox.addWidget(ok_button)
1431 vbox.addLayout(hbox)
1433 dialog.setLayout(vbox)
1438 def show_seed_dialog(self, wallet, parent=None):
1440 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1443 if wallet.use_encryption:
1444 password = parent.password_dialog()
1451 seed = wallet.decode_seed(password)
1453 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1456 self.show_seed(seed)
1459 def show_seed(self, seed):
1460 dialog = QDialog(None)
1462 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1464 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1466 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1468 seed_text = QTextEdit(brainwallet)
1469 seed_text.setReadOnly(True)
1470 seed_text.setMaximumHeight(130)
1472 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1473 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1474 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1475 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1476 label2 = QLabel(msg2)
1477 label2.setWordWrap(True)
1480 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1481 logo.setMaximumWidth(60)
1483 qrw = QRCodeWidget(seed, 4)
1485 ok_button = QPushButton(_("OK"))
1486 ok_button.setDefault(True)
1487 ok_button.clicked.connect(dialog.accept)
1489 grid = QGridLayout()
1490 #main_layout.addWidget(logo, 0, 0)
1492 grid.addWidget(logo, 0, 0)
1493 grid.addWidget(label1, 0, 1)
1495 grid.addWidget(seed_text, 1, 0, 1, 2)
1497 grid.addWidget(qrw, 0, 2, 2, 1)
1499 vbox = QVBoxLayout()
1500 vbox.addLayout(grid)
1501 vbox.addWidget(label2)
1503 hbox = QHBoxLayout()
1505 hbox.addWidget(ok_button)
1506 vbox.addLayout(hbox)
1508 dialog.setLayout(vbox)
1512 def show_qrcode(data, title = "QR code"):
1516 d.setWindowTitle(title)
1517 d.setMinimumSize(270, 300)
1518 vbox = QVBoxLayout()
1519 qrw = QRCodeWidget(data)
1520 vbox.addWidget(qrw, 1)
1521 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1522 hbox = QHBoxLayout()
1526 filename = "qrcode.bmp"
1527 bmp.save_qrcode(qrw.qr, filename)
1528 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1530 b = QPushButton(_("Save"))
1532 b.clicked.connect(print_qr)
1534 b = QPushButton(_("Close"))
1536 b.clicked.connect(d.accept)
1539 vbox.addLayout(hbox)
1543 def view_private_key(self,address):
1544 if not address: return
1545 if self.wallet.use_encryption:
1546 password = self.password_dialog()
1553 pk = self.wallet.get_private_key(address, password)
1554 except BaseException, e:
1555 self.show_message(str(e))
1558 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1561 def sign_message(self,address):
1562 if not address: return
1565 d.setWindowTitle(_('Sign Message'))
1566 d.setMinimumSize(410, 290)
1568 tab_widget = QTabWidget()
1570 layout = QGridLayout(tab)
1572 sign_address = QLineEdit()
1574 sign_address.setText(address)
1575 layout.addWidget(QLabel(_('Address')), 1, 0)
1576 layout.addWidget(sign_address, 1, 1)
1578 sign_message = QTextEdit()
1579 layout.addWidget(QLabel(_('Message')), 2, 0)
1580 layout.addWidget(sign_message, 2, 1)
1581 layout.setRowStretch(2,3)
1583 sign_signature = QTextEdit()
1584 layout.addWidget(QLabel(_('Signature')), 3, 0)
1585 layout.addWidget(sign_signature, 3, 1)
1586 layout.setRowStretch(3,1)
1589 if self.wallet.use_encryption:
1590 password = self.password_dialog()
1597 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1598 sign_signature.setText(signature)
1599 except BaseException, e:
1600 self.show_message(str(e))
1603 hbox = QHBoxLayout()
1604 b = QPushButton(_("Sign"))
1606 b.clicked.connect(do_sign)
1607 b = QPushButton(_("Close"))
1608 b.clicked.connect(d.accept)
1610 layout.addLayout(hbox, 4, 1)
1611 tab_widget.addTab(tab, _("Sign"))
1615 layout = QGridLayout(tab)
1617 verify_address = QLineEdit()
1618 layout.addWidget(QLabel(_('Address')), 1, 0)
1619 layout.addWidget(verify_address, 1, 1)
1621 verify_message = QTextEdit()
1622 layout.addWidget(QLabel(_('Message')), 2, 0)
1623 layout.addWidget(verify_message, 2, 1)
1624 layout.setRowStretch(2,3)
1626 verify_signature = QTextEdit()
1627 layout.addWidget(QLabel(_('Signature')), 3, 0)
1628 layout.addWidget(verify_signature, 3, 1)
1629 layout.setRowStretch(3,1)
1633 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1634 self.show_message(_("Signature verified"))
1635 except BaseException, e:
1636 self.show_message(str(e))
1639 hbox = QHBoxLayout()
1640 b = QPushButton(_("Verify"))
1641 b.clicked.connect(do_verify)
1643 b = QPushButton(_("Close"))
1644 b.clicked.connect(d.accept)
1646 layout.addLayout(hbox, 4, 1)
1647 tab_widget.addTab(tab, _("Verify"))
1649 vbox = QVBoxLayout()
1650 vbox.addWidget(tab_widget)
1655 def toggle_QR_window(self, show):
1656 if show and not self.qr_window:
1657 self.qr_window = QR_Window(self.exchanger)
1658 self.qr_window.setVisible(True)
1659 self.qr_window_geometry = self.qr_window.geometry()
1660 item = self.receive_list.currentItem()
1662 address = str(item.text(1))
1663 label = self.wallet.labels.get(address)
1664 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1665 self.qr_window.set_content( address, label, amount, currency )
1667 elif show and self.qr_window and not self.qr_window.isVisible():
1668 self.qr_window.setVisible(True)
1669 self.qr_window.setGeometry(self.qr_window_geometry)
1671 elif not show and self.qr_window and self.qr_window.isVisible():
1672 self.qr_window_geometry = self.qr_window.geometry()
1673 self.qr_window.setVisible(False)
1675 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1676 self.receive_list.setColumnHidden(2, self.qr_window is None or not self.qr_window.isVisible())
1677 #self.receive_list.setColumnWidth(1, 200)
1680 def question(self, msg):
1681 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1683 def show_message(self, msg):
1684 QMessageBox.information(self, _('Message'), msg, _('OK'))
1686 def password_dialog(self ):
1693 vbox = QVBoxLayout()
1694 msg = _('Please enter your password')
1695 vbox.addWidget(QLabel(msg))
1697 grid = QGridLayout()
1699 grid.addWidget(QLabel(_('Password')), 1, 0)
1700 grid.addWidget(pw, 1, 1)
1701 vbox.addLayout(grid)
1703 vbox.addLayout(ok_cancel_buttons(d))
1706 if not d.exec_(): return
1707 return unicode(pw.text())
1714 def change_password_dialog( wallet, parent=None ):
1717 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1725 new_pw = QLineEdit()
1726 new_pw.setEchoMode(2)
1727 conf_pw = QLineEdit()
1728 conf_pw.setEchoMode(2)
1730 vbox = QVBoxLayout()
1732 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1733 +_('To disable wallet encryption, enter an empty new password.')) \
1734 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1736 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1737 +_("Leave these fields empty if you want to disable encryption.")
1738 vbox.addWidget(QLabel(msg))
1740 grid = QGridLayout()
1743 if wallet.use_encryption:
1744 grid.addWidget(QLabel(_('Password')), 1, 0)
1745 grid.addWidget(pw, 1, 1)
1747 grid.addWidget(QLabel(_('New Password')), 2, 0)
1748 grid.addWidget(new_pw, 2, 1)
1750 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1751 grid.addWidget(conf_pw, 3, 1)
1752 vbox.addLayout(grid)
1754 vbox.addLayout(ok_cancel_buttons(d))
1757 if not d.exec_(): return
1759 password = unicode(pw.text()) if wallet.use_encryption else None
1760 new_password = unicode(new_pw.text())
1761 new_password2 = unicode(conf_pw.text())
1764 seed = wallet.decode_seed(password)
1766 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1769 if new_password != new_password2:
1770 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1771 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1773 wallet.update_password(seed, password, new_password)
1776 def seed_dialog(wallet, parent=None):
1780 vbox = QVBoxLayout()
1781 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1782 vbox.addWidget(QLabel(msg))
1784 grid = QGridLayout()
1787 seed_e = QLineEdit()
1788 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1789 grid.addWidget(seed_e, 1, 1)
1793 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1794 grid.addWidget(gap_e, 2, 1)
1795 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1796 vbox.addLayout(grid)
1798 vbox.addLayout(ok_cancel_buttons(d))
1801 if not d.exec_(): return
1804 gap = int(unicode(gap_e.text()))
1806 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1810 seed = str(seed_e.text())
1813 print_error("Warning: Not hex, trying decode")
1815 seed = mnemonic.mn_decode( seed.split(' ') )
1817 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1821 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1826 def generate_transaction_information_widget(self, tx):
1827 tabs = QTabWidget(self)
1830 grid_ui = QGridLayout(tab1)
1831 grid_ui.setColumnStretch(0,1)
1832 tabs.addTab(tab1, _('Outputs') )
1834 tree_widget = MyTreeWidget(self)
1835 tree_widget.setColumnCount(2)
1836 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1837 tree_widget.setColumnWidth(0, 300)
1838 tree_widget.setColumnWidth(1, 50)
1840 for output in tx.d["outputs"]:
1841 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1842 tree_widget.addTopLevelItem(item)
1844 tree_widget.setMaximumHeight(100)
1846 grid_ui.addWidget(tree_widget)
1849 grid_ui = QGridLayout(tab2)
1850 grid_ui.setColumnStretch(0,1)
1851 tabs.addTab(tab2, _('Inputs') )
1853 tree_widget = MyTreeWidget(self)
1854 tree_widget.setColumnCount(2)
1855 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1857 for input_line in tx.inputs:
1858 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1859 tree_widget.addTopLevelItem(item)
1861 tree_widget.setMaximumHeight(100)
1863 grid_ui.addWidget(tree_widget)
1867 def tx_dict_from_text(self, txt):
1869 tx_dict = json.loads(str(txt))
1870 assert "hex" in tx_dict.keys()
1871 assert "complete" in tx_dict.keys()
1872 if not tx_dict["complete"]:
1873 assert "input_info" in tx_dict.keys()
1875 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1880 def read_tx_from_file(self):
1881 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1885 with open(fileName, "r") as f:
1886 file_content = f.read()
1887 except (ValueError, IOError, os.error), reason:
1888 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1890 return self.tx_dict_from_text(file_content)
1893 def sign_raw_transaction(self, tx, input_info):
1894 if self.wallet.use_encryption:
1895 password = self.password_dialog()
1902 self.wallet.signrawtransaction(tx, input_info, [], password)
1904 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1906 with open(fileName, "w+") as f:
1907 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1908 self.show_message(_("Transaction saved succesfully"))
1909 except BaseException, e:
1910 self.show_message(str(e))
1913 def create_sign_transaction_window(self, tx_dict):
1914 tx = Transaction(tx_dict["hex"])
1916 dialog = QDialog(self)
1917 dialog.setMinimumWidth(500)
1918 dialog.setWindowTitle(_('Sign unsigned transaction'))
1921 vbox = QVBoxLayout()
1922 dialog.setLayout(vbox)
1923 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1925 if tx_dict["complete"] == True:
1926 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1928 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1929 vbox.addLayout(ok_cancel_buttons(dialog))
1930 input_info = json.loads(tx_dict["input_info"])
1933 self.sign_raw_transaction(tx, input_info)
1937 def do_sign_from_text(self):
1938 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1941 tx_dict = self.tx_dict_from_text(unicode(txt))
1943 self.create_sign_transaction_window(tx_dict)
1946 def do_sign_from_file(self):
1947 tx_dict = self.read_tx_from_file()
1949 self.create_sign_transaction_window(tx_dict)
1952 def send_raw_transaction(self, raw_tx):
1953 result, result_message = self.wallet.sendtx( raw_tx )
1955 self.show_message("Transaction succesfully sent: %s" % (result_message))
1957 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1960 def create_send_transaction_window(self, tx_dict):
1961 tx = Transaction(tx_dict["hex"])
1963 dialog = QDialog(self)
1964 dialog.setMinimumWidth(500)
1965 dialog.setWindowTitle(_('Send raw transaction'))
1968 vbox = QVBoxLayout()
1969 dialog.setLayout(vbox)
1970 vbox.addWidget( self.generate_transaction_information_widget(tx))
1972 if tx_dict["complete"] == False:
1973 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1975 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1976 vbox.addLayout(ok_cancel_buttons(dialog))
1979 self.send_raw_transaction(tx_dict["hex"])
1982 def do_send_from_file(self):
1983 tx_dict = self.read_tx_from_file()
1985 self.create_send_transaction_window(tx_dict)
1988 def do_send_from_text(self):
1989 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1992 tx_dict = self.tx_dict_from_text(unicode(txt))
1994 self.create_send_transaction_window(tx_dict)
1997 def do_export_privkeys(self):
1998 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.")))
2000 if self.wallet.use_encryption:
2001 password = self.password_dialog()
2007 select_export = _('Select file to export your private keys to')
2008 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
2010 with open(fileName, "w+") as csvfile:
2011 transaction = csv.writer(csvfile)
2012 transaction.writerow(["address", "private_key"])
2015 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
2016 transaction.writerow(["%34s"%addr,pk])
2018 self.show_message(_("Private keys exported."))
2020 except (IOError, os.error), reason:
2021 export_error_label = _("Electrum was unable to produce a private key-export.")
2022 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
2024 except BaseException, e:
2025 self.show_message(str(e))
2029 def do_import_labels(self):
2030 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
2031 if not labelsFile: return
2033 f = open(labelsFile, 'r')
2036 for key, value in json.loads(data).items():
2037 self.wallet.labels[key] = value
2039 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
2040 except (IOError, os.error), reason:
2041 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2045 def do_export_labels(self):
2046 labels = self.wallet.labels
2048 labelsFile = util.user_dir() + '/labels.dat'
2049 f = open(labelsFile, 'w+')
2050 json.dump(labels, f)
2052 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
2053 except (IOError, os.error), reason:
2054 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
2056 def do_export_history(self):
2057 from gui_lite import csv_transaction
2058 csv_transaction(self.wallet)
2060 def do_import_privkey(self):
2061 if not self.wallet.imported_keys:
2062 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
2063 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
2064 + _('Are you sure you understand what you are doing?'), 3, 4)
2067 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
2069 sec = str(text).strip()
2070 if self.wallet.use_encryption:
2071 password = self.password_dialog()
2077 addr = self.wallet.import_key(sec, password)
2079 QMessageBox.critical(None, _("Unable to import key"), "error")
2081 QMessageBox.information(None, _("Key imported"), addr)
2082 self.update_receive_tab()
2083 self.update_history_tab()
2084 except BaseException as e:
2085 QMessageBox.critical(None, _("Unable to import key"), str(e))
2087 def settings_dialog(self):
2089 d.setWindowTitle(_('Electrum Settings'))
2091 vbox = QVBoxLayout()
2093 tabs = QTabWidget(self)
2094 vbox.addWidget(tabs)
2097 grid_ui = QGridLayout(tab1)
2098 grid_ui.setColumnStretch(0,1)
2099 tabs.addTab(tab1, _('Display') )
2101 nz_label = QLabel(_('Display zeros'))
2102 grid_ui.addWidget(nz_label, 3, 0)
2104 nz_e.setText("%d"% self.wallet.num_zeros)
2105 grid_ui.addWidget(nz_e, 3, 1)
2106 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2107 grid_ui.addWidget(HelpButton(msg), 3, 2)
2108 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
2109 if not self.config.is_modifiable('num_zeros'):
2110 for w in [nz_e, nz_label]: w.setEnabled(False)
2112 lang_label=QLabel(_('Language') + ':')
2113 grid_ui.addWidget(lang_label , 8, 0)
2114 lang_combo = QComboBox()
2115 from i18n import languages
2116 lang_combo.addItems(languages.values())
2118 index = languages.keys().index(self.config.get("language",''))
2121 lang_combo.setCurrentIndex(index)
2122 grid_ui.addWidget(lang_combo, 8, 1)
2123 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
2124 if not self.config.is_modifiable('language'):
2125 for w in [lang_combo, lang_label]: w.setEnabled(False)
2127 currencies = self.exchanger.get_currencies()
2128 currencies.insert(0, "None")
2130 cur_label=QLabel(_('Currency') + ':')
2131 grid_ui.addWidget(cur_label , 9, 0)
2132 cur_combo = QComboBox()
2133 cur_combo.addItems(currencies)
2135 index = currencies.index(self.config.get('currency', "None"))
2138 cur_combo.setCurrentIndex(index)
2139 grid_ui.addWidget(cur_combo, 9, 1)
2140 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
2142 view_label=QLabel(_('Receive Tab') + ':')
2143 grid_ui.addWidget(view_label , 10, 0)
2144 view_combo = QComboBox()
2145 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
2146 view_combo.setCurrentIndex(self.receive_tab_mode)
2147 grid_ui.addWidget(view_combo, 10, 1)
2148 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
2149 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
2150 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
2151 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
2153 grid_ui.addWidget(HelpButton(hh), 10, 2)
2157 grid_wallet = QGridLayout(tab2)
2158 grid_wallet.setColumnStretch(0,1)
2159 tabs.addTab(tab2, _('Wallet') )
2161 fee_label = QLabel(_('Transaction fee'))
2162 grid_wallet.addWidget(fee_label, 0, 0)
2164 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
2165 grid_wallet.addWidget(fee_e, 0, 1)
2166 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
2167 + _('Recommended value') + ': 0.001'
2168 grid_wallet.addWidget(HelpButton(msg), 0, 2)
2169 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
2170 if not self.config.is_modifiable('fee'):
2171 for w in [fee_e, fee_label]: w.setEnabled(False)
2173 usechange_label = QLabel(_('Use change addresses'))
2174 grid_wallet.addWidget(usechange_label, 1, 0)
2175 usechange_combo = QComboBox()
2176 usechange_combo.addItems([_('Yes'), _('No')])
2177 usechange_combo.setCurrentIndex(not self.wallet.use_change)
2178 grid_wallet.addWidget(usechange_combo, 1, 1)
2179 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
2180 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
2182 gap_label = QLabel(_('Gap limit'))
2183 grid_wallet.addWidget(gap_label, 2, 0)
2185 gap_e.setText("%d"% self.wallet.gap_limit)
2186 grid_wallet.addWidget(gap_e, 2, 1)
2187 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2188 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2189 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2190 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2191 + _('Warning') + ': ' \
2192 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2193 + _('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'
2194 grid_wallet.addWidget(HelpButton(msg), 2, 2)
2195 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
2196 if not self.config.is_modifiable('gap_limit'):
2197 for w in [gap_e, gap_label]: w.setEnabled(False)
2199 grid_wallet.setRowStretch(3,1)
2204 grid_io = QGridLayout(tab3)
2205 grid_io.setColumnStretch(0,1)
2206 tabs.addTab(tab3, _('Import/Export') )
2208 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2209 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2210 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2211 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2213 grid_io.addWidget(QLabel(_('History')), 2, 0)
2214 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2215 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2217 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2219 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2220 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2221 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2223 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2224 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2225 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2226 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2227 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2229 grid_io.setRowStretch(4,1)
2232 grid_raw = QGridLayout(tab4)
2233 grid_raw.setColumnStretch(0,1)
2234 tabs.addTab(tab4, _('Raw transactions') )
2236 #grid_raw.addWidget(QLabel(_("Read raw transaction")), 3, 0)
2237 #grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),3,1)
2238 #grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),3,2)
2239 #grid_raw.addWidget(HelpButton(_("This will show you some useful information about an unsigned transaction")),3,3)
2241 if self.wallet.seed:
2242 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2243 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2244 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2245 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2247 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2248 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2249 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2250 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2251 grid_raw.setRowStretch(3,1)
2253 vbox.addLayout(ok_cancel_buttons(d))
2257 if not d.exec_(): return
2259 fee = unicode(fee_e.text())
2261 fee = int( 100000000 * Decimal(fee) )
2263 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2266 if self.wallet.fee != fee:
2267 self.wallet.fee = fee
2270 nz = unicode(nz_e.text())
2275 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2278 if self.wallet.num_zeros != nz:
2279 self.wallet.num_zeros = nz
2280 self.config.set_key('num_zeros', nz, True)
2281 self.update_history_tab()
2282 self.update_receive_tab()
2284 usechange_result = usechange_combo.currentIndex() == 0
2285 if self.wallet.use_change != usechange_result:
2286 self.wallet.use_change = usechange_result
2287 self.config.set_key('use_change', self.wallet.use_change, True)
2290 n = int(gap_e.text())
2292 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2295 if self.wallet.gap_limit != n:
2296 r = self.wallet.change_gap_limit(n)
2298 self.update_receive_tab()
2299 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2301 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2303 need_restart = False
2305 lang_request = languages.keys()[lang_combo.currentIndex()]
2306 if lang_request != self.config.get('language'):
2307 self.config.set_key("language", lang_request, True)
2310 cur_request = str(currencies[cur_combo.currentIndex()])
2311 if cur_request != self.config.get('currency', "None"):
2312 self.config.set_key('currency', cur_request, True)
2313 self.update_wallet()
2316 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2318 self.receive_tab_set_mode(view_combo.currentIndex())
2322 def network_dialog(wallet, parent=None):
2323 interface = wallet.interface
2325 if interface.is_connected:
2326 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2328 status = _("Not connected")
2329 server = interface.server
2332 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2333 server = interface.server
2335 plist, servers_list = interface.get_servers_list()
2339 d.setWindowTitle(_('Server'))
2340 d.setMinimumSize(375, 20)
2342 vbox = QVBoxLayout()
2345 hbox = QHBoxLayout()
2347 l.setPixmap(QPixmap(":icons/network.png"))
2350 hbox.addWidget(QLabel(status))
2352 vbox.addLayout(hbox)
2356 grid = QGridLayout()
2358 vbox.addLayout(grid)
2361 server_protocol = QComboBox()
2362 server_host = QLineEdit()
2363 server_host.setFixedWidth(200)
2364 server_port = QLineEdit()
2365 server_port.setFixedWidth(60)
2367 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2368 protocol_letters = 'thsg'
2369 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2370 server_protocol.addItems(protocol_names)
2372 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2373 grid.addWidget(server_protocol, 0, 1)
2374 grid.addWidget(server_host, 0, 2)
2375 grid.addWidget(server_port, 0, 3)
2377 def change_protocol(p):
2378 protocol = protocol_letters[p]
2379 host = unicode(server_host.text())
2380 pp = plist.get(host,DEFAULT_PORTS)
2381 if protocol not in pp.keys():
2382 protocol = pp.keys()[0]
2384 server_host.setText( host )
2385 server_port.setText( port )
2387 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2389 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2390 servers_list_widget = QTreeWidget(parent)
2391 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2392 servers_list_widget.setMaximumHeight(150)
2393 servers_list_widget.setColumnWidth(0, 240)
2394 for _host in servers_list.keys():
2395 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2396 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2398 def change_server(host, protocol=None):
2399 pp = plist.get(host,DEFAULT_PORTS)
2401 port = pp.get(protocol)
2402 if not port: protocol = None
2405 if 't' in pp.keys():
2407 port = pp.get(protocol)
2409 protocol = pp.keys()[0]
2410 port = pp.get(protocol)
2412 server_host.setText( host )
2413 server_port.setText( port )
2414 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2416 if not plist: return
2417 for p in protocol_letters:
2418 i = protocol_letters.index(p)
2419 j = server_protocol.model().index(i,0)
2420 if p not in pp.keys():
2421 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2423 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2427 host, port, protocol = server.split(':')
2428 change_server(host,protocol)
2430 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2431 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2433 if not wallet.config.is_modifiable('server'):
2434 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2437 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2438 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2439 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2440 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2443 proxy_mode = QComboBox()
2444 proxy_host = QLineEdit()
2445 proxy_host.setFixedWidth(200)
2446 proxy_port = QLineEdit()
2447 proxy_port.setFixedWidth(60)
2448 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2450 def check_for_disable(index = False):
2451 if proxy_mode.currentText() != 'NONE':
2452 proxy_host.setEnabled(True)
2453 proxy_port.setEnabled(True)
2455 proxy_host.setEnabled(False)
2456 proxy_port.setEnabled(False)
2459 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2461 if not wallet.config.is_modifiable('proxy'):
2462 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2464 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2465 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2466 proxy_host.setText(proxy_config.get("host"))
2467 proxy_port.setText(proxy_config.get("port"))
2469 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2470 grid.addWidget(proxy_mode, 2, 1)
2471 grid.addWidget(proxy_host, 2, 2)
2472 grid.addWidget(proxy_port, 2, 3)
2475 vbox.addLayout(ok_cancel_buttons(d))
2478 if not d.exec_(): return
2480 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2481 if proxy_mode.currentText() != 'NONE':
2482 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2486 wallet.config.set_key("proxy", proxy, True)
2487 wallet.config.set_key("server", server, True)
2488 interface.set_server(server, proxy)
2489 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2492 def closeEvent(self, event):
2494 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2495 self.save_column_widths()
2496 self.config.set_key("column-widths", self.column_widths, True)
2497 self.config.set_key("console-history",self.console.history[-50:])
2503 def __init__(self, wallet, config, app=None):
2504 self.wallet = wallet
2505 self.config = config
2507 self.app = QApplication(sys.argv)
2510 def restore_or_create(self):
2511 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2512 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2513 if r==2: return None
2514 return 'restore' if r==1 else 'create'
2516 def seed_dialog(self):
2517 return ElectrumWindow.seed_dialog( self.wallet )
2519 def network_dialog(self):
2520 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2523 def show_seed(self):
2524 ElectrumWindow.show_seed_dialog(self.wallet)
2527 def password_dialog(self):
2528 ElectrumWindow.change_password_dialog(self.wallet)
2531 def restore_wallet(self):
2532 wallet = self.wallet
2533 # wait until we are connected, because the user might have selected another server
2534 if not wallet.interface.is_connected:
2535 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2536 waiting_dialog(waiting)
2538 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2539 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2541 wallet.set_up_to_date(False)
2542 wallet.interface.poke('synchronizer')
2543 waiting_dialog(waiting)
2544 if wallet.is_found():
2545 print_error( "Recovery successful" )
2547 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2554 w = ElectrumWindow(self.wallet, self.config)
2555 if url: w.set_url(url)