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
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 import bmp, mnemonic, pyqrnative, qrscanner
44 from decimal import Decimal
48 if platform.system() == 'Windows':
49 MONOSPACE_FONT = 'Lucida Console'
50 elif platform.system() == 'Darwin':
51 MONOSPACE_FONT = 'Monaco'
53 MONOSPACE_FONT = 'monospace'
55 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
57 def numbify(entry, is_int = False):
58 text = unicode(entry.text()).strip()
59 pos = entry.cursorPosition()
61 if not is_int: chars +='.'
62 s = ''.join([i for i in text if i in chars])
67 s = s[:p] + '.' + s[p:p+8]
69 amount = int( Decimal(s) * 100000000 )
78 entry.setCursorPosition(pos)
82 class Timer(QtCore.QThread):
85 self.emit(QtCore.SIGNAL('timersignal'))
88 class HelpButton(QPushButton):
89 def __init__(self, text):
90 QPushButton.__init__(self, '?')
91 self.setFocusPolicy(Qt.NoFocus)
92 self.setFixedWidth(20)
93 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
96 class EnterButton(QPushButton):
97 def __init__(self, text, func):
98 QPushButton.__init__(self, text)
100 self.clicked.connect(func)
102 def keyPressEvent(self, e):
103 if e.key() == QtCore.Qt.Key_Return:
106 class MyTreeWidget(QTreeWidget):
107 def __init__(self, parent):
108 QTreeWidget.__init__(self, parent)
111 for i in range(0,self.viewport().height()/5):
112 if self.itemAt(QPoint(0,i*5)) == item:
116 for j in range(0,30):
117 if self.itemAt(QPoint(0,i*5 + j)) != item:
119 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
121 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
126 class StatusBarButton(QPushButton):
127 def __init__(self, icon, tooltip, func):
128 QPushButton.__init__(self, icon, '')
129 self.setToolTip(tooltip)
131 self.setMaximumWidth(25)
132 self.clicked.connect(func)
135 def keyPressEvent(self, e):
136 if e.key() == QtCore.Qt.Key_Return:
140 class QRCodeWidget(QWidget):
142 def __init__(self, data = None, size=4):
143 QWidget.__init__(self)
144 self.setMinimumSize(210, 210)
152 def set_addr(self, addr):
153 if self.addr != addr:
159 if self.addr and not self.qr:
160 self.qr = pyqrnative.QRCode(self.size, pyqrnative.QRErrorCorrectLevel.L)
161 self.qr.addData(self.addr)
165 def paintEvent(self, e):
170 black = QColor(0, 0, 0, 255)
171 white = QColor(255, 255, 255, 255)
174 qp = QtGui.QPainter()
178 qp.drawRect(0, 0, 198, 198)
182 k = self.qr.getModuleCount()
183 qp = QtGui.QPainter()
186 boxsize = min(r.width(), r.height())*0.8/k
188 left = (r.width() - size)/2
189 top = (r.height() - size)/2
193 if self.qr.isDark(r, c):
199 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
204 class QR_Window(QWidget):
206 def __init__(self, exchanger):
207 QWidget.__init__(self)
208 self.exchanger = exchanger
209 self.setWindowTitle('Electrum - Invoice')
210 self.setMinimumSize(800, 250)
214 self.setFocusPolicy(QtCore.Qt.NoFocus)
216 main_box = QHBoxLayout()
218 self.qrw = QRCodeWidget()
219 main_box.addWidget(self.qrw, 1)
222 main_box.addLayout(vbox)
224 self.address_label = QLabel("")
225 self.address_label.setFont(QFont(MONOSPACE_FONT))
226 vbox.addWidget(self.address_label)
228 self.label_label = QLabel("")
229 vbox.addWidget(self.label_label)
231 self.amount_label = QLabel("")
232 vbox.addWidget(self.amount_label)
235 self.setLayout(main_box)
238 def set_content(self, addr, label, amount, currency):
240 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
241 self.address_label.setText(address_text)
243 if currency == 'BTC': currency = None
247 self.amount = Decimal(amount) / self.exchanger.exchange(1, currency) if currency else amount
249 self.amount = Decimal(amount)
250 self.amount = self.amount.quantize(Decimal('1.0000'))
253 amount_text += "<span style='font-size: 18pt'>%s %s</span><br/>" % (amount, currency)
254 amount_text += "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % str(self.amount)
255 self.amount_label.setText(amount_text)
258 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
259 self.label_label.setText(label_text)
261 msg = 'bitcoin:'+self.address
262 if self.amount is not None:
263 msg += '?amount=%s'%(str( self.amount))
264 if self.label is not None:
265 msg += '&label=%s'%(self.label)
266 elif self.label is not None:
267 msg += '?label=%s'%(self.label)
269 self.qrw.set_addr( msg )
274 def waiting_dialog(f):
280 w.setWindowTitle('Electrum')
290 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
295 def ok_cancel_buttons(dialog):
298 b = QPushButton("OK")
300 b.clicked.connect(dialog.accept)
301 b = QPushButton("Cancel")
303 b.clicked.connect(dialog.reject)
307 default_column_widths = { "history":[40,140,350,140,140], "contacts":[350,330,100],
308 "receive":[[50,310,200,130,130,10],[50,310,200,130,130,10],[50,310,200,130,130,10]] }
310 class ElectrumWindow(QMainWindow):
312 def __init__(self, wallet, config):
313 QMainWindow.__init__(self)
317 self.wallet.interface.register_callback('updated', self.update_callback)
318 self.wallet.interface.register_callback('connected', self.update_callback)
319 self.wallet.interface.register_callback('disconnected', self.update_callback)
320 self.wallet.interface.register_callback('disconnecting', self.update_callback)
322 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
323 self.merchant_name = config.get('merchant_name', 'Invoice')
325 self.qr_window = None
326 self.funds_error = False
327 self.completions = QStringListModel()
329 self.tabs = tabs = QTabWidget(self)
330 self.column_widths = self.config.get("column-widths", default_column_widths )
331 tabs.addTab(self.create_history_tab(), _('History') )
332 tabs.addTab(self.create_send_tab(), _('Send') )
333 tabs.addTab(self.create_receive_tab(), _('Receive') )
334 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
335 tabs.addTab(self.create_wall_tab(), _('Wall') )
336 tabs.setMinimumSize(600, 400)
337 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
338 self.setCentralWidget(tabs)
339 self.create_status_bar()
341 g = self.config.get("winpos-qt",[100, 100, 840, 400])
342 self.setGeometry(g[0], g[1], g[2], g[3])
343 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
344 if not self.wallet.seed: title += ' [seedless]'
345 self.setWindowTitle( title )
347 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
348 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
349 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
350 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
352 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
353 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
354 self.history_list.setFocus(True)
356 self.exchanger = exchange_rate.Exchanger(self)
357 self.toggle_QR_window(self.receive_tab_mode == 2)
358 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
360 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
361 if platform.system() == 'Windows':
362 n = 3 if self.wallet.seed else 2
363 tabs.setCurrentIndex (n)
364 tabs.setCurrentIndex (0)
367 QMainWindow.close(self)
369 self.qr_window.close()
370 self.qr_window = None
372 def connect_slots(self, sender):
373 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
374 self.previous_payto_e=''
376 def timer_actions(self):
378 self.qr_window.qrw.update_qr()
380 if self.payto_e.hasFocus():
382 r = unicode( self.payto_e.text() )
383 if r != self.previous_payto_e:
384 self.previous_payto_e = r
386 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
388 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
392 s = r + ' <' + to_address + '>'
393 self.payto_e.setText(s)
396 def update_callback(self):
397 self.emit(QtCore.SIGNAL('updatesignal'))
399 def update_wallet(self):
400 if self.wallet.interface and self.wallet.interface.is_connected:
401 if not self.wallet.up_to_date:
402 text = _( "Synchronizing..." )
403 icon = QIcon(":icons/status_waiting.png")
405 c, u = self.wallet.get_balance()
406 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
407 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
408 text += self.create_quote_text(Decimal(c+u)/100000000)
409 icon = QIcon(":icons/status_connected.png")
411 text = _( "Not connected" )
412 icon = QIcon(":icons/status_disconnected.png")
414 self.status_text = text
415 self.statusBar().showMessage(text)
416 self.status_button.setIcon( icon )
418 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
419 self.textbox.setText( self.wallet.banner )
420 self.update_history_tab()
421 self.update_receive_tab()
422 self.update_contacts_tab()
423 self.update_completions()
425 def create_quote_text(self, btc_balance):
426 quote_currency = self.config.get("currency", "None")
427 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
428 if quote_balance is None:
431 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
434 def create_history_tab(self):
435 self.history_list = l = MyTreeWidget(self)
437 for i,width in enumerate(self.column_widths['history']):
438 l.setColumnWidth(i, width)
439 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
440 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
441 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
443 l.setContextMenuPolicy(Qt.CustomContextMenu)
444 l.customContextMenuRequested.connect(self.create_history_menu)
448 def create_history_menu(self, position):
449 self.history_list.selectedIndexes()
450 item = self.history_list.currentItem()
452 tx_hash = str(item.data(0, Qt.UserRole).toString())
453 if not tx_hash: return
455 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
456 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
457 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
458 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
461 def tx_details(self, tx_hash):
462 tx_details = self.wallet.get_tx_details(tx_hash)
463 QMessageBox.information(self, 'Details', tx_details, 'OK')
466 def tx_label_clicked(self, item, column):
467 if column==2 and item.isSelected():
469 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
470 self.history_list.editItem( item, column )
471 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
474 def tx_label_changed(self, item, column):
478 tx_hash = str(item.data(0, Qt.UserRole).toString())
479 tx = self.wallet.transactions.get(tx_hash)
480 s = self.wallet.labels.get(tx_hash)
481 text = unicode( item.text(2) )
483 self.wallet.labels[tx_hash] = text
484 item.setForeground(2, QBrush(QColor('black')))
486 if s: self.wallet.labels.pop(tx_hash)
487 text = self.wallet.get_default_label(tx_hash)
488 item.setText(2, text)
489 item.setForeground(2, QBrush(QColor('gray')))
493 def edit_label(self, is_recv):
494 l = self.receive_list if is_recv else self.contacts_list
495 c = 2 if is_recv else 1
496 item = l.currentItem()
497 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
498 l.editItem( item, c )
499 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
501 def edit_amount(self):
502 l = self.receive_list
503 item = l.currentItem()
504 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
505 l.editItem( item, 3 )
506 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
509 def address_label_clicked(self, item, column, l, column_addr, column_label):
510 if column == column_label and item.isSelected():
511 addr = unicode( item.text(column_addr) )
512 label = unicode( item.text(column_label) )
513 if label in self.wallet.aliases.keys():
515 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
516 l.editItem( item, column )
517 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
520 def address_label_changed(self, item, column, l, column_addr, column_label):
522 if column == column_label:
523 addr = unicode( item.text(column_addr) )
524 text = unicode( item.text(column_label) )
528 if text not in self.wallet.aliases.keys():
529 old_addr = self.wallet.labels.get(text)
531 self.wallet.labels[addr] = text
534 print_error("Error: This is one of your aliases")
535 label = self.wallet.labels.get(addr,'')
536 item.setText(column_label, QString(label))
538 s = self.wallet.labels.get(addr)
540 self.wallet.labels.pop(addr)
544 self.update_history_tab()
545 self.update_completions()
547 self.recv_changed(item)
550 address = str( item.text(column_addr) )
551 text = str( item.text(3) )
553 index = self.wallet.addresses.index(address)
557 text = text.strip().upper()
558 m = re.match('^(\d+(|\.\d*))\s*(|BTC|EUR|USD|GBP|CNY|JPY|RUB|BRL)$', text)
561 currency = m.group(3)
565 currency = currency.upper()
566 self.wallet.requested_amounts[address] = (amount, currency)
568 label = self.wallet.labels.get(address)
570 label = self.merchant_name + ' - %04d'%(index+1)
571 self.wallet.labels[address] = label
574 self.qr_window.set_content( address, label, amount, currency )
578 if address in self.wallet.requested_amounts:
579 self.wallet.requested_amounts.pop(address)
581 self.update_receive_item(self.receive_list.currentItem())
584 def recv_changed(self, a):
585 "current item changed"
586 if a is not None and self.qr_window and self.qr_window.isVisible():
587 address = str(a.text(1))
588 label = self.wallet.labels.get(address)
590 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
592 amount, currency = None, None
593 self.qr_window.set_content( address, label, amount, currency )
596 def update_history_tab(self):
598 self.history_list.clear()
599 for item in self.wallet.get_tx_history():
600 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
603 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
609 icon = QIcon(":icons/unconfirmed.png")
611 icon = QIcon(":icons/clock%d.png"%conf)
613 icon = QIcon(":icons/confirmed.png")
616 icon = QIcon(":icons/unconfirmed.png")
618 if value is not None:
619 v_str = format_satoshis(value, True, self.wallet.num_zeros)
623 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
626 label, is_default_label = self.wallet.get_label(tx_hash)
628 label = _('Pruned transaction outputs')
629 is_default_label = False
631 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
632 item.setFont(2, QFont(MONOSPACE_FONT))
633 item.setFont(3, QFont(MONOSPACE_FONT))
634 item.setFont(4, QFont(MONOSPACE_FONT))
636 item.setForeground(3, QBrush(QColor("#BC1E1E")))
638 item.setData(0, Qt.UserRole, tx_hash)
639 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
641 item.setForeground(2, QBrush(QColor('grey')))
643 item.setIcon(0, icon)
644 self.history_list.insertTopLevelItem(0,item)
647 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
650 def create_send_tab(self):
655 grid.setColumnMinimumWidth(3,300)
656 grid.setColumnStretch(5,1)
658 self.payto_e = QLineEdit()
659 grid.addWidget(QLabel(_('Pay to')), 1, 0)
660 grid.addWidget(self.payto_e, 1, 1, 1, 3)
663 qrcode = qrscanner.scan_qr()
664 if 'address' in qrcode:
665 self.payto_e.setText(qrcode['address'])
666 if 'amount' in qrcode:
667 self.amount_e.setText(str(qrcode['amount']))
668 if 'label' in qrcode:
669 self.message_e.setText(qrcode['label'])
670 if 'message' in qrcode:
671 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
674 if qrscanner.is_available():
675 b = QPushButton(_("Scan QR code"))
676 b.clicked.connect(fill_from_qr)
677 grid.addWidget(b, 1, 5)
679 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)
681 completer = QCompleter()
682 completer.setCaseSensitivity(False)
683 self.payto_e.setCompleter(completer)
684 completer.setModel(self.completions)
686 self.message_e = QLineEdit()
687 grid.addWidget(QLabel(_('Description')), 2, 0)
688 grid.addWidget(self.message_e, 2, 1, 1, 3)
689 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)
691 self.amount_e = QLineEdit()
692 grid.addWidget(QLabel(_('Amount')), 3, 0)
693 grid.addWidget(self.amount_e, 3, 1, 1, 2)
694 grid.addWidget(HelpButton(
695 _('Amount to be sent.') + '\n\n' \
696 + _('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)
698 self.fee_e = QLineEdit()
699 grid.addWidget(QLabel(_('Fee')), 4, 0)
700 grid.addWidget(self.fee_e, 4, 1, 1, 2)
701 grid.addWidget(HelpButton(
702 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
703 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
704 + _('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)
706 b = EnterButton(_("Send"), self.do_send)
707 grid.addWidget(b, 6, 1)
709 b = EnterButton(_("Clear"),self.do_clear)
710 grid.addWidget(b, 6, 2)
712 self.payto_sig = QLabel('')
713 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
715 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
716 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
725 def entry_changed( is_fee ):
726 self.funds_error = False
727 amount = numbify(self.amount_e)
728 fee = numbify(self.fee_e)
729 if not is_fee: fee = None
732 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
734 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
737 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
738 text = self.status_text
741 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
742 self.funds_error = True
743 text = _( "Not enough funds" )
745 self.statusBar().showMessage(text)
746 self.amount_e.setPalette(palette)
747 self.fee_e.setPalette(palette)
749 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
750 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
755 def update_completions(self):
757 for addr,label in self.wallet.labels.items():
758 if addr in self.wallet.addressbook:
759 l.append( label + ' <' + addr + '>')
760 l = l + self.wallet.aliases.keys()
762 self.completions.setStringList(l)
768 label = unicode( self.message_e.text() )
769 r = unicode( self.payto_e.text() )
773 m1 = re.match(ALIAS_REGEXP, r)
774 # label or alias, with address in brackets
775 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
778 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
782 to_address = m2.group(2)
786 if not self.wallet.is_valid(to_address):
787 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
791 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
793 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
796 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
798 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
801 if self.wallet.use_encryption:
802 password = self.password_dialog()
809 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
810 except BaseException, e:
811 self.show_message(str(e))
815 h = self.wallet.send_tx(tx)
816 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
817 status, msg = self.wallet.receive_tx( h )
819 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
821 self.update_contacts_tab()
823 QMessageBox.warning(self, _('Error'), msg, _('OK'))
825 filename = 'unsigned_tx'
826 f = open(filename,'w')
829 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
832 def set_url(self, url):
833 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
834 self.tabs.setCurrentIndex(1)
835 label = self.wallet.labels.get(payto)
836 m_addr = label + ' <'+ payto+'>' if label else payto
837 self.payto_e.setText(m_addr)
839 self.message_e.setText(message)
840 self.amount_e.setText(amount)
842 self.set_frozen(self.payto_e,True)
843 self.set_frozen(self.amount_e,True)
844 self.set_frozen(self.message_e,True)
845 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
847 self.payto_sig.setVisible(False)
850 self.payto_sig.setVisible(False)
851 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
853 self.set_frozen(e,False)
855 def set_frozen(self,entry,frozen):
857 entry.setReadOnly(True)
858 entry.setFrame(False)
860 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
861 entry.setPalette(palette)
863 entry.setReadOnly(False)
866 palette.setColor(entry.backgroundRole(), QColor('white'))
867 entry.setPalette(palette)
870 def toggle_freeze(self,addr):
872 if addr in self.wallet.frozen_addresses:
873 self.wallet.unfreeze(addr)
875 self.wallet.freeze(addr)
876 self.update_receive_tab()
878 def toggle_priority(self,addr):
880 if addr in self.wallet.prioritized_addresses:
881 self.wallet.unprioritize(addr)
883 self.wallet.prioritize(addr)
884 self.update_receive_tab()
887 def create_list_tab(self, headers):
888 "generic tab creation method"
889 l = MyTreeWidget(self)
890 l.setColumnCount( len(headers) )
891 l.setHeaderLabels( headers )
901 vbox.addWidget(buttons)
906 buttons.setLayout(hbox)
911 def create_receive_tab(self):
912 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
913 l.setContextMenuPolicy(Qt.CustomContextMenu)
914 l.customContextMenuRequested.connect(self.create_receive_menu)
915 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
916 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
917 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
918 self.receive_list = l
919 self.receive_buttons_hbox = hbox
925 def receive_tab_set_mode(self, i):
926 self.save_column_widths()
927 self.receive_tab_mode = i
928 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
930 self.update_receive_tab()
931 self.toggle_QR_window(self.receive_tab_mode == 2)
933 def save_column_widths(self):
935 for i in range(self.receive_list.columnCount()):
936 widths.append(self.receive_list.columnWidth(i))
937 self.column_widths["receive"][self.receive_tab_mode] = widths
938 self.column_widths["history"] = []
939 for i in range(self.history_list.columnCount()):
940 self.column_widths["history"].append(self.history_list.columnWidth(i))
941 self.column_widths["contacts"] = []
942 for i in range(self.contacts_list.columnCount()):
943 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
945 def create_contacts_tab(self):
946 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
947 l.setContextMenuPolicy(Qt.CustomContextMenu)
948 l.customContextMenuRequested.connect(self.create_contact_menu)
949 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
950 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
951 self.contacts_list = l
952 self.contacts_buttons_hbox = hbox
953 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
958 def delete_imported_key(self, addr):
959 if self.question("Do you want to remove %s from your wallet?"%addr):
960 self.wallet.imported_keys.pop(addr)
961 self.update_receive_tab()
962 self.update_history_tab()
966 def create_receive_menu(self, position):
967 # fixme: this function apparently has a side effect.
968 # if it is not called the menu pops up several times
969 #self.receive_list.selectedIndexes()
971 item = self.receive_list.itemAt(position)
973 addr = unicode(item.text(1))
975 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
976 if self.receive_tab_mode == 2:
977 menu.addAction(_("Request amount"), lambda: self.edit_amount())
978 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
979 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
980 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
981 if addr in self.wallet.imported_keys:
982 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
984 if self.receive_tab_mode == 1:
985 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
986 menu.addAction(t, lambda: self.toggle_freeze(addr))
987 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
988 menu.addAction(t, lambda: self.toggle_priority(addr))
990 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
993 def payto(self, x, is_alias):
1000 label = self.wallet.labels.get(addr)
1001 m_addr = label + ' <' + addr + '>' if label else addr
1002 self.tabs.setCurrentIndex(1)
1003 self.payto_e.setText(m_addr)
1004 self.amount_e.setFocus()
1006 def delete_contact(self, x, is_alias):
1007 if self.question("Do you want to remove %s from your list of contacts?"%x):
1008 if not is_alias and x in self.wallet.addressbook:
1009 self.wallet.addressbook.remove(x)
1010 if x in self.wallet.labels.keys():
1011 self.wallet.labels.pop(x)
1012 elif is_alias and x in self.wallet.aliases:
1013 self.wallet.aliases.pop(x)
1014 self.update_history_tab()
1015 self.update_contacts_tab()
1016 self.update_completions()
1018 def create_contact_menu(self, position):
1019 # fixme: this function apparently has a side effect.
1020 # if it is not called the menu pops up several times
1021 #self.contacts_list.selectedIndexes()
1023 item = self.contacts_list.itemAt(position)
1025 addr = unicode(item.text(0))
1026 label = unicode(item.text(1))
1027 is_alias = label in self.wallet.aliases.keys()
1028 x = label if is_alias else addr
1030 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1031 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1032 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
1034 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1036 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1037 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1038 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1041 def update_receive_item(self, item):
1042 address = str( item.data(1,0).toString() )
1044 flags = self.wallet.get_address_flags(address)
1045 item.setData(0,0,flags)
1047 label = self.wallet.labels.get(address,'')
1048 item.setData(2,0,label)
1051 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1053 amount, currency = None, None
1055 amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
1056 item.setData(3,0,amount_str)
1058 c, u = self.wallet.get_addr_balance(address)
1059 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1060 item.setData(4,0,balance)
1062 if self.receive_tab_mode == 1:
1063 if address in self.wallet.frozen_addresses:
1064 item.setBackgroundColor(1, QColor('lightblue'))
1065 elif address in self.wallet.prioritized_addresses:
1066 item.setBackgroundColor(1, QColor('lightgreen'))
1069 def update_receive_tab(self):
1070 l = self.receive_list
1073 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1074 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1075 l.setColumnHidden(4, self.receive_tab_mode == 0)
1076 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1077 for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
1078 l.setColumnWidth(i, width)
1082 for address in self.wallet.all_addresses():
1084 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1088 h = self.wallet.history.get(address,[])
1091 for tx_hash, tx_height in h:
1092 tx = self.wallet.transactions.get(tx_hash)
1100 if address in self.wallet.addresses:
1102 if gap > self.wallet.gap_limit:
1105 if address in self.wallet.addresses:
1108 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1109 item.setFont(0, QFont(MONOSPACE_FONT))
1110 item.setFont(1, QFont(MONOSPACE_FONT))
1111 item.setFont(3, QFont(MONOSPACE_FONT))
1112 self.update_receive_item(item)
1113 if is_red and address in self.wallet.addresses:
1114 item.setBackgroundColor(1, QColor('red'))
1115 l.addTopLevelItem(item)
1117 # we use column 1 because column 0 may be hidden
1118 l.setCurrentItem(l.topLevelItem(0),1)
1120 def show_contact_details(self, m):
1121 a = self.wallet.aliases.get(m)
1123 if a[0] in self.wallet.authorities.keys():
1124 s = self.wallet.authorities.get(a[0])
1127 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1128 QMessageBox.information(self, 'Alias', msg, 'OK')
1130 def update_contacts_tab(self):
1132 l = self.contacts_list
1134 for i,width in enumerate(self.column_widths['contacts']):
1135 l.setColumnWidth(i, width)
1138 for alias, v in self.wallet.aliases.items():
1140 alias_targets.append(target)
1141 item = QTreeWidgetItem( [ target, alias, '-'] )
1142 item.setBackgroundColor(0, QColor('lightgray'))
1143 l.addTopLevelItem(item)
1145 for address in self.wallet.addressbook:
1146 if address in alias_targets: continue
1147 label = self.wallet.labels.get(address,'')
1149 for item in self.wallet.transactions.values():
1150 if address in item['outputs'] : n=n+1
1152 item = QTreeWidgetItem( [ address, label, tx] )
1153 item.setFont(0, QFont(MONOSPACE_FONT))
1154 l.addTopLevelItem(item)
1156 l.setCurrentItem(l.topLevelItem(0))
1158 def create_wall_tab(self):
1159 self.textbox = textbox = QTextEdit(self)
1160 textbox.setFont(QFont(MONOSPACE_FONT))
1161 textbox.setReadOnly(True)
1164 def create_status_bar(self):
1165 self.status_text = ""
1167 sb.setFixedHeight(35)
1168 qtVersion = qVersion()
1169 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1170 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), "Switch to Lite Mode", self.go_lite ) )
1171 if self.wallet.seed:
1172 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1173 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1174 if self.wallet.seed:
1175 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1176 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1177 sb.addPermanentWidget( self.status_button )
1178 self.setStatusBar(sb)
1182 self.config.set_key('gui', 'lite', True)
1185 self.lite.mini.show()
1187 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1188 self.lite.main(None)
1190 def new_contact_dialog(self):
1191 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1192 address = unicode(text)
1194 if self.wallet.is_valid(address):
1195 self.wallet.addressbook.append(address)
1197 self.update_contacts_tab()
1198 self.update_history_tab()
1199 self.update_completions()
1201 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1203 def show_master_public_key(self):
1204 dialog = QDialog(None)
1206 dialog.setWindowTitle("Master Public Key")
1208 main_text = QTextEdit()
1209 main_text.setText(self.wallet.master_public_key)
1210 main_text.setReadOnly(True)
1211 main_text.setMaximumHeight(170)
1212 qrw = QRCodeWidget(self.wallet.master_public_key, 6)
1214 ok_button = QPushButton(_("OK"))
1215 ok_button.setDefault(True)
1216 ok_button.clicked.connect(dialog.accept)
1218 main_layout = QGridLayout()
1219 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1221 main_layout.addWidget(main_text, 1, 0)
1222 main_layout.addWidget(qrw, 1, 1 )
1224 vbox = QVBoxLayout()
1225 vbox.addLayout(main_layout)
1226 hbox = QHBoxLayout()
1228 hbox.addWidget(ok_button)
1229 vbox.addLayout(hbox)
1231 dialog.setLayout(vbox)
1236 def show_seed_dialog(wallet, parent=None):
1238 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1241 if wallet.use_encryption:
1242 password = parent.password_dialog()
1249 seed = wallet.decode_seed(password)
1251 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1254 dialog = QDialog(None)
1256 dialog.setWindowTitle(_("Electrum") + ' - ' + _('Seed'))
1258 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1260 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1262 seed_text = QTextEdit(brainwallet)
1263 seed_text.setReadOnly(True)
1264 seed_text.setMaximumHeight(130)
1266 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1267 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1268 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1269 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1270 label2 = QLabel(msg2)
1271 label2.setWordWrap(True)
1274 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1275 logo.setMaximumWidth(60)
1277 qrw = QRCodeWidget(seed, 4)
1279 ok_button = QPushButton(_("OK"))
1280 ok_button.setDefault(True)
1281 ok_button.clicked.connect(dialog.accept)
1283 grid = QGridLayout()
1284 #main_layout.addWidget(logo, 0, 0)
1286 grid.addWidget(logo, 0, 0)
1287 grid.addWidget(label1, 0, 1)
1289 grid.addWidget(seed_text, 1, 0, 1, 2)
1291 grid.addWidget(qrw, 0, 2, 2, 1)
1293 vbox = QVBoxLayout()
1294 vbox.addLayout(grid)
1295 vbox.addWidget(label2)
1297 hbox = QHBoxLayout()
1299 hbox.addWidget(ok_button)
1300 vbox.addLayout(hbox)
1302 dialog.setLayout(vbox)
1306 def show_qrcode(title, data):
1310 d.setWindowTitle(title)
1311 d.setMinimumSize(270, 300)
1312 vbox = QVBoxLayout()
1313 qrw = QRCodeWidget(data)
1314 vbox.addWidget(qrw, 1)
1315 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1316 hbox = QHBoxLayout()
1320 filename = "qrcode.bmp"
1321 bmp.save_qrcode(qrw.qr, filename)
1322 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1324 b = QPushButton(_("Print"))
1326 b.clicked.connect(print_qr)
1328 b = QPushButton(_("Close"))
1330 b.clicked.connect(d.accept)
1332 vbox.addLayout(hbox)
1336 def sign_message(self,address):
1337 if not address: return
1340 d.setWindowTitle('Sign Message')
1341 d.setMinimumSize(410, 290)
1343 tab_widget = QTabWidget()
1345 layout = QGridLayout(tab)
1347 sign_address = QLineEdit()
1349 sign_address.setText(address)
1350 layout.addWidget(QLabel(_('Address')), 1, 0)
1351 layout.addWidget(sign_address, 1, 1)
1353 sign_message = QTextEdit()
1354 layout.addWidget(QLabel(_('Message')), 2, 0)
1355 layout.addWidget(sign_message, 2, 1)
1356 layout.setRowStretch(2,3)
1358 sign_signature = QTextEdit()
1359 layout.addWidget(QLabel(_('Signature')), 3, 0)
1360 layout.addWidget(sign_signature, 3, 1)
1361 layout.setRowStretch(3,1)
1364 if self.wallet.use_encryption:
1365 password = self.password_dialog()
1372 signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
1373 sign_signature.setText(signature)
1374 except BaseException, e:
1375 self.show_message(str(e))
1378 hbox = QHBoxLayout()
1379 b = QPushButton(_("Sign"))
1381 b.clicked.connect(do_sign)
1382 b = QPushButton(_("Close"))
1383 b.clicked.connect(d.accept)
1385 layout.addLayout(hbox, 4, 1)
1386 tab_widget.addTab(tab, "Sign")
1390 layout = QGridLayout(tab)
1392 verify_address = QLineEdit()
1393 layout.addWidget(QLabel(_('Address')), 1, 0)
1394 layout.addWidget(verify_address, 1, 1)
1396 verify_message = QTextEdit()
1397 layout.addWidget(QLabel(_('Message')), 2, 0)
1398 layout.addWidget(verify_message, 2, 1)
1399 layout.setRowStretch(2,3)
1401 verify_signature = QTextEdit()
1402 layout.addWidget(QLabel(_('Signature')), 3, 0)
1403 layout.addWidget(verify_signature, 3, 1)
1404 layout.setRowStretch(3,1)
1408 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1409 self.show_message("Signature verified")
1410 except BaseException, e:
1411 self.show_message(str(e))
1414 hbox = QHBoxLayout()
1415 b = QPushButton(_("Verify"))
1416 b.clicked.connect(do_verify)
1418 b = QPushButton(_("Close"))
1419 b.clicked.connect(d.accept)
1421 layout.addLayout(hbox, 4, 1)
1422 tab_widget.addTab(tab, "Verify")
1424 vbox = QVBoxLayout()
1425 vbox.addWidget(tab_widget)
1430 def toggle_QR_window(self, show):
1431 if show and not self.qr_window:
1432 self.qr_window = QR_Window(self.exchanger)
1433 self.qr_window.setVisible(True)
1434 self.qr_window_geometry = self.qr_window.geometry()
1435 item = self.receive_list.currentItem()
1437 address = str(item.text(1))
1438 label = self.wallet.labels.get(address)
1439 amount, currency = self.wallet.requested_amounts.get(address, (None, None))
1440 self.qr_window.set_content( address, label, amount, currency )
1442 elif show and self.qr_window and not self.qr_window.isVisible():
1443 self.qr_window.setVisible(True)
1444 self.qr_window.setGeometry(self.qr_window_geometry)
1446 elif not show and self.qr_window and self.qr_window.isVisible():
1447 self.qr_window_geometry = self.qr_window.geometry()
1448 self.qr_window.setVisible(False)
1450 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1451 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1452 self.receive_list.setColumnWidth(2, 200)
1455 def question(self, msg):
1456 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1458 def show_message(self, msg):
1459 QMessageBox.information(self, _('Message'), msg, _('OK'))
1461 def password_dialog(self ):
1468 vbox = QVBoxLayout()
1469 msg = _('Please enter your password')
1470 vbox.addWidget(QLabel(msg))
1472 grid = QGridLayout()
1474 grid.addWidget(QLabel(_('Password')), 1, 0)
1475 grid.addWidget(pw, 1, 1)
1476 vbox.addLayout(grid)
1478 vbox.addLayout(ok_cancel_buttons(d))
1481 if not d.exec_(): return
1482 return unicode(pw.text())
1489 def change_password_dialog( wallet, parent=None ):
1492 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1500 new_pw = QLineEdit()
1501 new_pw.setEchoMode(2)
1502 conf_pw = QLineEdit()
1503 conf_pw.setEchoMode(2)
1505 vbox = QVBoxLayout()
1507 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1508 +_('To disable wallet encryption, enter an empty new password.')) \
1509 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1511 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1512 +_("Leave these fields empty if you want to disable encryption.")
1513 vbox.addWidget(QLabel(msg))
1515 grid = QGridLayout()
1518 if wallet.use_encryption:
1519 grid.addWidget(QLabel(_('Password')), 1, 0)
1520 grid.addWidget(pw, 1, 1)
1522 grid.addWidget(QLabel(_('New Password')), 2, 0)
1523 grid.addWidget(new_pw, 2, 1)
1525 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1526 grid.addWidget(conf_pw, 3, 1)
1527 vbox.addLayout(grid)
1529 vbox.addLayout(ok_cancel_buttons(d))
1532 if not d.exec_(): return
1534 password = unicode(pw.text()) if wallet.use_encryption else None
1535 new_password = unicode(new_pw.text())
1536 new_password2 = unicode(conf_pw.text())
1539 seed = wallet.decode_seed(password)
1541 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1544 if new_password != new_password2:
1545 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1546 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1548 wallet.update_password(seed, password, new_password)
1551 def seed_dialog(wallet, parent=None):
1555 vbox = QVBoxLayout()
1556 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1557 vbox.addWidget(QLabel(msg))
1559 grid = QGridLayout()
1562 seed_e = QLineEdit()
1563 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1564 grid.addWidget(seed_e, 1, 1)
1568 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1569 grid.addWidget(gap_e, 2, 1)
1570 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1571 vbox.addLayout(grid)
1573 vbox.addLayout(ok_cancel_buttons(d))
1576 if not d.exec_(): return
1579 gap = int(unicode(gap_e.text()))
1581 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1585 seed = str(seed_e.text())
1588 print_error("Warning: Not hex, trying decode")
1590 seed = mnemonic.mn_decode( seed.split(' ') )
1592 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1596 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1602 def do_import_labels(self):
1603 labelsFile = QFileDialog.getOpenFileName(QWidget(), "Open text file", util.user_dir(), self.tr("Text Files (labels.dat)"))
1604 if not labelsFile: return
1606 f = open(labelsFile, 'r')
1609 for key, value in json.loads(data).items():
1610 self.wallet.labels[key] = value
1612 QMessageBox.information(None, "Labels imported", "Your labels where imported from '%s'" % str(labelsFile))
1613 except (IOError, os.error), reason:
1614 QMessageBox.critical(None, "Unable to import labels", "Electrum was unable to import your labels.\n" + str(reason))
1618 def do_export_labels(self):
1619 labels = self.wallet.labels
1621 labelsFile = util.user_dir() + '/labels.dat'
1622 f = open(labelsFile, 'w+')
1623 json.dump(labels, f)
1625 QMessageBox.information(None, "Labels exported", "Your labels where exported to '%s'" % str(labelsFile))
1626 except (IOError, os.error), reason:
1627 QMessageBox.critical(None, "Unable to export labels", "Electrum was unable to export your labels.\n" + str(reason))
1629 def do_export_history(self):
1630 from gui_lite import csv_transaction
1631 csv_transaction(self.wallet)
1633 def do_import_privkey(self):
1634 if not self.wallet.imported_keys:
1635 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1636 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1637 + _('Are you sure you understand what you are doing?'), 3, 4)
1640 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1642 sec = str(text).strip()
1643 if self.wallet.use_encryption:
1644 password = self.password_dialog()
1650 addr = self.wallet.import_key(sec, password)
1652 QMessageBox.critical(None, "Unable to import key", "error")
1654 QMessageBox.information(None, "Key imported", addr)
1655 self.update_receive_tab()
1656 self.update_history_tab()
1657 except BaseException as e:
1658 QMessageBox.critical(None, "Unable to import key", str(e))
1660 def settings_dialog(self):
1662 d.setWindowTitle(_('Electrum Settings'))
1664 vbox = QVBoxLayout()
1666 tabs = QTabWidget(self)
1667 vbox.addWidget(tabs)
1670 grid_ui = QGridLayout(tab1)
1671 grid_ui.setColumnStretch(0,1)
1672 tabs.addTab(tab1, _('Display') )
1674 nz_label = QLabel(_('Display zeros'))
1675 grid_ui.addWidget(nz_label, 3, 0)
1677 nz_e.setText("%d"% self.wallet.num_zeros)
1678 grid_ui.addWidget(nz_e, 3, 1)
1679 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1680 grid_ui.addWidget(HelpButton(msg), 3, 2)
1681 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1682 if not self.config.is_modifiable('num_zeros'):
1683 for w in [nz_e, nz_label]: w.setEnabled(False)
1685 lang_label=QLabel(_('Language') + ':')
1686 grid_ui.addWidget(lang_label , 8, 0)
1687 lang_combo = QComboBox()
1688 from i18n import languages
1689 lang_combo.addItems(languages.values())
1691 index = languages.keys().index(self.config.get("language",''))
1694 lang_combo.setCurrentIndex(index)
1695 grid_ui.addWidget(lang_combo, 8, 1)
1696 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2)
1697 if not self.config.is_modifiable('language'):
1698 for w in [lang_combo, lang_label]: w.setEnabled(False)
1700 currencies = self.exchanger.get_currencies()
1701 currencies.insert(0, "None")
1703 cur_label=QLabel(_('Currency') + ':')
1704 grid_ui.addWidget(cur_label , 9, 0)
1705 cur_combo = QComboBox()
1706 cur_combo.addItems(currencies)
1708 index = currencies.index(self.config.get('currency', "None"))
1711 cur_combo.setCurrentIndex(index)
1712 grid_ui.addWidget(cur_combo, 9, 1)
1713 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2)
1715 view_label=QLabel(_('Receive Tab') + ':')
1716 grid_ui.addWidget(view_label , 10, 0)
1717 view_combo = QComboBox()
1718 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1719 view_combo.setCurrentIndex(self.receive_tab_mode)
1720 grid_ui.addWidget(view_combo, 10, 1)
1721 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1722 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1723 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1724 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1726 grid_ui.addWidget(HelpButton(hh), 10, 2)
1730 grid_wallet = QGridLayout(tab2)
1731 grid_wallet.setColumnStretch(0,1)
1732 tabs.addTab(tab2, _('Wallet') )
1734 fee_label = QLabel(_('Transaction fee'))
1735 grid_wallet.addWidget(fee_label, 0, 0)
1737 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1738 grid_wallet.addWidget(fee_e, 0, 1)
1739 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1740 + _('Recommended value') + ': 0.001'
1741 grid_wallet.addWidget(HelpButton(msg), 0, 2)
1742 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1743 if not self.config.is_modifiable('fee'):
1744 for w in [fee_e, fee_label]: w.setEnabled(False)
1746 usechange_label = QLabel(_('Use change addresses'))
1747 grid_wallet.addWidget(usechange_label, 1, 0)
1748 usechange_combo = QComboBox()
1749 usechange_combo.addItems(['Yes', 'No'])
1750 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1751 grid_wallet.addWidget(usechange_combo, 1, 1)
1752 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 2)
1753 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1755 gap_label = QLabel(_('Gap limit'))
1756 grid_wallet.addWidget(gap_label, 2, 0)
1758 gap_e.setText("%d"% self.wallet.gap_limit)
1759 grid_wallet.addWidget(gap_e, 2, 1)
1760 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1761 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1762 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1763 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1764 + _('Warning') + ': ' \
1765 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1766 + _('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'
1767 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1768 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1769 if not self.config.is_modifiable('gap_limit'):
1770 for w in [gap_e, gap_label]: w.setEnabled(False)
1772 grid_wallet.setRowStretch(3,1)
1777 grid_io = QGridLayout(tab3)
1778 grid_io.setColumnStretch(0,1)
1779 tabs.addTab(tab3, _('Import/Export') )
1781 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1782 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1783 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1784 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1786 grid_io.addWidget(QLabel(_('History')), 2, 0)
1787 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1788 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
1790 grid_io.addWidget(QLabel(_('Private key')), 3, 0)
1791 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
1792 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
1794 grid_io.addWidget(QLabel(_('Master Public key')), 4, 0)
1795 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
1796 grid_io.addWidget(HelpButton(_('Your master public key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
1797 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
1798 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
1800 grid_io.setRowStretch(4,1)
1801 vbox.addLayout(ok_cancel_buttons(d))
1805 if not d.exec_(): return
1807 fee = unicode(fee_e.text())
1809 fee = int( 100000000 * Decimal(fee) )
1811 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1814 if self.wallet.fee != fee:
1815 self.wallet.fee = fee
1818 nz = unicode(nz_e.text())
1823 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1826 if self.wallet.num_zeros != nz:
1827 self.wallet.num_zeros = nz
1828 self.config.set_key('num_zeros', nz, True)
1829 self.update_history_tab()
1830 self.update_receive_tab()
1832 usechange_result = usechange_combo.currentIndex() == 0
1833 if self.wallet.use_change != usechange_result:
1834 self.wallet.use_change = usechange_result
1835 self.config.set_key('use_change', self.wallet.use_change, True)
1838 n = int(gap_e.text())
1840 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1843 if self.wallet.gap_limit != n:
1844 r = self.wallet.change_gap_limit(n)
1846 self.update_receive_tab()
1847 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1849 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1851 need_restart = False
1853 lang_request = languages.keys()[lang_combo.currentIndex()]
1854 if lang_request != self.config.get('language'):
1855 self.config.set_key("language", lang_request, True)
1858 cur_request = str(currencies[cur_combo.currentIndex()])
1859 if cur_request != self.config.get('currency', "None"):
1860 self.config.set_key('currency', cur_request, True)
1861 self.update_wallet()
1864 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1866 self.receive_tab_set_mode(view_combo.currentIndex())
1870 def network_dialog(wallet, parent=None):
1871 interface = wallet.interface
1873 if interface.is_connected:
1874 status = _("Connected to")+" %s\n%d "+_("blocks")%(interface.host, wallet.verifier.height)
1876 status = _("Not connected")
1877 server = interface.server
1880 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1881 server = interface.server
1883 plist, servers_list = interface.get_servers_list()
1887 d.setWindowTitle(_('Server'))
1888 d.setMinimumSize(375, 20)
1890 vbox = QVBoxLayout()
1893 hbox = QHBoxLayout()
1895 l.setPixmap(QPixmap(":icons/network.png"))
1898 hbox.addWidget(QLabel(status))
1900 vbox.addLayout(hbox)
1904 grid = QGridLayout()
1906 vbox.addLayout(grid)
1909 server_protocol = QComboBox()
1910 server_host = QLineEdit()
1911 server_host.setFixedWidth(200)
1912 server_port = QLineEdit()
1913 server_port.setFixedWidth(60)
1915 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1916 protocol_letters = 'thsg'
1917 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1918 server_protocol.addItems(protocol_names)
1920 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1921 grid.addWidget(server_protocol, 0, 1)
1922 grid.addWidget(server_host, 0, 2)
1923 grid.addWidget(server_port, 0, 3)
1925 def change_protocol(p):
1926 protocol = protocol_letters[p]
1927 host = unicode(server_host.text())
1928 pp = plist.get(host,DEFAULT_PORTS)
1929 if protocol not in pp.keys():
1930 protocol = pp.keys()[0]
1932 server_host.setText( host )
1933 server_port.setText( port )
1935 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1937 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1938 servers_list_widget = QTreeWidget(parent)
1939 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1940 servers_list_widget.setMaximumHeight(150)
1941 servers_list_widget.setColumnWidth(0, 240)
1942 for _host in servers_list.keys():
1943 _type = 'P' if servers_list[_host].get('pruning') else 'F'
1944 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1946 def change_server(host, protocol=None):
1947 pp = plist.get(host,DEFAULT_PORTS)
1949 port = pp.get(protocol)
1950 if not port: protocol = None
1953 if 't' in pp.keys():
1955 port = pp.get(protocol)
1957 protocol = pp.keys()[0]
1958 port = pp.get(protocol)
1960 server_host.setText( host )
1961 server_port.setText( port )
1962 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1964 if not plist: return
1965 for p in protocol_letters:
1966 i = protocol_letters.index(p)
1967 j = server_protocol.model().index(i,0)
1968 if p not in pp.keys():
1969 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1971 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1975 host, port, protocol = server.split(':')
1976 change_server(host,protocol)
1978 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1979 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1981 if not wallet.config.is_modifiable('server'):
1982 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1985 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
1986 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
1987 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
1988 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
1991 proxy_mode = QComboBox()
1992 proxy_host = QLineEdit()
1993 proxy_host.setFixedWidth(200)
1994 proxy_port = QLineEdit()
1995 proxy_port.setFixedWidth(60)
1996 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1998 def check_for_disable(index = False):
1999 if proxy_mode.currentText() != 'NONE':
2000 proxy_host.setEnabled(True)
2001 proxy_port.setEnabled(True)
2003 proxy_host.setEnabled(False)
2004 proxy_port.setEnabled(False)
2007 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2009 if not wallet.config.is_modifiable('proxy'):
2010 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2012 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2013 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2014 proxy_host.setText(proxy_config.get("host"))
2015 proxy_port.setText(proxy_config.get("port"))
2017 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2018 grid.addWidget(proxy_mode, 2, 1)
2019 grid.addWidget(proxy_host, 2, 2)
2020 grid.addWidget(proxy_port, 2, 3)
2023 vbox.addLayout(ok_cancel_buttons(d))
2026 if not d.exec_(): return
2028 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2029 if proxy_mode.currentText() != 'NONE':
2030 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2034 wallet.config.set_key("proxy", proxy, True)
2035 wallet.config.set_key("server", server, True)
2036 interface.set_server(server, proxy)
2037 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2040 def closeEvent(self, event):
2042 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2043 self.save_column_widths()
2044 self.config.set_key("column-widths", self.column_widths, True)
2050 def __init__(self, wallet, config, app=None):
2051 self.wallet = wallet
2052 self.config = config
2054 self.app = QApplication(sys.argv)
2057 def restore_or_create(self):
2058 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2059 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2060 if r==2: return None
2061 return 'restore' if r==1 else 'create'
2063 def seed_dialog(self):
2064 return ElectrumWindow.seed_dialog( self.wallet )
2066 def network_dialog(self):
2067 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2070 def show_seed(self):
2071 ElectrumWindow.show_seed_dialog(self.wallet)
2074 def password_dialog(self):
2075 ElectrumWindow.change_password_dialog(self.wallet)
2078 def restore_wallet(self):
2079 wallet = self.wallet
2080 # wait until we are connected, because the user might have selected another server
2081 if not wallet.interface.is_connected:
2082 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
2083 waiting_dialog(waiting)
2085 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
2086 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
2088 wallet.set_up_to_date(False)
2089 wallet.interface.poke('synchronizer')
2090 waiting_dialog(waiting)
2091 if wallet.is_found():
2092 print_error( "Recovery successful" )
2094 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2101 w = ElectrumWindow(self.wallet, self.config)
2102 if url: w.set_url(url)