diff --git a/CMakeLists.txt b/CMakeLists.txt index c546dfc..5bf1c1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,102 +1,101 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.66.0") # handled by release scripts set(KF5_DEP_VERSION "5.66.0") # handled by release scripts project(KTextWidgets VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.66.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMGenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(ECMAddQch) ecm_setup_version(PROJECT VARIABLE_PREFIX KTEXTWIDGETS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/ktextwidgets_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5TextWidgetsConfigVersion.cmake" SOVERSION 5) # Dependencies set(REQUIRED_QT_VERSION 5.12.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets) find_package(Qt5 OPTIONAL_COMPONENTS TextToSpeech) if (NOT Qt5TextToSpeech_FOUND) message(STATUS "Qt5TextToSpeech not found, speech feature will be disabled") else() add_definitions(-DHAVE_SPEECH) endif() find_package(KF5Completion ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5ConfigWidgets ${KF5_DEP_VERSION} REQUIRED) find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5WidgetsAddons ${KF5_DEP_VERSION} REQUIRED) -find_package(KF5WindowSystem ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Sonnet ${KF5_DEP_VERSION} REQUIRED) set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON) add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer") add_definitions(-DTRANSLATION_DOMAIN=\"ktextwidgets5\") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054100) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() add_definitions(-DQT_NO_FOREACH) add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(tests) add_subdirectory(autotests) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5TextWidgets") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5TextWidgets_QCH FILE KF5TextWidgetsQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5TextWidgetsQchTargets.cmake\")") endif() configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF5TextWidgetsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5TextWidgetsConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(EXPORT KF5TextWidgetsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5TextWidgetsTargets.cmake NAMESPACE KF5:: ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5TextWidgetsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5TextWidgetsConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ktextwidgets_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c7f7ed..9fc9e62 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,132 +1,131 @@ set(ktextwidgets_LIB_SRCS dialogs/klinkdialog.cpp findreplace/kfind.cpp findreplace/kfinddialog.cpp findreplace/kreplace.cpp findreplace/kreplacedialog.cpp widgets/krichtextedit.cpp widgets/krichtextwidget.cpp widgets/ktextedit.cpp widgets/nestedlisthelper.cpp widgets/kpluralhandlingspinbox.cpp ) add_library(KF5TextWidgets ${ktextwidgets_LIB_SRCS}) add_library(KF5::TextWidgets ALIAS KF5TextWidgets) ecm_generate_export_header(KF5TextWidgets BASE_NAME KTextWidgets GROUP_BASE_NAME KF VERSION ${KF5_VERSION} DEPRECATED_BASE_VERSION 0 DEPRECATION_VERSIONS 5.0 5.65 EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} ) set(ktextwidgets_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/dialogs ${CMAKE_CURRENT_SOURCE_DIR}/findreplace ${CMAKE_CURRENT_SOURCE_DIR}/kregexpeditor ${CMAKE_CURRENT_SOURCE_DIR}/widgets ) target_include_directories(KF5TextWidgets PUBLIC "$" ) target_include_directories(KF5TextWidgets INTERFACE "$") target_link_libraries(KF5TextWidgets PUBLIC Qt5::Widgets KF5::SonnetUi KF5::I18n PRIVATE KF5::SonnetCore KF5::ConfigWidgets - KF5::WindowSystem KF5::Completion ) if (Qt5TextToSpeech_FOUND) target_link_libraries(KF5TextWidgets PRIVATE Qt5::TextToSpeech) endif() set_target_properties(KF5TextWidgets PROPERTIES VERSION ${KTEXTWIDGETS_VERSION_STRING} SOVERSION ${KTEXTWIDGETS_SOVERSION} EXPORT_NAME TextWidgets ) ecm_generate_headers(KTextWidgets_HEADERS HEADER_NAMES KRichTextEdit KRichTextWidget KTextEdit KPluralHandlingSpinBox RELATIVE widgets REQUIRED_HEADERS KTextWidgets_HEADERS ) ecm_generate_headers(KTextWidgets_HEADERS HEADER_NAMES KFind KFindDialog KReplace KReplaceDialog RELATIVE findreplace REQUIRED_HEADERS KTextWidgets_HEADERS ) ecm_generate_headers(KTextWidgets_HEADERS HEADER_NAMES KRegExpEditorInterface RELATIVE kregexpeditor REQUIRED_HEADERS KTextWidgets_HEADERS ) install(TARGETS KF5TextWidgets EXPORT KF5TextWidgetsTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ktextwidgets_export.h ${KTextWidgets_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KTextWidgets COMPONENT Devel ) if (BUILD_DESIGNERPLUGIN) add_subdirectory(designer) endif() if (BUILD_QCH) ecm_add_qch( KF5TextWidgets_QCH NAME KTextWidgets BASE_NAME KF5TextWidgets VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KTextWidgets_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" LINK_QCHS Qt5Widgets_QCH KF5SonnetUi_QCH KF5I18n_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} ${ktextwidgets_INCLUDES} BLANK_MACROS KTEXTWIDGETS_EXPORT KTEXTWIDGETS_DEPRECATED_EXPORT KTEXTWIDGETS_DEPRECATED "KTEXTWIDGETS_DEPRECATED_VERSION(x, y, t)" TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KTextWidgets LIB_NAME KF5TextWidgets DEPS "widgets SonnetUi KI18n" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KTextWidgets) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/findreplace/kfinddialog.h b/src/findreplace/kfinddialog.h index 004e1fa..ff1a22e 100644 --- a/src/findreplace/kfinddialog.h +++ b/src/findreplace/kfinddialog.h @@ -1,238 +1,238 @@ /* Copyright (C) 2001, S.R.Haque . Copyright (C) 2002, David Faure This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2, as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL-2. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFINDDIALOG_H #define KFINDDIALOG_H #include "ktextwidgets_export.h" #include /** * @class KFindDialog kfinddialog.h * * @brief A generic "find" dialog. * * @author S.R.Haque * * \b Detail: * * This widget inherits from KDialog and implements * the following additional functionalities: a find string * object and an area for a user-defined widget to extend the dialog. * * \b Example: * * To use the basic modal find dialog, and then run the search: * * \code * KFindDialog dlg(....) * if (dlg.exec() != QDialog::Accepted) * return; * * // proceed with KFind from here * \endcode * * To create a non-modal find dialog: * \code * if (m_findDialog) { - * KWindowSystem::activateWindow(m_findDialog->winId()); + * m_findDialog->activateWindow(); * } else { * m_findDialog = new KFindDialog(...); * connect(m_findDialog, &KFindDialog::okClicked, this, [this] { * m_findDialog->close(); * delete m_find; * m_find = new KFind(m_findDialog->pattern(), m_findDialog->options(), this); * // ... see KFind documentation for what else should be done here * }); * } * \endcode * Don't forget to delete and reset m_findDialog when closed. * (But do NOT delete your KFind object at that point, it's needed for "Find Next".) * * To use your own extensions: see findExtension(). * * \image html kfinddialog.png "KFindDialog Widget" */ class KTEXTWIDGETS_EXPORT KFindDialog : public QDialog { Q_OBJECT public: /** * Construct a modal find dialog * * @param parent The parent object of this widget. * @param options A bitfield of the Options to be checked. * @param findStrings The find history, see findHistory() * @param hasSelection Whether a selection exists */ explicit KFindDialog(QWidget *parent = nullptr, long options = 0, const QStringList &findStrings = QStringList(), bool hasSelection = false, bool replaceDialog = false); /** * Destructor. */ ~KFindDialog() override; /** * Provide the list of @p strings to be displayed as the history * of find strings. @p strings might get truncated if it is * too long. * * @param history The find history. * @see findHistory */ void setFindHistory(const QStringList &history); /** * Returns the list of history items. * * @see setFindHistory */ QStringList findHistory() const; /** * Enable/disable the 'search in selection' option, depending * on whether there actually is a selection. * * @param hasSelection true if a selection exists */ void setHasSelection(bool hasSelection); /** * Hide/show the 'from cursor' option, depending * on whether the application implements a cursor. * * @param hasCursor true if the application features a cursor * This is assumed to be the case by default. */ void setHasCursor(bool hasCursor); /** * Enable/disable the 'Find backwards' option, depending * on whether the application supports it. * * @param supports true if the application supports backwards find * This is assumed to be the case by default. */ void setSupportsBackwardsFind(bool supports); /** * Enable/disable the 'Case sensitive' option, depending * on whether the application supports it. * * @param supports true if the application supports case sensitive find * This is assumed to be the case by default. */ void setSupportsCaseSensitiveFind(bool supports); /** * Enable/disable the 'Whole words only' option, depending * on whether the application supports it. * * @param supports true if the application supports whole words only find * This is assumed to be the case by default. */ void setSupportsWholeWordsFind(bool supports); /** * Enable/disable the 'Regular expression' option, depending * on whether the application supports it. * * @param supports true if the application supports regular expression find * This is assumed to be the case by default. */ void setSupportsRegularExpressionFind(bool supports); /** * Set the options which are checked. * * @param options The setting of the Options. * * @see options() * @see KFind::Options */ void setOptions(long options); /** * Returns the state of the options. Disabled options may be returned in * an indeterminate state. * * @see setOptions() * @see KFind::Options */ long options() const; /** * Returns the pattern to find. */ QString pattern() const; /** * Sets the pattern to find */ void setPattern(const QString &pattern); /** * Returns an empty widget which the user may fill with additional UI * elements as required. The widget occupies the width of the dialog, * and is positioned immediately below the regular expression support * widgets for the pattern string. */ QWidget *findExtension() const; Q_SIGNALS: /** * This signal is sent whenever one of the option checkboxes is toggled. * Call options() to get the new state of the checkboxes. */ void optionsChanged(); /** * This signal is sent when the user clicks on Ok button. */ void okClicked(); /** * This signal is sent when the user clicks on Cancel button. */ void cancelClicked(); protected: void showEvent(QShowEvent *) override; private: friend class KReplaceDialog; friend class KReplaceDialogPrivate; class KFindDialogPrivate; KFindDialogPrivate *const d; Q_PRIVATE_SLOT(d, void _k_slotPlaceholdersAboutToShow()) Q_PRIVATE_SLOT(d, void _k_slotOk()) Q_PRIVATE_SLOT(d, void _k_slotReject()) Q_PRIVATE_SLOT(d, void _k_slotSelectedTextToggled(bool)) Q_PRIVATE_SLOT(d, void _k_showPatterns()) Q_PRIVATE_SLOT(d, void _k_showPlaceholders()) Q_PRIVATE_SLOT(d, void _k_textSearchChanged(const QString &)) }; #endif // KFINDDIALOG_H diff --git a/src/widgets/ktextedit.cpp b/src/widgets/ktextedit.cpp index 20d44e4..71d2a43 100644 --- a/src/widgets/ktextedit.cpp +++ b/src/widgets/ktextedit.cpp @@ -1,1063 +1,1062 @@ /* This file is part of the KDE libraries Copyright (C) 2002 Carsten Pfeiffer 2005 Michael Brade 2012 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktextedit.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_SPEECH #include #endif #include #include #include #include #include #include #include #include #include #include -#include #include "kreplacedialog.h" #include "kfinddialog.h" #include "kfind.h" #include "kreplace.h" class KTextDecorator : public Sonnet::SpellCheckDecorator { public: explicit KTextDecorator(KTextEdit *textEdit); bool isSpellCheckingEnabledForBlock(const QString &textBlock) const override; private: KTextEdit *m_textEdit; }; class Q_DECL_HIDDEN KTextEdit::Private { public: Private(KTextEdit *_parent) : parent(_parent), languagesMenu(nullptr), customPalette(false), spellCheckingEnabled(false), findReplaceEnabled(true), showTabAction(true), showAutoCorrectionButton(false), decorator(nullptr), speller(nullptr), findDlg(nullptr), find(nullptr), repDlg(nullptr), replace(nullptr), #ifdef HAVE_SPEECH textToSpeech(nullptr), #endif findIndex(0), repIndex(0), lastReplacedPosition(-1) { //Check the default sonnet settings to see if spellchecking should be enabled. QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); spellCheckingEnabled = settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool(); } ~Private() { delete decorator; delete findDlg; delete find; delete replace; delete repDlg; delete speller; #ifdef HAVE_SPEECH delete textToSpeech; #endif } /** * Checks whether we should/should not consume a key used as a shortcut. * This makes it possible to handle shortcuts in the focused widget before any * window-global QAction is triggered. */ bool overrideShortcut(const QKeyEvent *e); /** * Actually handle a shortcut event. */ bool handleShortcut(const QKeyEvent *e); void spellCheckerMisspelling(const QString &text, int pos); void spellCheckerCorrected(const QString &, int, const QString &); void spellCheckerAutoCorrect(const QString &, const QString &); void spellCheckerCanceled(); void spellCheckerFinished(); void toggleAutoSpellCheck(); void slotFindHighlight(const QString &text, int matchingIndex, int matchingLength); void slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength); /** * Similar to QTextEdit::clear(), only that it is possible to undo this * action. */ void undoableClear(); void slotAllowTab(); void menuActivated(QAction *action); void init(); void checkSpelling(bool force); KTextEdit *parent; QAction *autoSpellCheckAction; QAction *allowTab; QAction *spellCheckAction; QMenu *languagesMenu; bool customPalette : 1; bool spellCheckingEnabled : 1; bool findReplaceEnabled: 1; bool showTabAction: 1; bool showAutoCorrectionButton: 1; QTextDocumentFragment originalDoc; QString spellCheckingLanguage; Sonnet::SpellCheckDecorator *decorator; Sonnet::Speller *speller; KFindDialog *findDlg; KFind *find; KReplaceDialog *repDlg; KReplace *replace; #ifdef HAVE_SPEECH QTextToSpeech *textToSpeech; #endif int findIndex, repIndex; int lastReplacedPosition; }; void KTextEdit::Private::checkSpelling(bool force) { if (parent->document()->isEmpty()) { KMessageBox::information(parent, i18n("Nothing to spell check.")); if (force) { emit parent->spellCheckingFinished(); } return; } Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker; if (!spellCheckingLanguage.isEmpty()) { backgroundSpellCheck->changeLanguage(spellCheckingLanguage); } Sonnet::Dialog *spellDialog = new Sonnet::Dialog( backgroundSpellCheck, force ? parent : nullptr); backgroundSpellCheck->setParent(spellDialog); spellDialog->setAttribute(Qt::WA_DeleteOnClose, true); spellDialog->activeAutoCorrect(showAutoCorrectionButton); connect(spellDialog, SIGNAL(replace(QString,int,QString)), parent, SLOT(spellCheckerCorrected(QString,int,QString))); connect(spellDialog, SIGNAL(misspelling(QString,int)), parent, SLOT(spellCheckerMisspelling(QString,int))); connect(spellDialog, &Sonnet::Dialog::autoCorrect, parent, &KTextEdit::spellCheckerAutoCorrect); connect(spellDialog, SIGNAL(done(QString)), parent, SLOT(spellCheckerFinished())); connect(spellDialog, SIGNAL(cancel()), parent, SLOT(spellCheckerCanceled())); //Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary /* connect(spellDialog, SIGNAL(stop()), parent, SLOT(spellCheckerFinished())); */ connect(spellDialog, &Sonnet::Dialog::spellCheckStatus, parent, &KTextEdit::spellCheckStatus); connect(spellDialog, &Sonnet::Dialog::languageChanged, parent, &KTextEdit::languageChanged); if (force) { connect(spellDialog, SIGNAL(done(QString)), parent, SIGNAL(spellCheckingFinished())); connect(spellDialog, &Sonnet::Dialog::cancel, parent, &KTextEdit::spellCheckingCanceled); //Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary //connect(spellDialog, SIGNAL(stop()), parent, SIGNAL(spellCheckingFinished())); } originalDoc = QTextDocumentFragment(parent->document()); spellDialog->setBuffer(parent->toPlainText()); spellDialog->show(); } void KTextEdit::Private::spellCheckerCanceled() { QTextDocument *doc = parent->document(); doc->clear(); QTextCursor cursor(doc); cursor.insertFragment(originalDoc); spellCheckerFinished(); } void KTextEdit::Private::spellCheckerAutoCorrect(const QString ¤tWord, const QString &autoCorrectWord) { emit parent->spellCheckerAutoCorrect(currentWord, autoCorrectWord); } void KTextEdit::Private::spellCheckerMisspelling(const QString &text, int pos) { //qDebug()<<"TextEdit::Private::spellCheckerMisspelling :"<highlightWord(text.length(), pos); } void KTextEdit::Private::spellCheckerCorrected(const QString &oldWord, int pos, const QString &newWord) { //qDebug()<<" oldWord :"<document()); cursor.setPosition(pos); cursor.setPosition(pos + oldWord.length(), QTextCursor::KeepAnchor); cursor.insertText(newWord); } } void KTextEdit::Private::spellCheckerFinished() { QTextCursor cursor(parent->document()); cursor.clearSelection(); parent->setTextCursor(cursor); if (parent->highlighter()) { parent->highlighter()->rehighlight(); } } void KTextEdit::Private::toggleAutoSpellCheck() { parent->setCheckSpellingEnabled(!parent->checkSpellingEnabled()); } void KTextEdit::Private::undoableClear() { QTextCursor cursor = parent->textCursor(); cursor.beginEditBlock(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.removeSelectedText(); cursor.endEditBlock(); } void KTextEdit::Private::slotAllowTab() { parent->setTabChangesFocus(!parent->tabChangesFocus()); } void KTextEdit::Private::menuActivated(QAction *action) { if (action == spellCheckAction) { parent->checkSpelling(); } else if (action == autoSpellCheckAction) { toggleAutoSpellCheck(); } else if (action == allowTab) { slotAllowTab(); } } void KTextEdit::Private::slotFindHighlight(const QString &text, int matchingIndex, int matchingLength) { Q_UNUSED(text) //qDebug() << "Highlight: [" << text << "] mi:" << matchingIndex << " ml:" << matchingLength; QTextCursor tc = parent->textCursor(); tc.setPosition(matchingIndex); tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchingLength); parent->setTextCursor(tc); parent->ensureCursorVisible(); } void KTextEdit::Private::slotReplaceText(const QString &text, int replacementIndex, int replacedLength, int matchedLength) { //qDebug() << "Replace: [" << text << "] ri:" << replacementIndex << " rl:" << replacedLength << " ml:" << matchedLength; QTextCursor tc = parent->textCursor(); tc.setPosition(replacementIndex); tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchedLength); tc.removeSelectedText(); tc.insertText(text.mid(replacementIndex, replacedLength)); if (replace->options() & KReplaceDialog::PromptOnReplace) { parent->setTextCursor(tc); parent->ensureCursorVisible(); } lastReplacedPosition = replacementIndex; } void KTextEdit::Private::init() { KCursor::setAutoHideCursor(parent, true, false); parent->connect(parent, &KTextEdit::languageChanged, parent, &KTextEdit::setSpellCheckingLanguage); } KTextDecorator::KTextDecorator(KTextEdit *textEdit): SpellCheckDecorator(textEdit), m_textEdit(textEdit) { } bool KTextDecorator::isSpellCheckingEnabledForBlock(const QString &textBlock) const { return m_textEdit->shouldBlockBeSpellChecked(textBlock); } KTextEdit::KTextEdit(const QString &text, QWidget *parent) : QTextEdit(text, parent), d(new Private(this)) { d->init(); } KTextEdit::KTextEdit(QWidget *parent) : QTextEdit(parent), d(new Private(this)) { d->init(); } KTextEdit::~KTextEdit() { delete d; } const QString &KTextEdit::spellCheckingLanguage() const { return d->spellCheckingLanguage; } void KTextEdit::setSpellCheckingLanguage(const QString &_language) { if (highlighter()) { highlighter()->setCurrentLanguage(_language); highlighter()->rehighlight(); } if (_language != d->spellCheckingLanguage) { d->spellCheckingLanguage = _language; emit languageChanged(_language); } } void KTextEdit::showSpellConfigDialog(const QString &windowIcon) { Sonnet::ConfigDialog dialog(this); if (!d->spellCheckingLanguage.isEmpty()) { dialog.setLanguage(d->spellCheckingLanguage); } if (!windowIcon.isEmpty()) { dialog.setWindowIcon(QIcon::fromTheme(windowIcon, dialog.windowIcon())); } if (dialog.exec()) { setSpellCheckingLanguage(dialog.language()); } } bool KTextEdit::event(QEvent *ev) { if (ev->type() == QEvent::ShortcutOverride) { QKeyEvent *e = static_cast(ev); if (d->overrideShortcut(e)) { e->accept(); return true; } } return QTextEdit::event(ev); } bool KTextEdit::Private::handleShortcut(const QKeyEvent *event) { const int key = event->key() | event->modifiers(); if (KStandardShortcut::copy().contains(key)) { parent->copy(); return true; } else if (KStandardShortcut::paste().contains(key)) { parent->paste(); return true; } else if (KStandardShortcut::cut().contains(key)) { parent->cut(); return true; } else if (KStandardShortcut::undo().contains(key)) { if (!parent->isReadOnly()) { parent->undo(); } return true; } else if (KStandardShortcut::redo().contains(key)) { if (!parent->isReadOnly()) { parent->redo(); } return true; } else if (KStandardShortcut::deleteWordBack().contains(key)) { if (!parent->isReadOnly()) { parent->deleteWordBack(); } return true; } else if (KStandardShortcut::deleteWordForward().contains(key)) { if (!parent->isReadOnly()) { parent->deleteWordForward(); } return true; } else if (KStandardShortcut::backwardWord().contains(key)) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::PreviousWord); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::forwardWord().contains(key)) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::NextWord); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::next().contains(key)) { QTextCursor cursor = parent->textCursor(); bool moved = false; qreal lastY = parent->cursorRect(cursor).bottom(); qreal distance = 0; do { qreal y = parent->cursorRect(cursor).bottom(); distance += qAbs(y - lastY); lastY = y; moved = cursor.movePosition(QTextCursor::Down); } while (moved && distance < parent->viewport()->height()); if (moved) { cursor.movePosition(QTextCursor::Up); parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); } parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::prior().contains(key)) { QTextCursor cursor = parent->textCursor(); bool moved = false; qreal lastY = parent->cursorRect(cursor).bottom(); qreal distance = 0; do { qreal y = parent->cursorRect(cursor).bottom(); distance += qAbs(y - lastY); lastY = y; moved = cursor.movePosition(QTextCursor::Up); } while (moved && distance < parent->viewport()->height()); if (moved) { cursor.movePosition(QTextCursor::Down); parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); } parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::begin().contains(key)) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::Start); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::end().contains(key)) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::End); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::beginningOfLine().contains(key)) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::StartOfLine); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::endOfLine().contains(key)) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::EndOfLine); parent->setTextCursor(cursor); return true; } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) { parent->slotFind(); return true; } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { parent->slotFindNext(); return true; } else if (findReplaceEnabled && KStandardShortcut::findPrev().contains(key)) { parent->slotFindPrevious(); return true; } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { if (!parent->isReadOnly()) { parent->slotReplace(); } return true; } else if (KStandardShortcut::pasteSelection().contains(key)) { QString text = QApplication::clipboard()->text(QClipboard::Selection); if (!text.isEmpty()) { parent->insertPlainText(text); // TODO: check if this is html? (MiB) } return true; } return false; } static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op) { cursor.clearSelection(); cursor.movePosition(op, QTextCursor::KeepAnchor); cursor.removeSelectedText(); } void KTextEdit::deleteWordBack() { deleteWord(textCursor(), QTextCursor::PreviousWord); } void KTextEdit::deleteWordForward() { deleteWord(textCursor(), QTextCursor::WordRight); } QMenu *KTextEdit::mousePopupMenu() { QMenu *popup = createStandardContextMenu(); if (!popup) { return nullptr; } connect(popup, SIGNAL(triggered(QAction*)), this, SLOT(menuActivated(QAction*))); const bool emptyDocument = document()->isEmpty(); if (!isReadOnly()) { QList actionList = popup->actions(); enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; QAction *separatorAction = nullptr; int idx = actionList.indexOf(actionList[SelectAllAct]) + 1; if (idx < actionList.count()) { separatorAction = actionList.at(idx); } if (separatorAction) { QAction *clearAllAction = KStandardAction::clear(this, SLOT(undoableClear()), popup); if (emptyDocument) { clearAllAction->setEnabled(false); } popup->insertAction(separatorAction, clearAllAction); } } if (!isReadOnly()) { popup->addSeparator(); if (!d->speller) { d->speller = new Sonnet::Speller(); } if (!d->speller->availableBackends().isEmpty()) { d->spellCheckAction = popup->addAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("Check Spelling...")); if (emptyDocument) { d->spellCheckAction->setEnabled(false); } if (checkSpellingEnabled()) { d->languagesMenu = new QMenu(i18n("Spell Checking Language"), popup); QActionGroup *languagesGroup = new QActionGroup(d->languagesMenu); languagesGroup->setExclusive(true); QMapIterator i(d->speller->availableDictionaries()); const QString language = spellCheckingLanguage(); while (i.hasNext()) { i.next(); QAction *languageAction = d->languagesMenu->addAction(i.key()); languageAction->setCheckable(true); languageAction->setChecked(language == i.value() || (language.isEmpty() && d->speller->defaultLanguage() == i.value())); languageAction->setData(i.value()); languageAction->setActionGroup(languagesGroup); connect(languageAction, &QAction::triggered, [this, languageAction]() { setSpellCheckingLanguage(languageAction->data().toString()); }); } popup->addMenu(d->languagesMenu); } d->autoSpellCheckAction = popup->addAction(i18n("Auto Spell Check")); d->autoSpellCheckAction->setCheckable(true); d->autoSpellCheckAction->setChecked(checkSpellingEnabled()); popup->addSeparator(); } if (d->showTabAction) { d->allowTab = popup->addAction(i18n("Allow Tabulations")); d->allowTab->setCheckable(true); d->allowTab->setChecked(!tabChangesFocus()); } } if (d->findReplaceEnabled) { QAction *findAction = KStandardAction::find(this, SLOT(slotFind()), popup); QAction *findNextAction = KStandardAction::findNext(this, SLOT(slotFindNext()), popup); QAction *findPrevAction = KStandardAction::findPrev(this, SLOT(slotFindPrevious()), popup); if (emptyDocument) { findAction->setEnabled(false); findNextAction->setEnabled(false); findPrevAction->setEnabled(false); } else { findNextAction->setEnabled(d->find != nullptr); findPrevAction->setEnabled(d->find != nullptr); } popup->addSeparator(); popup->addAction(findAction); popup->addAction(findNextAction); popup->addAction(findPrevAction); if (!isReadOnly()) { QAction *replaceAction = KStandardAction::replace(this, SLOT(slotReplace()), popup); if (emptyDocument) { replaceAction->setEnabled(false); } popup->addAction(replaceAction); } } #ifdef HAVE_SPEECH popup->addSeparator(); QAction *speakAction = popup->addAction(i18n("Speak Text")); speakAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech"))); speakAction->setEnabled(!emptyDocument); connect(speakAction, &QAction::triggered, this, &KTextEdit::slotSpeakText); #endif return popup; } void KTextEdit::slotSpeakText() { #ifdef HAVE_SPEECH QString text; if (textCursor().hasSelection()) { text = textCursor().selectedText(); } else { text = toPlainText(); } if (!d->textToSpeech) { d->textToSpeech = new QTextToSpeech(this); } d->textToSpeech->say(text); #endif } void KTextEdit::contextMenuEvent(QContextMenuEvent *event) { QMenu *popup = mousePopupMenu(); if (popup) { aboutToShowContextMenu(popup); popup->exec(event->globalPos()); delete popup; } } void KTextEdit::createHighlighter() { setHighlighter(new Sonnet::Highlighter(this)); } Sonnet::Highlighter *KTextEdit::highlighter() const { if (d->decorator) { return d->decorator->highlighter(); } else { return nullptr; } } void KTextEdit::clearDecorator() { delete d->decorator; d->decorator = nullptr; } void KTextEdit::addTextDecorator(Sonnet::SpellCheckDecorator *decorator) { d->decorator = decorator; } void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter) { KTextDecorator *decorator = new KTextDecorator(this); // The old default highlighter must be manually deleted. delete decorator->highlighter(); decorator->setHighlighter(_highLighter); //KTextEdit used to take ownership of the highlighter, Sonnet::SpellCheckDecorator does not. //so we reparent the highlighter so it will be deleted when the decorator is destroyed _highLighter->setParent(decorator); addTextDecorator(decorator); } void KTextEdit::setCheckSpellingEnabled(bool check) { emit checkSpellingChanged(check); if (check == d->spellCheckingEnabled) { return; } // From the above statment we know know that if we're turning checking // on that we need to create a new highlighter and if we're turning it // off we should remove the old one. d->spellCheckingEnabled = check; if (check) { if (hasFocus()) { createHighlighter(); if (!spellCheckingLanguage().isEmpty()) { setSpellCheckingLanguage(spellCheckingLanguage()); } } } else { clearDecorator(); } } void KTextEdit::focusInEvent(QFocusEvent *event) { if (d->spellCheckingEnabled && !isReadOnly() && !d->decorator) { createHighlighter(); } QTextEdit::focusInEvent(event); } bool KTextEdit::checkSpellingEnabled() const { return d->spellCheckingEnabled; } bool KTextEdit::shouldBlockBeSpellChecked(const QString &) const { return true; } void KTextEdit::setReadOnly(bool readOnly) { if (!readOnly && hasFocus() && d->spellCheckingEnabled && !d->decorator) { createHighlighter(); } if (readOnly == isReadOnly()) { return; } if (readOnly) { delete d->decorator; d->decorator = nullptr; d->customPalette = testAttribute(Qt::WA_SetPalette); QPalette p = palette(); QColor color = p.color(QPalette::Disabled, QPalette::Window); p.setColor(QPalette::Base, color); p.setColor(QPalette::Window, color); setPalette(p); } else { if (d->customPalette && testAttribute(Qt::WA_SetPalette)) { QPalette p = palette(); QColor color = p.color(QPalette::Normal, QPalette::Base); p.setColor(QPalette::Base, color); p.setColor(QPalette::Window, color); setPalette(p); } else { setPalette(QPalette()); } } QTextEdit::setReadOnly(readOnly); } void KTextEdit::checkSpelling() { d->checkSpelling(false); } void KTextEdit::forceSpellChecking() { d->checkSpelling(true); } void KTextEdit::highlightWord(int length, int pos) { QTextCursor cursor(document()); cursor.setPosition(pos); cursor.setPosition(pos + length, QTextCursor::KeepAnchor); setTextCursor(cursor); ensureCursorVisible(); } void KTextEdit::replace() { if (document()->isEmpty()) { // saves having to track the text changes return; } if (d->repDlg) { - KWindowSystem::activateWindow(d->repDlg->winId()); + d->repDlg->activateWindow(); } else { d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false); connect(d->repDlg, &KFindDialog::okClicked, this, &KTextEdit::slotDoReplace); } d->repDlg->show(); } void KTextEdit::slotDoReplace() { if (!d->repDlg) { // Should really assert() return; } if (d->repDlg->pattern().isEmpty()) { delete d->replace; d->replace = nullptr; ensureCursorVisible(); return; } delete d->replace; d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this); d->repIndex = 0; if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) { d->repIndex = textCursor().anchor(); } // Connect highlight signal to code which handles highlighting // of found text. connect(d->replace, SIGNAL(highlight(QString,int,int)), this, SLOT(slotFindHighlight(QString,int,int))); connect(d->replace, &KFind::findNext, this, &KTextEdit::slotReplaceNext); connect(d->replace, SIGNAL(replace(QString,int,int,int)), this, SLOT(slotReplaceText(QString,int,int,int))); d->repDlg->close(); slotReplaceNext(); } void KTextEdit::slotReplaceNext() { if (!d->replace) { return; } d->lastReplacedPosition = -1; if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { textCursor().beginEditBlock(); // #48541 viewport()->setUpdatesEnabled(false); } KFind::Result res = KFind::NoMatch; if (d->replace->needData()) { d->replace->setData(toPlainText(), d->repIndex); } res = d->replace->replace(); if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { textCursor().endEditBlock(); // #48541 if (d->lastReplacedPosition >= 0) { QTextCursor tc = textCursor(); tc.setPosition(d->lastReplacedPosition); setTextCursor(tc); ensureCursorVisible(); } viewport()->setUpdatesEnabled(true); viewport()->update(); } if (res == KFind::NoMatch) { d->replace->displayFinalDialog(); d->replace->disconnect(this); d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away d->replace = nullptr; ensureCursorVisible(); //or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } } else { //m_replace->closeReplaceNextDialog(); } } void KTextEdit::slotDoFind() { if (!d->findDlg) { // Should really assert() return; } if (d->findDlg->pattern().isEmpty()) { delete d->find; d->find = nullptr; return; } delete d->find; d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this); d->findIndex = 0; if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) { d->findIndex = textCursor().anchor(); } // Connect highlight signal to code which handles highlighting // of found text. connect(d->find, SIGNAL(highlight(QString,int,int)), this, SLOT(slotFindHighlight(QString,int,int))); connect(d->find, &KFind::findNext, this, &KTextEdit::slotFindNext); d->findDlg->close(); d->find->closeFindNextDialog(); slotFindNext(); } void KTextEdit::slotFindNext() { if (!d->find) { return; } if (document()->isEmpty()) { d->find->disconnect(this); d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away d->find = nullptr; return; } KFind::Result res = KFind::NoMatch; if (d->find->needData()) { d->find->setData(toPlainText(), d->findIndex); } res = d->find->find(); if (res == KFind::NoMatch) { d->find->displayFinalDialog(); d->find->disconnect(this); d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away d->find = nullptr; //or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); } } else { //m_find->closeFindNextDialog(); } } void KTextEdit::slotFindPrevious() { if (!d->find) { return; } const long oldOptions = d->find->options(); d->find->setOptions(oldOptions ^ KFind::FindBackwards); slotFindNext(); if (d->find) { d->find->setOptions(oldOptions); } } void KTextEdit::slotFind() { if (document()->isEmpty()) { // saves having to track the text changes return; } if (d->findDlg) { - KWindowSystem::activateWindow(d->findDlg->winId()); + d->findDlg->activateWindow(); } else { d->findDlg = new KFindDialog(this); connect(d->findDlg, &KFindDialog::okClicked, this, &KTextEdit::slotDoFind); } d->findDlg->show(); } void KTextEdit::slotReplace() { if (document()->isEmpty()) { // saves having to track the text changes return; } if (d->repDlg) { - KWindowSystem::activateWindow(d->repDlg->winId()); + d->repDlg->activateWindow(); } else { d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false); connect(d->repDlg, &KFindDialog::okClicked, this, &KTextEdit::slotDoReplace); } d->repDlg->show(); } void KTextEdit::enableFindReplace(bool enabled) { d->findReplaceEnabled = enabled; } void KTextEdit::showTabAction(bool show) { d->showTabAction = show; } bool KTextEdit::Private::overrideShortcut(const QKeyEvent *event) { const int key = event->key() | event->modifiers(); if (KStandardShortcut::copy().contains(key)) { return true; } else if (KStandardShortcut::paste().contains(key)) { return true; } else if (KStandardShortcut::cut().contains(key)) { return true; } else if (KStandardShortcut::undo().contains(key)) { return true; } else if (KStandardShortcut::redo().contains(key)) { return true; } else if (KStandardShortcut::deleteWordBack().contains(key)) { return true; } else if (KStandardShortcut::deleteWordForward().contains(key)) { return true; } else if (KStandardShortcut::backwardWord().contains(key)) { return true; } else if (KStandardShortcut::forwardWord().contains(key)) { return true; } else if (KStandardShortcut::next().contains(key)) { return true; } else if (KStandardShortcut::prior().contains(key)) { return true; } else if (KStandardShortcut::begin().contains(key)) { return true; } else if (KStandardShortcut::end().contains(key)) { return true; } else if (KStandardShortcut::beginningOfLine().contains(key)) { return true; } else if (KStandardShortcut::endOfLine().contains(key)) { return true; } else if (KStandardShortcut::pasteSelection().contains(key)) { return true; } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) { return true; } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { return true; } else if (findReplaceEnabled && KStandardShortcut::findPrev().contains(key)) { return true; } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { return true; } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit return true; } return false; } void KTextEdit::keyPressEvent(QKeyEvent *event) { if (d->handleShortcut(event)) { event->accept(); } else { QTextEdit::keyPressEvent(event); } } void KTextEdit::showAutoCorrectButton(bool show) { d->showAutoCorrectionButton = show; } #include "moc_ktextedit.cpp"