diff options
Diffstat (limited to 'Swift/QtUI/QtTextEdit.cpp')
-rw-r--r-- | Swift/QtUI/QtTextEdit.cpp | 402 |
1 files changed, 225 insertions, 177 deletions
diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp index 2c4677e..b3c57a7 100644 --- a/Swift/QtUI/QtTextEdit.cpp +++ b/Swift/QtUI/QtTextEdit.cpp @@ -1,235 +1,283 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <boost/tuple/tuple.hpp> +#include <Swift/QtUI/QtTextEdit.h> + #include <boost/algorithm/string.hpp> #include <boost/bind.hpp> +#include <boost/tuple/tuple.hpp> -#include <Swiften/Base/foreach.h> +#include <QApplication> +#include <QKeyEvent> +#include <QKeySequence> +#include <QMenu> +#include <QTextDocument> +#include <QMimeData> + +#include <Swiften/Base/Log.h> -#include <SwifTools/SpellCheckerFactory.h> #include <SwifTools/SpellChecker.h> +#include <SwifTools/SpellCheckerFactory.h> -#include <Swift/QtUI/QtTextEdit.h> -#include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtSpellCheckerWindow.h> -#include <Swift/Controllers/SettingConstants.h> - -#include <QApplication> -#include <QFontMetrics> -#include <QKeyEvent> -#include <QDebug> -#include <QMenu> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtUtilities.h> namespace Swift { -QtTextEdit::QtTextEdit(SettingsProvider* settings, QWidget* parent) : QTextEdit(parent) { - connect(this, SIGNAL(textChanged()), this, SLOT(handleTextChanged())); - checker_ = NULL; - settings_ = settings; +QtTextEdit::QtTextEdit(SettingsProvider* settings, QWidget* parent) : QTextEdit(parent), checker_(nullptr), highlighter_(nullptr) { + connect(this, SIGNAL(textChanged()), this, SLOT(handleTextChanged())); + settings_ = settings; #ifdef HAVE_SPELLCHECKER - setUpSpellChecker(); + setUpSpellChecker(); #endif - handleTextChanged(); + handleTextChanged(); + QTextOption textOption = document()->defaultTextOption(); + textOption.setWrapMode(QTextOption::WordWrap); + document()->setDefaultTextOption(textOption); } QtTextEdit::~QtTextEdit() { - delete checker_; + delete checker_; } void QtTextEdit::keyPressEvent(QKeyEvent* event) { - int key = event->key(); - Qt::KeyboardModifiers modifiers = event->modifiers(); - if ((key == Qt::Key_Enter || key == Qt::Key_Return) - && (modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier)) { - emit returnPressed(); - } - else if (((key == Qt::Key_PageUp || key == Qt::Key_PageDown) && modifiers == Qt::ShiftModifier) - || (key == Qt::Key_C && modifiers == Qt::ControlModifier && textCursor().selectedText().isEmpty()) - || (key == Qt::Key_W && modifiers == Qt::ControlModifier) - || (key == Qt::Key_PageUp && modifiers == Qt::ControlModifier) - || (key == Qt::Key_PageDown && modifiers == Qt::ControlModifier) - || (key == Qt::Key_Tab && modifiers == Qt::ControlModifier) - || (key == Qt::Key_A && modifiers == Qt::AltModifier) - || (key == Qt::Key_Tab) - ) { - emit unhandledKeyPressEvent(event); - } - else if ((key == Qt::Key_Up) - || (key == Qt::Key_Down)) { - emit unhandledKeyPressEvent(event); - QTextEdit::keyPressEvent(event); - } - else { - QTextEdit::keyPressEvent(event); -#ifdef HAVE_SPELLCHECKER - if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { - underlineMisspells(); - } + int key = event->key(); + Qt::KeyboardModifiers modifiers = event->modifiers(); + if ((key == Qt::Key_Enter || key == Qt::Key_Return) + && (modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier)) { + emit returnPressed(); + } + else if (((key == Qt::Key_PageUp || key == Qt::Key_PageDown) && modifiers == Qt::ShiftModifier) + || (key == Qt::Key_C && modifiers == Qt::ControlModifier && textCursor().selectedText().isEmpty()) + || (key == Qt::Key_W && modifiers == Qt::ControlModifier) + || (key == Qt::Key_PageUp && modifiers == Qt::ControlModifier) + || (key == Qt::Key_PageDown && modifiers == Qt::ControlModifier) + || (key == Qt::Key_Tab && modifiers == Qt::ControlModifier) + || (key == Qt::Key_A && modifiers == Qt::AltModifier) + || (key == Qt::Key_Tab) +#ifdef SWIFTEN_PLATFORM_MACOSX + || (key == Qt::Key_Minus && (modifiers & Qt::ControlModifier)) + || (key == Qt::Key_Equal && (modifiers & Qt::ControlModifier)) #endif - } -} - -void QtTextEdit::underlineMisspells() { - QTextCursor cursor = textCursor(); - misspelledPositions_.clear(); - QTextCharFormat normalFormat; - cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor, 1); - cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor, 1); - cursor.setCharFormat(normalFormat); - if (checker_ == NULL) { - return; - } - QTextCharFormat spellingErrorFormat; - spellingErrorFormat.setUnderlineColor(QColor(Qt::red)); - spellingErrorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); - std::string fragment = Q2PSTRING(cursor.selectedText()); - checker_->checkFragment(fragment, misspelledPositions_); - foreach (PositionPair position, misspelledPositions_) { - cursor.setPosition(boost::get<0>(position), QTextCursor::MoveAnchor); - cursor.setPosition(boost::get<1>(position), QTextCursor::KeepAnchor); - cursor.setCharFormat(spellingErrorFormat); - cursor.clearSelection(); - cursor.setCharFormat(normalFormat); - } + || (event->matches(QKeySequence::ZoomIn)) + || (event->matches(QKeySequence::ZoomOut)) + ) { + emit unhandledKeyPressEvent(event); + } + else if ((key == Qt::Key_Up) + || (key == Qt::Key_Down)) { + emit unhandledKeyPressEvent(event); + QTextEdit::keyPressEvent(event); + } + else if ((key == Qt::Key_K && modifiers == QtUtilities::ctrlHardwareKeyModifier)) { + QTextCursor cursor = textCursor(); + cursor.setPosition(toPlainText().size(), QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + else { + QTextEdit::keyPressEvent(event); + } +} + +void QtTextEdit::setEmphasiseFocus(bool emphasise) { + emphasiseFocus_ = emphasise; + updateStyleSheet(); +} + +void QtTextEdit::setCorrectionHighlight(bool correctionHighlight) { + correctionHighlight_ = correctionHighlight; + updateStyleSheet(); +} + +void QtTextEdit::updateStyleSheet() { + QString newStyleSheet; + + if (correctionHighlight_) { + newStyleSheet += "background: rgb(255, 255, 153); color: black;"; + } + + if (emphasiseFocus_) { + if (hasFocus()) { + newStyleSheet += "border: 2px solid palette(highlight);"; + } + } + + setStyleSheet(newStyleSheet); + handleTextChanged(); +} + +void QtTextEdit::focusInEvent(QFocusEvent* event) { + receivedFocus(); + QTextEdit::focusInEvent(event); + updateStyleSheet(); +} + +void QtTextEdit::focusOutEvent(QFocusEvent* event) { + lostFocus(); + QTextEdit::focusOutEvent(event); + updateStyleSheet(); } void QtTextEdit::handleTextChanged() { - QSize previous(maximumSize()); - setMaximumSize(QSize(maximumWidth(), sizeHint().height())); - if (previous != maximumSize()) { - updateGeometry(); - } + QSize previous(maximumSize()); + setMaximumSize(QSize(maximumWidth(), sizeHint().height())); + if (previous != maximumSize()) { + updateGeometry(); + } } -void QtTextEdit::replaceMisspelledWord(const QString& word, int cursorPosition) { - QTextCursor cursor = textCursor(); - PositionPair wordPosition = getWordFromCursor(cursorPosition); - cursor.setPosition(boost::get<0>(wordPosition), QTextCursor::MoveAnchor); - cursor.setPosition(boost::get<1>(wordPosition), QTextCursor::KeepAnchor); - QTextCharFormat normalFormat; - cursor.insertText(word, normalFormat); +void QtTextEdit::replaceMisspelledWord(const QString& word, size_t cursorPosition) { + QTextCursor cursor = textCursor(); + auto wordPosition = getWordFromCursor(cursorPosition); + if (wordPosition) { + cursor.setPosition(boost::get<0>(*wordPosition), QTextCursor::MoveAnchor); + cursor.setPosition(boost::get<1>(*wordPosition), QTextCursor::KeepAnchor); + QTextCharFormat normalFormat; + cursor.insertText(word, normalFormat); + } } -PositionPair QtTextEdit::getWordFromCursor(int cursorPosition) { - for (PositionPairList::iterator it = misspelledPositions_.begin(); it != misspelledPositions_.end(); ++it) { - if (cursorPosition >= boost::get<0>(*it) && cursorPosition <= boost::get<1>(*it)) { - return *it; - } - } - return boost::make_tuple(-1,-1); +boost::optional<PositionPair> QtTextEdit::getWordFromCursor(size_t cursorPosition) { + PositionPairList misspelledPositions = highlighter_->getMisspelledPositions(); + for (auto& misspelledPosition : misspelledPositions) { + if (cursorPosition >= boost::get<0>(misspelledPosition) && cursorPosition <= boost::get<1>(misspelledPosition)) { + return misspelledPosition; + } + } + return boost::optional<PositionPair>(boost::make_tuple(-1,-1)); } QSize QtTextEdit::sizeHint() const { - QFontMetrics inputMetrics(currentFont()); - QRect horizontalBounds = contentsRect().adjusted(0,0,0,9999); - QRect boundingRect = inputMetrics.boundingRect(horizontalBounds, Qt::TextWordWrap, toPlainText() + "A"); - int left, top, right, bottom; - getContentsMargins(&left, &top, &right, &bottom); - int height = boundingRect.height() + top + bottom + inputMetrics.height(); - return QSize(width(), height); - //int numberOfLines = 1; - //int lineHeight = inputMetrics.lineSpacing(); - //return QSize(QTextEdit::sizeHint().width(), lineHeight * numberOfLines); + QSize hint = document()->size().toSize(); + QMargins margins = contentsMargins(); + return hint + QSize(margins.left() + margins.right(), margins.top() + margins.bottom()); } void QtTextEdit::contextMenuEvent(QContextMenuEvent* event) { - QMenu* menu = createStandardContextMenu(); - QTextCursor cursor = cursorForPosition(event->pos()); + QMenu* menu = createStandardContextMenu(); + QTextCursor cursor = cursorForPosition(event->pos()); #ifdef HAVE_SPELLCHECKER - QAction* insertPoint = menu->actions().first(); - QAction* settingsAction = new QAction(tr("Spell Checker Options"), menu); - menu->insertAction(insertPoint, settingsAction); - menu->insertAction(insertPoint, menu->addSeparator()); - addSuggestions(menu, event); - QAction* result = menu->exec(event->globalPos()); - if (result == settingsAction) { - spellCheckerSettingsWindow(); - } - for (std::vector<QAction*>::iterator it = replaceWordActions_.begin(); it != replaceWordActions_.end(); ++it) { - if (*it == result) { - replaceMisspelledWord((*it)->text(), cursor.position()); - } - } + QAction* insertPoint = menu->actions().first(); + QAction* settingsAction = new QAction(tr("Spell Checker Options"), menu); + menu->insertAction(insertPoint, settingsAction); + menu->insertAction(insertPoint, menu->addSeparator()); + addSuggestions(menu, event); + QAction* result = menu->exec(event->globalPos()); + if (result == settingsAction) { + spellCheckerSettingsWindow(); + } + for (auto& replaceWordAction : replaceWordActions_) { + if (replaceWordAction == result) { + replaceMisspelledWord(replaceWordAction->text(), cursor.position()); + } + } #else - menu->exec(event->globalPos()); + menu->exec(event->globalPos()); #endif - delete menu; + delete menu; +} + +void QtTextEdit::dropEvent(QDropEvent* event) { + if (event->mimeData()->hasUrls()) { + itemDropped(event); + } + else { + QTextEdit::dropEvent(event); + } } void QtTextEdit::addSuggestions(QMenu* menu, QContextMenuEvent* event) { - replaceWordActions_.clear(); - QAction* insertPoint = menu->actions().first(); - QTextCursor cursor = cursorForPosition(event->pos()); - PositionPair wordPosition = getWordFromCursor(cursor.position()); - if (boost::get<0>(wordPosition) < 0) { - // The click was executed outside a spellable word so no - // suggestions are necessary - return; - } - cursor.setPosition(boost::get<0>(wordPosition), QTextCursor::MoveAnchor); - cursor.setPosition(boost::get<1>(wordPosition), QTextCursor::KeepAnchor); - std::vector<std::string> wordList; - checker_->getSuggestions(Q2PSTRING(cursor.selectedText()), wordList); - if (wordList.size() == 0) { - QAction* noSuggestions = new QAction(tr("No Suggestions"), menu); - noSuggestions->setDisabled(true); - menu->insertAction(insertPoint, noSuggestions); - } - else { - for (std::vector<std::string>::iterator it = wordList.begin(); it != wordList.end(); ++it) { - QAction* wordAction = new QAction(it->c_str(), menu); - menu->insertAction(insertPoint, wordAction); - replaceWordActions_.push_back(wordAction); - } - } - menu->insertAction(insertPoint, menu->addSeparator()); + replaceWordActions_.clear(); + if (checker_ && highlighter_) { + QAction* insertPoint = menu->actions().first(); + QTextCursor cursor = cursorForPosition(event->pos()); + auto wordPosition = getWordFromCursor(cursor.position()); + if (!wordPosition) { + // The click was executed outside a spellable word so no + // suggestions are necessary + return; + } + cursor.setPosition(boost::get<0>(*wordPosition), QTextCursor::MoveAnchor); + cursor.setPosition(boost::get<1>(*wordPosition), QTextCursor::KeepAnchor); + std::vector<std::string> wordList; + checker_->getSuggestions(Q2PSTRING(cursor.selectedText()), wordList); + if (wordList.size() == 0) { + QAction* noSuggestions = new QAction(tr("No Suggestions"), menu); + noSuggestions->setDisabled(true); + menu->insertAction(insertPoint, noSuggestions); + } + else { + for (auto& word : wordList) { + QAction* wordAction = new QAction(word.c_str(), menu); + menu->insertAction(insertPoint, wordAction); + replaceWordActions_.push_back(wordAction); + } + } + menu->insertAction(insertPoint, menu->addSeparator()); + } } #ifdef HAVE_SPELLCHECKER -void QtTextEdit::setUpSpellChecker() -{ - SpellCheckerFactory* checkerFactory = new SpellCheckerFactory(); - delete checker_; - if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { - std::string dictPath = settings_->getSetting(SettingConstants::DICT_PATH); - std::string dictFile = settings_->getSetting(SettingConstants::DICT_FILE); - checker_ = checkerFactory->createSpellChecker(dictPath + dictFile); - delete checkerFactory; - } - else { - checker_ = NULL; - } +void QtTextEdit::setUpSpellChecker() { + delete highlighter_; + highlighter_ = nullptr; + delete checker_; + checker_ = nullptr; + if (settings_->getSetting(QtUISettingConstants::SPELL_CHECKER)) { + checker_ = SpellCheckerFactory().createSpellChecker(); + if (checker_) { + if (!checker_->isAutomaticallyDetectingLanguage()) { + checker_->setActiveLanguage(settings_->getSetting(QtUISettingConstants::SPELL_CHECKER_LANGUAGE)); + } + highlighter_ = new QtSpellCheckHighlighter(document(), checker_); + } + else { + // Spellchecking is not working, as we did not get a valid checker from the factory. Disable spellchecking. + SWIFT_LOG(warning) << "Spellchecking is currently misconfigured in Swift (e.g. missing dictionary or broken dictionary file). Disable spellchecking."; + settings_->storeSetting(QtUISettingConstants::SPELL_CHECKER, false); + } + + } } #endif void QtTextEdit::spellCheckerSettingsWindow() { - if (!spellCheckerWindow_) { - spellCheckerWindow_ = new QtSpellCheckerWindow(settings_); - settings_->onSettingChanged.connect(boost::bind(&QtTextEdit::handleSettingChanged, this, _1)); - spellCheckerWindow_->show(); - } - else { - spellCheckerWindow_->show(); - spellCheckerWindow_->raise(); - spellCheckerWindow_->activateWindow(); - } + if (!spellCheckerWindow_) { + spellCheckerWindow_ = new QtSpellCheckerWindow(settings_); + settings_->onSettingChanged.connect(boost::bind(&QtTextEdit::handleSettingChanged, this, _1)); + spellCheckerWindow_->show(); + } + else { + spellCheckerWindow_->show(); + spellCheckerWindow_->raise(); + spellCheckerWindow_->activateWindow(); + } + if (checker_) { + spellCheckerWindow_->setAutomaticallyIdentifiesLanguage(checker_->isAutomaticallyDetectingLanguage()); + if (!checker_->isAutomaticallyDetectingLanguage()) { + spellCheckerWindow_->setSupportedLanguages(checker_->supportedLanguages()); + spellCheckerWindow_->setActiveLanguage(checker_->activeLanguage()); + } + } } void QtTextEdit::handleSettingChanged(const std::string& settings) { - if (settings == SettingConstants::SPELL_CHECKER.getKey() - || settings == SettingConstants::DICT_PATH.getKey() - || settings == SettingConstants::DICT_FILE.getKey()) { + if (settings == QtUISettingConstants::SPELL_CHECKER.getKey() || + settings == QtUISettingConstants::SPELL_CHECKER_LANGUAGE.getKey()) { #ifdef HAVE_SPELLCHECKER - setUpSpellChecker(); - underlineMisspells(); + setUpSpellChecker(); + if (highlighter_) { + highlighter_->rehighlight(); + } #endif - } + } } } |