Added spellchecked and auto-resizing height input area.

This commit is contained in:
ricky 2015-11-21 04:00:47 -08:00
parent fa2b812c93
commit fe28b526d7
2 changed files with 283 additions and 12 deletions

View File

@ -21,11 +21,12 @@
# #
import qt_compat import qt_compat
from inputlinespell import InputLineSpell
QtCore = qt_compat.import_module('QtCore') QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui') QtGui = qt_compat.import_module('QtGui')
class InputLineEdit(QtGui.QLineEdit): class InputLineEdit(InputLineSpell):
"""Input line.""" """Input line."""
bufferSwitchPrev = qt_compat.Signal() bufferSwitchPrev = qt_compat.Signal()
@ -33,11 +34,10 @@ class InputLineEdit(QtGui.QLineEdit):
textSent = qt_compat.Signal(str) textSent = qt_compat.Signal(str)
def __init__(self, scroll_widget): def __init__(self, scroll_widget):
QtGui.QLineEdit.__init__(self) InputLineSpell.__init__(self, False)
self.scroll_widget = scroll_widget self.scroll_widget = scroll_widget
self._history = [] self._history = []
self._history_index = -1 self._history_index = -1
self.returnPressed.connect(self._input_return_pressed)
def keyPressEvent(self, event): def keyPressEvent(self, event):
key = event.key() key = event.key()
@ -49,7 +49,7 @@ class InputLineEdit(QtGui.QLineEdit):
elif key == QtCore.Qt.Key_PageDown: elif key == QtCore.Qt.Key_PageDown:
self.bufferSwitchNext.emit() self.bufferSwitchNext.emit()
else: else:
QtGui.QLineEdit.keyPressEvent(self, event) InputLineSpell.keyPressEvent(self, event)
elif modifiers == QtCore.Qt.AltModifier: elif modifiers == QtCore.Qt.AltModifier:
if key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Up): if key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Up):
self.bufferSwitchPrev.emit() self.bufferSwitchPrev.emit()
@ -64,22 +64,35 @@ class InputLineEdit(QtGui.QLineEdit):
elif key == QtCore.Qt.Key_End: elif key == QtCore.Qt.Key_End:
bar.setValue(bar.maximum()) bar.setValue(bar.maximum())
else: else:
QtGui.QLineEdit.keyPressEvent(self, event) InputLineSpell.keyPressEvent(self, event)
elif key == QtCore.Qt.Key_PageUp: elif key == QtCore.Qt.Key_PageUp:
bar.setValue(bar.value() - bar.pageStep()) bar.setValue(bar.value() - bar.pageStep())
elif key == QtCore.Qt.Key_PageDown: elif key == QtCore.Qt.Key_PageDown:
bar.setValue(bar.value() + bar.pageStep()) bar.setValue(bar.value() + bar.pageStep())
elif key == QtCore.Qt.Key_Up: elif key == QtCore.Qt.Key_Up or key == QtCore.Qt.Key_Down:
self._history_navigate(-1) # Compare position, optionally only nativate history if no change:
elif key == QtCore.Qt.Key_Down: pos1 = self.textCursor().position()
self._history_navigate(1) InputLineSpell.keyPressEvent(self, event)
pos2 = self.textCursor().position()
if pos1 == pos2:
if key == QtCore.Qt.Key_Up:
# Add to history if there is text like curses weechat:
txt = self.toPlainText().encode('utf-8')
if txt != "" and len(self._history) == self._history_index:
self._history.append(txt)
self._history_navigate(-1)
elif key == QtCore.Qt.Key_Down:
self._history_navigate(1)
elif ((key == QtCore.Qt.Key_Enter or key == QtCore.Qt.Key_Return)
and modifiers != QtCore.Qt.ShiftModifier):
self._input_return_pressed()
else: else:
QtGui.QLineEdit.keyPressEvent(self, event) InputLineSpell.keyPressEvent(self, event)
def _input_return_pressed(self): def _input_return_pressed(self):
self._history.append(self.text().encode('utf-8')) self._history.append(self.toPlainText().encode('utf-8'))
self._history_index = len(self._history) self._history_index = len(self._history)
self.textSent.emit(self.text()) self.textSent.emit(self.toPlainText())
self.clear() self.clear()
def _history_navigate(self, direction): def _history_navigate(self, direction):
@ -93,3 +106,7 @@ class InputLineEdit(QtGui.QLineEdit):
self.clear() self.clear()
return return
self.setText(self._history[self._history_index]) self.setText(self._history[self._history_index])
# End of line:
textCursor = self.textCursor()
textCursor.setPosition(len(self._history[self._history_index]))
self.setTextCursor(textCursor)

254
qweechat/inputlinespell.py Normal file
View File

