Merge 22ec16f63b
into 8335009dae
This commit is contained in:
commit
8b4d78c7bc
3 changed files with 295 additions and 19 deletions
|
@ -28,6 +28,10 @@ Following packages are *required*:
|
||||||
* Python 2.x >= 2.6
|
* Python 2.x >= 2.6
|
||||||
* PySide (recommended, packages: python.pyside.*) or PyQt4 (python-qt4)
|
* PySide (recommended, packages: python.pyside.*) or PyQt4 (python-qt4)
|
||||||
|
|
||||||
|
Following packages are *optional*:
|
||||||
|
|
||||||
|
* PyEnchant (for spell check)
|
||||||
|
|
||||||
=== Install via source distribution
|
=== Install via source distribution
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
|
@ -21,12 +21,13 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
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()
|
||||||
|
@ -34,53 +35,65 @@ 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()
|
||||||
modifiers = event.modifiers()
|
modifiers = event.modifiers()
|
||||||
bar = self.scroll_widget.verticalScrollBar()
|
scroll = self.scroll_widget.verticalScrollBar()
|
||||||
|
newline = (key == QtCore.Qt.Key_Enter or key == QtCore.Qt.Key_Return)
|
||||||
if modifiers == QtCore.Qt.ControlModifier:
|
if modifiers == QtCore.Qt.ControlModifier:
|
||||||
if key == QtCore.Qt.Key_PageUp:
|
if key == QtCore.Qt.Key_PageUp:
|
||||||
self.bufferSwitchPrev.emit()
|
self.bufferSwitchPrev.emit()
|
||||||
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()
|
||||||
elif key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_Down):
|
elif key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_Down):
|
||||||
self.bufferSwitchNext.emit()
|
self.bufferSwitchNext.emit()
|
||||||
elif key == QtCore.Qt.Key_PageUp:
|
elif key == QtCore.Qt.Key_PageUp:
|
||||||
bar.setValue(bar.value() - (bar.pageStep() / 10))
|
scroll.setValue(scroll.value() - (scroll.pageStep() / 10))
|
||||||
elif key == QtCore.Qt.Key_PageDown:
|
elif key == QtCore.Qt.Key_PageDown:
|
||||||
bar.setValue(bar.value() + (bar.pageStep() / 10))
|
scroll.setValue(scroll.value() + (scroll.pageStep() / 10))
|
||||||
elif key == QtCore.Qt.Key_Home:
|
elif key == QtCore.Qt.Key_Home:
|
||||||
bar.setValue(bar.minimum())
|
scroll.setValue(scroll.minimum())
|
||||||
elif key == QtCore.Qt.Key_End:
|
elif key == QtCore.Qt.Key_End:
|
||||||
bar.setValue(bar.maximum())
|
scroll.setValue(scroll.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())
|
scroll.setValue(scroll.value() - scroll.pageStep())
|
||||||
elif key == QtCore.Qt.Key_PageDown:
|
elif key == QtCore.Qt.Key_PageDown:
|
||||||
bar.setValue(bar.value() + bar.pageStep())
|
scroll.setValue(scroll.value() + scroll.pageStep())
|
||||||
elif key == QtCore.Qt.Key_Up:
|
elif key == QtCore.Qt.Key_Up or key == QtCore.Qt.Key_Down:
|
||||||
|
# Compare position, optionally only nativate history if no change:
|
||||||
|
pos1 = self.textCursor().position()
|
||||||
|
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)
|
self._history_navigate(-1)
|
||||||
elif key == QtCore.Qt.Key_Down:
|
elif key == QtCore.Qt.Key_Down:
|
||||||
self._history_navigate(1)
|
self._history_navigate(1)
|
||||||
|
elif newline 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):
|
||||||
|
@ -94,3 +107,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:
|
||||||
|
text_cursor = self.textCursor()
|
||||||
|
text_cursor.setPosition(len(self._history[self._history_index]))
|
||||||
|
self.setTextCursor(text_cursor)
|
||||||
|
|
255
qweechat/inputlinespell.py
Normal file
255
qweechat/inputlinespell.py
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
# -*- 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 functools
|
||||||
|
import re
|
||||||
|
import config
|
||||||
|
import qt_compat
|
||||||
|
import weechat.color as color
|
||||||
|
|
||||||
|
# Spell checker support
|
||||||
|
try:
|
||||||
|
import enchant
|
||||||
|
except ImportError:
|
||||||
|
enchant = None
|
||||||
|
QtCore = qt_compat.import_module('QtCore')
|
||||||
|
QtGui = qt_compat.import_module('QtGui')
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
font_metric = QtGui.QFontMetrics(self.currentFont())
|
||||||
|
self.setMinimumHeight(font_metric.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())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hasHeightForWidth():
|
||||||
|
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):
|
||||||
|
scroll = self.verticalScrollBar()
|
||||||
|
scroll.setValue(scroll.maximum())
|
||||||
|
|
||||||
|
def initDict(self, lang=None):
|
||||||
|
if enchant:
|
||||||
|
if lang is 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 toggleDict(self, label=None):
|
||||||
|
if self.spelldict:
|
||||||
|
self.killDict()
|
||||||
|
else:
|
||||||
|
self.initDict()
|
||||||
|
|
||||||
|
def killDict(self):
|
||||||
|
self.highlighter.setDocument(None)
|
||||||
|
self.highlighter.setDict(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()
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
top_action = popup_menu.actions()[0]
|
||||||
|
if self.textCursor().hasSelection():
|
||||||
|
text = unicode(self.textCursor().selectedText())
|
||||||
|
if not self.spelldict.check(text):
|
||||||
|
suggestions = self.spelldict.suggest(text)
|
||||||
|
if len(suggestions) != 0:
|
||||||
|
popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||||
|
for suggest in suggestions:
|
||||||
|
self._menu_action(suggest, popup_menu,
|
||||||
|
self.correctWord, after=top_action)
|
||||||
|
popup_menu.insertSeparator(top_action)
|
||||||
|
add_action = QtGui.QAction("Add to dictionary", self)
|
||||||
|
add_action.triggered.connect(lambda: self.addWord(text))
|
||||||
|
popup_menu.insertAction(top_action, add_action)
|
||||||
|
spell_menu = QtGui.QMenu(popup_menu)
|
||||||
|
spell_menu.setTitle('Spellcheck')
|
||||||
|
popup_menu.insertMenu(top_action, spell_menu)
|
||||||
|
for lang in enchant.list_languages():
|
||||||
|
self._menu_action(lang, spell_menu, self.initDict,
|
||||||
|
checked=(lang == self.spelldict.tag))
|
||||||
|
toggle = self._menu_action('Check spelling', spell_menu,
|
||||||
|
self.toggleDict,
|
||||||
|
checked=(self.spelldict is not False))
|
||||||
|
spell_menu.insertSeparator(toggle)
|
||||||
|
elif enchant:
|
||||||
|
toggle = self._menu_action('Check spelling', popup_menu,
|
||||||
|
self.toggleDict, checked=False)
|
||||||
|
popup_menu.insertSeparator(toggle)
|
||||||
|
popup_menu.exec_(event.globalPos())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _menu_action(text, menu, method, after=None, checked=None):
|
||||||
|
action = QtGui.QAction(text, menu)
|
||||||
|
action.connect(
|
||||||
|
action,
|
||||||
|
QtCore.SIGNAL("triggered()"),
|
||||||
|
functools.partial(method, text)
|
||||||
|
)
|
||||||
|
if checked is not None:
|
||||||
|
action.setCheckable(True)
|
||||||
|
action.setChecked(checked)
|
||||||
|
if after is not None:
|
||||||
|
menu.insertAction(after, action)
|
||||||
|
else:
|
||||||
|
menu.addAction(action)
|
||||||
|
return action
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def list_languages():
|
||||||
|
if enchant:
|
||||||
|
return enchant.list_languages()
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class SpellHighlighter(QtGui.QSyntaxHighlighter):
|
||||||
|
|
||||||
|
WORDS = r'(?iu)[\w\']+'
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
QtGui.QSyntaxHighlighter.__init__(self, *args)
|
||||||
|
|
||||||
|
self.spelldict = None
|
||||||
|
self._mispelled = QtGui.QTextCharFormat()
|
||||||
|
self._mispelled.setUnderlineColor(QtGui.QColor('red'))
|
||||||
|
self._mispelled.setUnderlineStyle(QtGui.QTextCharFormat.DotLine)
|
||||||
|
|
||||||
|
def setDict(self, spelldict):
|
||||||
|
self.spelldict = spelldict
|
||||||
|
|
||||||
|
def highlightBlock(self, text):
|
||||||
|
if not self.spelldict:
|
||||||
|
return
|
||||||
|
|
||||||
|
text = unicode(text)
|
||||||
|
|
||||||
|
for word_object in re.finditer(self.WORDS, text):
|
||||||
|
if not self.spelldict.check(word_object.group()):
|
||||||
|
word_len = word_object.end() - word_object.start()
|
||||||
|
self.setFormat(word_object.start(),
|
||||||
|
word_len, self._mispelled)
|
Loading…
Add table
Add a link
Reference in a new issue