URI: 
       tMerge pull request #4033 from Lastrellik/TextCompleter - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 9a0cf6376989d72f297a3fb744f19912c24275b5
   DIR parent e4dad0a42565a32824e8202c27e9f7b19ac790ed
  HTML Author: ghost43 <somber.night@protonmail.com>
       Date:   Thu,  5 Apr 2018 15:29:58 +0200
       
       Merge pull request #4033 from Lastrellik/TextCompleter
       
       Text completer
       Diffstat:
         A gui/qt/completion_text_edit.py      |     121 +++++++++++++++++++++++++++++++
         M gui/qt/main_window.py               |       2 +-
         M gui/qt/paytoedit.py                 |      74 +++----------------------------
         M gui/qt/seed_dialog.py               |      24 ++++++++++++++++++++----
       
       4 files changed, 147 insertions(+), 74 deletions(-)
       ---
   DIR diff --git a/gui/qt/completion_text_edit.py b/gui/qt/completion_text_edit.py
       t@@ -0,0 +1,120 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2018 The Electrum developers
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +from PyQt5.QtGui import *
       +from PyQt5.QtCore import *
       +from PyQt5.QtWidgets import *
       +from .util import ButtonsTextEdit
       +
       +class CompletionTextEdit(ButtonsTextEdit):
       +
       +    def __init__(self, parent=None):
       +        super(CompletionTextEdit, self).__init__(parent)
       +        self.completer = None
       +        self.moveCursor(QTextCursor.End)
       +        self.disable_suggestions()
       +
       +    def set_completer(self, completer):
       +        self.completer = completer
       +        self.initialize_completer()
       +
       +    def initialize_completer(self):
       +        self.completer.setWidget(self)
       +        self.completer.setCompletionMode(QCompleter.PopupCompletion)
       +        self.completer.activated.connect(self.insert_completion)
       +        self.enable_suggestions()
       +
       +    def insert_completion(self, completion):
       +        if self.completer.widget() != self:
       +            return
       +        text_cursor = self.textCursor()
       +        extra = len(completion) - len(self.completer.completionPrefix())
       +        text_cursor.movePosition(QTextCursor.Left)
       +        text_cursor.movePosition(QTextCursor.EndOfWord)
       +        if extra == 0:
       +            text_cursor.insertText(" ")
       +        else:
       +            text_cursor.insertText(completion[-extra:] + " ")
       +        self.setTextCursor(text_cursor)
       +
       +    def text_under_cursor(self):
       +        tc = self.textCursor()
       +        tc.select(QTextCursor.WordUnderCursor)
       +        return tc.selectedText()
       +
       +    def enable_suggestions(self):
       +        self.suggestions_enabled = True
       +
       +    def disable_suggestions(self):
       +        self.suggestions_enabled = False
       +
       +    def keyPressEvent(self, e):
       +        if self.isReadOnly():
       +            return
       +
       +        if self.is_special_key(e):
       +            e.ignore()
       +            return
       +
       +        QPlainTextEdit.keyPressEvent(self, e)
       +
       +        ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier)
       +        if self.completer is None or (ctrlOrShift and not e.text()):
       +            return
       +
       +        if not self.suggestions_enabled:
       +            return
       +
       +        eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
       +        hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift
       +        completionPrefix = self.text_under_cursor()
       +
       +        if hasModifier or not e.text() or len(completionPrefix) < 1 or eow.find(e.text()[-1]) >= 0:
       +            self.completer.popup().hide()
       +            return
       +
       +        if completionPrefix != self.completer.completionPrefix():
       +            self.completer.setCompletionPrefix(completionPrefix)
       +            self.completer.popup().setCurrentIndex(self.completer.completionModel().index(0, 0))
       +
       +        cr = self.cursorRect()
       +        cr.setWidth(self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width())
       +        self.completer.complete(cr)
       +
       +    def is_special_key(self, e):
       +        if self.completer != None and self.completer.popup().isVisible():
       +            if e.key() in [Qt.Key_Enter, Qt.Key_Return]:
       +                return True
       +        if e.key() in [Qt.Key_Tab, Qt.Key_Down, Qt.Key_Up]:
       +            return True
       +        return False
       +
       +if __name__ == "__main__":
       +    app = QApplication([])
       +    completer = QCompleter(["alabama", "arkansas", "avocado", "breakfast", "sausage"])
       +    te = CompletionTextEdit()
       +    te.set_completer(completer)
       +    te.show()
       +    app.exec_()
       +\ No newline at end of file
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -1050,7 +1050,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
                completer = QCompleter()
                completer.setCaseSensitivity(False)
       -        self.payto_e.setCompleter(completer)
       +        self.payto_e.set_completer(completer)
                completer.setModel(self.completions)
        
                msg = _('Description of the transaction (not mandatory).') + '\n\n'\
   DIR diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py
       t@@ -23,16 +23,15 @@
        # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        # SOFTWARE.
        
       -from PyQt5.QtCore import *
        from PyQt5.QtGui import *
       -from PyQt5.QtWidgets import QCompleter, QPlainTextEdit
       -from .qrtextedit import ScanQRTextEdit
       -
        import re
        from decimal import Decimal
       +
        from electrum import bitcoin
        from electrum.util import bfh
        
       +from .qrtextedit import ScanQRTextEdit
       +from .completion_text_edit import CompletionTextEdit
        from . import util
        
        RE_ADDRESS = '[1-9A-HJ-NP-Za-km-z]{26,}'
       t@@ -41,9 +40,10 @@ RE_ALIAS = '(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>'
        frozen_style = "QWidget { background-color:none; border:none;}"
        normal_style = "QPlainTextEdit { }"
        
       -class PayToEdit(ScanQRTextEdit):
       +class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
        
            def __init__(self, win):
       +        CompletionTextEdit.__init__(self)
                ScanQRTextEdit.__init__(self)
                self.win = win
                self.amount_edit = win.amount_e
       t@@ -198,70 +198,6 @@ class PayToEdit(ScanQRTextEdit):
                    self.setMaximumHeight(h)
                self.verticalScrollBar().hide()
        
       -
       -    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 = len(completion) - len(self.c.completionPrefix())
       -        tc.movePosition(QTextCursor.Left)
       -        tc.movePosition(QTextCursor.EndOfWord)
       -        tc.insertText(completion[-extra:])
       -        self.setTextCursor(tc)
       -
       -
       -    def textUnderCursor(self):
       -        tc = self.textCursor()
       -        tc.select(QTextCursor.WordUnderCursor)
       -        return tc.selectedText()
       -
       -
       -    def keyPressEvent(self, e):
       -        if self.isReadOnly():
       -            return
       -
       -        if self.c.popup().isVisible():
       -            if e.key() in [Qt.Key_Enter, Qt.Key_Return]:
       -                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
       -
       -        QPlainTextEdit.keyPressEvent(self, e)
       -
       -        ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier)
       -        if self.c is None or (ctrlOrShift and not e.text()):
       -            return
       -
       -        eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
       -        hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift
       -        completionPrefix = self.textUnderCursor()
       -
       -        if hasModifier or not e.text() or len(completionPrefix) < 1 or eow.find(e.text()[-1]) >= 0:
       -            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)
       -
            def qr_input(self):
                data = super(PayToEdit,self).qr_input()
                if data.startswith("bitcoin:"):
   DIR diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py
       t@@ -23,13 +23,13 @@
        # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        # SOFTWARE.
        
       -from PyQt5.QtGui import *
       -from PyQt5.QtCore import *
       -from PyQt5.QtWidgets import *
        from electrum.i18n import _
       +from electrum.mnemonic import Mnemonic
       +import electrum.old_mnemonic
        
        from .util import *
        from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
       +from .completion_text_edit import CompletionTextEdit
        
        
        def seed_warning_msg(seed):
       t@@ -92,7 +92,7 @@ class SeedLayout(QVBoxLayout):
                self.options = options
                if title:
                    self.addWidget(WWLabel(title))
       -        self.seed_e = ButtonsTextEdit()
       +        self.seed_e = CompletionTextEdit()
                if seed:
                    self.seed_e.setText(seed)
                else:
       t@@ -100,6 +100,8 @@ class SeedLayout(QVBoxLayout):
                    self.is_seed = is_seed
                    self.saved_is_seed = self.is_seed
                    self.seed_e.textChanged.connect(self.on_edit)
       +            self.initialize_completer()
       +
                self.seed_e.setMaximumHeight(75)
                hbox = QHBoxLayout()
                if icon:
       t@@ -131,6 +133,14 @@ class SeedLayout(QVBoxLayout):
                    self.seed_warning.setText(seed_warning_msg(seed))
                self.addWidget(self.seed_warning)
        
       +    def initialize_completer(self):
       +        english_list = Mnemonic('en').wordlist
       +        old_list = electrum.old_mnemonic.words
       +        self.wordlist = english_list + list(set(old_list) - set(english_list)) #concat both lists
       +        self.wordlist.sort()
       +        self.completer = QCompleter(self.wordlist)
       +        self.seed_e.set_completer(self.completer)
       +
            def get_seed(self):
                text = self.seed_e.text()
                return ' '.join(text.split())
       t@@ -150,6 +160,12 @@ class SeedLayout(QVBoxLayout):
                self.seed_type_label.setText(label)
                self.parent.next_button.setEnabled(b)
        
       +        # to account for bip39 seeds
       +        for word in self.get_seed().split(" ")[:-1]:
       +            if word not in self.wordlist:
       +                self.seed_e.disable_suggestions()
       +                return
       +        self.seed_e.enable_suggestions()
        
        class KeysLayout(QVBoxLayout):
            def __init__(self, parent=None, title=None, is_valid=None, allow_multi=False):