File indexing completed on 2024-04-14 03:55:30

0001 /*
0002     SPDX-FileCopyrightText: 2001-2010 Christoph Cullmann <cullmann@kde.org>
0003     SPDX-FileCopyrightText: 2009 Erlend Hamberg <ehamberg@gmail.com>
0004 
0005     For the addScrollablePage original
0006     SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
0007     SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0008     SPDX-FileCopyrightText: 2004 Michael Brade <brade@kde.org>
0009     SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
0010 
0011     SPDX-License-Identifier: LGPL-2.0-or-later
0012 */
0013 
0014 #include "kateglobal.h"
0015 
0016 #include <ktexteditor_version.h>
0017 
0018 #include "katebuffer.h"
0019 #include "katecmd.h"
0020 #include "katecmds.h"
0021 #include "kateconfig.h"
0022 #include "katedialogs.h"
0023 #include "katedocument.h"
0024 #include "katehighlightingcmds.h"
0025 #include "katekeywordcompletion.h"
0026 #include "katemodemanager.h"
0027 #include "katescriptmanager.h"
0028 #include "katesedcmd.h"
0029 #include "katesyntaxmanager.h"
0030 #include "katethemeconfig.h"
0031 #include "katevariableexpansionmanager.h"
0032 #include "kateview.h"
0033 #include "katewordcompletion.h"
0034 #include "spellcheck/spellcheck.h"
0035 
0036 #include "katenormalinputmodefactory.h"
0037 #include "kateviinputmodefactory.h"
0038 
0039 #include <KConfigGroup>
0040 #include <KDirWatch>
0041 #include <KLocalizedString>
0042 #include <KPageDialog>
0043 
0044 #include <QApplication>
0045 #include <QBoxLayout>
0046 #include <QClipboard>
0047 #include <QFrame>
0048 #include <QPushButton>
0049 #include <QScreen>
0050 #include <QScrollArea>
0051 #include <QScrollBar>
0052 #include <QStringListModel>
0053 #include <QTextToSpeech>
0054 #include <QTimer>
0055 
0056 // BEGIN unit test mode
0057 static bool kateUnitTestMode = false;
0058 
0059 void KTextEditor::EditorPrivate::enableUnitTestMode()
0060 {
0061     kateUnitTestMode = true;
0062 }
0063 
0064 bool KTextEditor::EditorPrivate::unitTestMode()
0065 {
0066     return kateUnitTestMode;
0067 }
0068 // END unit test mode
0069 
0070 KTextEditor::EditorPrivate::EditorPrivate(QPointer<KTextEditor::EditorPrivate> &staticInstance)
0071     : KTextEditor::Editor(this)
0072     , m_aboutData(QStringLiteral("katepart"),
0073                   i18n("Kate Part"),
0074                   QStringLiteral(KTEXTEDITOR_VERSION_STRING),
0075                   i18n("Embeddable editor component"),
0076                   KAboutLicense::LGPL_V2,
0077                   i18n("(c) 2000-2022 The Kate Authors"),
0078                   QString(),
0079                   QStringLiteral("https://kate-editor.org"))
0080     , m_dummyApplication(nullptr)
0081     , m_application(&m_dummyApplication)
0082     , m_dummyMainWindow(nullptr)
0083     , m_searchHistoryModel(nullptr)
0084     , m_replaceHistoryModel(nullptr)
0085 {
0086     // remember this
0087     staticInstance = this;
0088 
0089     // register some datatypes
0090     qRegisterMetaType<KTextEditor::Cursor>("KTextEditor::Cursor");
0091     qRegisterMetaType<KTextEditor::Document *>("KTextEditor::Document*");
0092     qRegisterMetaType<KTextEditor::View *>("KTextEditor::View*");
0093 
0094     //
0095     // fill about data
0096     //
0097     m_aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("https://cullmann.io"));
0098     m_aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org"));
0099     m_aboutData.addAuthor(i18n("Milian Wolff"), i18n("Core Developer"), QStringLiteral("mail@milianw.de"), QStringLiteral("https://milianw.de/"));
0100     m_aboutData.addAuthor(i18n("Joseph Wenninger"),
0101                           i18n("Core Developer"),
0102                           QStringLiteral("jowenn@kde.org"),
0103                           QStringLiteral("http://stud3.tuwien.ac.at/~e9925371"));
0104     m_aboutData.addAuthor(i18n("Erlend Hamberg"), i18n("Vi Input Mode"), QStringLiteral("ehamberg@gmail.com"), QStringLiteral("https://hamberg.no/erlend"));
0105     m_aboutData.addAuthor(i18n("Bernhard Beschow"),
0106                           i18n("Developer"),
0107                           QStringLiteral("bbeschow@cs.tu-berlin.de"),
0108                           QStringLiteral("https://user.cs.tu-berlin.de/~bbeschow"));
0109     m_aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("https://alweb.dk"));
0110     m_aboutData.addAuthor(i18n("Michel Ludwig"), i18n("On-the-fly spell checking"), QStringLiteral("michel.ludwig@kdemail.net"));
0111     m_aboutData.addAuthor(i18n("Pascal Létourneau"), i18n("Large scale bug fixing"), QStringLiteral("pascal.letourneau@gmail.com"));
0112     m_aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org"));
0113     m_aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org"));
0114     m_aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org"));
0115     m_aboutData.addAuthor(i18n("Matt Newell"), i18n("Testing, ..."), QStringLiteral("newellm@proaxis.com"));
0116     m_aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at"));
0117     m_aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz"));
0118     m_aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org"));
0119     m_aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org"));
0120     m_aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org"));
0121     m_aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com"));
0122     m_aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net"));
0123     m_aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org"));
0124     m_aboutData.addAuthor(i18n("Andreas Kling"), i18n("Developer"), QStringLiteral("kling@impul.se"));
0125     m_aboutData.addAuthor(i18n("Mirko Stocker"), i18n("Various bugfixes"), QStringLiteral("me@misto.ch"), QStringLiteral("https://misto.ch/"));
0126     m_aboutData.addAuthor(i18n("Matthew Woehlke"), i18n("Selection, KColorScheme integration"), QStringLiteral("mw_triad@users.sourceforge.net"));
0127     m_aboutData.addAuthor(i18n("Sebastian Pipping"),
0128                           i18n("Search bar back- and front-end"),
0129                           QStringLiteral("webmaster@hartwork.org"),
0130                           QStringLiteral("https://hartwork.org/"));
0131     m_aboutData.addAuthor(i18n("Jochen Wilhelmy"), i18n("Original KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de"));
0132     m_aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"),
0133                           i18n("QA and Scripting"),
0134                           QStringLiteral("oss@senarclens.eu"),
0135                           QStringLiteral("http://find-santa.eu/"));
0136 
0137     m_aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it"));
0138     m_aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu"));
0139     m_aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL"), QString());
0140     m_aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite"), QString());
0141     m_aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG"), QString());
0142     m_aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX"), QString());
0143     m_aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python"), QString());
0144     m_aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python"), QString());
0145     m_aboutData.addCredit(i18n("Daniel Naber"));
0146     m_aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme"), QString());
0147     m_aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list"), QString());
0148     m_aboutData.addCredit(i18n("Carsten Pfeiffer"), i18n("Very nice help"), QString());
0149     m_aboutData.addCredit(i18n("Bruno Massa"), i18n("Highlighting for Lua"), QStringLiteral("brmassa@gmail.com"));
0150 
0151     m_aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention"));
0152 
0153     m_aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
0154 
0155     // set proper Kate icon for our about dialog
0156     m_aboutData.setProgramLogo(QIcon(QStringLiteral(":/ktexteditor/kate.svg")));
0157 
0158     //
0159     // dir watch
0160     //
0161     m_dirWatch = new KDirWatch();
0162 
0163     //
0164     // command manager
0165     //
0166     m_cmdManager = new KateCmd();
0167 
0168     //
0169     // variable expansion manager
0170     //
0171     m_variableExpansionManager = new KateVariableExpansionManager(this);
0172 
0173     //
0174     // hl manager
0175     //
0176     m_hlManager = new KateHlManager();
0177 
0178     //
0179     // mode man
0180     //
0181     m_modeManager = new KateModeManager();
0182 
0183     //
0184     // input mode factories
0185     //
0186     Q_ASSERT(m_inputModeFactories.size() == KTextEditor::View::ViInputMode + 1);
0187     m_inputModeFactories[KTextEditor::View::NormalInputMode].reset(new KateNormalInputModeFactory());
0188     m_inputModeFactories[KTextEditor::View::ViInputMode].reset(new KateViInputModeFactory());
0189 
0190     //
0191     // spell check manager
0192     //
0193     m_spellCheckManager = new KateSpellCheckManager();
0194 
0195     // config objects
0196     m_globalConfig = new KateGlobalConfig();
0197     m_documentConfig = new KateDocumentConfig();
0198     m_viewConfig = new KateViewConfig();
0199     m_rendererConfig = new KateRendererConfig();
0200 
0201     // create script manager (search scripts)
0202     m_scriptManager = KateScriptManager::self();
0203 
0204     //
0205     // init the cmds
0206     //
0207     m_cmds = {
0208         KateCommands::CoreCommands::self(),
0209         KateCommands::Character::self(),
0210         KateCommands::Date::self(),
0211         KateCommands::SedReplace::self(),
0212         KateCommands::Highlighting::self(),
0213     };
0214 
0215     // global word completion model
0216     m_wordCompletionModel = new KateWordCompletionModel(this);
0217 
0218     // global keyword completion model
0219     m_keywordCompletionModel = new KateKeywordCompletionModel(this);
0220 
0221     // tap to QApplication object for color palette changes
0222     qApp->installEventFilter(this);
0223 }
0224 
0225 KTextEditor::EditorPrivate::~EditorPrivate()
0226 {
0227     delete m_globalConfig;
0228     delete m_documentConfig;
0229     delete m_viewConfig;
0230     delete m_rendererConfig;
0231 
0232     delete m_modeManager;
0233 
0234     delete m_dirWatch;
0235 
0236     // cu managers
0237     delete m_scriptManager;
0238     delete m_hlManager;
0239 
0240     delete m_spellCheckManager;
0241 
0242     // cu model
0243     delete m_wordCompletionModel;
0244 
0245     // delete variable expansion manager
0246     delete m_variableExpansionManager;
0247     m_variableExpansionManager = nullptr;
0248 
0249     // delete the commands before we delete the cmd manager
0250     qDeleteAll(m_cmds);
0251     delete m_cmdManager;
0252 }
0253 
0254 KTextEditor::Document *KTextEditor::EditorPrivate::createDocument(QObject *parent)
0255 {
0256     KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(false, false, nullptr, parent);
0257 
0258     Q_EMIT documentCreated(this, doc);
0259 
0260     return doc;
0261 }
0262 
0263 // END KTextEditor::Editor config stuff
0264 
0265 // config dialog with improved sizing and that allows scrolling
0266 class KTextEditorConfigDialog : public KPageDialog
0267 {
0268 public:
0269     std::vector<KTextEditor::ConfigPage *> editorPages;
0270 
0271     KTextEditorConfigDialog(KTextEditor::EditorPrivate *editor, QWidget *parent)
0272         : KPageDialog(parent)
0273     {
0274         setWindowTitle(i18n("Configure"));
0275         setFaceType(KPageDialog::List);
0276         setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Help);
0277 
0278         // create pages already in construct to have proper layout for sizeHint
0279         editorPages.reserve(editor->configPages());
0280         for (int i = 0; i < editor->configPages(); ++i) {
0281             KTextEditor::ConfigPage *page = editor->configPage(i, this);
0282             KPageWidgetItem *item = addScrollablePage(page, page->name());
0283             item->setHeader(page->fullName());
0284             item->setIcon(page->icon());
0285 
0286             connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, page, &KTextEditor::ConfigPage::apply);
0287             editorPages.push_back(page);
0288         }
0289     }
0290 
0291     QSize sizeHint() const override
0292     {
0293         // start with a bit enlarged default size hint to minimize changes of useless scrollbars
0294         QSize size = KPageDialog::sizeHint() * 1.3;
0295 
0296         // enlarge it to half of the main window size, if that is larger
0297         if (parentWidget() && parentWidget()->window()) {
0298             size = size.expandedTo(parentWidget()->window()->size() * 0.5);
0299         }
0300 
0301         // return bounded size to available real screen space
0302         return size.boundedTo(screen()->availableSize() * 0.9);
0303     }
0304 
0305     KPageWidgetItem *addScrollablePage(QWidget *page, const QString &itemName)
0306     {
0307         // inspired by KPageWidgetItem *KConfigDialogPrivate::addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString
0308         // &header)
0309         QWidget *frame = new QWidget;
0310         QVBoxLayout *boxLayout = new QVBoxLayout(frame);
0311         boxLayout->setContentsMargins(0, 0, 0, 0);
0312         boxLayout->setContentsMargins(0, 0, 0, 0);
0313 
0314         QScrollArea *scroll = new QScrollArea;
0315         scroll->setFrameShape(QFrame::NoFrame);
0316         scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0317         scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0318         scroll->setWidget(page);
0319         scroll->setWidgetResizable(true);
0320         scroll->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0321 
0322         if (page->minimumSizeHint().height() > scroll->sizeHint().height() - 2) {
0323             if (page->sizeHint().width() < scroll->sizeHint().width() + 2) {
0324                 // QScrollArea is planning only a vertical scroll bar,
0325                 // try to avoid the horizontal one by reserving space for the vertical one.
0326                 // Currently KPageViewPrivate::_k_modelChanged() queries the minimumSizeHint().
0327                 // We can only set the minimumSize(), so this approach relies on QStackedWidget size calculation.
0328                 scroll->setMinimumWidth(scroll->sizeHint().width() + qBound(0, scroll->verticalScrollBar()->sizeHint().width(), 200) + 4);
0329             }
0330         }
0331 
0332         boxLayout->addWidget(scroll);
0333         return addPage(frame, itemName);
0334     }
0335 };
0336 
0337 void KTextEditor::EditorPrivate::configDialog(QWidget *parent)
0338 {
0339     QPointer<KTextEditorConfigDialog> kd = new KTextEditorConfigDialog(this, parent);
0340     if (kd->exec() && kd) {
0341         KateGlobalConfig::global()->configStart();
0342         KateDocumentConfig::global()->configStart();
0343         KateViewConfig::global()->configStart();
0344         KateRendererConfig::global()->configStart();
0345 
0346         for (auto *page : kd->editorPages) {
0347             page->apply();
0348         }
0349 
0350         KateGlobalConfig::global()->configEnd();
0351         KateDocumentConfig::global()->configEnd();
0352         KateViewConfig::global()->configEnd();
0353         KateRendererConfig::global()->configEnd();
0354     }
0355     delete kd;
0356 }
0357 
0358 int KTextEditor::EditorPrivate::configPages() const
0359 {
0360     return 4;
0361 }
0362 
0363 KTextEditor::ConfigPage *KTextEditor::EditorPrivate::configPage(int number, QWidget *parent)
0364 {
0365     switch (number) {
0366     case 0:
0367         return new KateViewDefaultsConfig(parent);
0368 
0369     case 1:
0370         return new KateThemeConfigPage(parent);
0371 
0372     case 2:
0373         return new KateEditConfigTab(parent);
0374 
0375     case 3:
0376         return new KateSaveConfigTab(parent);
0377 
0378     default:
0379         break;
0380     }
0381 
0382     return nullptr;
0383 }
0384 
0385 /**
0386  * Cleanup the KTextEditor::EditorPrivate during QCoreApplication shutdown
0387  */
0388 static void cleanupGlobal()
0389 {
0390     // delete if there
0391     delete KTextEditor::EditorPrivate::self();
0392 }
0393 
0394 KTextEditor::EditorPrivate *KTextEditor::EditorPrivate::self()
0395 {
0396     // remember the static instance in a QPointer
0397     static bool inited = false;
0398     static QPointer<KTextEditor::EditorPrivate> staticInstance;
0399 
0400     // just return it, if already inited
0401     if (inited) {
0402         return staticInstance.data();
0403     }
0404 
0405     // start init process
0406     inited = true;
0407 
0408     // now create the object and store it
0409     new KTextEditor::EditorPrivate(staticInstance);
0410 
0411     // register cleanup
0412     // let use be deleted during QCoreApplication shutdown
0413     qAddPostRoutine(cleanupGlobal);
0414 
0415     // return instance
0416     return staticInstance.data();
0417 }
0418 
0419 void KTextEditor::EditorPrivate::registerDocument(KTextEditor::DocumentPrivate *doc)
0420 {
0421     Q_ASSERT(!m_documents.contains(doc));
0422     m_documents.push_back(doc);
0423 }
0424 
0425 void KTextEditor::EditorPrivate::deregisterDocument(KTextEditor::DocumentPrivate *doc)
0426 {
0427     int i = m_documents.indexOf(doc);
0428     Q_ASSERT(i != -1);
0429     m_documents.removeAt(i);
0430 }
0431 
0432 void KTextEditor::EditorPrivate::registerView(KTextEditor::ViewPrivate *view)
0433 {
0434     Q_ASSERT(std::find(m_views.begin(), m_views.end(), view) == m_views.end());
0435     m_views.push_back(view);
0436 }
0437 
0438 void KTextEditor::EditorPrivate::deregisterView(KTextEditor::ViewPrivate *view)
0439 {
0440     auto it = std::find(m_views.begin(), m_views.end(), view);
0441     Q_ASSERT(it != m_views.end());
0442     m_views.erase(it);
0443 }
0444 
0445 KTextEditor::Command *KTextEditor::EditorPrivate::queryCommand(const QString &cmd) const
0446 {
0447     return m_cmdManager->queryCommand(cmd);
0448 }
0449 
0450 QList<KTextEditor::Command *> KTextEditor::EditorPrivate::commands() const
0451 {
0452     return m_cmdManager->commands();
0453 }
0454 
0455 QStringList KTextEditor::EditorPrivate::commandList() const
0456 {
0457     return m_cmdManager->commandList();
0458 }
0459 
0460 KateVariableExpansionManager *KTextEditor::EditorPrivate::variableExpansionManager()
0461 {
0462     return m_variableExpansionManager;
0463 }
0464 
0465 void KTextEditor::EditorPrivate::updateColorPalette()
0466 {
0467     // reload the global schema (triggers reload for every view as well)
0468     // might trigger selection of better matching theme for new palette
0469     m_rendererConfig->reloadSchema();
0470 
0471     // force full update of all view caches and colors
0472     m_rendererConfig->updateConfig();
0473 }
0474 
0475 void KTextEditor::EditorPrivate::copyToClipboard(const QString &text, const QString &fileName)
0476 {
0477     // empty => nop
0478     if (text.isEmpty()) {
0479         return;
0480     }
0481 
0482     // move to clipboard
0483     QApplication::clipboard()->setText(text, QClipboard::Clipboard);
0484 
0485     // LRU, kill potential duplicated, move new entry to top
0486     // cut after X entries
0487     auto entry = ClipboardEntry{text, fileName};
0488     m_clipboardHistory.removeOne(entry);
0489     m_clipboardHistory.prepend(entry);
0490     if (m_clipboardHistory.size() > m_viewConfig->clipboardHistoryEntries()) {
0491         m_clipboardHistory.removeLast();
0492     }
0493 
0494     // notify about change
0495     Q_EMIT clipboardHistoryChanged();
0496 }
0497 
0498 bool KTextEditor::EditorPrivate::eventFilter(QObject *obj, QEvent *event)
0499 {
0500     if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) {
0501         // only update the color once for the event that belongs to the qApp
0502         updateColorPalette();
0503     }
0504 
0505     return false; // always continue processing
0506 }
0507 
0508 QStringListModel *KTextEditor::EditorPrivate::searchHistoryModel()
0509 {
0510     if (!m_searchHistoryModel) {
0511         KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KTextEditor::Search"));
0512         const QStringList history = cg.readEntry(QStringLiteral("Search History"), QStringList());
0513         m_searchHistoryModel = new QStringListModel(history, this);
0514     }
0515     return m_searchHistoryModel;
0516 }
0517 
0518 QStringListModel *KTextEditor::EditorPrivate::replaceHistoryModel()
0519 {
0520     if (!m_replaceHistoryModel) {
0521         KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KTextEditor::Search"));
0522         const QStringList history = cg.readEntry(QStringLiteral("Replace History"), QStringList());
0523         m_replaceHistoryModel = new QStringListModel(history, this);
0524     }
0525     return m_replaceHistoryModel;
0526 }
0527 
0528 void KTextEditor::EditorPrivate::saveSearchReplaceHistoryModels()
0529 {
0530     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KTextEditor::Search"));
0531     if (m_searchHistoryModel) {
0532         cg.writeEntry(QStringLiteral("Search History"), m_searchHistoryModel->stringList());
0533     }
0534     if (m_replaceHistoryModel) {
0535         cg.writeEntry(QStringLiteral("Replace History"), m_replaceHistoryModel->stringList());
0536     }
0537 }
0538 
0539 KSharedConfigPtr KTextEditor::EditorPrivate::config()
0540 {
0541     // use dummy config for unit tests!
0542     if (KTextEditor::EditorPrivate::unitTestMode()) {
0543         return KSharedConfig::openConfig(QStringLiteral("katepartrc-unittest"), KConfig::SimpleConfig, QStandardPaths::TempLocation);
0544     }
0545 
0546     // else: use application configuration, but try to transfer global settings on first use
0547     auto applicationConfig = KSharedConfig::openConfig();
0548     if (!KConfigGroup(applicationConfig, QStringLiteral("KTextEditor Editor")).exists()) {
0549         auto globalConfig = KSharedConfig::openConfig(QStringLiteral("katepartrc"));
0550         for (const auto &group : {QStringLiteral("Editor"), QStringLiteral("Document"), QStringLiteral("View"), QStringLiteral("Renderer")}) {
0551             KConfigGroup origin(globalConfig, group);
0552             KConfigGroup destination(applicationConfig, QStringLiteral("KTextEditor ") + group);
0553             origin.copyTo(&destination);
0554         }
0555     }
0556     return applicationConfig;
0557 }
0558 
0559 void KTextEditor::EditorPrivate::triggerConfigChanged()
0560 {
0561     // trigger delayed emission, will collapse multiple events to one signal emission
0562     m_configWasChanged = true;
0563     QTimer::singleShot(0, this, &KTextEditor::EditorPrivate::emitConfigChanged);
0564 }
0565 
0566 void KTextEditor::EditorPrivate::emitConfigChanged()
0567 {
0568     // emit only once, if still needed
0569     if (m_configWasChanged) {
0570         m_configWasChanged = false;
0571         Q_EMIT configChanged(this);
0572     }
0573 }
0574 
0575 void KTextEditor::EditorPrivate::copyToMulticursorClipboard(const QStringList &texts)
0576 {
0577     if (texts.size() == 1) {
0578         qWarning() << "Unexpected size 1 of multicursorClipboard. It should either be empty or greater than 1";
0579         m_multicursorClipboard = QStringList();
0580         Q_ASSERT(false);
0581         return;
0582     }
0583     m_multicursorClipboard = texts;
0584 }
0585 
0586 QStringList KTextEditor::EditorPrivate::multicursorClipboard() const
0587 {
0588     return m_multicursorClipboard;
0589 }
0590 
0591 QTextToSpeech *KTextEditor::EditorPrivate::speechEngine(KTextEditor::ViewPrivate *view)
0592 {
0593     Q_ASSERT(view);
0594     if (!m_speechEngine) {
0595         m_speechEngine = new QTextToSpeech(this);
0596 
0597         // error handler for errors happening during speech output
0598         connect(m_speechEngine, &QTextToSpeech::errorOccurred, this, [this](QTextToSpeech::ErrorReason, const QString &errorString) {
0599             if (m_speechEngineLastUser) {
0600                 speechError(m_speechEngineLastUser, errorString);
0601             }
0602         });
0603 
0604         // handle init errors, that will not emit errorOccurred later
0605         if (m_speechEngine->errorReason() != QTextToSpeech::ErrorReason::NoError) {
0606             speechError(view, m_speechEngine->errorString());
0607         }
0608     }
0609 
0610     // register current view, handle error output and stopping of the speaking
0611     if (view != m_speechEngineLastUser) {
0612         if (m_speechEngineLastUser) {
0613             disconnect(m_speechEngineLastUser, &QObject::destroyed, this, &KTextEditor::EditorPrivate::speechEngineUserDestoyed);
0614         }
0615         m_speechEngineLastUser = view;
0616         connect(m_speechEngineLastUser, &QObject::destroyed, this, &KTextEditor::EditorPrivate::speechEngineUserDestoyed);
0617     }
0618 
0619     return m_speechEngine;
0620 }
0621 
0622 void KTextEditor::EditorPrivate::speechEngineUserDestoyed()
0623 {
0624     Q_ASSERT(m_speechEngine);
0625     m_speechEngine->stop();
0626 }
0627 
0628 void KTextEditor::EditorPrivate::speechError(KTextEditor::ViewPrivate *view, const QString &errorString)
0629 {
0630     Q_ASSERT(view);
0631     auto message = new KTextEditor::Message(errorString, Message::Error);
0632     message->setPosition(KTextEditor::Message::TopInView);
0633     message->setView(view);
0634     view->document()->postMessage(message);
0635 }
0636 
0637 #include "moc_kateglobal.cpp"