@ -0,0 +1,254 @@
# -*- coding: utf-8 -*-
#
# inputlinespell.py - single line edit with spellcheck for qweechat
#
# Copyright (C) Ricky Brent <ricky@rickybrent.com>
# Copyright for auto-resizing portions of code are held by Kamil Śliwak as
# part of git@github.com:cameel/auto-resizing-text-edit.git and for
# spellcheck portions by John Schember, both under the MIT license.
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
# QWeeChat 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.
#
# QWeeChat 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 QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
import qt_compat
QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui')
import config
import re
import weechat.color as color
# Spell checker support
try:
import enchant
except ImportError:
enchant = None
class InputLineSpell(QtGui.QTextEdit):
"""Chat area."""
def __init__(self, debug, *args):
QtGui.QTextEdit.__init__(*(self,) + args)
self.debug = debug
self.setFontFamily('monospace')
self._textcolor = self.textColor()
self._bgcolor = QtGui.QColor('#FFFFFF')
self._setcolorcode = {
'F': (self.setTextColor, self._textcolor),
'B': (self.setTextBackgroundColor, self._bgcolor)
}
self._setfont = {
'*': self.setFontWeight,
'_': self.setFontUnderline,
'/': self.setFontItalic
}
self._fontvalues = {
False: {
'*': QtGui.QFont.Normal,
'_': False,
'/': False
},
True: {
'*': QtGui.QFont.Bold,
'_': True,
'/': True
}
}
self._color = color.Color(config.color_options(), self.debug)
self.initDict()
# Set height to one line:
fm = QtGui.QFontMetrics(self.currentFont())
self.setMinimumHeight(fm.height() + 8)
size_policy = self.sizePolicy()
size_policy.setHeightForWidth(True)
size_policy.setVerticalPolicy(QtGui.QSizePolicy.Preferred)
self.setSizePolicy(size_policy)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.textChanged.connect(lambda: self.updateGeometry())
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
margins = self.contentsMargins()
if width >= margins.left() + margins.right():
document_width = width - margins.left() - margins.right()
else:
# If specified width can't even fit the margin, no space left.
document_width = 0
# Cloning seems wasteful but is the preferred way to in Qt >= 4.
document = self.document().clone()
document.setTextWidth(document_width)
return margins.top() + document.size().height() + margins.bottom()
def sizeHint(self):
original_hint = super(InputLineSpell, self).sizeHint()
return QtCore.QSize(original_hint.width(),
self.heightForWidth(original_hint.width()))
def scroll_bottom(self):
bar = self.verticalScrollBar()
bar.setValue(bar.maximum())
def initDict(self, lang=None):
if enchant:
if lang == None:
# Default dictionary based on the current locale.
try:
self.spelldict = enchant.Dict()
except enchant.DictNotFoundError:
self.spelldict = None
else:
self.spelldict = enchant.Dict(lang)
else:
self.spelldict = None
self.highlighter = SpellHighlighter(self.document())
if self.spelldict:
self.highlighter.setDict(self.spelldict)
self.highlighter.rehighlight()
def killDict(self):
self.highlighter.setDocument(None)
self.spelldict = None
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.RightButton:
# Rewrite the mouse event to a left button event so the cursor
# is moved to the location of the pointer.
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton,
QtCore.Qt.NoModifier)
QtGui.QTextEdit.mousePressEvent(self, event)
def contextMenuEvent(self, event):
popup_menu = self.createStandardContextMenu()
pal = QtGui.QApplication.instance().palette()
# This fixes Issue 20
menu_style = """ * { background-color: %s;
color: %s;}
"""%(unicode(pal.color(QtGui.QPalette.Button).name()),
unicode(pal.color(QtGui.QPalette.WindowText).name()))
popup_menu.setStyleSheet(menu_style)
# Select the word under the cursor.
cursor = self.textCursor()
cursor.select(QtGui.QTextCursor.WordUnderCursor)
self.setTextCursor(cursor)
# Check if the selected word is misspelled and offer spelling
# suggestions if it is.
if enchant and self.spelldict:
if self.textCursor().hasSelection():
text = unicode(self.textCursor().selectedText())
#Add to dictionary
#Spell-checker options
if not self.spelldict.check(text):
suggestions = self.spelldict.suggest(text)
if len(suggestions) != 0:
popup_menu.insertSeparator(popup_menu.actions()[0])
topAction = popup_menu.actions()[0]
for word in suggestions:
action = SpellAction(word, popup_menu)
action.correct.connect(self.correctWord)
popup_menu.insertAction(topAction, action)
popup_menu.insertSeparator(topAction)
add = SpellAddAction(text, popup_menu)
add.add.connect(self.addWord)
popup_menu.insertAction(topAction, add)
# FIXME: add change dict and disable spellcheck options
popup_menu.exec_(event.globalPos())
def addWord(self, word):
self.spelldict.add(word)
self.highlighter.rehighlight()
def correctWord(self, word):
'''
Replaces the selected text with word.
'''
cursor = self.textCursor()
cursor.beginEditBlock()
cursor.removeSelectedText()
cursor.insertText(word)
cursor.endEditBlock()
def killDict(self):
self.highlighter.setDocument(None)
self.spelldict = None
class SpellHighlighter(QtGui.QSyntaxHighlighter):
WORDS = u'(?iu)[\w\']+'
def __init__(self, *args):
QtGui.QSyntaxHighlighter.__init__(self, *args)
self.spelldict = None
def setDict(self, spelldict):
self.spelldict = spelldict
def highlightBlock(self, text):
if not self.spelldict:
return
text = unicode(text)
format = QtGui.QTextCharFormat()
format.setUnderlineColor(QtGui.QColor('red'))
format.setUnderlineStyle(QtGui.QTextCharFormat.DotLine)
for word_object in re.finditer(self.WORDS, text):
if not self.spelldict.check(word_object.group()):
self.setFormat(word_object.start(),
word_object.end() - word_object.start(), format)
class SpellAction(QtGui.QAction):
'''
A special QAction that returns the text in a signal.
'''
correct = qt_compat.Signal(unicode)
def __init__(self, *args):
QtGui.QAction.__init__(self, *args)
self.triggered.connect(lambda x: self.correct.emit(
unicode(self.text())))
class SpellAddAction(QtGui.QAction):
'''
An action to add the given word to a dictionary.
'''
add = qt_compat.Signal(unicode)
def __init__(self, word, *args):
QtGui.QAction.__init__(self, "Add to dictionary", *args)
self._word = word
self.triggered.connect(lambda x: self.add.emit(
unicode(self._word)))