File indexing completed on 2024-05-19 05:21:45
0001 /* 0002 SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "richtextexternalcomposer.h" 0008 #include "richtextcomposer.h" 0009 0010 #include <KLocalizedString> 0011 #include <KMacroExpander> 0012 #include <KMessageBox> 0013 #include <KProcess> 0014 #include <KShell> 0015 #include <QTemporaryFile> 0016 0017 using namespace KPIMTextEdit; 0018 0019 class Q_DECL_HIDDEN RichTextExternalComposer::RichTextExternalComposerPrivate 0020 { 0021 public: 0022 RichTextExternalComposerPrivate(RichTextComposer *composer) 0023 : richTextComposer(composer) 0024 { 0025 } 0026 0027 void cannotStartProcess(const QString &commandLine); 0028 QString extEditorPath; 0029 KProcess *externalEditorProcess = nullptr; 0030 QTemporaryFile *extEditorTempFile = nullptr; 0031 RichTextComposer *const richTextComposer; 0032 bool useExtEditor = false; 0033 }; 0034 0035 RichTextExternalComposer::RichTextExternalComposer(RichTextComposer *composer, QObject *parent) 0036 : QObject(parent) 0037 , d(new RichTextExternalComposerPrivate(composer)) 0038 { 0039 } 0040 0041 RichTextExternalComposer::~RichTextExternalComposer() = default; 0042 0043 bool RichTextExternalComposer::useExternalEditor() const 0044 { 0045 return d->useExtEditor; 0046 } 0047 0048 void RichTextExternalComposer::setUseExternalEditor(bool value) 0049 { 0050 d->useExtEditor = value; 0051 } 0052 0053 void RichTextExternalComposer::setExternalEditorPath(const QString &path) 0054 { 0055 d->extEditorPath = path; 0056 } 0057 0058 QString RichTextExternalComposer::externalEditorPath() const 0059 { 0060 return d->extEditorPath; 0061 } 0062 0063 void RichTextExternalComposer::startExternalEditor() 0064 { 0065 if (d->useExtEditor && !d->externalEditorProcess) { 0066 const QString commandLine = d->extEditorPath.trimmed(); 0067 if (d->extEditorPath.isEmpty()) { 0068 setUseExternalEditor(false); 0069 KMessageBox::error(d->richTextComposer, i18n("Command line is empty. Please verify settings."), i18nc("@title:window", "Empty command line")); 0070 return; 0071 } 0072 0073 d->extEditorTempFile = new QTemporaryFile(); 0074 if (!d->extEditorTempFile->open()) { 0075 delete d->extEditorTempFile; 0076 d->extEditorTempFile = nullptr; 0077 setUseExternalEditor(false); 0078 return; 0079 } 0080 0081 d->extEditorTempFile->write(d->richTextComposer->textOrHtml().toUtf8()); 0082 d->extEditorTempFile->close(); 0083 0084 d->externalEditorProcess = new KProcess(); 0085 // construct command line... 0086 QHash<QChar, QString> map; 0087 map.insert(QLatin1Char('l'), QString::number(d->richTextComposer->textCursor().blockNumber() + 1)); 0088 map.insert(QLatin1Char('w'), QString::number(static_cast<qulonglong>(d->richTextComposer->winId()))); 0089 map.insert(QLatin1Char('f'), d->extEditorTempFile->fileName()); 0090 const QString cmd = KMacroExpander::expandMacrosShellQuote(commandLine, map); 0091 const QStringList arg = KShell::splitArgs(cmd); 0092 bool filenameAdded = false; 0093 if (commandLine.contains(QLatin1StringView("%f"))) { 0094 filenameAdded = true; 0095 } 0096 QStringList command; 0097 if (!arg.isEmpty()) { 0098 command << arg; 0099 } 0100 if (command.isEmpty()) { 0101 d->cannotStartProcess(commandLine); 0102 return; 0103 } 0104 0105 (*d->externalEditorProcess) << command; 0106 if (!filenameAdded) { // no %f in the editor command 0107 (*d->externalEditorProcess) << d->extEditorTempFile->fileName(); 0108 } 0109 0110 connect(d->externalEditorProcess, &KProcess::finished, this, &RichTextExternalComposer::slotEditorFinished); 0111 d->externalEditorProcess->start(); 0112 if (!d->externalEditorProcess->waitForStarted()) { 0113 d->cannotStartProcess(commandLine); 0114 } else { 0115 Q_EMIT externalEditorStarted(); 0116 } 0117 } 0118 } 0119 0120 void RichTextExternalComposer::RichTextExternalComposerPrivate::cannotStartProcess(const QString &commandLine) 0121 { 0122 KMessageBox::error(richTextComposer, i18n("External editor cannot be started. Please verify command \"%1\"", commandLine)); 0123 richTextComposer->killExternalEditor(); 0124 richTextComposer->setUseExternalEditor(false); 0125 } 0126 0127 void RichTextExternalComposer::slotEditorFinished(int codeError, QProcess::ExitStatus exitStatus) 0128 { 0129 if (exitStatus == QProcess::NormalExit) { 0130 // the external editor could have renamed the original file and recreated a new file 0131 // with the given filename, so we need to reopen the file after the editor exited 0132 QFile localFile(d->extEditorTempFile->fileName()); 0133 if (localFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 0134 const QByteArray f = localFile.readAll(); 0135 d->richTextComposer->setTextOrHtml(QString::fromUtf8(f.data(), f.size())); 0136 d->richTextComposer->document()->setModified(true); 0137 localFile.close(); 0138 } 0139 if (codeError > 0) { 0140 KMessageBox::error(d->richTextComposer, i18n("Error was found when we started external editor."), i18n("External Editor Closed")); 0141 setUseExternalEditor(false); 0142 } 0143 Q_EMIT externalEditorClosed(); 0144 } 0145 0146 killExternalEditor(); // cleanup... 0147 } 0148 0149 bool RichTextExternalComposer::checkExternalEditorFinished() 0150 { 0151 if (!d->externalEditorProcess) { 0152 return true; 0153 } 0154 0155 const int ret = KMessageBox::warningTwoActionsCancel(d->richTextComposer, 0156 xi18nc("@info", 0157 "The external editor is still running.<nl/>" 0158 "Do you want to stop the editor or keep it running?<nl/>" 0159 "<warning>Stopping the editor will cause all your " 0160 "unsaved changes to be lost.</warning>"), 0161 i18nc("@title:window", "External Editor Running"), 0162 KGuiItem(i18nc("@action:button", "Stop Editor")), 0163 KGuiItem(i18nc("@action:button", "Keep Editor Running"))); 0164 0165 switch (ret) { 0166 case KMessageBox::ButtonCode::PrimaryAction: 0167 killExternalEditor(); 0168 return true; 0169 case KMessageBox::ButtonCode::SecondaryAction: 0170 return true; 0171 default: 0172 return false; 0173 } 0174 } 0175 0176 void RichTextExternalComposer::killExternalEditor() 0177 { 0178 if (d->externalEditorProcess) { 0179 d->externalEditorProcess->deleteLater(); 0180 } 0181 d->externalEditorProcess = nullptr; 0182 delete d->extEditorTempFile; 0183 d->extEditorTempFile = nullptr; 0184 } 0185 0186 bool RichTextExternalComposer::isInProgress() const 0187 { 0188 return d->externalEditorProcess; 0189 } 0190 0191 #include "moc_richtextexternalcomposer.cpp"