KPimTextEdit

richtextexternalcomposer.cpp
1/*
2 SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "richtextexternalcomposer.h"
8using namespace Qt::Literals::StringLiterals;
9
10#include "richtextcomposer.h"
11
12#include <KLocalizedString>
13#include <KMacroExpander>
14#include <KMessageBox>
15#include <KProcess>
16#include <KShell>
17#include <QTemporaryFile>
18
19using namespace KPIMTextEdit;
20
21class Q_DECL_HIDDEN RichTextExternalComposer::RichTextExternalComposerPrivate
22{
23public:
24 RichTextExternalComposerPrivate(RichTextComposer *composer)
25 : richTextComposer(composer)
26 {
27 }
28
29 void cannotStartProcess(const QString &commandLine);
30 QString extEditorPath;
31 KProcess *externalEditorProcess = nullptr;
32 QTemporaryFile *extEditorTempFile = nullptr;
33 RichTextComposer *const richTextComposer;
34 bool useExtEditor = false;
35};
36
37RichTextExternalComposer::RichTextExternalComposer(RichTextComposer *composer, QObject *parent)
38 : QObject(parent)
39 , d(new RichTextExternalComposerPrivate(composer))
40{
41}
42
43RichTextExternalComposer::~RichTextExternalComposer() = default;
44
45bool RichTextExternalComposer::useExternalEditor() const
46{
47 return d->useExtEditor;
48}
49
50void RichTextExternalComposer::setUseExternalEditor(bool value)
51{
52 d->useExtEditor = value;
53}
54
55void RichTextExternalComposer::setExternalEditorPath(const QString &path)
56{
57 d->extEditorPath = path;
58}
59
60QString RichTextExternalComposer::externalEditorPath() const
61{
62 return d->extEditorPath;
63}
64
65void RichTextExternalComposer::startExternalEditor()
66{
67 if (d->useExtEditor && !d->externalEditorProcess) {
68 const QString commandLine = d->extEditorPath.trimmed();
69 if (d->extEditorPath.isEmpty()) {
70 setUseExternalEditor(false);
71 KMessageBox::error(d->richTextComposer, i18n("Command line is empty. Please verify settings."), i18nc("@title:window", "Empty command line"));
72 return;
73 }
74
75 d->extEditorTempFile = new QTemporaryFile();
76 if (!d->extEditorTempFile->open()) {
77 delete d->extEditorTempFile;
78 d->extEditorTempFile = nullptr;
79 setUseExternalEditor(false);
80 return;
81 }
82
83 d->extEditorTempFile->write(d->richTextComposer->textOrHtml().toUtf8());
84 d->extEditorTempFile->close();
85
86 d->externalEditorProcess = new KProcess();
87 // construct command line...
89 map.insert(QLatin1Char('l'), QString::number(d->richTextComposer->textCursor().blockNumber() + 1));
90 map.insert(QLatin1Char('w'), QString::number(static_cast<qulonglong>(d->richTextComposer->winId())));
91 map.insert(QLatin1Char('f'), d->extEditorTempFile->fileName());
92 const QString cmd = KMacroExpander::expandMacrosShellQuote(commandLine, map);
93 const QStringList arg = KShell::splitArgs(cmd);
94 bool filenameAdded = false;
95 if (commandLine.contains("%f"_L1)) {
96 filenameAdded = true;
97 }
98 QStringList command;
99 if (!arg.isEmpty()) {
100 command << arg;
101 }
102 if (command.isEmpty()) {
103 d->cannotStartProcess(commandLine);
104 return;
105 }
106
107 (*d->externalEditorProcess) << command;
108 if (!filenameAdded) { // no %f in the editor command
109 (*d->externalEditorProcess) << d->extEditorTempFile->fileName();
110 }
111
112 connect(d->externalEditorProcess, &KProcess::finished, this, &RichTextExternalComposer::slotEditorFinished);
113 d->externalEditorProcess->start();
114 if (!d->externalEditorProcess->waitForStarted()) {
115 d->cannotStartProcess(commandLine);
116 } else {
117 Q_EMIT externalEditorStarted();
118 }
119 }
120}
121
122void RichTextExternalComposer::RichTextExternalComposerPrivate::cannotStartProcess(const QString &commandLine)
123{
124 KMessageBox::error(richTextComposer, i18n("External editor cannot be started. Please verify command \"%1\"", commandLine));
125 richTextComposer->killExternalEditor();
126 richTextComposer->setUseExternalEditor(false);
127}
128
129void RichTextExternalComposer::slotEditorFinished(int codeError, QProcess::ExitStatus exitStatus)
130{
131 if (exitStatus == QProcess::NormalExit) {
132 // the external editor could have renamed the original file and recreated a new file
133 // with the given filename, so we need to reopen the file after the editor exited
134 QFile localFile(d->extEditorTempFile->fileName());
135 if (localFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
136 const QByteArray f = localFile.readAll();
137 d->richTextComposer->setTextOrHtml(QString::fromUtf8(f.data(), f.size()));
138 d->richTextComposer->document()->setModified(true);
139 localFile.close();
140 }
141 if (codeError > 0) {
142 KMessageBox::error(d->richTextComposer, i18n("Error was found when we started external editor."), i18nc("@title:window", "External Editor Closed"));
143 setUseExternalEditor(false);
144 }
145 Q_EMIT externalEditorClosed();
146 }
147
148 killExternalEditor(); // cleanup...
149}
150
151bool RichTextExternalComposer::checkExternalEditorFinished()
152{
153 if (!d->externalEditorProcess) {
154 return true;
155 }
156
157 const int ret = KMessageBox::warningTwoActionsCancel(d->richTextComposer,
158 xi18nc("@info",
159 "The external editor is still running.<nl/>"
160 "Do you want to stop the editor or keep it running?<nl/>"
161 "<warning>Stopping the editor will cause all your "
162 "unsaved changes to be lost.</warning>"),
163 i18nc("@title:window", "External Editor Running"),
164 KGuiItem(i18nc("@action:button", "Stop Editor")),
165 KGuiItem(i18nc("@action:button", "Keep Editor Running")));
166
167 switch (ret) {
168 case KMessageBox::ButtonCode::PrimaryAction:
169 killExternalEditor();
170 return true;
171 case KMessageBox::ButtonCode::SecondaryAction:
172 return true;
173 default:
174 return false;
175 }
176}
177
178void RichTextExternalComposer::killExternalEditor()
179{
180 if (d->externalEditorProcess) {
181 d->externalEditorProcess->deleteLater();
182 }
183 d->externalEditorProcess = nullptr;
184 delete d->extEditorTempFile;
185 d->extEditorTempFile = nullptr;
186}
187
188bool RichTextExternalComposer::isInProgress() const
189{
190 return d->externalEditorProcess;
191}
192
193#include "moc_richtextexternalcomposer.cpp"
The RichTextComposer class.
The RichTextExternalComposer class.
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString path(const QString &relativePath)
QString expandMacrosShellQuote(const QString &str, const QHash< QChar, QString > &map, QChar c=QLatin1Char('%'))
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
ButtonCode warningTwoActionsCancel(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
char * data()
qsizetype size() const const
bool isEmpty() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void finished(int exitCode, QProcess::ExitStatus exitStatus)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:35:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.