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

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