From b2cd1ce7e67b597a78aa3455941ac49e704cec8e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Jun 2014 21:53:25 +0200 Subject: [PATCH 1/2] paytoedit --- gui/qt/main_window.py | 3 +- gui/qt/paytoedit.py | 98 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 gui/qt/paytoedit.py diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 4f8acb19f..b4109c472 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -643,7 +643,8 @@ class ElectrumWindow(QMainWindow): grid.setColumnStretch(5,1) grid.setRowStretch(8, 1) - self.payto_e = QLineEdit() + from paytoedit import PayToEdit + self.payto_e = PayToEdit() self.payto_help = 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)')) grid.addWidget(QLabel(_('Pay to')), 1, 0) grid.addWidget(self.payto_e, 1, 1, 1, 3) diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py new file mode 100644 index 000000000..3357a2c2b --- /dev/null +++ b/gui/qt/paytoedit.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2012 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + + +class PayToEdit(QTextEdit): + + def __init__(self, *args, **kwargs): + QTextEdit.__init__(self, *args, **kwargs) + self.document().contentsChanged.connect(self.update_size) + self.heightMin = 0 + self.heightMax = 150 + self.setMinimumHeight(27) + self.setMaximumHeight(27) + #self.setStyleSheet("QTextEdit { border-style:solid; border-width: 1px;}") + self.c = None + + + def update_size(self): + docHeight = self.document().size().height() + if self.heightMin <= docHeight <= self.heightMax: + self.setMinimumHeight(docHeight + 2) + self.setMaximumHeight(docHeight + 2) + + + def setCompleter(self, completer): + self.c = completer + self.c.setWidget(self) + self.c.setCompletionMode(QCompleter.PopupCompletion) + self.c.activated.connect(self.insertCompletion) + + + def insertCompletion(self, completion): + if self.c.widget() != self: + return + tc = self.textCursor() + extra = completion.length() - self.c.completionPrefix().length() + tc.movePosition(QTextCursor.Left) + tc.movePosition(QTextCursor.EndOfWord) + tc.insertText(completion.right(extra)) + self.setTextCursor(tc) + + + def textUnderCursor(self): + tc = self.textCursor() + tc.select(QTextCursor.WordUnderCursor) + return tc.selectedText() + + + def keyPressEvent(self, e): + if self.c.popup().isVisible(): + if e.key() in [Qt.Key_Enter, Qt.Key_Return]: + e.ignore() + return + + isShortcut = (e.modifiers() and Qt.ControlModifier) and e.key() == Qt.Key_E + + if not self.c or not isShortcut: + QTextEdit.keyPressEvent(self, e) + + ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier) + if self.c is None or (ctrlOrShift and e.text().isEmpty()): + return + + eow = QString("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=") + hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift; + completionPrefix = self.textUnderCursor() + + if not isShortcut and (hasModifier or e.text().isEmpty() or completionPrefix.length() < 1 or eow.contains(e.text().right(1)) ): + self.c.popup().hide() + return + + if completionPrefix != self.c.completionPrefix(): + self.c.setCompletionPrefix(completionPrefix); + self.c.popup().setCurrentIndex(self.c.completionModel().index(0, 0)) + + cr = self.cursorRect() + cr.setWidth(self.c.popup().sizeHintForColumn(0) + self.c.popup().verticalScrollBar().sizeHint().width()) + self.c.complete(cr) + + From d3f136d609903d3735319944f4873985025e1f87 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 4 Jun 2014 14:49:55 +0200 Subject: [PATCH 2/2] parse payto text --- gui/qt/amountedit.py | 4 +++ gui/qt/main_window.py | 4 +-- gui/qt/paytoedit.py | 73 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/gui/qt/amountedit.py b/gui/qt/amountedit.py index 737502734..cb054b531 100644 --- a/gui/qt/amountedit.py +++ b/gui/qt/amountedit.py @@ -41,3 +41,7 @@ class AmountEdit(QLineEdit): s = s[:p] + '.' + s[p:p+8] self.setText(s) self.setCursorPosition(pos) + + + def setAmount(self, amount): + self.setText(self.format_amount(self.wallet.fee).strip()) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index b4109c472..d28bddf25 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -644,7 +644,8 @@ class ElectrumWindow(QMainWindow): grid.setRowStretch(8, 1) from paytoedit import PayToEdit - self.payto_e = PayToEdit() + self.amount_e = AmountEdit(self.base_unit) + self.payto_e = PayToEdit(self.amount_e) self.payto_help = 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)')) grid.addWidget(QLabel(_('Pay to')), 1, 0) grid.addWidget(self.payto_e, 1, 1, 1, 3) @@ -672,7 +673,6 @@ class ElectrumWindow(QMainWindow): grid.addWidget(self.from_list, 3, 1, 1, 3) self.set_pay_from([]) - self.amount_e = AmountEdit(self.base_unit) self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \ + _('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.') \ + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')) diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py index 3357a2c2b..3691148e8 100644 --- a/gui/qt/paytoedit.py +++ b/gui/qt/paytoedit.py @@ -19,11 +19,18 @@ from PyQt4.QtCore import * from PyQt4.QtGui import * +import re +from decimal import Decimal +from electrum import bitcoin + +RE_ADDRESS = '[1-9A-HJ-NP-Za-km-z]{26,}' +RE_ALIAS = '(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>' class PayToEdit(QTextEdit): - def __init__(self, *args, **kwargs): - QTextEdit.__init__(self, *args, **kwargs) + def __init__(self, amount_edit): + QTextEdit.__init__(self) + self.amount_edit = amount_edit self.document().contentsChanged.connect(self.update_size) self.heightMin = 0 self.heightMax = 150 @@ -33,6 +40,57 @@ class PayToEdit(QTextEdit): self.c = None + def lock_amount(self): + e = self.amount_edit + e.setReadOnly(True) + e.setFrame(False) + + def unlock_amount(self): + e = self.amount_edit + e.setReadOnly(False) + e.setFrame(True) + + + def parse_line(self, line): + recipient, amount = line.split(',') + amount = Decimal(amount.strip()) + recipient = recipient.strip() + m = re.match(RE_ALIAS, recipient) + to_address = m.group(2) if m else recipient + assert bitcoin.is_address(to_address) + return to_address, amount + + + def check_text(self): + # filter out empty lines + lines = filter( lambda x: x, self.lines()) + outputs = [] + total = 0 + + for line in lines: + try: + to_address, amount = self.parse_line(line) + except: + continue + outputs.append((to_address, amount)) + total += amount + + self.outputs = outputs + + self.amount_edit.setText(str(total) if total else "") + if total or len(lines)>1: + self.lock_amount() + else: + self.unlock_amount() + + def lines(self): + return str(self.toPlainText()).split('\n') + + + def is_multiline(self): + return len(self.lines()) > 1 + + def update_size(self): docHeight = self.document().size().height() if self.heightMin <= docHeight <= self.heightMax: @@ -56,6 +114,7 @@ class PayToEdit(QTextEdit): tc.movePosition(QTextCursor.EndOfWord) tc.insertText(completion.right(extra)) self.setTextCursor(tc) + self.check_text() def textUnderCursor(self): @@ -70,10 +129,20 @@ class PayToEdit(QTextEdit): e.ignore() return + if e.key() in [Qt.Key_Tab]: + e.ignore() + return + + if e.key() in [Qt.Key_Down, Qt.Key_Up] and not self.is_multiline(): + e.ignore() + return + isShortcut = (e.modifiers() and Qt.ControlModifier) and e.key() == Qt.Key_E if not self.c or not isShortcut: QTextEdit.keyPressEvent(self, e) + self.check_text() + ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier) if self.c is None or (ctrlOrShift and e.text().isEmpty()):