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 ¤tURL) 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 }