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

0001 /*
0002     SPDX-FileCopyrightText: 2004 Jonas Bähr <jonas.baehr@web.de>
0003     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "useraction.h"
0009 
0010 // QtCore
0011 #include <QDebug>
0012 #include <QFile>
0013 #include <QHash>
0014 #include <QStandardPaths>
0015 #include <QTextStream>
0016 #include <QUrl>
0017 // QtXml
0018 #include <QDomDocument>
0019 #include <QDomElement>
0020 // QtWidgets
0021 #include <QMenu>
0022 
0023 #include <KI18n/KLocalizedString>
0024 #include <KWidgetsAddons/KActionMenu>
0025 #include <KWidgetsAddons/KMessageBox>
0026 #include <KXmlGui/KActionCollection>
0027 
0028 #include "../FileSystem/filesystem.h"
0029 #include "../Panel/PanelView/krview.h"
0030 #include "../Panel/krpanel.h"
0031 #include "../Panel/panelfunc.h"
0032 #include "../krusader.h"
0033 #include "../krusaderview.h"
0034 #include "kraction.h"
0035 
0036 UserAction::UserAction()
0037 {
0038     readAllFiles();
0039 }
0040 
0041 UserAction::~UserAction()
0042 {
0043     // KrActions are deleted by Krusader's KActionCollection
0044 }
0045 
0046 void UserAction::removeKrAction(KrAction *action)
0047 {
0048     _actions.removeAll(action);
0049     if (_defaultActions.contains(action->objectName()))
0050         _deletedActions.insert(action->objectName());
0051 }
0052 
0053 void UserAction::setAvailability()
0054 {
0055     setAvailability(ACTIVE_FUNC->files()->getUrl(ACTIVE_PANEL->view->getCurrentItem()));
0056 }
0057 
0058 void UserAction::setAvailability(const QUrl &currentURL)
0059 {
0060     // qDebug() << "UserAction::setAvailability currentFile: " << currentURL.url();
0061     //  disable the entries that should not appear in this folder
0062     QListIterator<KrAction *> it(_actions);
0063     while (it.hasNext()) {
0064         KrAction *action = it.next();
0065         action->setEnabled(action->isAvailable(currentURL));
0066     }
0067 }
0068 
0069 void UserAction::populateMenu(KActionMenu *menu, const QUrl *currentURL)
0070 {
0071     // I have not found any method in Qt/KDE
0072     // for non-recursive searching of children by name ...
0073     QMap<QString, KActionMenu *> categoryMap;
0074     QList<KrAction *> uncategorised;
0075 
0076     foreach (KrAction *action, _actions) {
0077         const QString category = action->category();
0078         if (!action->isEnabled())
0079             continue;
0080         if (currentURL != nullptr && !action->isAvailable(*currentURL))
0081             continue;
0082         if (category.isEmpty()) {
0083             uncategorised.append(action);
0084         } else {
0085             if (!categoryMap.contains(category)) {
0086                 auto *categoryMenu = new KActionMenu(category, menu);
0087                 categoryMenu->setObjectName(category);
0088                 categoryMap.insert(category, categoryMenu);
0089             }
0090             KActionMenu *targetMenu = categoryMap.value(category);
0091             targetMenu->addAction(action);
0092         }
0093     }
0094 
0095     QMutableMapIterator<QString, KActionMenu *> mapIter(categoryMap);
0096     while (mapIter.hasNext()) {
0097         mapIter.next();
0098         menu->addAction(mapIter.value());
0099     }
0100 
0101     foreach (KrAction *action, uncategorised) {
0102         menu->addAction(action);
0103     };
0104 }
0105 
0106 QStringList UserAction::allCategories()
0107 {
0108     QStringList actionCategories;
0109 
0110     QListIterator<KrAction *> it(_actions);
0111     while (it.hasNext()) {
0112         KrAction *action = it.next();
0113         if (actionCategories.indexOf(action->category()) == -1)
0114             actionCategories.append(action->category());
0115     }
0116 
0117     return actionCategories;
0118 }
0119 
0120 QStringList UserAction::allNames()
0121 {
0122     QStringList actionNames;
0123 
0124     QListIterator<KrAction *> it(_actions);
0125     while (it.hasNext()) {
0126         KrAction *action = it.next();
0127         actionNames.append(action->objectName());
0128     }
0129 
0130     return actionNames;
0131 }
0132 
0133 void UserAction::readAllFiles()
0134 {
0135     QString filename = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
0136                                               ACTION_XML); // locate returns the local file if it exists, else the global one is retrieved.
0137     if (!filename.isEmpty())
0138         readFromFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, ACTION_XML), renameDoublicated);
0139 
0140     filename = QStandardPaths::locate(QStandardPaths::GenericDataLocation, ACTION_XML_EXAMPLES);
0141     if (!filename.isEmpty())
0142         readFromFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, ACTION_XML_EXAMPLES),
0143                      ignoreDoublicated); // ignore samples which are already in the normal file
0144 }
0145 
0146 void UserAction::readFromFile(const QString &filename, ReadMode mode, KrActionList *list)
0147 {
0148     QDomDocument *doc = new QDomDocument(ACTION_DOCTYPE);
0149     QFile file(filename);
0150     if (file.open(QIODevice::ReadOnly)) {
0151         // qDebug() << "UserAction::readFromFile - " << filename << "could be opened";
0152         if (!doc->setContent(&file)) {
0153             // qDebug() << "UserAction::readFromFile - content set - failed";
0154             //  if the file doesn't exist till now, the content CAN be set but is empty.
0155             //  if the content can't be set, the file exists and is NOT an xml-file.
0156             file.close();
0157             delete doc;
0158             doc = nullptr;
0159             KMessageBox::error(MAIN_VIEW,
0160                                i18n("The file %1 does not contain valid UserActions.\n", filename), // text
0161                                i18n("UserActions - cannot read from file") // caption
0162             );
0163         }
0164         file.close();
0165 
0166         if (doc) {
0167             QDomElement root = doc->documentElement();
0168             // check if the file has got the right root-element (ACTION_ROOT)
0169             // this finds out if the xml-file read to the DOM is really a krusader
0170             // useraction-file
0171             if (root.tagName() != ACTION_ROOT) {
0172                 KMessageBox::error(MAIN_VIEW,
0173                                    i18n("The actionfile's root element is not called %1, using %2", QString::fromLatin1(ACTION_ROOT), filename),
0174                                    i18n("UserActions - cannot read from file"));
0175                 delete doc;
0176                 doc = nullptr;
0177             }
0178             readFromElement(root, mode, list);
0179             delete doc;
0180         }
0181 
0182     } // if ( file.open( QIODevice::ReadOnly ) )
0183     else {
0184         KMessageBox::error(MAIN_VIEW, i18n("Unable to open actions file %1", filename), i18n("UserActions - cannot read from file"));
0185     }
0186 }
0187 
0188 void UserAction::readFromElement(const QDomElement &element, ReadMode mode, KrActionList *list)
0189 {
0190     for (QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) {
0191         QDomElement e = node.toElement();
0192         if (e.isNull())
0193             continue; // this should skip nodes which are not elements ( i.e. comments, <!-- -->, or text nodes)
0194 
0195         if (e.tagName() == "action") {
0196             QString name = e.attribute("name");
0197             if (name.isEmpty()) {
0198                 KMessageBox::error(
0199                     MAIN_VIEW,
0200                     i18n("Action without name detected. This action will not be imported.\nThis is an error in the file, you may want to correct it."),
0201                     i18n("UserActions - invalid action"));
0202                 continue;
0203             }
0204 
0205             if (mode == ignoreDoublicated) {
0206                 _defaultActions.insert(name);
0207                 if (krApp->actionCollection()->action(name) || _deletedActions.contains(name))
0208                     continue;
0209             }
0210 
0211             QString basename = name + "_%1";
0212             int i = 0;
0213             // append a counter till the name is unique... (this checks every action, not only useractions)
0214             while (krApp->actionCollection()->action(name))
0215                 name = basename.arg(++i);
0216 
0217             KrAction *act = new KrAction(krApp->actionCollection(), name);
0218             if (act->xmlRead(e)) {
0219                 _actions.append(act);
0220                 if (list)
0221                     list->append(act);
0222             } else
0223                 delete act;
0224         } else if (e.tagName() == "deletedAction") {
0225             QString name = e.attribute("name");
0226             if (name.isEmpty()) {
0227                 qWarning() << "A deleted action without name detected! \nThis is an error in the file.";
0228                 continue;
0229             }
0230             _deletedActions.insert(name);
0231         }
0232     } // for
0233 }
0234 
0235 QDomDocument UserAction::createEmptyDoc()
0236 {
0237     QDomDocument doc = QDomDocument(ACTION_DOCTYPE);
0238     // adding: <?xml version="1.0" encoding="UTF-8" ?>
0239     doc.appendChild(doc.createProcessingInstruction("xml", ACTION_PROCESSINSTR));
0240     // adding root-element
0241     doc.appendChild(doc.createElement(ACTION_ROOT)); // create new actionfile by adding a root-element ACTION_ROOT
0242     return doc;
0243 }
0244 
0245 bool UserAction::writeActionFile()
0246 {
0247     QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + ACTION_XML;
0248 
0249     QDomDocument doc = createEmptyDoc();
0250     QDomElement root = doc.documentElement();
0251 
0252     foreach (const QString &name, _deletedActions) {
0253         QDomElement element = doc.createElement("deletedAction");
0254         element.setAttribute("name", name);
0255         root.appendChild(element);
0256     }
0257 
0258     QListIterator<KrAction *> it(_actions);
0259     while (it.hasNext()) {
0260         KrAction *action = it.next();
0261         root.appendChild(action->xmlDump(doc));
0262     }
0263 
0264     return writeToFile(doc, filename);
0265 }
0266 
0267 bool UserAction::writeToFile(const QDomDocument &doc, const QString &filename)
0268 {
0269     QFile file(filename);
0270     if (!file.open(QIODevice::WriteOnly))
0271         return false;
0272 
0273     /* // This is not needed, because each DomDocument created with UserAction::createEmptyDoc already contains the processinstruction
0274        if ( ! doc.firstChild().isProcessingInstruction() ) {
0275           // adding: <?xml version="1.0" encoding="UTF-8" ?> if not already present
0276           QDomProcessingInstruction instr = doc.createProcessingInstruction( "xml", ACTION_PROCESSINSTR );
0277           doc.insertBefore( instr, doc.firstChild() );
0278        }
0279     */
0280 
0281     QTextStream ts(&file);
0282     ts.setCodec("UTF-8");
0283     ts << doc.toString();
0284 
0285     file.close();
0286     return true;
0287 }