File indexing completed on 2024-04-28 17:06:31

0001 /*
0002     SPDX-FileCopyrightText: 2004 Shie Erlich <erlich@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2004 Rafi Yanai <yanai@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2006 Jonas Bähr <jonas.baehr@web.de>
0005     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "kraction.h"
0011 
0012 // QtCore
0013 #include <QDebug>
0014 #include <QEvent>
0015 #include <QFile>
0016 #include <QMimeDatabase>
0017 #include <QMimeType>
0018 #include <QRegExp>
0019 #include <QTextStream>
0020 // QtGui
0021 #include <QKeyEvent>
0022 // QtWidgets
0023 #include <QAction>
0024 #include <QBoxLayout>
0025 #include <QCheckBox>
0026 #include <QDialogButtonBox>
0027 #include <QFileDialog>
0028 #include <QLabel>
0029 #include <QLayout>
0030 #include <QPushButton>
0031 #include <QSplitter>
0032 // QtXml
0033 #include <QDomElement>
0034 
0035 #include <KConfigCore/KSharedConfig>
0036 #include <KCoreAddons/KShell>
0037 #include <KI18n/KLocalizedString>
0038 #include <KParts/ReadOnlyPart>
0039 #include <KWidgetsAddons/KMessageBox>
0040 #include <KXmlGui/KActionCollection>
0041 
0042 #include "../GUI/terminaldock.h"
0043 #include "../defaults.h"
0044 #include "../icon.h"
0045 #include "../krglobal.h"
0046 #include "../krservices.h"
0047 #include "../krusaderview.h"
0048 #include "expander.h"
0049 #include "useraction.h"
0050 
0051 // KrActionProcDlg
0052 KrActionProcDlg::KrActionProcDlg(const QString &caption, bool enableStderr, QWidget *parent)
0053     : QDialog(parent)
0054     , _stdout(nullptr)
0055     , _stderr(nullptr)
0056     , _currentTextEdit(nullptr)
0057 {
0058     setWindowTitle(caption);
0059     setWindowModality(Qt::NonModal);
0060 
0061     auto *mainLayout = new QVBoxLayout;
0062     setLayout(mainLayout);
0063 
0064     // do we need to separate stderr and stdout?
0065     if (enableStderr) {
0066         auto *splitt = new QSplitter(Qt::Horizontal, this);
0067         mainLayout->addWidget(splitt);
0068         // create stdout
0069         QWidget *stdoutWidget = new QWidget(splitt);
0070         auto *stdoutBox = new QVBoxLayout(stdoutWidget);
0071 
0072         stdoutBox->addWidget(new QLabel(i18n("Standard Output (stdout)"), stdoutWidget));
0073         _stdout = new KTextEdit(stdoutWidget);
0074         _stdout->setReadOnly(true);
0075         stdoutBox->addWidget(_stdout);
0076         // create stderr
0077         QWidget *stderrWidget = new QWidget(splitt);
0078         auto *stderrBox = new QVBoxLayout(stderrWidget);
0079 
0080         stderrBox->addWidget(new QLabel(i18n("Standard Error (stderr)"), stderrWidget));
0081         _stderr = new KTextEdit(stderrWidget);
0082         _stderr->setReadOnly(true);
0083         stderrBox->addWidget(_stderr);
0084     } else {
0085         // create stdout
0086         mainLayout->addWidget(new QLabel(i18n("Output")));
0087         _stdout = new KTextEdit;
0088         _stdout->setReadOnly(true);
0089         mainLayout->addWidget(_stdout);
0090     }
0091 
0092     _currentTextEdit = _stdout;
0093     connect(_stdout, &KTextEdit::textChanged, this, &KrActionProcDlg::currentTextEditChanged);
0094     if (_stderr)
0095         connect(_stderr, &KTextEdit::textChanged, this, &KrActionProcDlg::currentTextEditChanged);
0096 
0097     KConfigGroup group(krConfig, "UserActions");
0098     normalFont = group.readEntry("Normal Font", _UserActions_NormalFont);
0099     fixedFont = group.readEntry("Fixed Font", _UserActions_FixedFont);
0100     bool startupState = group.readEntry("Use Fixed Font", _UserActions_UseFixedFont);
0101     toggleFixedFont(startupState);
0102 
0103     auto *hbox = new QHBoxLayout;
0104     QCheckBox *useFixedFont = new QCheckBox(i18n("Use font with fixed width"));
0105     useFixedFont->setChecked(startupState);
0106     hbox->addWidget(useFixedFont);
0107 
0108     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0109     hbox->addWidget(buttonBox);
0110 
0111     mainLayout->addLayout(hbox);
0112 
0113     closeButton = buttonBox->button(QDialogButtonBox::Close);
0114     closeButton->setEnabled(false);
0115 
0116     auto *saveAsButton = new QPushButton;
0117     KGuiItem::assign(saveAsButton, KStandardGuiItem::saveAs());
0118     buttonBox->addButton(saveAsButton, QDialogButtonBox::ActionRole);
0119 
0120     killButton = new QPushButton(i18n("Kill"));
0121     killButton->setToolTip(i18n("Kill the running process"));
0122     killButton->setDefault(true);
0123     buttonBox->addButton(killButton, QDialogButtonBox::ActionRole);
0124 
0125     connect(killButton, &QPushButton::clicked, this, &KrActionProcDlg::killClicked);
0126     connect(saveAsButton, &QPushButton::clicked, this, &KrActionProcDlg::slotSaveAs);
0127     connect(buttonBox, &QDialogButtonBox::rejected, this, &KrActionProcDlg::reject);
0128     connect(useFixedFont, &QCheckBox::toggled, this, &KrActionProcDlg::toggleFixedFont);
0129 
0130     resize(sizeHint() * 2);
0131 }
0132 
0133 void KrActionProcDlg::addStderr(const QString &str)
0134 {
0135     if (_stderr)
0136         _stderr->append(str);
0137     else {
0138         _stdout->setFontItalic(true);
0139         _stdout->append(str);
0140         _stdout->setFontItalic(false);
0141     }
0142 }
0143 
0144 void KrActionProcDlg::addStdout(const QString &str)
0145 {
0146     _stdout->append(str);
0147 }
0148 
0149 void KrActionProcDlg::toggleFixedFont(bool state)
0150 {
0151     if (state) {
0152         _stdout->setFont(fixedFont);
0153         if (_stderr)
0154             _stderr->setFont(fixedFont);
0155     } else {
0156         _stdout->setFont(normalFont);
0157         if (_stderr)
0158             _stderr->setFont(normalFont);
0159     }
0160 }
0161 
0162 void KrActionProcDlg::slotSaveAs()
0163 {
0164     QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), i18n("*.txt|Text files\n*|All files"));
0165     if (filename.isEmpty())
0166         return;
0167     QFile file(filename);
0168     int answer = KMessageBox::Yes;
0169     if (file.exists())
0170         answer = KMessageBox::warningYesNoCancel(this, // parent
0171                                                  i18n("This file already exists.\nDo you want to overwrite it or append the output?"), // text
0172                                                  i18n("Overwrite or append?"), // caption
0173                                                  KStandardGuiItem::overwrite(), // label for Yes-Button
0174                                                  KGuiItem(i18n("Append")) // label for No-Button
0175         );
0176     if (answer == KMessageBox::Cancel)
0177         return;
0178     bool open;
0179     if (answer == KMessageBox::No) // this means to append
0180         open = file.open(QIODevice::WriteOnly | QIODevice::Append);
0181     else
0182         open = file.open(QIODevice::WriteOnly);
0183 
0184     if (!open) {
0185         KMessageBox::error(this, i18n("Cannot open %1 for writing.\nNothing exported.", filename), i18n("Export failed"));
0186         return;
0187     }
0188 
0189     QTextStream stream(&file);
0190     stream << _currentTextEdit->toPlainText();
0191     file.close();
0192 }
0193 
0194 void KrActionProcDlg::slotProcessFinished()
0195 {
0196     closeButton->setEnabled(true);
0197     killButton->setEnabled(false);
0198 }
0199 
0200 void KrActionProcDlg::currentTextEditChanged()
0201 {
0202     if (_stderr && _stderr->hasFocus())
0203         _currentTextEdit = _stderr;
0204     else
0205         _currentTextEdit = _stdout;
0206 }
0207 
0208 // KrActionProc
0209 KrActionProc::KrActionProc(KrActionBase *action)
0210     : _action(action)
0211     , _proc(nullptr)
0212     , _output(nullptr)
0213 {
0214 }
0215 
0216 KrActionProc::~KrActionProc()
0217 {
0218     delete _proc;
0219 }
0220 
0221 void KrActionProc::start(const QString &cmdLine)
0222 {
0223     QStringList list;
0224     list << cmdLine;
0225     start(list);
0226 }
0227 
0228 void KrActionProc::start(QStringList cmdLineList)
0229 {
0230     QString cmd; // this is the command which is really executed (with  maybe kdesu, maybe konsole, ...)
0231     // in case no specific working directory has been requested, execute in a relatively safe place
0232     QString workingDir = QDir::tempPath();
0233 
0234     if (!_action->startpath().isEmpty())
0235         workingDir = _action->startpath();
0236 
0237     if (!_action->user().isEmpty()) {
0238         if (!KrServices::isExecutable(KDESU_PATH)) {
0239             KMessageBox::error(nullptr,
0240                                i18n("Cannot run user action, %1 not found or not executable. "
0241                                     "Please verify that kde-cli-tools are installed.",
0242                                     QString(KDESU_PATH)));
0243             return;
0244         }
0245     }
0246 
0247     if (_action->execType() == KrAction::RunInTE && (!MAIN_VIEW->terminalDock()->initialise())) {
0248         KMessageBox::error(nullptr, i18n("Embedded terminal emulator does not work, using output collection instead."));
0249     }
0250 
0251     for (QStringList::Iterator it = cmdLineList.begin(); it != cmdLineList.end(); ++it) {
0252         if (!cmd.isEmpty())
0253             cmd += " ; "; // TODO make this separator configurable (users may want && or ||)
0254         // TODO: read header fom config or action-properties and place it on top of each command
0255         if (cmdLineList.count() > 1)
0256             cmd += "echo --------------------------------------- ; ";
0257         cmd += *it;
0258     }
0259 
0260     // make sure the command gets executed in the right directory
0261     cmd = "(cd " + KrServices::quote(workingDir) + " && (" + cmd + "))";
0262 
0263     if (_action->execType() == KrAction::RunInTE && MAIN_VIEW->terminalDock()->isInitialised()) { // send the commandline contents to the terminal emulator
0264         if (!_action->user().isEmpty()) {
0265             // "-t" is necessary that kdesu displays the terminal-output of the command
0266             cmd = KrServices::quote(KDESU_PATH) + " -t -u " + _action->user() + " -c " + KrServices::quote(cmd);
0267         }
0268         MAIN_VIEW->terminalDock()->sendInput(cmd + '\n');
0269         deleteLater();
0270     } else { // will start a new process
0271         _proc = new KProcess(this);
0272         _proc->clearProgram(); // this clears the arglist too
0273         _proc->setWorkingDirectory(workingDir);
0274         connect(_proc, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), this, &KrActionProc::processExited);
0275 
0276         if (_action->execType() == KrAction::Normal || _action->execType() == KrAction::Terminal) { // not collect output
0277             if (_action->execType() == KrAction::Terminal) { // run in terminal
0278                 KConfigGroup group(krConfig, "UserActions");
0279                 QString term = group.readEntry("Terminal", _UserActions_Terminal);
0280                 QStringList termArgs = KShell::splitArgs(term, KShell::TildeExpand);
0281                 if (termArgs.isEmpty()) {
0282                     KMessageBox::error(nullptr, i18nc("Arg is a string containing the bad quoting.", "Bad quoting in terminal command:\n%1", term));
0283                     deleteLater();
0284                     return;
0285                 }
0286                 for (int i = 0; i != termArgs.size(); i++) {
0287                     if (termArgs[i] == "%t")
0288                         termArgs[i] = cmdLineList.join(" ; ");
0289                     else if (termArgs[i] == "%d")
0290                         termArgs[i] = workingDir;
0291                 }
0292                 termArgs << "sh"
0293                          << "-c" << cmd;
0294                 cmd = KrServices::quote(termArgs).join(" ");
0295             }
0296             if (!_action->user().isEmpty()) {
0297                 cmd = KrServices::quote(KDESU_PATH) + " -u " + _action->user() + " -c " + KrServices::quote(cmd);
0298             }
0299         } else { // collect output
0300             bool separateStderr = false;
0301             if (_action->execType() == KrAction::CollectOutputSeparateStderr)
0302                 separateStderr = true;
0303             _output = new KrActionProcDlg(_action->text(), separateStderr);
0304             // connect the output to the dialog
0305             _proc->setOutputChannelMode(KProcess::SeparateChannels);
0306             connect(_proc, &KProcess::readyReadStandardError, this, &KrActionProc::addStderr);
0307             connect(_proc, &KProcess::readyReadStandardOutput, this, &KrActionProc::addStdout);
0308             connect(_output, &KrActionProcDlg::killClicked, this, &KrActionProc::kill);
0309             _output->show();
0310 
0311             if (!_action->user().isEmpty()) {
0312                 // "-t" is necessary that kdesu displays the terminal-output of the command
0313                 cmd = KrServices::quote(KDESU_PATH) + " -t -u " + _action->user() + " -c " + KrServices::quote(cmd);
0314             }
0315         }
0316         // printf("cmd: %s\n", cmd.toAscii().data());
0317         _proc->setShellCommand(cmd);
0318         _proc->start();
0319     }
0320 }
0321 
0322 void KrActionProc::processExited(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/)
0323 {
0324     // enable the 'close' button on the dialog (if active), disable 'kill' button
0325     if (_output) {
0326         // TODO tell the user the program exit code
0327         _output->slotProcessFinished();
0328     }
0329     delete this; // banzai!!
0330 }
0331 
0332 void KrActionProc::addStderr()
0333 {
0334     if (_output) {
0335         _output->addStderr(QString::fromLocal8Bit(_proc->readAllStandardError().data()));
0336     }
0337 }
0338 
0339 void KrActionProc::addStdout()
0340 {
0341     if (_output) {
0342         _output->addStdout(QString::fromLocal8Bit(_proc->readAllStandardOutput().data()));
0343     }
0344 }
0345 
0346 // KrAction
0347 KrAction::KrAction(KActionCollection *parent, const QString &name)
0348     : QAction((QObject *)parent)
0349 {
0350     _actionCollection = parent;
0351     setObjectName(name);
0352     parent->addAction(name, this);
0353 
0354     connect(this, &KrAction::triggered, this, &KrAction::exec);
0355 }
0356 
0357 KrAction::~KrAction()
0358 {
0359     foreach (QWidget *w, associatedWidgets())
0360         w->removeAction(this);
0361     krUserAction->removeKrAction(this); // Importent! Else Krusader will crash when writing the actions to file
0362 }
0363 
0364 bool KrAction::isAvailable(const QUrl &currentURL)
0365 {
0366     bool available = true; // show per default (FIXME: make the default an attribute of <availability>)
0367 
0368     // check protocol
0369     if (!_showonlyProtocol.empty()) {
0370         available = false;
0371         for (auto &it : _showonlyProtocol) {
0372             // qDebug() << "KrAction::isAvailable currentProtocol: " << currentURL.scheme() << " =?= " << *it;
0373             if (currentURL.scheme() == it) { // FIXME remove trailing slashes at the xml-parsing (faster because done only once)
0374                 available = true;
0375                 break;
0376             }
0377         }
0378     } // check protocol: done
0379 
0380     // check the Path-list:
0381     if (available && !_showonlyPath.empty()) {
0382         available = false;
0383         for (auto &it : _showonlyPath) {
0384             if (it.right(1) == "*") {
0385                 if (currentURL.path().indexOf(it.left(it.length() - 1)) == 0) {
0386                     available = true;
0387                     break;
0388                 }
0389             } else if (currentURL.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path()
0390                        == it) { // FIXME remove trailing slashes at the xml-parsing (faster because done only once)
0391                 available = true;
0392                 break;
0393             }
0394         }
0395     } // check the Path-list: done
0396 
0397     // check mime-type
0398     if (available && !_showonlyMime.empty()) {
0399         available = false;
0400         QMimeDatabase db;
0401         QMimeType mime = db.mimeTypeForUrl(currentURL);
0402         if (mime.isValid()) {
0403             for (auto &it : _showonlyMime) {
0404                 if (it.contains("/")) {
0405                     if (mime.inherits(it)) { // don't use ==; use 'inherits()' instead, which is aware of inheritance (ie: text/x-makefile is also text/plain)
0406                         available = true;
0407                         break;
0408                     }
0409                 } else {
0410                     if (mime.name().indexOf(it) == 0) { // 0 is the beginning, -1 is not found
0411                         available = true;
0412                         break;
0413                     }
0414                 }
0415             } // for
0416         }
0417     } // check the mime-type: done
0418 
0419     // check filename
0420     if (available && !_showonlyFile.empty()) {
0421         available = false;
0422         for (auto &it : _showonlyFile) {
0423             QRegExp regex = QRegExp(it, Qt::CaseInsensitive, QRegExp::Wildcard); // case-sensitive = false; wildcards = true
0424             if (regex.exactMatch(currentURL.fileName())) {
0425                 available = true;
0426                 break;
0427             }
0428         }
0429     } // check the filename: done
0430 
0431     return available;
0432 }
0433 
0434 bool KrAction::xmlRead(const QDomElement &element)
0435 {
0436     /*
0437        This has to be done elsewhere!!
0438 
0439        if ( element.tagName() != "action" )
0440           return false;
0441 
0442        Also the name has to be checked before the action is created!
0443 
0444        setName( element.attribute( "name" ).toLatin1() );
0445     */
0446     QString attr;
0447 
0448     attr = element.attribute("enabled", "true"); // default: "true"
0449     if (attr == "false") {
0450         setEnabled(false);
0451         setVisible(false);
0452     }
0453 
0454     for (QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) {
0455         QDomElement e = node.toElement();
0456         if (e.isNull())
0457             continue; // this should skip nodes which are not elements ( i.e. comments, <!-- -->, or text nodes)
0458 
0459         if (e.tagName() == "title")
0460             setText(i18n(e.text().toUtf8().constData()));
0461         else if (e.tagName() == "tooltip")
0462             setToolTip(i18n(e.text().toUtf8().constData()));
0463         else if (e.tagName() == "icon")
0464             setIcon(Icon(_iconName = e.text()));
0465         else if (e.tagName() == "category")
0466             setCategory(i18n(e.text().toUtf8().constData()));
0467         else if (e.tagName() == "description")
0468             setWhatsThis(i18n(e.text().toUtf8().constData()));
0469         else if (e.tagName() == "command")
0470             readCommand(e);
0471         else if (e.tagName() == "startpath")
0472             setStartpath(e.text());
0473         else if (e.tagName() == "availability")
0474             readAvailability(e);
0475         else if (e.tagName() == "defaultshortcut")
0476             _actionCollection->setDefaultShortcut(this, QKeySequence(e.text()));
0477         else
0478 
0479             // unknown but not empty
0480             if (!e.tagName().isEmpty())
0481                 qWarning() << "KrAction::xmlRead() - unrecognized tag found: <action name=\"" << objectName() << "\"><" << e.tagName() << ">";
0482 
0483     } // for ( QDomNode node = action->firstChild(); !node.isNull(); node = node.nextSibling() )
0484 
0485     return true;
0486 } // KrAction::xmlRead
0487 
0488 QDomElement KrAction::xmlDump(QDomDocument &doc) const
0489 {
0490     QDomElement actionElement = doc.createElement("action");
0491     actionElement.setAttribute("name", objectName());
0492 
0493     if (!isVisible()) {
0494         actionElement.setAttribute("enabled", "false");
0495     }
0496 
0497 #define TEXT_ELEMENT(TAGNAME, TEXT)                                                                                                                            \
0498     {                                                                                                                                                          \
0499         QDomElement e = doc.createElement(TAGNAME);                                                                                                            \
0500         e.appendChild(doc.createTextNode(TEXT));                                                                                                               \
0501         actionElement.appendChild(e);                                                                                                                          \
0502     }
0503 
0504     TEXT_ELEMENT("title", text())
0505 
0506     if (!toolTip().isEmpty())
0507         TEXT_ELEMENT("tooltip", toolTip())
0508 
0509     if (!_iconName.isEmpty())
0510         TEXT_ELEMENT("icon", _iconName)
0511 
0512     if (!category().isEmpty())
0513         TEXT_ELEMENT("category", category())
0514 
0515     if (!whatsThis().isEmpty())
0516         TEXT_ELEMENT("description", whatsThis())
0517 
0518     actionElement.appendChild(dumpCommand(doc));
0519 
0520     if (!startpath().isEmpty())
0521         TEXT_ELEMENT("startpath", startpath())
0522 
0523     QDomElement availabilityElement = dumpAvailability(doc);
0524 
0525     if (availabilityElement.hasChildNodes())
0526         actionElement.appendChild(availabilityElement);
0527 
0528     if (!shortcut().isEmpty())
0529         TEXT_ELEMENT("defaultshortcut", shortcut().toString()) //.toString() would return a localised string which can't be read again
0530 
0531     return actionElement;
0532 } // KrAction::xmlDump
0533 
0534 void KrAction::readCommand(const QDomElement &element)
0535 {
0536     QString attr;
0537 
0538     attr = element.attribute("executionmode", "normal"); // default: "normal"
0539     if (attr == "normal")
0540         setExecType(Normal);
0541     else if (attr == "terminal")
0542         setExecType(Terminal);
0543     else if (attr == "collect_output")
0544         setExecType(CollectOutput);
0545     else if (attr == "collect_output_separate_stderr")
0546         setExecType(CollectOutputSeparateStderr);
0547     else if (attr == "embedded_terminal")
0548         setExecType(RunInTE);
0549     else
0550         qWarning() << "KrAction::readCommand() - unrecognized attribute value found: <action name=\"" << objectName() << "\"><command executionmode=\"" << attr
0551                    << "\"";
0552 
0553     attr = element.attribute("accept", "local"); // default: "local"
0554     if (attr == "local")
0555         setAcceptURLs(false);
0556     else if (attr == "url")
0557         setAcceptURLs(true);
0558     else
0559         qWarning() << "KrAction::readCommand() - unrecognized attribute value found: <action name=\"" << objectName() << "\"><command accept=\"" << attr
0560                    << "\"";
0561 
0562     attr = element.attribute("confirmexecution", "false"); // default: "false"
0563     if (attr == "true")
0564         setConfirmExecution(true);
0565     else
0566         setConfirmExecution(false);
0567 
0568     setUser(element.attribute("run_as"));
0569 
0570     setCommand(element.text());
0571 
0572 } // KrAction::readCommand
0573 
0574 QDomElement KrAction::dumpCommand(QDomDocument &doc) const
0575 {
0576     QDomElement commandElement = doc.createElement("command");
0577 
0578     switch (execType()) {
0579     case Terminal:
0580         commandElement.setAttribute("executionmode", "terminal");
0581         break;
0582     case CollectOutput:
0583         commandElement.setAttribute("executionmode", "collect_output");
0584         break;
0585     case CollectOutputSeparateStderr:
0586         commandElement.setAttribute("executionmode", "collect_output_separate_stderr");
0587         break;
0588     case RunInTE:
0589         commandElement.setAttribute("executionmode", "embedded_terminal");
0590         break;
0591     default:
0592         // don't write the default to file
0593         break;
0594     }
0595 
0596     if (acceptURLs())
0597         commandElement.setAttribute("accept", "url");
0598 
0599     if (confirmExecution())
0600         commandElement.setAttribute("confirmexecution", "true");
0601 
0602     if (!user().isEmpty())
0603         commandElement.setAttribute("run_as", user());
0604 
0605     commandElement.appendChild(doc.createTextNode(command()));
0606 
0607     return commandElement;
0608 } // KrAction::dumpCommand
0609 
0610 void KrAction::readAvailability(const QDomElement &element)
0611 {
0612     for (QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) {
0613         QDomElement e = node.toElement();
0614         if (e.isNull())
0615             continue; // this should skip nodes which are not elements ( i.e. comments, <!-- -->, or text nodes)
0616 
0617         QStringList *showlist = nullptr;
0618 
0619         if (e.tagName() == "protocol")
0620             showlist = &_showonlyProtocol;
0621         else if (e.tagName() == "path")
0622             showlist = &_showonlyPath;
0623         else if (e.tagName() == "mimetype")
0624             showlist = &_showonlyMime;
0625         else if (e.tagName() == "filename")
0626             showlist = &_showonlyFile;
0627         else {
0628             qWarning() << "KrAction::readAvailability() - unrecognized element found: <action name=\"" << objectName() << "\"><availability><" << e.tagName()
0629                        << ">";
0630             showlist = nullptr;
0631         }
0632 
0633         if (showlist) {
0634             for (QDomNode subnode = e.firstChild(); !subnode.isNull(); subnode = subnode.nextSibling()) {
0635                 QDomElement subelement = subnode.toElement();
0636                 if (subelement.tagName() == "show")
0637                     showlist->append(subelement.text());
0638             } // for
0639         } // if ( showlist )
0640 
0641     } // for
0642 } // KrAction::readAvailability
0643 
0644 QDomElement KrAction::dumpAvailability(QDomDocument &doc) const
0645 {
0646     QDomElement availabilityElement = doc.createElement("availability");
0647 
0648 #define LIST_ELEMENT(TAGNAME, LIST)                                                                                                                            \
0649     {                                                                                                                                                          \
0650         QDomElement e = doc.createElement(TAGNAME);                                                                                                            \
0651         for (QStringList::const_iterator it = LIST.constBegin(); it != LIST.constEnd(); ++it) {                                                                \
0652             QDomElement show = doc.createElement("show");                                                                                                      \
0653             show.appendChild(doc.createTextNode(*it));                                                                                                         \
0654             e.appendChild(show);                                                                                                                               \
0655         }                                                                                                                                                      \
0656         availabilityElement.appendChild(e);                                                                                                                    \
0657     }
0658 
0659     if (!_showonlyProtocol.isEmpty())
0660         LIST_ELEMENT("protocol", _showonlyProtocol)
0661 
0662     if (!_showonlyPath.isEmpty())
0663         LIST_ELEMENT("path", _showonlyPath)
0664 
0665     if (!_showonlyMime.isEmpty())
0666         LIST_ELEMENT("mimetype", _showonlyMime)
0667 
0668     if (!_showonlyFile.isEmpty())
0669         LIST_ELEMENT("filename", _showonlyFile)
0670 
0671     return availabilityElement;
0672 } // KrAction::dumpAvailability