1/*
2 SPDX-FileCopyrightText: 2001-2010 Christoph Cullmann <cullmann@kde.org>
3 SPDX-FileCopyrightText: 2009 Erlend Hamberg <ehamberg@gmail.com>
4
5 For the addScrollablePage original
6 SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
7 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
8 SPDX-FileCopyrightText: 2004 Michael Brade <brade@kde.org>
9 SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
10
11 SPDX-License-Identifier: LGPL-2.0-or-later
12*/
13
14#include "kateglobal.h"
15
16#include <ktexteditor_version.h>
17
18#include "katebuffer.h"
19#include "katecmd.h"
20#include "katecmds.h"
21#include "kateconfig.h"
22#include "katedialogs.h"
23#include "katedocument.h"
24#include "katehighlightingcmds.h"
25#include "katekeywordcompletion.h"
26#include "katemodemanager.h"
27#include "katescriptmanager.h"
28#include "katesedcmd.h"
29#include "katesyntaxmanager.h"
30#include "katethemeconfig.h"
31#include "katevariableexpansionmanager.h"
32#include "kateview.h"
33#include "katewordcompletion.h"
34#include "spellcheck/spellcheck.h"
35
36#include "katenormalinputmodefactory.h"
37#include "kateviinputmodefactory.h"
38
39#include <KConfigGroup>
40#include <KDirWatch>
41#include <KLocalizedString>
42#include <KPageDialog>
43
44#include <QApplication>
45#include <QBoxLayout>
46#include <QClipboard>
47#include <QFrame>
48#include <QPushButton>
49#include <QScreen>
50#include <QScrollArea>
51#include <QScrollBar>
52#include <QStringListModel>
53#include <QTextToSpeech>
54#include <QTimer>
55
56// BEGIN unit test mode
57static bool kateUnitTestMode = false;
58
59void KTextEditor::EditorPrivate::enableUnitTestMode()
60{
61 kateUnitTestMode = true;
62}
63
64bool KTextEditor::EditorPrivate::unitTestMode()
65{
66 return kateUnitTestMode;
67}
68// END unit test mode
69
70KTextEditor::EditorPrivate::EditorPrivate(QPointer<KTextEditor::EditorPrivate> &staticInstance)
71 : KTextEditor::Editor(this)
72 , m_aboutData(QStringLiteral("katepart"),
73 i18n("Kate Part"),
74 QStringLiteral(KTEXTEDITOR_VERSION_STRING),
75 i18n("Embeddable editor component"),
76 KAboutLicense::LGPL_V2,
77 i18n("(c) 2000-2022 The Kate Authors"),
78 QString(),
79 QStringLiteral("https://kate-editor.org"))
80 , m_dummyApplication(nullptr)
81 , m_application(&m_dummyApplication)
82 , m_dummyMainWindow(nullptr)
83 , m_searchHistoryModel(nullptr)
84 , m_replaceHistoryModel(nullptr)
85{
86 // remember this
87 staticInstance = this;
88
89 // register some datatypes
90 qRegisterMetaType<KTextEditor::Cursor>(typeName: "KTextEditor::Cursor");
91 qRegisterMetaType<KTextEditor::Document *>(typeName: "KTextEditor::Document*");
92 qRegisterMetaType<KTextEditor::View *>(typeName: "KTextEditor::View*");
93
94 //
95 // fill about data
96 //
97 m_aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("https://cullmann.io"));
98 m_aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org"));
99 m_aboutData.addAuthor(i18n("Milian Wolff"), i18n("Core Developer"), QStringLiteral("mail@milianw.de"), QStringLiteral("https://milianw.de/"));
100 m_aboutData.addAuthor(i18n("Joseph Wenninger"),
101 i18n("Core Developer"),
102 QStringLiteral("jowenn@kde.org"),
103 QStringLiteral("http://stud3.tuwien.ac.at/~e9925371"));
104 m_aboutData.addAuthor(i18n("Erlend Hamberg"), i18n("Vi Input Mode"), QStringLiteral("ehamberg@gmail.com"), QStringLiteral("https://hamberg.no/erlend"));
105 m_aboutData.addAuthor(i18n("Bernhard Beschow"),
106 i18n("Developer"),
107 QStringLiteral("bbeschow@cs.tu-berlin.de"),
108 QStringLiteral("https://user.cs.tu-berlin.de/~bbeschow"));
109 m_aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("https://alweb.dk"));
110 m_aboutData.addAuthor(i18n("Michel Ludwig"), i18n("On-the-fly spell checking"), QStringLiteral("michel.ludwig@kdemail.net"));
111 m_aboutData.addAuthor(i18n("Pascal Létourneau"), i18n("Large scale bug fixing"), QStringLiteral("pascal.letourneau@gmail.com"));
112 m_aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org"));
113 m_aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org"));
114 m_aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org"));
115 m_aboutData.addAuthor(i18n("Matt Newell"), i18n("Testing, ..."), QStringLiteral("newellm@proaxis.com"));
116 m_aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at"));
117 m_aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz"));
118 m_aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org"));
119 m_aboutData.addAuthor(i18n("Christian Gebauer"), task: QString(), QStringLiteral("gebauer@kde.org"));
120 m_aboutData.addAuthor(i18n("Simon Hausmann"), task: QString(), QStringLiteral("hausmann@kde.org"));
121 m_aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com"));
122 m_aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net"));
123 m_aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org"));
124 m_aboutData.addAuthor(i18n("Andreas Kling"), i18n("Developer"), QStringLiteral("kling@impul.se"));
125 m_aboutData.addAuthor(i18n("Mirko Stocker"), i18n("Various bugfixes"), QStringLiteral("me@misto.ch"), QStringLiteral("https://misto.ch/"));
126 m_aboutData.addAuthor(i18n("Matthew Woehlke"), i18n("Selection, KColorScheme integration"), QStringLiteral("mw_triad@users.sourceforge.net"));
127 m_aboutData.addAuthor(i18n("Sebastian Pipping"),
128 i18n("Search bar back- and front-end"),
129 QStringLiteral("webmaster@hartwork.org"),
130 QStringLiteral("https://hartwork.org/"));
131 m_aboutData.addAuthor(i18n("Jochen Wilhelmy"), i18n("Original KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de"));
132 m_aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"),
133 i18n("QA and Scripting"),
134 QStringLiteral("oss@senarclens.eu"),
135 QStringLiteral("http://find-santa.eu/"));
136
137 m_aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it"));
138 m_aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu"));
139 m_aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL"), emailAddress: QString());
140 m_aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite"), emailAddress: QString());
141 m_aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG"), emailAddress: QString());
142 m_aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX"), emailAddress: QString());
143 m_aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python"), emailAddress: QString());
144 m_aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python"), emailAddress: QString());
145 m_aboutData.addCredit(i18n("Daniel Naber"));
146 m_aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme"), emailAddress: QString());
147 m_aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list"), emailAddress: QString());
148 m_aboutData.addCredit(i18n("Carsten Pfeiffer"), i18n("Very nice help"), emailAddress: QString());
149 m_aboutData.addCredit(i18n("Bruno Massa"), i18n("Highlighting for Lua"), QStringLiteral("brmassa@gmail.com"));
150
151 m_aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention"));
152
153 m_aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
154
155 // set proper Kate icon for our about dialog
156 m_aboutData.setProgramLogo(QIcon(QStringLiteral(":/ktexteditor/kate.svg")));
157
158 //
159 // dir watch
160 //
161 m_dirWatch = new KDirWatch();
162
163 //
164 // command manager
165 //
166 m_cmdManager = new KateCmd();
167
168 //
169 // variable expansion manager
170 //
171 m_variableExpansionManager = new KateVariableExpansionManager(this);
172
173 //
174 // hl manager
175 //
176 m_hlManager = new KateHlManager();
177
178 //
179 // mode man
180 //
181 m_modeManager = new KateModeManager();
182
183 //
184 // input mode factories
185 //
186 Q_ASSERT(m_inputModeFactories.size() == KTextEditor::View::ViInputMode + 1);
187 m_inputModeFactories[KTextEditor::View::NormalInputMode].reset(p: new KateNormalInputModeFactory());
188 m_inputModeFactories[KTextEditor::View::ViInputMode].reset(p: new KateViInputModeFactory());
189
190 //
191 // spell check manager
192 //
193 m_spellCheckManager = new KateSpellCheckManager();
194
195 // config objects
196 m_globalConfig = new KateGlobalConfig();
197 m_documentConfig = new KateDocumentConfig();
198 m_viewConfig = new KateViewConfig();
199 m_rendererConfig = new KateRendererConfig();
200
201 // create script manager (search scripts)
202 m_scriptManager = KateScriptManager::self();
203
204 //
205 // init the cmds
206 //
207 m_cmds = {
208 KateCommands::CoreCommands::self(),
209 KateCommands::Character::self(),
210 KateCommands::Date::self(),
211 KateCommands::SedReplace::self(),
212 KateCommands::Highlighting::self(),
213 };
214
215 // global word completion model
216 m_wordCompletionModel = new KateWordCompletionModel(this);
217
218 // global keyword completion model
219 m_keywordCompletionModel = new KateKeywordCompletionModel(this);
220
221 // tap to QApplication object for color palette changes
222 qApp->installEventFilter(filterObj: this);
223}
224
225KTextEditor::EditorPrivate::~EditorPrivate()
226{
227 delete m_globalConfig;
228 delete m_documentConfig;
229 delete m_viewConfig;
230 delete m_rendererConfig;
231
232 delete m_modeManager;
233
234 delete m_dirWatch;
235
236 // cu managers
237 delete m_scriptManager;
238 delete m_hlManager;
239
240 delete m_spellCheckManager;
241
242 // cu model
243 delete m_wordCompletionModel;
244
245 // delete variable expansion manager
246 delete m_variableExpansionManager;
247 m_variableExpansionManager = nullptr;
248
249 // delete the commands before we delete the cmd manager
250 qDeleteAll(c: m_cmds);
251 delete m_cmdManager;
252}
253
254KTextEditor::Document *KTextEditor::EditorPrivate::createDocument(QObject *parent)
255{
256 KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(false, false, nullptr, parent);
257
258 Q_EMIT documentCreated(editor: this, document: doc);
259
260 return doc;
261}
262
263// END KTextEditor::Editor config stuff
264
265// config dialog with improved sizing and that allows scrolling
266class KTextEditorConfigDialog : public KPageDialog
267{
268public:
269 std::vector<KTextEditor::ConfigPage *> editorPages;
270
271 KTextEditorConfigDialog(KTextEditor::EditorPrivate *editor, QWidget *parent)
272 : KPageDialog(parent)
273 {
274 setWindowTitle(i18n("Configure"));
275 setFaceType(KPageDialog::List);
276 setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Help);
277
278 // create pages already in construct to have proper layout for sizeHint
279 editorPages.reserve(n: editor->configPages());
280 for (int i = 0; i < editor->configPages(); ++i) {
281 KTextEditor::ConfigPage *page = editor->configPage(number: i, parent: this);
282 KPageWidgetItem *item = addScrollablePage(page, itemName: page->name());
283 item->setHeader(page->fullName());
284 item->setIcon(page->icon());
285
286 connect(sender: button(which: QDialogButtonBox::Apply), signal: &QPushButton::clicked, context: page, slot: &KTextEditor::ConfigPage::apply);
287 editorPages.push_back(x: page);
288 }
289 }
290
291 QSize sizeHint() const override
292 {
293 // start with a bit enlarged default size hint to minimize changes of useless scrollbars
294 QSize size = KPageDialog::sizeHint() * 1.3;
295
296 // enlarge it to half of the main window size, if that is larger
297 if (parentWidget() && parentWidget()->window()) {
298 size = size.expandedTo(otherSize: parentWidget()->window()->size() * 0.5);
299 }
300
301 // return bounded size to available real screen space
302 return size.boundedTo(otherSize: screen()->availableSize() * 0.9);
303 }
304
305 KPageWidgetItem *addScrollablePage(QWidget *page, const QString &itemName)
306 {
307 // inspired by KPageWidgetItem *KConfigDialogPrivate::addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString
308 // &header)
309 QWidget *frame = new QWidget;
310 QVBoxLayout *boxLayout = new QVBoxLayout(frame);
311 boxLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
312 boxLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
313
314 QScrollArea *scroll = new QScrollArea;
315 scroll->setFrameShape(QFrame::NoFrame);
316 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
317 scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
318 scroll->setWidget(page);
319 scroll->setWidgetResizable(true);
320 scroll->setSizePolicy(hor: QSizePolicy::MinimumExpanding, ver: QSizePolicy::MinimumExpanding);
321
322 if (page->minimumSizeHint().height() > scroll->sizeHint().height() - 2) {
323 if (page->sizeHint().width() < scroll->sizeHint().width() + 2) {
324 // QScrollArea is planning only a vertical scroll bar,
325 // try to avoid the horizontal one by reserving space for the vertical one.
326 // Currently KPageViewPrivate::_k_modelChanged() queries the minimumSizeHint().
327 // We can only set the minimumSize(), so this approach relies on QStackedWidget size calculation.
328 scroll->setMinimumWidth(scroll->sizeHint().width() + qBound(min: 0, val: scroll->verticalScrollBar()->sizeHint().width(), max: 200) + 4);
329 }
330 }
331
332 boxLayout->addWidget(scroll);
333 return addPage(widget: frame, name: itemName);
334 }
335};
336
337void KTextEditor::EditorPrivate::configDialog(QWidget *parent)
338{
339 QPointer<KTextEditorConfigDialog> kd = new KTextEditorConfigDialog(this, parent);
340 if (kd->exec() && kd) {
341 KateGlobalConfig::global()->configStart();
342 KateDocumentConfig::global()->configStart();
343 KateViewConfig::global()->configStart();
344 KateRendererConfig::global()->configStart();
345
346 for (auto *page : kd->editorPages) {
347 page->apply();
348 }
349
350 KateGlobalConfig::global()->configEnd();
351 KateDocumentConfig::global()->configEnd();
352 KateViewConfig::global()->configEnd();
353 KateRendererConfig::global()->configEnd();
354 }
355 delete kd;
356}
357
358int KTextEditor::EditorPrivate::configPages() const
359{
360 return 4;
361}
362
363KTextEditor::ConfigPage *KTextEditor::EditorPrivate::configPage(int number, QWidget *parent)
364{
365 switch (number) {
366 case 0:
367 return new KateViewDefaultsConfig(parent);
368
369 case 1:
370 return new KateThemeConfigPage(parent);
371
372 case 2:
373 return new KateEditConfigTab(parent);
374
375 case 3:
376 return new KateSaveConfigTab(parent);
377
378 default:
379 break;
380 }
381
382 return nullptr;
383}
384
385/**
386 * Cleanup the KTextEditor::EditorPrivate during QCoreApplication shutdown
387 */
388static void cleanupGlobal()
389{
390 // delete if there
391 delete KTextEditor::EditorPrivate::self();
392}
393
394KTextEditor::EditorPrivate *KTextEditor::EditorPrivate::self()
395{
396 // remember the static instance in a QPointer
397 static bool inited = false;
398 static QPointer<KTextEditor::EditorPrivate> staticInstance;
399
400 // just return it, if already inited
401 if (inited) {
402 return staticInstance.data();
403 }
404
405 // start init process
406 inited = true;
407
408 // now create the object and store it
409 new KTextEditor::EditorPrivate(staticInstance);
410
411 // register cleanup
412 // let use be deleted during QCoreApplication shutdown
413 qAddPostRoutine(cleanupGlobal);
414
415 // return instance
416 return staticInstance.data();
417}
418
419void KTextEditor::EditorPrivate::registerDocument(KTextEditor::DocumentPrivate *doc)
420{
421 Q_ASSERT(!m_documents.contains(doc));
422 m_documents.push_back(t: doc);
423}
424
425void KTextEditor::EditorPrivate::deregisterDocument(KTextEditor::DocumentPrivate *doc)
426{
427 int i = m_documents.indexOf(t: doc);
428 Q_ASSERT(i != -1);
429 m_documents.removeAt(i);
430}
431
432void KTextEditor::EditorPrivate::registerView(KTextEditor::ViewPrivate *view)
433{
434 Q_ASSERT(std::find(m_views.begin(), m_views.end(), view) == m_views.end());
435 m_views.push_back(x: view);
436}
437
438void KTextEditor::EditorPrivate::deregisterView(KTextEditor::ViewPrivate *view)
439{
440 auto it = std::find(first: m_views.begin(), last: m_views.end(), val: view);
441 Q_ASSERT(it != m_views.end());
442 m_views.erase(position: it);
443}
444
445KTextEditor::Command *KTextEditor::EditorPrivate::queryCommand(const QString &cmd) const
446{
447 return m_cmdManager->queryCommand(cmd);
448}
449
450QList<KTextEditor::Command *> KTextEditor::EditorPrivate::commands() const
451{
452 return m_cmdManager->commands();
453}
454
455QStringList KTextEditor::EditorPrivate::commandList() const
456{
457 return m_cmdManager->commandList();
458}
459
460KateVariableExpansionManager *KTextEditor::EditorPrivate::variableExpansionManager()
461{
462 return m_variableExpansionManager;
463}
464
465void KTextEditor::EditorPrivate::updateColorPalette()
466{
467 // reload the global schema (triggers reload for every view as well)
468 // might trigger selection of better matching theme for new palette
469 m_rendererConfig->reloadSchema();
470
471 // force full update of all view caches and colors
472 m_rendererConfig->updateConfig();
473}
474
475void KTextEditor::EditorPrivate::copyToClipboard(const QString &text, const QString &fileName)
476{
477 // empty => nop
478 if (text.isEmpty()) {
479 return;
480 }
481
482 // move to clipboard
483 QApplication::clipboard()->setText(text, mode: QClipboard::Clipboard);
484
485 // LRU, kill potential duplicated, move new entry to top
486 // cut after X entries
487 auto entry = ClipboardEntry{.text: text, .fileName: fileName};
488 m_clipboardHistory.removeOne(t: entry);
489 m_clipboardHistory.prepend(t: entry);
490 if (m_clipboardHistory.size() > m_viewConfig->clipboardHistoryEntries()) {
491 m_clipboardHistory.removeLast();
492 }
493
494 // notify about change
495 Q_EMIT clipboardHistoryChanged();
496}
497
498bool KTextEditor::EditorPrivate::eventFilter(QObject *obj, QEvent *event)
499{
500 if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) {
501 // only update the color once for the event that belongs to the qApp
502 updateColorPalette();
503 }
504
505 return false; // always continue processing
506}
507
508QStringListModel *KTextEditor::EditorPrivate::searchHistoryModel()
509{
510 if (!m_searchHistoryModel) {
511 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KTextEditor::Search"));
512 const QStringList history = cg.readEntry(QStringLiteral("Search History"), aDefault: QStringList());
513 m_searchHistoryModel = new QStringListModel(history, this);
514 }
515 return m_searchHistoryModel;
516}
517
518QStringListModel *KTextEditor::EditorPrivate::replaceHistoryModel()
519{
520 if (!m_replaceHistoryModel) {
521 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KTextEditor::Search"));
522 const QStringList history = cg.readEntry(QStringLiteral("Replace History"), aDefault: QStringList());
523 m_replaceHistoryModel = new QStringListModel(history, this);
524 }
525 return m_replaceHistoryModel;
526}
527
528void KTextEditor::EditorPrivate::saveSearchReplaceHistoryModels()
529{
530 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KTextEditor::Search"));
531 if (m_searchHistoryModel) {
532 cg.writeEntry(QStringLiteral("Search History"), value: m_searchHistoryModel->stringList());
533 }
534 if (m_replaceHistoryModel) {
535 cg.writeEntry(QStringLiteral("Replace History"), value: m_replaceHistoryModel->stringList());
536 }
537}
538
539KSharedConfigPtr KTextEditor::EditorPrivate::config()
540{
541 // use dummy config for unit tests!
542 if (KTextEditor::EditorPrivate::unitTestMode()) {
543 return KSharedConfig::openConfig(QStringLiteral("katepartrc-unittest"), mode: KConfig::SimpleConfig, type: QStandardPaths::TempLocation);
544 }
545
546 // else: use application configuration, but try to transfer global settings on first use
547 auto applicationConfig = KSharedConfig::openConfig();
548 if (!KConfigGroup(applicationConfig, QStringLiteral("KTextEditor Editor")).exists()) {
549 auto globalConfig = KSharedConfig::openConfig(QStringLiteral("katepartrc"));
550 for (const auto &group : {QStringLiteral("Editor"), QStringLiteral("Document"), QStringLiteral("View"), QStringLiteral("Renderer")}) {
551 KConfigGroup origin(globalConfig, group);
552 KConfigGroup destination(applicationConfig, QStringLiteral("KTextEditor ") + group);
553 origin.copyTo(other: &destination);
554 }
555 }
556 return applicationConfig;
557}
558
559void KTextEditor::EditorPrivate::triggerConfigChanged()
560{
561 // trigger delayed emission, will collapse multiple events to one signal emission
562 m_configWasChanged = true;
563 QTimer::singleShot(interval: 0, receiver: this, slot: &KTextEditor::EditorPrivate::emitConfigChanged);
564}
565
566void KTextEditor::EditorPrivate::emitConfigChanged()
567{
568 // emit only once, if still needed
569 if (m_configWasChanged) {
570 m_configWasChanged = false;
571 Q_EMIT configChanged(editor: this);
572 }
573}
574
575void KTextEditor::EditorPrivate::copyToMulticursorClipboard(const QStringList &texts)
576{
577 if (texts.size() == 1) {
578 qWarning() << "Unexpected size 1 of multicursorClipboard. It should either be empty or greater than 1";
579 m_multicursorClipboard = QStringList();
580 Q_ASSERT(false);
581 return;
582 }
583 m_multicursorClipboard = texts;
584}
585
586QStringList KTextEditor::EditorPrivate::multicursorClipboard() const
587{
588 return m_multicursorClipboard;
589}
590
591QTextToSpeech *KTextEditor::EditorPrivate::speechEngine(KTextEditor::ViewPrivate *view)
592{
593 Q_ASSERT(view);
594 if (!m_speechEngine) {
595 m_speechEngine = new QTextToSpeech(this);
596
597 // error handler for errors happening during speech output
598 connect(sender: m_speechEngine, signal: &QTextToSpeech::errorOccurred, context: this, slot: [this](QTextToSpeech::ErrorReason, const QString &errorString) {
599 if (m_speechEngineLastUser) {
600 speechError(view: m_speechEngineLastUser, errorString);
601 }
602 });
603
604 // handle init errors, that will not emit errorOccurred later
605 if (m_speechEngine->errorReason() != QTextToSpeech::ErrorReason::NoError) {
606 speechError(view, errorString: m_speechEngine->errorString());
607 }
608 }
609
610 // register current view, handle error output and stopping of the speaking
611 if (view != m_speechEngineLastUser) {
612 if (m_speechEngineLastUser) {
613 disconnect(sender: m_speechEngineLastUser, signal: &QObject::destroyed, receiver: this, slot: &KTextEditor::EditorPrivate::speechEngineUserDestoyed);
614 }
615 m_speechEngineLastUser = view;
616 connect(sender: m_speechEngineLastUser, signal: &QObject::destroyed, context: this, slot: &KTextEditor::EditorPrivate::speechEngineUserDestoyed);
617 }
618
619 return m_speechEngine;
620}
621
622void KTextEditor::EditorPrivate::speechEngineUserDestoyed()
623{
624 Q_ASSERT(m_speechEngine);
625 m_speechEngine->stop();
626}
627
628void KTextEditor::EditorPrivate::speechError(KTextEditor::ViewPrivate *view, const QString &errorString)
629{
630 Q_ASSERT(view);
631 auto message = new KTextEditor::Message(errorString, Message::Error);
632 message->setPosition(KTextEditor::Message::TopInView);
633 message->setView(view);
634 view->document()->postMessage(message);
635}
636
637#include "moc_kateglobal.cpp"
638

source code of ktexteditor/src/utils/kateglobal.cpp