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
21 from util import print_error
22 import os.path, json, util, ast
27 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
32 import PyQt4.QtGui as QtGui
33 from interface import DEFAULT_SERVERS
38 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
40 from wallet import format_satoshis
41 from bitcoin import Transaction, is_valid
42 import bmp, mnemonic, pyqrnative, qrscanner
45 from decimal import Decimal
53 if platform.system() == 'Windows':
54 MONOSPACE_FONT = 'Lucida Console'
55 elif platform.system() == 'Darwin':
56 MONOSPACE_FONT = 'Monaco'
58 MONOSPACE_FONT = 'monospace'
60 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
62 from version import ELECTRUM_VERSION
65 class UpdateLabel(QtGui.QLabel):
66 def __init__(self, config, parent=None):
67 QtGui.QLabel.__init__(self, parent)
68 self.new_version = False
71 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
72 con.request("GET", "/version")
73 res = con.getresponse()
74 except socket.error as msg:
75 print_error("Could not retrieve version information")
79 self.latest_version = res.read()
80 self.latest_version = self.latest_version.replace("\n","")
81 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
83 self.current_version = ELECTRUM_VERSION
84 if(self.compare_versions(self.latest_version, self.current_version) == 1):
85 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
86 if(self.compare_versions(self.latest_version, latest_seen) == 1):
87 self.new_version = True
88 self.setText(_("New version available") + ": " + self.latest_version)
91 def compare_versions(self, version1, version2):
93 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
94 return cmp(normalize(version1), normalize(version2))
96 def ignore_this_version(self):
98 self.config.set_key("last_seen_version", self.latest_version, True)
99 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
102 def ignore_all_version(self):
104 self.config.set_key("last_seen_version", "9.9.9", True)
105 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
108 def open_website(self):
109 webbrowser.open("http://electrum.org/download.html")
112 def mouseReleaseEvent(self, event):
113 dialog = QDialog(self)
114 dialog.setWindowTitle(_('Electrum update'))
117 main_layout = QGridLayout()
118 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
120 ignore_version = QPushButton(_("Ignore this version"))
121 ignore_version.clicked.connect(self.ignore_this_version)
123 ignore_all_versions = QPushButton(_("Ignore all versions"))
124 ignore_all_versions.clicked.connect(self.ignore_all_version)
126 open_website = QPushButton(_("Goto download page"))
127 open_website.clicked.connect(self.open_website)
129 main_layout.addWidget(ignore_version, 1, 0)
130 main_layout.addWidget(ignore_all_versions, 1, 1)
131 main_layout.addWidget(open_website, 1, 2)
133 dialog.setLayout(main_layout)
137 if not dialog.exec_(): return
139 def numbify(entry, is_int = False):
140 text = unicode(entry.text()).strip()
141 pos = entry.cursorPosition()
143 if not is_int: chars +='.'
144 s = ''.join([i for i in text if i in chars])
148 s = s.replace('.','')
149 s = s[:p] + '.' + s[p:p+8]
151 amount = int( Decimal(s) * 100000000 )
160 entry.setCursorPosition(pos)
164 class Timer(QtCore.QThread):
167 self.emit(QtCore.SIGNAL('timersignal'))
170 class HelpButton(QPushButton):
171 def __init__(self, text):
172 QPushButton.__init__(self, '?')
173 self.setFocusPolicy(Qt.NoFocus)
174 self.setFixedWidth(20)
175 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
178 class EnterButton(QPushButton):
179 def __init__(self, text, func):
180 QPushButton.__init__(self, text)
182 self.clicked.connect(func)
184 def keyPressEvent(self, e):
185 if e.key() == QtCore.Qt.Key_Return:
188 class MyTreeWidget(QTreeWidget):
189 def __init__(self, parent):
190 QTreeWidget.__init__(self, parent)
193 for i in range(0,self.viewport().height()/5):
194 if self.itemAt(QPoint(0,i*5)) == item:
198 for j in range(0,30):
199 if self.itemAt(QPoint(0,i*5 + j)) != item:
201 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
203 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
208 class StatusBarButton(QPushButton):
209 def __init__(self, icon, tooltip, func):
210 QPushButton.__init__(self, icon, '')
211 self.setToolTip(tooltip)
213 self.setMaximumWidth(25)
214 self.clicked.connect(func)
217 def keyPressEvent(self, e):
218 if e.key() == QtCore.Qt.Key_Return:
222 class QRCodeWidget(QWidget):
224 def __init__(self, data = None, size=4):
225 QWidget.__init__(self)
226 self.setMinimumSize(210, 210)
234 def set_addr(self, addr):
235 if self.addr != addr:
241 if self.addr and not self.qr:
242 self.qr = pyqrnative.QRCode(self.size, pyqrnative.QRErrorCorrectLevel.L)
243 self.qr.addData(self.addr)
247 def paintEvent(self, e):
252 black = QColor(0, 0, 0, 255)
253 white = QColor(255, 255, 255, 255)
256 qp = QtGui.QPainter()
260 qp.drawRect(0, 0, 198, 198)
264 k = self.qr.getModuleCount()
265 qp = QtGui.QPainter()
268 boxsize = min(r.width(), r.height())*0.8/k
270 left = (r.width() - size)/2
271 top = (r.height() - size)/2
275 if self.qr.isDark(r, c):
281 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
286 class QR_Window(QWidget):
288 def __init__(self, exchanger):
289 QWidget.__init__(self)
290 self.exchanger = exchanger
291 self.setWindowTitle('Electrum - '+_('Invoice'))
292 self.setMinimumSize(800, 250)
296 self.setFocusPolicy(QtCore.Qt.NoFocus)
298 main_box = QHBoxLayout()
300 self.qrw = QRCodeWidget()
301 main_box.addWidget(self.qrw, 1)
304 main_box.addLayout(vbox)
306 self.address_label = QLabel("")
307 self.address_label.setFont(QFont(MONOSPACE_FONT))
308 vbox.addWidget(self.address_label)
310 self.label_label = QLabel("")
311 vbox.addWidget(self.label_label)
313 self.amount_label = QLabel("")
314 vbox.addWidget(self.amount_label)
317 self.setLayout(main_box)
320 def set_content(self, addr, label, amount, currency):
322 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
323 self.address_label.setText(address_text)
325 if currency == 'BTC': currency = None
329 self.amount = Decimal(amount) / self.exchanger.exchange(1, currency) if currency else amount
331 self.amount = Decimal(amount)
332 self.amount = self.amount.quantize(Decimal('1.0000'))
335 amount_text += "<span style='font-size: 18pt'>%s %s</span><br/>" % (amount, currency)
336 amount_text += "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % str(self.amount)
337 self.amount_label.setText(amount_text)
340 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
341 self.label_label.setText(label_text)
343 msg = 'bitcoin:'+self.address
344 if self.amount is not None:
345 msg += '?amount=%s'%(str( self.amount))
346 if self.label is not None:
347 msg += '&label=%s'%(self.label)
348 elif self.label is not None:
349 msg += '?label=%s'%(self.label)
351 self.qrw.set_addr( msg )
356 def waiting_dialog(f):
362 w.setWindowTitle('Electrum')
372 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
377 def ok_cancel_buttons(dialog):
380 b = QPushButton("OK")
382 b.clicked.connect(dialog.accept)
383 b = QPushButton("Cancel")
385 b.clicked.connect(dialog.reject)
389 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
390 "receive":[[310],[310,200,130,130],[310,200,130,130]] }
392 class ElectrumWindow(QMainWindow):
394 def __init__(self, wallet, config):
395 QMainWindow.__init__(self)
399 self.wallet.interface.register_callback('updated', self.update_callback)
400 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
401 self.wallet.interface.register_callback('disconnected', self.update_callback)
402 self.wallet.interface.register_callback('disconnecting', self.update_callback)
404 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
405 self.merchant_name = config.get('merchant_name', 'Invoice')
407 self.qr_window = None
408 self.funds_error = False
409 self.completions = QStringListModel()
411 self.tabs = tabs = QTabWidget(self)
412 self.column_widths = self.config.get("column-widths", default_column_widths )
413 tabs.addTab(self.create_history_tab(), _('History') )
414 tabs.addTab(self.create_send_tab(), _('Send') )
415 tabs.addTab(self.create_receive_tab(), _('Receive') )
416 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
417 tabs.addTab(self.create_console_tab(), _('Console') )
418 tabs.setMinimumSize(600, 400)
419 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
420 self.setCentralWidget(tabs)
421 self.create_status_bar()
423 g = self.config.get("winpos-qt",[100, 100, 840, 400])
424 self.setGeometry(g[0], g[1], g[2], g[3])
425 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
426 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
427 self.setWindowTitle( title )
429 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
430 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
431 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
432 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
434 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
435 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
436 self.history_list.setFocus(True)
438 self.exchanger = exchange_rate.Exchanger(self)
439 self.toggle_QR_window(self.receive_tab_mode == 2)
440 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
442 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
443 if platform.system() == 'Windows':
444 n = 3 if self.wallet.seed else 2
445 tabs.setCurrentIndex (n)
446 tabs.setCurrentIndex (0)
448 # set initial message
449 self.console.showMessage(self.wallet.banner)
452 QMainWindow.close(self)
454 self.qr_window.close()
455 self.qr_window = None
457 def connect_slots(self, sender):
458 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
459 self.previous_payto_e=''
461 def timer_actions(self):
463 self.qr_window.qrw.update_qr()
465 if self.payto_e.hasFocus():
467 r = unicode( self.payto_e.text() )
468 if r != self.previous_payto_e:
469 self.previous_payto_e = r
471 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
473 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
477 s = r + ' <' + to_address + '>'
478 self.payto_e.setText(s)
481 def update_callback(self):
482 self.emit(QtCore.SIGNAL('updatesignal'))
484 def update_wallet(self):
485 if self.wallet.interface and self.wallet.interface.is_connected:
486 if not self.wallet.up_to_date:
487 text = _("Synchronizing...")
488 icon = QIcon(":icons/status_waiting.png")
490 c, u = self.wallet.get_balance()
491 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
492 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
493 text += self.create_quote_text(Decimal(c+u)/100000000)
494 icon = QIcon(":icons/status_connected.png")
496 text = _("Not connected")
497 icon = QIcon(":icons/status_disconnected.png")
499 self.status_text = text
500 self.statusBar().showMessage(text)
501 self.status_button.setIcon( icon )
503 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
504 self.update_history_tab()
505 self.update_receive_tab()
506 self.update_contacts_tab()
507 self.update_completions()
510 def create_quote_text(self, btc_balance):
511 quote_currency = self.config.get("currency", "None")
512 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
513 if quote_balance is None:
516 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
519 def create_history_tab(self):
520 self.history_list = l = MyTreeWidget(self)
522 for i,width in enumerate(self.column_widths['history']):
523 l.setColumnWidth(i, width)
524 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
525 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
526 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
528 l.setContextMenuPolicy(Qt.CustomContextMenu)
529 l.customContextMenuRequested.connect(self.create_history_menu)
533 def create_history_menu(self, position):
534 self.history_list.selectedIndexes()
535 item = self.history_list.currentItem()
537 tx_hash = str(item.data(0, Qt.UserRole).toString())
538 if not tx_hash: return
540 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
541 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
542 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
543 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
546 def show_tx_details(self, tx):
547 dialog = QDialog(None)
549 dialog.setWindowTitle(_("Transaction Details"))
551 dialog.setLayout(vbox)
552 dialog.setMinimumSize(600,300)
555 if tx_hash in self.wallet.transactions.keys():
556 is_mine, v, fee = self.wallet.get_tx_value(tx)
557 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
559 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
565 vbox.addWidget(QLabel("Transaction ID:"))
566 e = QLineEdit(tx_hash)
570 vbox.addWidget(QLabel("Date: %s"%time_str))
571 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
574 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
575 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
577 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
578 vbox.addWidget(QLabel("Transaction fee: unknown"))
580 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
582 vbox.addWidget( self.generate_transaction_information_widget(tx) )
584 ok_button = QPushButton(_("Close"))
585 ok_button.setDefault(True)
586 ok_button.clicked.connect(dialog.accept)
590 hbox.addWidget(ok_button)
594 def tx_label_clicked(self, item, column):
595 if column==2 and item.isSelected():
597 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
598 self.history_list.editItem( item, column )
599 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
602 def tx_label_changed(self, item, column):
606 tx_hash = str(item.data(0, Qt.UserRole).toString())
607 tx = self.wallet.transactions.get(tx_hash)
608 s = self.wallet.labels.get(tx_hash)
609 text = unicode( item.text(2) )
611 self.wallet.labels[tx_hash] = text
612 item.setForeground(2, QBrush(QColor('black')))
614 if s: self.wallet.labels.pop(tx_hash)
615 text = self.wallet.get_default_label(tx_hash)
616 item.setText(2, text)
617 item.setForeground(2, QBrush(QColor('gray')))
621 def edit_label(self, is_recv):
622 l = self.receive_list if is_recv else self.contacts_list
623 c = 2 if is_recv else 1
624 item = l.currentItem()
625 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
626 l.editItem( item, c )
627 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
629 def edit_amount(self):
630 l = self.receive_list
631 item = l.currentItem()
632 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
633 l.editItem( item, 3 )
634 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
637 def address_label_clicked(self, item, column, l, column_addr, column_label):
638 if column == column_label and item.isSelected():
639 addr = unicode( item.text(column_addr) )
640 label = unicode( item.text(column_label) )
641 if label in self.wallet.aliases.keys():
643 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
644 l.editItem( item, column )
645 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
648 def address_label_changed(self, item, column, l, column_addr, column_label):
650 if column == column_label:
651 addr = unicode( item.text(column_addr) )
652 text = unicode( item.text(column_label) )
656 if text not in self.wallet.aliases.keys():
657 old_addr = self.wallet.labels.get(text)
659 self.wallet.labels[addr] = text
662 print_error("Error: This is one of your aliases")
663 label = self.wallet.labels.get(addr,'')
664 item.setText(column_label, QString(label))
666 s = self.wallet.labels.get(addr)
668 self.wallet.labels.pop(addr)
672 self.update_history_tab()
673 self.update_completions()
675 self.recv_changed(item)
678 address = str( item.text(column_addr) )
679 text = str( item.text(3) )
681 index = self.wallet.addresses.index(address)
685 text = text.strip().upper()
686 m = re.match('^(\d+(|\.\d*))\s*(|BTC|EUR|USD|GBP|CNY|JPY|RUB|BRL)$', text)
689 currency = m.group(3)
693 currency = currency.upper()
694 self.wallet.requested_amounts[address] = (amount, currency)
696 label = self.wallet.labels.get(address)
698 label = self.merchant_name + ' - %04d'%(index+1)
699 self.wallet.labels[address] = label
702 self.qr_window.set_content( address, label, amount, currency )
706 if address in self.wallet.requested_amounts:
707 self.wallet.requested_amounts.pop(address)
709 self.update_receive_item(self.receive_list.currentItem())
712 def recv_changed(self, a):
713 "current item changed"
714 if a is not None and self.qr_window and self.qr_window.isVisible():
715 address = str(a.text(1))
716 label = self.wallet.labels.get(address)
718 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
720 amount, currency = None, None
721 self.qr_window.set_content( address, label, amount, currency )
724 def update_history_tab(self):
726 self.history_list.clear()
727 for item in self.wallet.get_tx_history():
728 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
731 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
737 icon = QIcon(":icons/unconfirmed.png")
739 icon = QIcon(":icons/clock%d.png"%conf)
741 icon = QIcon(":icons/confirmed.png")
744 icon = QIcon(":icons/unconfirmed.png")
746 if value is not None:
747 v_str = format_satoshis(value, True, self.wallet.num_zeros)
751 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
754 label, is_default_label = self.wallet.get_label(tx_hash)
756 label = _('Pruned transaction outputs')
757 is_default_label = False
759 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
760 item.setFont(2, QFont(MONOSPACE_FONT))
761 item.setFont(3, QFont(MONOSPACE_FONT))
762 item.setFont(4, QFont(MONOSPACE_FONT))
764 item.setForeground(3, QBrush(QColor("#BC1E1E")))
766 item.setData(0, Qt.UserRole, tx_hash)
767 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
769 item.setForeground(2, QBrush(QColor('grey')))
771 item.setIcon(0, icon)
772 self.history_list.insertTopLevelItem(0,item)
775 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
778 def create_send_tab(self):
783 grid.setColumnMinimumWidth(3,300)
784 grid.setColumnStretch(5,1)
786 self.payto_e = QLineEdit()
787 grid.addWidget(QLabel(_('Pay to')), 1, 0)
788 grid.addWidget(self.payto_e, 1, 1, 1, 3)
791 qrcode = qrscanner.scan_qr()
792 if 'address' in qrcode:
793 self.payto_e.setText(qrcode['address'])
794 if 'amount' in qrcode:
795 self.amount_e.setText(str(qrcode['amount']))
796 if 'label' in qrcode:
797 self.message_e.setText(qrcode['label'])
798 if 'message' in qrcode:
799 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
802 if qrscanner.is_available():
803 b = QPushButton(_("Scan QR code"))
804 b.clicked.connect(fill_from_qr)
805 grid.addWidget(b, 1, 5)
807 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)
809 completer = QCompleter()
810 completer.setCaseSensitivity(False)
811 self.payto_e.setCompleter(completer)
812 completer.setModel(self.completions)
814 self.message_e = QLineEdit()
815 grid.addWidget(QLabel(_('Description')), 2, 0)
816 grid.addWidget(self.message_e, 2, 1, 1, 3)
817 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)
819 self.amount_e = QLineEdit()
820 grid.addWidget(QLabel(_('Amount')), 3, 0)
821 grid.addWidget(self.amount_e, 3, 1, 1, 2)
822 grid.addWidget(HelpButton(
823 _('Amount to be sent.') + '\n\n' \
824 + _('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)
826 self.fee_e = QLineEdit()
827 grid.addWidget(QLabel(_('Fee')), 4, 0)
828 grid.addWidget(self.fee_e, 4, 1, 1, 2)
829 grid.addWidget(HelpButton(
830 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
831 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
832 + _('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)
835 b = EnterButton(_("Send"), self.do_send)
837 b = EnterButton(_("Create unsigned transaction"), self.do_send)
838 grid.addWidget(b, 6, 1)
840 b = EnterButton(_("Clear"),self.do_clear)
841 grid.addWidget(b, 6, 2)
843 self.payto_sig = QLabel('')
844 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
846 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
847 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
856 def entry_changed( is_fee ):
857 self.funds_error = False
858 amount = numbify(self.amount_e)
859 fee = numbify(self.fee_e)
860 if not is_fee: fee = None
863 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
865 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
868 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
869 text = self.status_text
872 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
873 self.funds_error = True
874 text = _( "Not enough funds" )
876 self.statusBar().showMessage(text)
877 self.amount_e.setPalette(palette)
878 self.fee_e.setPalette(palette)
880 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
881 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
886 def update_completions(self):
888 for addr,label in self.wallet.labels.items():
889 if addr in self.wallet.addressbook:
890 l.append( label + ' <' + addr + '>')
891 l = l + self.wallet.aliases.keys()
893 self.completions.setStringList(l)
899 label = unicode( self.message_e.text() )
900 r = unicode( self.payto_e.text() )
904 m1 = re.match(ALIAS_REGEXP, r)
905 # label or alias, with address in brackets
906 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
909 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
913 to_address = m2.group(2)
917 if not is_valid(to_address):
918 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
922 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
924 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
927 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
929 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
932 if self.wallet.use_encryption:
933 password = self.password_dialog()
940 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
941 except BaseException, e:
942 self.show_message(str(e))
946 self.wallet.labels[tx.hash()] = label
949 h = self.wallet.send_tx(tx)
950 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
951 status, msg = self.wallet.receive_tx( h )
953 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
955 self.update_contacts_tab()
957 QMessageBox.warning(self, _('Error'), msg, _('OK'))
959 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
961 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
962 with open(fileName,'w') as f:
963 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
964 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
966 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
971 def set_url(self, url):
972 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
973 self.tabs.setCurrentIndex(1)
974 label = self.wallet.labels.get(payto)
975 m_addr = label + ' <'+ payto+'>' if label else payto
976 self.payto_e.setText(m_addr)
978 self.message_e.setText(message)
979 self.amount_e.setText(amount)
981 self.set_frozen(self.payto_e,True)
982 self.set_frozen(self.amount_e,True)
983 self.set_frozen(self.message_e,True)
984 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
986 self.payto_sig.setVisible(False)
989 self.payto_sig.setVisible(False)
990 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
992 self.set_frozen(e,False)
994 def set_frozen(self,entry,frozen):
996 entry.setReadOnly(True)
997 entry.setFrame(False)
999 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1000 entry.setPalette(palette)
1002 entry.setReadOnly(False)
1003 entry.setFrame(True)
1004 palette = QPalette()
1005 palette.setColor(entry.backgroundRole(), QColor('white'))
1006 entry.setPalette(palette)
1009 def toggle_freeze(self,addr):
1011 if addr in self.wallet.frozen_addresses:
1012 self.wallet.unfreeze(addr)
1014 self.wallet.freeze(addr)
1015 self.update_receive_tab()
1017 def toggle_priority(self,addr):
1019 if addr in self.wallet.prioritized_addresses:
1020 self.wallet.unprioritize(addr)
1022 self.wallet.prioritize(addr)
1023 self.update_receive_tab()
1026 def create_list_tab(self, headers):
1027 "generic tab creation method"
1028 l = MyTreeWidget(self)
1029 l.setColumnCount( len(headers) )
1030 l.setHeaderLabels( headers )
1033 vbox = QVBoxLayout()
1040 vbox.addWidget(buttons)
1042 hbox = QHBoxLayout()
1045 buttons.setLayout(hbox)
1050 def create_receive_tab(self):
1051 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
1052 l.setContextMenuPolicy(Qt.CustomContextMenu)
1053 l.customContextMenuRequested.connect(self.create_receive_menu)
1054 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1055 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1056 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
1057 self.receive_list = l
1058 self.receive_buttons_hbox = hbox
1063 def receive_tab_set_mode(self, i):
1064 self.save_column_widths()
1065 self.receive_tab_mode = i
1066 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
1068 self.update_receive_tab()
1069 self.toggle_QR_window(self.receive_tab_mode == 2)
1072 def save_column_widths(self):
1073 if self.receive_tab_mode == 0:
1074 widths = [ self.receive_list.columnWidth(0) ]
1077 for i in range(self.receive_list.columnCount() -1):
1078 widths.append(self.receive_list.columnWidth(i))
1079 self.column_widths["receive"][self.receive_tab_mode] = widths
1081 self.column_widths["history"] = []
1082 for i in range(self.history_list.columnCount() - 1):
1083 self.column_widths["history"].append(self.history_list.columnWidth(i))
1085 self.column_widths["contacts"] = []
1086 for i in range(self.contacts_list.columnCount() - 1):
1087 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1090 def create_contacts_tab(self):
1091 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1092 l.setContextMenuPolicy(Qt.CustomContextMenu)
1093 l.customContextMenuRequested.connect(self.create_contact_menu)
1094 for i,width in enumerate(self.column_widths['contacts']):
1095 l.setColumnWidth(i, width)
1097 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1098 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1099 self.contacts_list = l
1100 self.contacts_buttons_hbox = hbox
1101 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1106 def delete_imported_key(self, addr):
1107 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1108 self.wallet.imported_keys.pop(addr)
1109 self.update_receive_tab()
1110 self.update_history_tab()
1114 def create_receive_menu(self, position):
1115 # fixme: this function apparently has a side effect.
1116 # if it is not called the menu pops up several times
1117 #self.receive_list.selectedIndexes()
1119 item = self.receive_list.itemAt(position)
1121 addr = unicode(item.text(0))
1123 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1124 if self.receive_tab_mode == 2:
1125 menu.addAction(_("Request amount"), lambda: self.edit_amount())
1126 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode(_("Address"),"bitcoin:"+addr) )
1127 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1128 menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
1129 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1130 if addr in self.wallet.imported_keys:
1131 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1133 if self.receive_tab_mode == 1:
1134 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1135 menu.addAction(t, lambda: self.toggle_freeze(addr))
1136 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1137 menu.addAction(t, lambda: self.toggle_priority(addr))
1139 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1142 def payto(self, x, is_alias):
1149 label = self.wallet.labels.get(addr)
1150 m_addr = label + ' <' + addr + '>' if label else addr
1151 self.tabs.setCurrentIndex(1)
1152 self.payto_e.setText(m_addr)
1153 self.amount_e.setFocus()
1155 def delete_contact(self, x, is_alias):
1156 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1157 if not is_alias and x in self.wallet.addressbook:
1158 self.wallet.addressbook.remove(x)
1159 if x in self.wallet.labels.keys():
1160 self.wallet.labels.pop(x)
1161 elif is_alias and x in self.wallet.aliases:
1162 self.wallet.aliases.pop(x)
1163 self.update_history_tab()
1164 self.update_contacts_tab()
1165 self.update_completions()
1167 def create_contact_menu(self, position):
1168 # fixme: this function apparently has a side effect.
1169 # if it is not called the menu pops up several times
1170 #self.contacts_list.selectedIndexes()
1172 item = self.contacts_list.itemAt(position)
1174 addr = unicode(item.text(0))
1175 label = unicode(item.text(1))
1176 is_alias = label in self.wallet.aliases.keys()
1177 x = label if is_alias else addr
1179 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1180 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1181 menu.addAction(_("View QR code"),lambda: self.show_qrcode(_("Address"),"bitcoin:"+addr))
1183 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1185 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1186 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1187 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1190 def update_receive_item(self, item):
1191 address = str(item.data(0,0).toString())
1192 label = self.wallet.labels.get(address,'')
1193 item.setData(1,0,label)
1196 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1198 amount, currency = None, None
1200 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1201 item.setData(2,0,amount_str)
1203 c, u = self.wallet.get_addr_balance(address)
1204 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1205 item.setData(3,0,balance)
1207 if self.receive_tab_mode == 1:
1208 if address in self.wallet.frozen_addresses:
1209 item.setBackgroundColor(0, QColor('lightblue'))
1210 elif address in self.wallet.prioritized_addresses:
1211 item.setBackgroundColor(0, QColor('lightgreen'))
1214 def update_receive_tab(self):
1215 l = self.receive_list
1218 l.setColumnHidden(2, not self.receive_tab_mode == 2)
1219 l.setColumnHidden(3, self.receive_tab_mode == 0)
1220 l.setColumnHidden(4, not self.receive_tab_mode == 1)
1221 if self.receive_tab_mode == 0:
1222 width = self.column_widths['receive'][0][0]
1223 l.setColumnWidth(0, width)
1225 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1226 l.setColumnWidth(i, width)
1229 for k, account in self.wallet.accounts.items():
1230 name = account.get('name',str(k))
1231 account_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1232 l.addTopLevelItem(account_item)
1233 account_item.setExpanded(True)
1235 for is_change in [0,1]:
1236 name = "Receiving" if not is_change else "Change"
1237 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1238 account_item.addChild(seq_item)
1239 if not is_change: seq_item.setExpanded(True)
1243 for address in account[is_change]:
1244 h = self.wallet.history.get(address,[])
1249 if gap > self.wallet.gap_limit:
1254 num_tx = '*' if h == ['*'] else "%d"%len(h)
1255 item = QTreeWidgetItem( [ address, '', '', '', num_tx] )
1256 item.setFont(0, QFont(MONOSPACE_FONT))
1257 item.setFont(2, QFont(MONOSPACE_FONT))
1258 self.update_receive_item(item)
1259 if is_red and not is_change:
1260 item.setBackgroundColor(1, QColor('red'))
1261 seq_item.addChild(item)
1263 if self.wallet.imported_keys:
1264 account_item = QTreeWidgetItem( [ "Imported", '', '', '', ''] )
1265 l.addTopLevelItem(account_item)
1266 account_item.setExpanded(True)
1267 for address in self.wallet.imported_keys.keys():
1268 item = QTreeWidgetItem( [ address, '', '', '', ''] )
1269 item.setFont(0, QFont(MONOSPACE_FONT))
1270 item.setFont(2, QFont(MONOSPACE_FONT))
1271 self.update_receive_item(item)
1272 if is_red and not is_change:
1273 item.setBackgroundColor(1, QColor('red'))
1274 account_item.addChild(item)
1277 # we use column 1 because column 0 may be hidden
1278 l.setCurrentItem(l.topLevelItem(0),1)
1280 def show_contact_details(self, m):
1281 a = self.wallet.aliases.get(m)
1283 if a[0] in self.wallet.authorities.keys():
1284 s = self.wallet.authorities.get(a[0])
1287 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1288 QMessageBox.information(self, 'Alias', msg, 'OK')
1290 def update_contacts_tab(self):
1292 l = self.contacts_list
1296 for alias, v in self.wallet.aliases.items():
1298 alias_targets.append(target)
1299 item = QTreeWidgetItem( [ target, alias, '-'] )
1300 item.setBackgroundColor(0, QColor('lightgray'))
1301 l.addTopLevelItem(item)
1303 for address in self.wallet.addressbook:
1304 if address in alias_targets: continue
1305 label = self.wallet.labels.get(address,'')
1307 for tx in self.wallet.transactions.values():
1308 if address in map(lambda x: x[0], tx.outputs): n += 1
1310 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1311 item.setFont(0, QFont(MONOSPACE_FONT))
1312 l.addTopLevelItem(item)
1314 l.setCurrentItem(l.topLevelItem(0))
1317 def create_console_tab(self):
1318 from qt_console import Console
1319 import util, bitcoin, commands
1320 self.console = console = Console()
1321 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1322 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1324 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1326 def mkfunc(f, method):
1327 return lambda *args: apply( f, (method, args, self.password_dialog ))
1329 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1330 methods[m] = mkfunc(c._run, m)
1332 console.updateNamespace(methods)
1336 def create_status_bar(self):
1337 self.status_text = ""
1339 sb.setFixedHeight(35)
1340 qtVersion = qVersion()
1342 update_notification = UpdateLabel(self.config)
1343 if(update_notification.new_version):
1344 sb.addPermanentWidget(update_notification)
1346 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1347 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1348 if self.wallet.seed:
1349 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1350 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1351 if self.wallet.seed:
1352 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
1353 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1354 sb.addPermanentWidget( self.status_button )
1356 self.setStatusBar(sb)
1360 self.config.set_key('gui', 'lite', True)
1363 self.lite.mini.show()
1365 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1366 self.lite.main(None)
1368 def new_contact_dialog(self):
1369 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1370 address = unicode(text)
1372 if is_valid(address):
1373 self.wallet.addressbook.append(address)
1375 self.update_contacts_tab()
1376 self.update_history_tab()
1377 self.update_completions()
1379 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1381 def show_master_public_key(self):
1382 dialog = QDialog(None)
1384 dialog.setWindowTitle(_("Master Public Key"))
1386 main_text = QTextEdit()
1387 main_text.setText(self.wallet.get_master_public_key())
1388 main_text.setReadOnly(True)
1389 main_text.setMaximumHeight(170)
1390 qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
1392 ok_button = QPushButton(_("OK"))
1393 ok_button.setDefault(True)
1394 ok_button.clicked.connect(dialog.accept)
1396 main_layout = QGridLayout()
1397 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1399 main_layout.addWidget(main_text, 1, 0)
1400 main_layout.addWidget(qrw, 1, 1 )
1402 vbox = QVBoxLayout()
1403 vbox.addLayout(main_layout)
1404 hbox = QHBoxLayout()
1406 hbox.addWidget(ok_button)
1407 vbox.addLayout(hbox)
1409 dialog.setLayout(vbox)
1414 def show_seed_dialog(self, wallet, parent=None):
1416 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1419 if wallet.use_encryption:
1420 password = parent.password_dialog()
1427 seed = wallet.decode_seed(password)
1429 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1432 self.show_seed(seed)
1435 def show_seed(self, seed):
1436 dialog = QDialog(None)
1438 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1440 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1442 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1444 seed_text = QTextEdit(brainwallet)
1445 seed_text.setReadOnly(True)
1446 seed_text.setMaximumHeight(130)
1448 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1449 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1450 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1451 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1452 label2 = QLabel(msg2)
1453 label2.setWordWrap(True)
1456 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1457 logo.setMaximumWidth(60)
1459 qrw = QRCodeWidget(seed, 4)
1461 ok_button = QPushButton(_("OK"))
1462 ok_button.setDefault(True)
1463 ok_button.clicked.connect(dialog.accept)
1465 grid = QGridLayout()
1466 #main_layout.addWidget(logo, 0, 0)
1468 grid.addWidget(logo, 0, 0)
1469 grid.addWidget(label1, 0, 1)
1471 grid.addWidget(seed_text, 1, 0, 1, 2)
1473 grid.addWidget(qrw, 0, 2, 2, 1)
1475 vbox = QVBoxLayout()
1476 vbox.addLayout(grid)
1477 vbox.addWidget(label2)
1479 hbox = QHBoxLayout()
1481 hbox.addWidget(ok_button)
1482 vbox.addLayout(hbox)
1484 dialog.setLayout(vbox)
1488 def show_qrcode(title, data):
1492 d.setWindowTitle(title)
1493 d.setMinimumSize(270, 300)
1494 vbox = QVBoxLayout()
1495 qrw = QRCodeWidget(data)
1496 vbox.addWidget(qrw, 1)
1497 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1498 hbox = QHBoxLayout()
1502 filename = "qrcode.bmp"
1503 bmp.save_qrcode(qrw.qr, filename)
1504 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1506 b = QPushButton(_("Print"))
1508 b.clicked.connect(print_qr)
1510 b = QPushButton(_("Close"))
1512 b.clicked.connect(d.accept)
1514 vbox.addLayout(hbox)
1518 def view_private_key(self,address):
1519 if not address: return
1520 if self.wallet.use_encryption:
1521 password = self.password_dialog()
1528 pk = self.wallet.get_private_key(address, password)
1529 except BaseException, e:
1530 self.show_message(str(e))
1533 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1536 def sign_message(self,address):
1537 if not address: return
1540 d.setWindowTitle(_('Sign Message'))
1541 d.setMinimumSize(410, 290)
1543 tab_widget = QTabWidget()
1545 layout = QGridLayout(tab)
1547 sign_address = QLineEdit()
1549 sign_address.setText(address)
1550 layout.addWidget(QLabel(_('Address')), 1, 0)
1551 layout.addWidget(sign_address, 1, 1)
1553 sign_message = QTextEdit()
1554 layout.addWidget(QLabel(_('Message')), 2, 0)
1555 layout.addWidget(sign_message, 2, 1)
1556 layout.setRowStretch(2,3)
1558 sign_signature = QTextEdit()
1559 layout.addWidget(QLabel(_('Signature')), 3, 0)
1560 layout.addWidget(sign_signature, 3, 1)
1561 layout.setRowStretch(3,1)
1564 if self.wallet.use_encryption:
1565 password = self.password_dialog()
1572 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1573 sign_signature.setText(signature)
1574 except BaseException, e:
1575 self.show_message(str(e))
1578 hbox = QHBoxLayout()
1579 b = QPushButton(_("Sign"))
1581 b.clicked.connect(do_sign)
1582 b = QPushButton(_("Close"))
1583 b.clicked.connect(d.accept)
1585 layout.addLayout(hbox, 4, 1)
1586 tab_widget.addTab(tab, _("Sign"))
1590 layout = QGridLayout(tab)
1592 verify_address = QLineEdit()
1593 layout.addWidget(QLabel(_('Address')), 1, 0)
1594 layout.addWidget(verify_address, 1, 1)
1596 verify_message = QTextEdit()
1597 layout.addWidget(QLabel(_('Message')), 2, 0)
1598 layout.addWidget(verify_message, 2, 1)
1599 layout.setRowStretch(2,3)
1601 verify_signature = QTextEdit()
1602 layout.addWidget(QLabel(_('Signature')), 3, 0)
1603 layout.addWidget(verify_signature, 3, 1)
1604 layout.setRowStretch(3,1)
1608 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1609 self.show_message(_("Signature verified"))
1610 except BaseException, e:
1611 self.show_message(str(e))
1614 hbox = QHBoxLayout()
1615 b = QPushButton(_("Verify"))
1616 b.clicked.connect(do_verify)
1618 b = QPushButton(_("Close"))
1619 b.clicked.connect(d.accept)
1621 layout.addLayout(hbox, 4, 1)
1622 tab_widget.addTab(tab, _("Verify"))
1624 vbox = QVBoxLayout()
1625 vbox.addWidget(tab_widget)
1630 def toggle_QR_window(self, show):
1631 if show and not self.qr_window:
1632 self.qr_window = QR_Window(self.exchanger)
1633 self.qr_window.setVisible(True)
1634 self.qr_window_geometry = self.qr_window.geometry()
1635 item = self.receive_list.currentItem()
1637 address = str(item.text(1))
1638 label = self.wallet.labels.get(address)
1639 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1640 self.qr_window.set_content( address, label, amount, currency )
1642 elif show and self.qr_window and not self.qr_window.isVisible():
1643 self.qr_window.setVisible(True)
1644 self.qr_window.setGeometry(self.qr_window_geometry)
1646 elif not show and self.qr_window and self.qr_window.isVisible():
1647 self.qr_window_geometry = self.qr_window.geometry()
1648 self.qr_window.setVisible(False)
1650 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1651 self.receive_list.setColumnHidden(2, self.qr_window is None or not self.qr_window.isVisible())
1652 self.receive_list.setColumnWidth(1, 200)
1655 def question(self, msg):
1656 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1658 def show_message(self, msg):
1659 QMessageBox.information(self, _('Message'), msg, _('OK'))
1661 def password_dialog(self ):
1668 vbox = QVBoxLayout()
1669 msg = _('Please enter your password')
1670 vbox.addWidget(QLabel(msg))
1672 grid = QGridLayout()
1674 grid.addWidget(QLabel(_('Password')), 1, 0)
1675 grid.addWidget(pw, 1, 1)
1676 vbox.addLayout(grid)
1678 vbox.addLayout(ok_cancel_buttons(d))
1681 if not d.exec_(): return
1682 return unicode(pw.text())
1689 def change_password_dialog( wallet, parent=None ):
1692 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1700 new_pw = QLineEdit()
1701 new_pw.setEchoMode(2)
1702 conf_pw = QLineEdit()
1703 conf_pw.setEchoMode(2)
1705 vbox = QVBoxLayout()
1707 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1708 +_('To disable wallet encryption, enter an empty new password.')) \
1709 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1711 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1712 +_("Leave these fields empty if you want to disable encryption.")
1713 vbox.addWidget(QLabel(msg))
1715 grid = QGridLayout()
1718 if wallet.use_encryption:
1719 grid.addWidget(QLabel(_('Password')), 1, 0)
1720 grid.addWidget(pw, 1, 1)
1722 grid.addWidget(QLabel(_('New Password')), 2, 0)
1723 grid.addWidget(new_pw, 2, 1)
1725 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1726 grid.addWidget(conf_pw, 3, 1)
1727 vbox.addLayout(grid)
1729 vbox.addLayout(ok_cancel_buttons(d))
1732 if not d.exec_(): return
1734 password = unicode(pw.text()) if wallet.use_encryption else None
1735 new_password = unicode(new_pw.text())
1736 new_password2 = unicode(conf_pw.text())
1739 seed = wallet.decode_seed(password)
1741 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1744 if new_password != new_password2:
1745 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1746 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1748 wallet.update_password(seed, password, new_password)
1751 def seed_dialog(wallet, parent=None):
1755 vbox = QVBoxLayout()
1756 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1757 vbox.addWidget(QLabel(msg))
1759 grid = QGridLayout()
1762 seed_e = QLineEdit()
1763 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1764 grid.addWidget(seed_e, 1, 1)
1768 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1769 grid.addWidget(gap_e, 2, 1)
1770 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1771 vbox.addLayout(grid)
1773 vbox.addLayout(ok_cancel_buttons(d))
1776 if not d.exec_(): return
1779 gap = int(unicode(gap_e.text()))
1781 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1785 seed = str(seed_e.text())
1788 print_error("Warning: Not hex, trying decode")
1790 seed = mnemonic.mn_decode( seed.split(' ') )
1792 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1796 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1801 def generate_transaction_information_widget(self, tx):
1802 tabs = QTabWidget(self)
1805 grid_ui = QGridLayout(tab1)
1806 grid_ui.setColumnStretch(0,1)
1807 tabs.addTab(tab1, _('Outputs') )
1809 tree_widget = MyTreeWidget(self)
1810 tree_widget.setColumnCount(2)
1811 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1812 tree_widget.setColumnWidth(0, 300)
1813 tree_widget.setColumnWidth(1, 50)
1815 for output in tx.d["outputs"]:
1816 item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
1817 tree_widget.addTopLevelItem(item)
1819 tree_widget.setMaximumHeight(100)
1821 grid_ui.addWidget(tree_widget)
1824 grid_ui = QGridLayout(tab2)
1825 grid_ui.setColumnStretch(0,1)
1826 tabs.addTab(tab2, _('Inputs') )
1828 tree_widget = MyTreeWidget(self)
1829 tree_widget.setColumnCount(2)
1830 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1832 for input_line in tx.inputs:
1833 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1834 tree_widget.addTopLevelItem(item)
1836 tree_widget.setMaximumHeight(100)
1838 grid_ui.addWidget(tree_widget)
1842 def tx_dict_from_text(self, txt):
1844 tx_dict = json.loads(str(txt))
1845 assert "hex" in tx_dict.keys()
1846 assert "complete" in tx_dict.keys()
1847 if not tx_dict["complete"]:
1848 assert "input_info" in tx_dict.keys()
1850 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:"))
1855 def read_tx_from_file(self):
1856 fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~'))
1860 with open(fileName, "r") as f:
1861 file_content = f.read()
1862 except (ValueError, IOError, os.error), reason:
1863 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1865 return self.tx_dict_from_text(file_content)
1868 def sign_raw_transaction(self, tx, input_info):
1869 if self.wallet.use_encryption:
1870 password = self.password_dialog()
1877 self.wallet.signrawtransaction(tx, input_info, [], password)
1879 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8])))
1881 with open(fileName, "w+") as f:
1882 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1883 self.show_message(_("Transaction saved succesfully"))
1884 except BaseException, e:
1885 self.show_message(str(e))
1888 def create_sign_transaction_window(self, tx_dict):
1889 tx = Transaction(tx_dict["hex"])
1891 dialog = QDialog(self)
1892 dialog.setMinimumWidth(500)
1893 dialog.setWindowTitle(_('Sign unsigned transaction'))
1896 vbox = QVBoxLayout()
1897 dialog.setLayout(vbox)
1898 vbox.addWidget( self.generate_transaction_information_widget(tx) )
1900 if tx_dict["complete"] == True:
1901 vbox.addWidget(QLabel(_("This transaction is already signed.")))
1903 vbox.addWidget(QLabel(_("Create a signed transaction.")))
1904 vbox.addLayout(ok_cancel_buttons(dialog))
1905 input_info = json.loads(tx_dict["input_info"])
1908 self.sign_raw_transaction(tx, input_info)
1912 def do_sign_from_text(self):
1913 txt, ok = QInputDialog.getText(QTextEdit(), _('Sign raw transaction'), _('Transaction data in JSON') + ':')
1916 tx_dict = self.tx_dict_from_text(unicode(txt))
1918 self.create_sign_transaction_window(tx_dict)
1921 def do_sign_from_file(self):
1922 tx_dict = self.read_tx_from_file()
1924 self.create_sign_transaction_window(tx_dict)
1927 def send_raw_transaction(self, raw_tx):
1928 result, result_message = self.wallet.sendtx( raw_tx )
1930 self.show_message("Transaction succesfully sent: %s" % (result_message))
1932 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1935 def create_send_transaction_window(self, tx_dict):
1936 tx = Transaction(tx_dict["hex"])
1938 dialog = QDialog(self)
1939 dialog.setMinimumWidth(500)
1940 dialog.setWindowTitle(_('Send raw transaction'))
1943 vbox = QVBoxLayout()
1944 dialog.setLayout(vbox)
1945 vbox.addWidget( self.generate_transaction_information_widget(tx))
1947 if tx_dict["complete"] == False:
1948 vbox.addWidget(QLabel(_("This transaction is not signed yet.")))
1950 vbox.addWidget(QLabel(_("Broadcast this transaction")))
1951 vbox.addLayout(ok_cancel_buttons(dialog))
1954 self.send_raw_transaction(tx_dict["hex"])
1957 def do_send_from_file(self):
1958 tx_dict = self.read_tx_from_file()
1960 self.create_send_transaction_window(tx_dict)
1963 def do_send_from_text(self):
1964 txt, ok = QInputDialog.getText(QTextEdit(), _('Send raw transaction'), _('Transaction data in JSON') + ':')
1967 tx_dict = self.tx_dict_from_text(unicode(txt))
1969 self.create_send_transaction_window(tx_dict)
1972 def do_export_privkeys(self):
1973 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.")))
1975 if self.wallet.use_encryption:
1976 password = self.password_dialog()
1982 select_export = _('Select file to export your private keys to')
1983 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
1985 with open(fileName, "w+") as csvfile:
1986 transaction = csv.writer(csvfile)
1987 transaction.writerow(["address", "private_key"])
1990 for addr, pk in self.wallet.get_private_keys(self.wallet.all_addresses(), password).items():
1991 transaction.writerow(["%34s"%addr,pk])
1993 self.show_message(_("Private keys exported."))
1995 except (IOError, os.error), reason:
1996 export_error_label = _("Electrum was unable to produce a private key-export.")
1997 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1999 except BaseException, e:
2000 self.show_message(str(e))
2004 def do_import_labels(self):
2005 labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)"))
2006 if not labelsFile: return
2008 f = open(labelsFile, 'r')
2011 for key, value in json.loads(data).items():
2012 self.wallet.labels[key] = value
2014 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
2015 except (IOError, os.error), reason:
2016 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2020 def do_export_labels(self):
2021 labels = self.wallet.labels
2023 labelsFile = util.user_dir() + '/labels.dat'
2024 f = open(labelsFile, 'w+')
2025 json.dump(labels, f)
2027 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile))
2028 except (IOError, os.error), reason:
2029 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
2031 def do_export_history(self):
2032 from gui_lite import csv_transaction
2033 csv_transaction(self.wallet)
2035 def do_import_privkey(self):
2036 if not self.wallet.imported_keys:
2037 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
2038 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
2039 + _('Are you sure you understand what you are doing?'), 3, 4)
2042 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
2044 sec = str(text).strip()
2045 if self.wallet.use_encryption:
2046 password = self.password_dialog()
2052 addr = self.wallet.import_key(sec, password)
2054 QMessageBox.critical(None, _("Unable to import key"), "error")
2056 QMessageBox.information(None, _("Key imported"), addr)
2057 self.update_receive_tab()
2058 self.update_history_tab()
2059 except BaseException as e:
2060 QMessageBox.critical(None, _("Unable to import key"), str(e))
2062 def settings_dialog(self):
2064 d.setWindowTitle(_('Electrum Settings'))
2066 vbox = QVBoxLayout()
2068 tabs = QTabWidget(self)
2069 vbox.addWidget(tabs)
2072 grid_ui = QGridLayout(tab1)
2073 grid_ui.setColumnStretch(0,1)
2074 tabs.addTab(tab1, _('Display') )
2076 nz_label = QLabel(_('Display zeros'))
2077 grid_ui.addWidget(nz_label, 3, 0)
2079 nz_e.setText("%d"% self.wallet.num_zeros)
2080 grid_ui.addWidget(nz_e, 3, 1)
2081 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2082 grid_ui.addWidget(HelpButton(msg), 3, 2)
2083 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
2084 if not self.config.is_modifiable('num_zeros'):
2085 for w in [nz_e, nz_label]: w.setEnabled(False)
2087 lang_label=QLabel(_('Language') + ':')
2088 grid_ui.addWidget(lang_label , 8, 0)
2089 lang_combo = QComboBox()
2090 from i18n import languages
2091 lang_combo.addItems(languages.values())
2093 index = languages.keys().index(self.config.get("language",''))
2096 lang_combo.setCurrentIndex(index)
2097 grid_ui.addWidget(lang_combo, 8, 1)
2098 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
2099 if not self.config.is_modifiable('language'):
2100 for w in [lang_combo, lang_label]: w.setEnabled(False)
2102 currencies = self.exchanger.get_currencies()
2103 currencies.insert(0, "None")
2105 cur_label=QLabel(_('Currency') + ':')
2106 grid_ui.addWidget(cur_label , 9, 0)
2107 cur_combo = QComboBox()
2108 cur_combo.addItems(currencies)
2110 index = currencies.index(self.config.get('currency', "None"))
2113 cur_combo.setCurrentIndex(index)
2114 grid_ui.addWidget(cur_combo, 9, 1)
2115 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
2117 view_label=QLabel(_('Receive Tab') + ':')
2118 grid_ui.addWidget(view_label , 10, 0)
2119 view_combo = QComboBox()
2120 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
2121 view_combo.setCurrentIndex(self.receive_tab_mode)
2122 grid_ui.addWidget(view_combo, 10, 1)
2123 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
2124 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
2125 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
2126 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
2128 grid_ui.addWidget(HelpButton(hh), 10, 2)
2132 grid_wallet = QGridLayout(tab2)
2133 grid_wallet.setColumnStretch(0,1)
2134 tabs.addTab(tab2, _('Wallet') )
2136 fee_label = QLabel(_('Transaction fee'))
2137 grid_wallet.addWidget(fee_label, 0, 0)
2139 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
2140 grid_wallet.addWidget(fee_e, 0, 1)
2141 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
2142 + _('Recommended value') + ': 0.001'
2143 grid_wallet.addWidget(HelpButton(msg), 0, 2)
2144 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
2145 if not self.config.is_modifiable('fee'):
2146 for w in [fee_e, fee_label]: w.setEnabled(False)
2148 usechange_label = QLabel(_('Use change addresses'))
2149 grid_wallet.addWidget(usechange_label, 1, 0)
2150 usechange_combo = QComboBox()
2151 usechange_combo.addItems([_('Yes'), _('No')])
2152 usechange_combo.setCurrentIndex(not self.wallet.use_change)
2153 grid_wallet.addWidget(usechange_combo, 1, 1)
2154 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
2155 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
2157 gap_label = QLabel(_('Gap limit'))
2158 grid_wallet.addWidget(gap_label, 2, 0)
2160 gap_e.setText("%d"% self.wallet.gap_limit)
2161 grid_wallet.addWidget(gap_e, 2, 1)
2162 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2163 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2164 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2165 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2166 + _('Warning') + ': ' \
2167 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2168 + _('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'
2169 grid_wallet.addWidget(HelpButton(msg), 2, 2)
2170 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
2171 if not self.config.is_modifiable('gap_limit'):
2172 for w in [gap_e, gap_label]: w.setEnabled(False)
2174 grid_wallet.setRowStretch(3,1)
2179 grid_io = QGridLayout(tab3)
2180 grid_io.setColumnStretch(0,1)
2181 tabs.addTab(tab3, _('Import/Export') )
2183 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2184 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2185 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2186 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2188 grid_io.addWidget(QLabel(_('History')), 2, 0)
2189 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2190 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2192 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2194 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2195 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2196 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2198 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2199 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2200 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2201 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2202 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2204 grid_io.setRowStretch(4,1)
2207 grid_raw = QGridLayout(tab4)
2208 grid_raw.setColumnStretch(0,1)
2209 tabs.addTab(tab4, _('Raw transactions') )
2211 #grid_raw.addWidget(QLabel(_("Read raw transaction")), 3, 0)
2212 #grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),3,1)
2213 #grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),3,2)
2214 #grid_raw.addWidget(HelpButton(_("This will show you some useful information about an unsigned transaction")),3,3)
2216 if self.wallet.seed:
2217 grid_raw.addWidget(QLabel(_("Sign transaction")), 1, 0)
2218 grid_raw.addWidget(EnterButton(_("From file"), self.do_sign_from_file),1,1)
2219 grid_raw.addWidget(EnterButton(_("From text"), self.do_sign_from_text),1,2)
2220 grid_raw.addWidget(HelpButton(_("Sign an unsigned transaction generated by a watching-only wallet")),1,3)
2222 grid_raw.addWidget(QLabel(_("Send signed transaction")), 2, 0)
2223 grid_raw.addWidget(EnterButton(_("From file"), self.do_send_from_file),2,1)
2224 grid_raw.addWidget(EnterButton(_("From text"), self.do_send_from_text),2,2)
2225 grid_raw.addWidget(HelpButton(_("This will broadcast a transaction to the network.")),2,3)
2226 grid_raw.setRowStretch(3,1)
2228 vbox.addLayout(ok_cancel_buttons(d))
2232 if not d.exec_(): return
2234 fee = unicode(fee_e.text())
2236 fee = int( 100000000 * Decimal(fee) )
2238 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2241 if self.wallet.fee != fee:
2242 self.wallet.fee = fee
2245 nz = unicode(nz_e.text())
2250 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2253 if self.wallet.num_zeros != nz:
2254 self.wallet.num_zeros = nz
2255 self.config.set_key('num_zeros', nz, True)
2256 self.update_history_tab()
2257 self.update_receive_tab()
2259 usechange_result = usechange_combo.currentIndex() == 0
2260 if self.wallet.use_change != usechange_result:
2261 self.wallet.use_change = usechange_result
2262 self.config.set_key('use_change', self.wallet.use_change, True)
2265 n = int(gap_e.text())
2267 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2270 if self.wallet.gap_limit != n:
2271 r = self.wallet.change_gap_limit(n)
2273 self.update_receive_tab()
2274 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2276 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2278 need_restart = False
2280 lang_request = languages.keys()[lang_combo.currentIndex()]
2281 if lang_request != self.config.get('language'):
2282 self.config.set_key("language", lang_request, True)
2285 cur_request = str(currencies[cur_combo.currentIndex()])
2286 if cur_request != self.config.get('currency', "None"):
2287 self.config.set_key('currency', cur_request, True)
2288 self.update_wallet()
2291 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2293 self.receive_tab_set_mode(view_combo.currentIndex())
2297 def network_dialog(wallet, parent=None):
2298 interface = wallet.interface
2300 if interface.is_connected:
2301 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2303 status = _("Not connected")
2304 server = interface.server
2307 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2308 server = interface.server
2310 plist, servers_list = interface.get_servers_list()
2314 d.setWindowTitle(_('Server'))
2315 d.setMinimumSize(375, 20)
2317 vbox = QVBoxLayout()
2320 hbox = QHBoxLayout()
2322 l.setPixmap(QPixmap(":icons/network.png"))
2325 hbox.addWidget(QLabel(status))
2327 vbox.addLayout(hbox)
2331 grid = QGridLayout()
2333 vbox.addLayout(grid)
2336 server_protocol = QComboBox()
2337 server_host = QLineEdit()
2338 server_host.setFixedWidth(200)
2339 server_port = QLineEdit()
2340 server_port.setFixedWidth(60)
2342 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2343 protocol_letters = 'thsg'
2344 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2345 server_protocol.addItems(protocol_names)
2347 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2348 grid.addWidget(server_protocol, 0, 1)
2349 grid.addWidget(server_host, 0, 2)
2350 grid.addWidget(server_port, 0, 3)
2352 def change_protocol(p):
2353 protocol = protocol_letters[p]
2354 host = unicode(server_host.text())
2355 pp = plist.get(host,DEFAULT_PORTS)
2356 if protocol not in pp.keys():
2357 protocol = pp.keys()[0]
2359 server_host.setText( host )
2360 server_port.setText( port )
2362 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2364 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2365 servers_list_widget = QTreeWidget(parent)
2366 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2367 servers_list_widget.setMaximumHeight(150)
2368 servers_list_widget.setColumnWidth(0, 240)
2369 for _host in servers_list.keys():
2370 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2371 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2373 def change_server(host, protocol=None):
2374 pp = plist.get(host,DEFAULT_PORTS)
2376 port = pp.get(protocol)
2377 if not port: protocol = None
2380 if 't' in pp.keys():
2382 port = pp.get(protocol)
2384 protocol = pp.keys()[0]
2385 port = pp.get(protocol)
2387 server_host.setText( host )
2388 server_port.setText( port )
2389 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2391 if not plist: return
2392 for p in protocol_letters:
2393 i = protocol_letters.index(p)
2394 j = server_protocol.model().index(i,0)
2395 if p not in pp.keys():
2396 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2398 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2402 host, port, protocol = server.split(':')
2403 change_server(host,protocol)
2405 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2406 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2408 if not wallet.config.is_modifiable('server'):
2409 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2412 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2413 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2414 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2415 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2418 proxy_mode = QComboBox()
2419 proxy_host = QLineEdit()
2420 proxy_host.setFixedWidth(200)
2421 proxy_port = QLineEdit()
2422 proxy_port.setFixedWidth(60)
2423 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2425 def check_for_disable(index = False):
2426 if proxy_mode.currentText() != 'NONE':
2427 proxy_host.setEnabled(True)
2428 proxy_port.setEnabled(True)
2430 proxy_host.setEnabled(False)
2431 proxy_port.setEnabled(False)
2434 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2436 if not wallet.config.is_modifiable('proxy'):
2437 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2439 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2440 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2441 proxy_host.setText(proxy_config.get("host"))
2442 proxy_port.setText(proxy_config.get("port"))
2444 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2445 grid.addWidget(proxy_mode, 2, 1)
2446 grid.addWidget(proxy_host, 2, 2)
2447 grid.addWidget(proxy_port, 2, 3)
2450 vbox.addLayout(ok_cancel_buttons(d))
2453 if not d.exec_(): return
2455 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2456 if proxy_mode.currentText() != 'NONE':
2457 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2461 wallet.config.set_key("proxy", proxy, True)
2462 wallet.config.set_key("server", server, True)
2463 interface.set_server(server, proxy)
2464 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2467 def closeEvent(self, event):
2469 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2470 self.save_column_widths()
2471 self.config.set_key("column-widths", self.column_widths, True)
2477 def __init__(self, wallet, config, app=None):
2478 self.wallet = wallet
2479 self.config = config
2481 self.app = QApplication(sys.argv)
2484 def restore_or_create(self):
2485 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2486 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2487 if r==2: return None
2488 return 'restore' if r==1 else 'create'
2490 def seed_dialog(self):
2491 return ElectrumWindow.seed_dialog( self.wallet )
2493 def network_dialog(self):
2494 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2497 def show_seed(self):
2498 ElectrumWindow.show_seed_dialog(self.wallet)
2501 def password_dialog(self):
2502 ElectrumWindow.change_password_dialog(self.wallet)
2505 def restore_wallet(self):
2506 wallet = self.wallet
2507 # wait until we are connected, because the user might have selected another server
2508 if not wallet.interface.is_connected:
2509 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2510 waiting_dialog(waiting)
2512 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2513 %(_("Please wait..."),_("Addresses generated:"),len(wallet.all_addresses()),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2515 wallet.set_up_to_date(False)
2516 wallet.interface.poke('synchronizer')
2517 waiting_dialog(waiting)
2518 if wallet.is_found():
2519 print_error( "Recovery successful" )
2521 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2528 w = ElectrumWindow(self.wallet, self.config)
2529 if url: w.set_url(url)