File indexing completed on 2024-04-28 17:06:31
0001 /* 0002 SPDX-FileCopyrightText: 2004 Jonas Bähr <jonas.baehr@web.de> 0003 SPDX-FileCopyrightText: 2004 Shie Erlich <erlich@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "expander.h" 0010 0011 #include "../FileSystem/filesystemprovider.h" 0012 #include "../GUI/profilemanager.h" 0013 #include "../KViewer/krviewer.h" 0014 #include "../Panel/PanelView/krview.h" 0015 #include "../Panel/listpanel.h" 0016 #include "../Panel/panelfunc.h" 0017 #include "../Search/krsearchdialog.h" 0018 #include "../krservices.h" 0019 #include "../krusader.h" 0020 #include "../krusaderview.h" 0021 #include "../panelmanager.h" 0022 0023 #ifdef SYNCHRONIZER_ENABLED 0024 #include "../Synchronizer/synchronizergui.h" 0025 #endif 0026 0027 // QtCore 0028 #include <QDebug> 0029 #include <QList> 0030 #include <QStringList> 0031 #include <QTemporaryFile> 0032 #include <QTextStream> 0033 // QtGui 0034 #include <QClipboard> 0035 // QtWidgets 0036 #include <QApplication> 0037 #include <QInputDialog> 0038 0039 #include <KI18n/KLocalizedString> 0040 #include <KWidgetsAddons/KMessageBox> 0041 0042 #include <algorithm> 0043 #include <functional> 0044 using namespace std; 0045 0046 #define NEED_PANEL \ 0047 if (panel == nullptr) { \ 0048 panelMissingError(_expression, exp); \ 0049 return QString(); \ 0050 } 0051 0052 inline void exp_placeholder::setError(Expander &exp, const Error &e) 0053 { 0054 exp.setError(e); 0055 } 0056 inline QStringList exp_placeholder::splitEach(const TagString &s) 0057 { 0058 return Expander::splitEach(s); 0059 } 0060 inline exp_placeholder::exp_placeholder() = default; 0061 0062 void exp_placeholder::panelMissingError(const QString &s, Expander &exp) 0063 { 0064 exp.setError(Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Needed panel specification missing in expander %1", s))); 0065 } 0066 0067 QStringList exp_placeholder::fileList(const KrPanel *const panel, 0068 const QString &type, 0069 const QString &mask, 0070 const bool omitPath, 0071 const bool useUrl, 0072 Expander &exp, 0073 const QString &error) 0074 { 0075 QStringList items; 0076 if (type.isEmpty() || type == "all") 0077 panel->view->getItemsByMask(mask, &items); 0078 else if (type == "files") 0079 panel->view->getItemsByMask(mask, &items, false, true); 0080 else if (type == "dirs") 0081 panel->view->getItemsByMask(mask, &items, true, false); 0082 else if (type == "selected") 0083 panel->view->getSelectedItems(&items); 0084 else { 0085 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: Bad argument to %1: %2 is not valid item specifier", error, type))); 0086 return QStringList(); 0087 } 0088 if (!omitPath) { // add the current path 0089 // translate to urls using filesystem 0090 QList<QUrl> list = panel->func->files()->getUrls(items); 0091 items.clear(); 0092 // parse everything to a single qstring 0093 foreach (const QUrl &url, list) { 0094 items.push_back(useUrl ? url.url() : url.path()); 0095 } 0096 } 0097 0098 return items; 0099 } 0100 0101 namespace 0102 { 0103 0104 class exp_simpleplaceholder : public exp_placeholder 0105 { 0106 public: 0107 EXP_FUNC override; 0108 virtual TagString expFunc(const KrPanel *, const QStringList &, const bool &, Expander &) const = 0; 0109 }; 0110 0111 #define PLACEHOLDER_CLASS(name) \ 0112 class name : public exp_placeholder \ 0113 { \ 0114 public: \ 0115 name(); \ 0116 virtual TagString expFunc(const KrPanel *, const TagStringList &, const bool &, Expander &) const override; \ 0117 }; 0118 0119 #define SIMPLE_PLACEHOLDER_CLASS(name) \ 0120 class name : public exp_simpleplaceholder \ 0121 { \ 0122 public: \ 0123 using exp_simpleplaceholder::expFunc; \ 0124 name(); \ 0125 virtual TagString expFunc(const KrPanel *, const QStringList &, const bool &, Expander &) const override; \ 0126 }; 0127 0128 /** 0129 * expands %_Path% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the path of the specified panel 0130 */ 0131 SIMPLE_PLACEHOLDER_CLASS(exp_Path) 0132 0133 /** 0134 * expands %_Count% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the number of items, which type is 0135 * specified by the first Parameter 0136 */ 0137 SIMPLE_PLACEHOLDER_CLASS(exp_Count) 0138 0139 /** 0140 * expands %_Filter% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the correspondent filter (ie: "*.cpp") 0141 */ 0142 SIMPLE_PLACEHOLDER_CLASS(exp_Filter) 0143 0144 /** 0145 * expands %_Current% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the current item ( != the selected ones) 0146 */ 0147 SIMPLE_PLACEHOLDER_CLASS(exp_Current) 0148 0149 /** 0150 * expands %_List% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with a list of items, which type is specified by 0151 * the first Parameter 0152 */ 0153 SIMPLE_PLACEHOLDER_CLASS(exp_List) 0154 0155 /** 0156 * expands %_ListFile% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate 0157 * the active, other, right or left panel) with the name of a temporary file, 0158 * containing a list of items, which type is specified by the first Parameter 0159 */ 0160 SIMPLE_PLACEHOLDER_CLASS(exp_ListFile) 0161 0162 /** 0163 * expands %_Ask% ('_' is necessary because there is no panel needed) 0164 * with the return of an input-dialog 0165 */ 0166 SIMPLE_PLACEHOLDER_CLASS(exp_Ask) 0167 0168 /** 0169 * This copies it's first Parameter to the clipboard 0170 */ 0171 PLACEHOLDER_CLASS(exp_Clipboard) 0172 0173 /** 0174 * This selects all items by the mask given with the first Parameter 0175 */ 0176 SIMPLE_PLACEHOLDER_CLASS(exp_Select) 0177 0178 /** 0179 * This changes the panel'spath to the value given with the first Parameter. 0180 */ 0181 SIMPLE_PLACEHOLDER_CLASS(exp_Goto) 0182 0183 /** 0184 * This is equal to 'cp <first Parameter> <second Parameter>'. 0185 */ 0186 PLACEHOLDER_CLASS(exp_Copy) 0187 0188 /** 0189 * This is equal to 'mv <first Parameter> <second Parameter>'. 0190 */ 0191 PLACEHOLDER_CLASS(exp_Move) 0192 0193 #ifdef SYNCHRONIZER_ENABLED 0194 /** 0195 * This opens the synchronizer with a given profile 0196 */ 0197 SIMPLE_PLACEHOLDER_CLASS(exp_Sync) 0198 #endif 0199 0200 /** 0201 * This opens the searchmodule with a given profile 0202 */ 0203 SIMPLE_PLACEHOLDER_CLASS(exp_NewSearch) 0204 0205 /** 0206 * This loads the panel-profile with a given name 0207 */ 0208 SIMPLE_PLACEHOLDER_CLASS(exp_Profile) 0209 0210 /** 0211 * This is setting marks in the string where he is later split up for each {all, selected, files, dirs} 0212 */ 0213 SIMPLE_PLACEHOLDER_CLASS(exp_Each) 0214 0215 /** 0216 * This sets the sorting on a specific column 0217 */ 0218 SIMPLE_PLACEHOLDER_CLASS(exp_ColSort) 0219 0220 /** 0221 * This sets relation between the left and right panel 0222 */ 0223 SIMPLE_PLACEHOLDER_CLASS(exp_PanelSize) 0224 0225 /** 0226 * This loads a file in the internal viewer 0227 */ 0228 SIMPLE_PLACEHOLDER_CLASS(exp_View) 0229 0230 //////////////////////////////////////////////////////////// 0231 //////////////////////// utils //////////////////////// 0232 //////////////////////////////////////////////////////////// 0233 0234 /** 0235 * escapes everything that confuses bash in filenames 0236 * @param s String to manipulate 0237 * @return escaped string 0238 */ 0239 QString bashquote(QString s) 0240 { 0241 /* 0242 // we _can_not_ use this function because it _encloses_ the sting in single-quotes! 0243 // In this case quotes strings could not be concatenated anymore 0244 return KrServices::quote(s); 0245 */ 0246 0247 static const QString evilstuff = "\\\"'`()[]{}!?;$&<>| \t\r\n"; // stuff that should get escaped 0248 0249 for (auto i : evilstuff) 0250 s.replace(i, ('\\' + i)); 0251 0252 return s; 0253 } 0254 0255 QString separateAndQuote(QStringList list, const QString &separator, const bool quote) 0256 { 0257 if (quote) 0258 transform(list.begin(), list.end(), list.begin(), bashquote); 0259 0260 // QLineEdit::text() always escapes special characters, revert this for newline and tab 0261 QString decodedSeparator = separator; 0262 decodedSeparator.replace("\\n", "\n").replace("\\t", "\t"); 0263 return list.join(decodedSeparator); 0264 } 0265 ///////////////////////////////////////////////////////////////////////////////////////////////// 0266 /////////////////////////////////// expander classes //////////////////////////////////// 0267 ///////////////////////////////////////////////////////////////////////////////////////////////// 0268 0269 exp_Path::exp_Path() 0270 { 0271 _expression = "Path"; 0272 _description = i18n("Panel's Path..."); 0273 _needPanel = true; 0274 0275 addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false)); 0276 } 0277 TagString exp_Path::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &useUrl, Expander &exp) const 0278 { 0279 NEED_PANEL 0280 0281 QString result; 0282 0283 if (useUrl) 0284 result = panel->func->files()->currentDirectory().url() + '/'; 0285 else 0286 result = panel->func->files()->currentDirectory().path() + '/'; 0287 0288 if (parameter.count() > 0 && parameter[0].toLower() == "no") // don't escape spaces 0289 return TagString(result); 0290 else 0291 return TagString(bashquote(result)); 0292 } 0293 0294 exp_Count::exp_Count() 0295 { 0296 _expression = "Count"; 0297 _description = i18n("Number of..."); 0298 _needPanel = true; 0299 0300 addParameter(exp_parameter(i18n("Count:"), "__choose:All;Files;Dirs;Selected", false)); 0301 } 0302 TagString exp_Count::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &, Expander &exp) const 0303 { 0304 NEED_PANEL 0305 0306 int n = -1; 0307 if (parameter.count() == 0 || parameter[0].isEmpty() || parameter[0].toLower() == "all") 0308 n = panel->view->numDirs() + panel->view->numFiles(); 0309 else if (parameter[0].toLower() == "files") 0310 n = panel->view->numFiles(); 0311 else if (parameter[0].toLower() == "dirs") 0312 n = panel->view->numDirs(); 0313 else if (parameter[0].toLower() == "selected") 0314 n = panel->view->numSelected(); 0315 else { 0316 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: Bad argument to Count: %1 is not valid item specifier", parameter[0]))); 0317 return QString(); 0318 } 0319 0320 return TagString(QString("%1").arg(n)); 0321 } 0322 0323 exp_Filter::exp_Filter() 0324 { 0325 _expression = "Filter"; 0326 _description = i18n("Filter Mask (*.h, *.cpp, etc.)"); 0327 _needPanel = true; 0328 } 0329 TagString exp_Filter::expFunc(const KrPanel *panel, const QStringList &, const bool &, Expander &exp) const 0330 { 0331 NEED_PANEL 0332 0333 return panel->view->filterMask().nameFilter(); 0334 } 0335 0336 exp_Current::exp_Current() 0337 { 0338 _expression = "Current"; 0339 _description = i18n("Current File (!= Selected File)..."); 0340 _needPanel = true; 0341 0342 addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false)); 0343 addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false)); 0344 } 0345 TagString exp_Current::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &useUrl, Expander &exp) const 0346 { 0347 NEED_PANEL 0348 0349 QString item = panel->view->getCurrentItem(); 0350 if (item == "..") { 0351 // if ".." is current, treat this as nothing is current 0352 return QString(); 0353 } 0354 0355 QString result; 0356 if (parameter.count() > 0 && parameter[0].toLower() == "yes") // omit the current path 0357 result = item; 0358 else { 0359 const QUrl url = panel->func->files()->getUrl(item); 0360 result = useUrl ? url.url() : url.path(); 0361 } 0362 0363 const bool escapeSpaces = parameter.count() < 2 || parameter[1].toLower() != "no"; 0364 return escapeSpaces ? bashquote(result) : result; 0365 } 0366 0367 exp_List::exp_List() 0368 { 0369 _expression = "List"; 0370 _description = i18n("Item List of..."); 0371 _needPanel = true; 0372 0373 addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false)); 0374 addParameter(exp_parameter(i18n("Separator between the items (optional):"), " ", false)); 0375 addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false)); 0376 addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false)); 0377 addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false)); 0378 } 0379 TagString exp_List::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &useUrl, Expander &exp) const 0380 { 0381 NEED_PANEL 0382 0383 // get selected items from view 0384 QStringList items; 0385 QString mask; 0386 0387 if (parameter.count() <= 3 || parameter[3].isEmpty()) 0388 mask = '*'; 0389 else 0390 mask = parameter[3]; 0391 0392 return separateAndQuote(fileList(panel, 0393 parameter.isEmpty() ? QString() : parameter[0].toLower(), 0394 mask, 0395 parameter.count() > 2 ? parameter[2].toLower() == "yes" : false, 0396 useUrl, 0397 exp, 0398 "List"), 0399 parameter.count() > 1 ? parameter[1] : " ", 0400 parameter.count() > 4 ? parameter[4].toLower() == "yes" : true); 0401 } 0402 0403 exp_ListFile::exp_ListFile() 0404 { 0405 _expression = "ListFile"; 0406 _description = i18n("Filename of an Item List..."); 0407 _needPanel = true; 0408 0409 addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false)); 0410 addParameter(exp_parameter(i18n("Separator between the items (optional)"), "\n", false)); 0411 addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false)); 0412 addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false)); 0413 addParameter(exp_parameter(i18n("Automatically escape spaces"), "__no", false)); 0414 } 0415 TagString exp_ListFile::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &useUrl, Expander &exp) const 0416 { 0417 NEED_PANEL 0418 0419 // get selected items from view 0420 QStringList items; 0421 QString mask; 0422 0423 if (parameter.count() <= 3 || parameter[3].isEmpty()) 0424 mask = '*'; 0425 else 0426 mask = parameter[3]; 0427 QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.itemlist")); 0428 tmpFile.setAutoRemove(false); 0429 0430 if (!tmpFile.open()) { 0431 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_WORLD, i18n("Expander: temporary file could not be opened (%1)", tmpFile.errorString()))); 0432 return QString(); 0433 } 0434 0435 QTextStream stream(&tmpFile); 0436 stream << separateAndQuote(fileList(panel, 0437 parameter.isEmpty() ? QString() : parameter[0].toLower(), 0438 mask, 0439 parameter.count() > 2 ? parameter[2].toLower() == "yes" : false, 0440 useUrl, 0441 exp, 0442 "ListFile"), 0443 parameter.count() > 1 ? parameter[1] : "\n", 0444 parameter.count() > 4 ? parameter[4].toLower() == "yes" : true) 0445 << "\n"; 0446 tmpFile.close(); 0447 0448 return tmpFile.fileName(); 0449 } 0450 0451 exp_Select::exp_Select() 0452 { 0453 _expression = "Select"; 0454 _description = i18n("Manipulate the Selection..."); 0455 _needPanel = true; 0456 0457 addParameter(exp_parameter(i18n("Selection mask:"), "__select", true)); 0458 addParameter(exp_parameter(i18n("Manipulate in which way:"), "__choose:Set;Add;Remove", false)); 0459 } 0460 TagString exp_Select::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &, Expander &exp) const 0461 { 0462 NEED_PANEL 0463 0464 KrQuery mask; 0465 if (parameter.count() <= 0 || parameter[0].isEmpty()) 0466 mask = KrQuery("*"); 0467 else 0468 mask = KrQuery(parameter[0]); 0469 0470 if (parameter.count() > 1 && parameter[1].toLower() == "list-add") 0471 panel->view->select(mask); 0472 else if (parameter.count() > 1 && parameter[1].toLower() == "list-remove") 0473 panel->view->unselect(mask); 0474 else { // parameter[1].toLower() == "set" or isEmpty() or whatever 0475 panel->view->unselect(KrQuery("*")); 0476 panel->view->select(mask); 0477 } 0478 0479 return QString(); // this doesn't return anything, that's normal! 0480 } 0481 0482 exp_Goto::exp_Goto() 0483 { 0484 _expression = "Goto"; 0485 _description = i18n("Jump to a Location..."); 0486 _needPanel = true; 0487 0488 addParameter(exp_parameter(i18n("Choose a path:"), "__goto", true)); 0489 addParameter(exp_parameter(i18n("Open location in a new tab"), "__no", false)); 0490 } 0491 TagString exp_Goto::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &, Expander &exp) const 0492 { 0493 NEED_PANEL 0494 0495 bool newTab = false; 0496 if (parameter.count() > 1 && parameter[1].toLower() == "yes") 0497 newTab = true; 0498 0499 if (parameter.count() == 0) { 0500 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Goto."))); 0501 return QString(); 0502 } 0503 0504 QUrl url = QUrl::fromUserInput(parameter[0], QString(), QUrl::AssumeLocalFile); 0505 if (newTab) { 0506 if (panel == LEFT_PANEL) 0507 MAIN_VIEW->leftManager()->slotNewTab(url); 0508 else 0509 MAIN_VIEW->rightManager()->slotNewTab(url); 0510 } else { 0511 panel->func->openUrl(url, ""); 0512 panel->gui->slotFocusOnMe(); 0513 } 0514 0515 return QString(); // this doesn't return anything, that's normal! 0516 } 0517 0518 /* 0519 exp_Search::exp_Search() { 0520 _expression = "Search"; 0521 _description = i18n("Search for files"); 0522 _needPanel = true; 0523 0524 addParameter( new exp_parameter( i18n("please choose the setting"), "__searchprofile", true ) ); 0525 addParameter( new exp_parameter( i18n("open the search in a new tab"), "__yes", false ) ); //TODO: add this also to panel-dependent as soon as filesystem 0526 support the display of search-results 0527 } 0528 */ 0529 0530 exp_Ask::exp_Ask() 0531 { 0532 _expression = "Ask"; 0533 _description = i18n("Ask Parameter from User..."); 0534 _needPanel = false; 0535 0536 addParameter(exp_parameter(i18n("Question:"), "Where do you want do go today?", true)); 0537 addParameter(exp_parameter(i18n("Preset (optional):"), "", false)); 0538 addParameter(exp_parameter(i18n("Caption (optional):"), "", false)); 0539 } 0540 TagString exp_Ask::expFunc(const KrPanel *, const QStringList ¶meter, const bool &, Expander &exp) const 0541 { 0542 QString caption, preset, result; 0543 0544 if (parameter.count() == 0) { 0545 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Ask."))); 0546 return QString(); 0547 } 0548 0549 if (parameter.count() <= 2 || parameter[2].isEmpty()) 0550 caption = i18n("User Action"); 0551 else 0552 caption = parameter[2]; 0553 if (parameter.count() <= 1 || parameter[1].isEmpty()) 0554 preset.clear(); 0555 else 0556 preset = parameter[1]; 0557 0558 bool ok; 0559 result = QInputDialog::getText(krMainWindow, caption, parameter[0], QLineEdit::Normal, preset, &ok); 0560 0561 if (ok) 0562 return result; 0563 else { 0564 // user cancelled 0565 setError(exp, Error(Error::exp_S_ERROR, Error::exp_C_USER)); 0566 return QString(); 0567 } 0568 } 0569 0570 exp_Clipboard::exp_Clipboard() 0571 { 0572 _expression = "Clipboard"; 0573 _description = i18n("Copy to Clipboard..."); 0574 _needPanel = false; 0575 0576 addParameter(exp_parameter(i18n("What to copy:"), "__placeholder", true)); 0577 addParameter(exp_parameter(i18n("Append to current clipboard content with this separator (optional):"), "", false)); 0578 } 0579 TagString exp_Clipboard::expFunc(const KrPanel *, const TagStringList ¶meter, const bool &, Expander &exp) const 0580 { 0581 // qDebug() << "Expander::exp_Clipboard, parameter[0]: '" << parameter[0] << "', Clipboard: " << QApplication::clipboard()->text(); 0582 if (parameter.count() == 0) { 0583 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Clipboard."))); 0584 return QString(); 0585 } 0586 0587 QStringList lst = splitEach(parameter[0]); 0588 if (parameter.count() > 1 && !parameter[1].isSimple()) { 0589 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: %Each% may not be in the second argument of %Clipboard%"))); 0590 return QString(); 0591 } 0592 if (parameter.count() <= 1 || parameter[1].string().isEmpty() || QApplication::clipboard()->text().isEmpty()) 0593 QApplication::clipboard()->setText(lst.join("\n")); 0594 else 0595 QApplication::clipboard()->setText(QApplication::clipboard()->text() + parameter[1].string() + lst.join("\n")); 0596 0597 return QString(); // this doesn't return anything, that's normal! 0598 } 0599 0600 exp_Copy::exp_Copy() 0601 { 0602 _expression = "Copy"; 0603 _description = i18n("Copy a File/Folder..."); 0604 _needPanel = false; 0605 0606 addParameter(exp_parameter(i18n("What to copy:"), "__placeholder", true)); 0607 addParameter(exp_parameter(i18n("Where to copy:"), "__placeholder", true)); 0608 } 0609 TagString exp_Copy::expFunc(const KrPanel *, const TagStringList ¶meter, const bool &, Expander &exp) const 0610 { 0611 if (parameter.count() < 2) { 0612 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 2 parameter is required for Copy."))); 0613 return QString(); 0614 } 0615 0616 // basically the parameter can already be used as URL, but since QUrl has problems with ftp-proxy-urls (like ftp://username@proxyusername@url...) this is 0617 // necessary: 0618 const QStringList sourceList = splitEach(parameter[0]); 0619 QList<QUrl> sourceURLs; 0620 for (const QString &source : sourceList) { 0621 sourceURLs.append(QUrl::fromUserInput(source, QString(), QUrl::AssumeLocalFile)); 0622 } 0623 0624 if (!parameter[1].isSimple()) { 0625 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: %Each% may not be in the second argument of %Copy%"))); 0626 return QString(); 0627 } 0628 0629 // or transform(...) ? 0630 const QUrl dest = QUrl::fromUserInput(parameter[1].string(), QString(), QUrl::AssumeLocalFile); 0631 0632 if (!dest.isValid() || find_if(sourceURLs.constBegin(), sourceURLs.constEnd(), not1(mem_fun_ref(&QUrl::isValid))) != sourceURLs.constEnd()) { 0633 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: invalid URLs in %_Copy(\"src\", \"dest\")%"))); 0634 return QString(); 0635 } 0636 0637 FileSystemProvider::instance().startCopyFiles(sourceURLs, dest); 0638 0639 return QString(); // this doesn't return everything, that's normal! 0640 } 0641 0642 exp_Move::exp_Move() 0643 { 0644 _expression = "Move"; 0645 _description = i18n("Move/Rename a File/Folder..."); 0646 _needPanel = false; 0647 0648 addParameter(exp_parameter(i18n("What to move/rename:"), "__placeholder", true)); 0649 addParameter(exp_parameter(i18n("New target/name:"), "__placeholder", true)); 0650 } 0651 TagString exp_Move::expFunc(const KrPanel *, const TagStringList ¶meter, const bool &, Expander &exp) const 0652 { 0653 if (parameter.count() < 2) { 0654 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 2 parameter is required for Move."))); 0655 return QString(); 0656 } 0657 0658 // basically the parameter can already be used as URL, but since QUrl has problems with ftp-proxy-urls (like ftp://username@proxyusername@url...) this is 0659 // necessary: 0660 QStringList lst = splitEach(parameter[0]); 0661 if (!parameter[1].isSimple()) { 0662 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("%Each% may not be in the second argument of %Move%"))); 0663 return QString(); 0664 } 0665 QList<QUrl> src; 0666 for (QStringList::const_iterator it = lst.constBegin(), end = lst.constEnd(); it != end; ++it) 0667 src.push_back(QUrl::fromUserInput(*it, QString(), QUrl::AssumeLocalFile)); 0668 // or transform(...) ? 0669 QUrl dest = QUrl::fromUserInput(parameter[1].string(), QString(), QUrl::AssumeLocalFile); 0670 0671 if (!dest.isValid() || find_if(src.constBegin(), src.constEnd(), not1(mem_fun_ref(&QUrl::isValid))) != src.constEnd()) { 0672 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: invalid URLs in %_Move(\"src\", \"dest\")%"))); 0673 return QString(); 0674 } 0675 0676 FileSystemProvider::instance().startCopyFiles(src, dest, KIO::CopyJob::Move); 0677 0678 return QString(); // this doesn't return anything, that's normal! 0679 } 0680 0681 #ifdef SYNCHRONIZER_ENABLED 0682 exp_Sync::exp_Sync() 0683 { 0684 _expression = "Sync"; 0685 _description = i18n("Load a Synchronizer Profile..."); 0686 _needPanel = false; 0687 0688 addParameter(exp_parameter(i18n("Choose a profile:"), "__syncprofile", true)); 0689 } 0690 TagString exp_Sync::expFunc(const KrPanel *, const QStringList ¶meter, const bool &, Expander &exp) const 0691 { 0692 if (parameter.count() == 0 || parameter[0].isEmpty()) { 0693 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_Sync(profile)%"))); 0694 return QString(); 0695 } 0696 0697 SynchronizerGUI *synchronizerDialog = new SynchronizerGUI(MAIN_VIEW, parameter[0]); 0698 synchronizerDialog->show(); // destroyed on close 0699 0700 return QString(); // this doesn't return everything, that's normal! 0701 } 0702 #endif 0703 0704 exp_NewSearch::exp_NewSearch() 0705 { 0706 _expression = "NewSearch"; 0707 _description = i18n("Load a Searchmodule Profile..."); 0708 _needPanel = false; 0709 0710 addParameter(exp_parameter(i18n("Choose a profile:"), "__searchprofile", true)); 0711 } 0712 TagString exp_NewSearch::expFunc(const KrPanel *, const QStringList ¶meter, const bool &, Expander &exp) const 0713 { 0714 if (parameter.count() == 0 || parameter[0].isEmpty()) { 0715 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_NewSearch(profile)%"))); 0716 return QString(); 0717 } 0718 0719 new KrSearchDialog(parameter[0], krApp); 0720 0721 return QString(); // this doesn't return everything, that's normal! 0722 } 0723 0724 exp_Profile::exp_Profile() 0725 { 0726 _expression = "Profile"; 0727 _description = i18n("Load a Panel Profile..."); 0728 _needPanel = false; 0729 0730 addParameter(exp_parameter(i18n("Choose a profile:"), "__panelprofile", true)); 0731 } 0732 TagString exp_Profile::expFunc(const KrPanel *, const QStringList ¶meter, const bool &, Expander &exp) const 0733 { 0734 if (parameter.count() == 0 || parameter[0].isEmpty()) { 0735 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_Profile(profile)%; abort..."))); 0736 return QString(); 0737 } 0738 0739 MAIN_VIEW->profiles(parameter[0]); 0740 0741 return QString(); // this doesn't return everything, that's normal! 0742 } 0743 0744 exp_Each::exp_Each() 0745 { 0746 _expression = "Each"; 0747 _description = i18n("Separate Program Call for Each..."); 0748 _needPanel = true; 0749 0750 addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false)); 0751 addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false)); 0752 addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false)); 0753 addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false)); 0754 } 0755 TagString exp_Each::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &useUrl, Expander &exp) const 0756 { 0757 NEED_PANEL 0758 0759 QString mask; 0760 if (parameter.count() <= 2 || parameter[2].isEmpty()) 0761 mask = '*'; 0762 else 0763 mask = parameter[2]; 0764 0765 TagString ret; 0766 QStringList l = fileList(panel, 0767 parameter.empty() ? QString() : parameter[0].toLower(), 0768 mask, 0769 parameter.count() > 1 && parameter[1].toLower() == "yes", 0770 useUrl, 0771 exp, 0772 "Each"); 0773 0774 if (!(parameter.count() <= 3 || parameter[3].toLower() != "yes")) 0775 transform(l.begin(), l.end(), l.begin(), bashquote); 0776 0777 ret.insertTag(0, l); 0778 return ret; 0779 } 0780 0781 exp_ColSort::exp_ColSort() 0782 { 0783 _expression = "ColSort"; 0784 _description = i18n("Set Sorting for This Panel..."); 0785 _needPanel = true; 0786 0787 addParameter(exp_parameter(i18n("Choose a column:"), "__choose:Name;Ext;Type;Size;Modified;Perms;rwx;Owner;Group", true)); 0788 addParameter(exp_parameter(i18n("Choose a sort sequence:"), "__choose:Toggle;Asc;Desc", false)); 0789 } 0790 TagString exp_ColSort::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &, Expander &exp) const 0791 { 0792 NEED_PANEL 0793 0794 if (parameter.count() == 0 || parameter[0].isEmpty()) { 0795 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no column specified for %_ColSort(column)%"))); 0796 return QString(); 0797 } 0798 0799 KrViewProperties::ColumnType oldColumn = panel->view->properties()->sortColumn; 0800 KrViewProperties::ColumnType column = oldColumn; 0801 0802 if (parameter[0].toLower() == "name") { 0803 column = KrViewProperties::Name; 0804 } else if (parameter[0].toLower() == "ext") { 0805 column = KrViewProperties::Ext; 0806 } else if (parameter[0].toLower() == "type") { 0807 column = KrViewProperties::Type; 0808 } else if (parameter[0].toLower() == "size") { 0809 column = KrViewProperties::Size; 0810 } else if (parameter[0].toLower() == "modified") { 0811 column = KrViewProperties::Modified; 0812 } else if (parameter[0].toLower() == "changed") { 0813 column = KrViewProperties::Changed; 0814 } else if (parameter[0].toLower() == "accessed") { 0815 column = KrViewProperties::Accessed; 0816 } else if (parameter[0].toLower() == "perms") { 0817 column = KrViewProperties::Permissions; 0818 } else if (parameter[0].toLower() == "rwx") { 0819 column = KrViewProperties::KrPermissions; 0820 } else if (parameter[0].toLower() == "owner") { 0821 column = KrViewProperties::Owner; 0822 } else if (parameter[0].toLower() == "group") { 0823 column = KrViewProperties::Group; 0824 } else { 0825 setError(exp, Error(Error::exp_S_WARNING, Error::exp_C_ARGUMENT, i18n("Expander: unknown column specified for %_ColSort(%1)%", parameter[0]))); 0826 return QString(); 0827 } 0828 0829 bool descending = panel->view->properties()->sortOptions & KrViewProperties::Descending; 0830 0831 if (parameter.count() <= 1 || (parameter[1].toLower() != "asc" && parameter[1].toLower() != "desc")) { // no sortdir parameter 0832 if (column == oldColumn) // reverse direction if column is unchanged 0833 descending = !descending; 0834 else // otherwise set to ascending 0835 descending = false; 0836 } else { // sortdir specified 0837 if (parameter[1].toLower() == "asc") 0838 descending = false; 0839 else // == desc 0840 descending = true; 0841 } 0842 0843 panel->view->setSortMode(column, descending); 0844 0845 return QString(); // this doesn't return anything, that's normal! 0846 } 0847 0848 exp_PanelSize::exp_PanelSize() 0849 { 0850 _expression = "PanelSize"; 0851 _description = i18n("Set Relation Between the Panels..."); 0852 _needPanel = true; 0853 0854 addParameter(exp_parameter(i18n("Set the new size in percent:"), "__int:0;100;5;50", true)); 0855 } 0856 TagString exp_PanelSize::expFunc(const KrPanel *panel, const QStringList ¶meter, const bool &, Expander &exp) const 0857 { 0858 NEED_PANEL 0859 int newSize; 0860 0861 if (parameter.count() == 0 || parameter[0].isEmpty()) 0862 newSize = 50; // default is 50% 0863 else 0864 newSize = parameter[0].toInt(); 0865 0866 if (newSize < 0 || newSize > 100) { 0867 setError(exp, 0868 Error(Error::exp_S_FATAL, 0869 Error::exp_C_ARGUMENT, 0870 i18n("Expander: Value %1 out of range for %_PanelSize(percent)%. The first parameter has to be >0 and <100", newSize))); 0871 return QString(); 0872 } 0873 0874 MAIN_VIEW->setPanelSize(panel->isLeft(), newSize); 0875 0876 return QString(); // this doesn't return everything, that's normal! 0877 } 0878 0879 exp_View::exp_View() 0880 { 0881 _expression = "View"; 0882 _description = i18n("View File with Krusader's Internal Viewer..."); 0883 _needPanel = false; 0884 0885 addParameter(exp_parameter(i18n("Which file to view (normally '%aCurrent%'):"), "__placeholder", true)); 0886 addParameter(exp_parameter(i18n("Choose a view mode:"), "__choose:generic;text;hex", false)); 0887 // addParameter( exp_parameter( i18n("Choose a window-mode"), "__choose:tab;window;panel", false ) ); 0888 // TODO: window-mode 'panel' should open the file in the third-hand viewer 0889 addParameter(exp_parameter(i18n("Choose a window mode:"), "__choose:tab;window", false)); 0890 } 0891 TagString exp_View::expFunc(const KrPanel *, const QStringList ¶meter, const bool &, Expander &exp) const 0892 { 0893 if (parameter.count() == 0 || parameter[0].isEmpty()) { 0894 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no file to view in %_View(filename)%"))); 0895 return QString(); 0896 } 0897 0898 QString viewMode, windowMode; 0899 if (parameter.count() <= 1 || parameter[1].isEmpty()) 0900 viewMode = "generic"; 0901 else 0902 viewMode = parameter[1]; 0903 0904 if (parameter.count() <= 2 || parameter[2].isEmpty()) 0905 windowMode = "tab"; 0906 else 0907 windowMode = parameter[2]; 0908 0909 KrViewer::Mode mode = KrViewer::Generic; 0910 if (viewMode == "text") 0911 mode = KrViewer::Text; 0912 else if (viewMode == "hex") 0913 mode = KrViewer::Hex; 0914 0915 QUrl url = QUrl::fromUserInput(parameter[0], QString(), QUrl::AssumeLocalFile); 0916 KrViewer::view(url, mode, (windowMode == "window")); 0917 // TODO: Call the viewer with viewMode and windowMode. Filename is in parameter[0]. 0918 // It would be nice if parameter[0] could also be a space-separated filename-list (provided if the first parameter is %aList(selected)%) 0919 0920 return QString(); // this doesn't return everything, that's normal! 0921 } 0922 0923 ///////////////////////////////////////////////////////////////////////////////////////////////// 0924 ////////////////////////////// end of expander classes //////////////////////////////// 0925 ///////////////////////////////////////////////////////////////////////////////////////////////// 0926 0927 TagString exp_simpleplaceholder::expFunc(const KrPanel *p, const TagStringList ¶meter, const bool &useUrl, Expander &exp) const 0928 { 0929 QStringList lst; 0930 for (const auto &it : parameter) 0931 if (it.isSimple()) 0932 lst.push_back(it.string()); 0933 else { 0934 setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("%Each% is not allowed in parameter to %1", description()))); 0935 return QString(); 0936 } 0937 return expFunc(p, lst, useUrl, exp); 0938 } 0939 0940 } 0941 0942 KrPanel *Expander::getPanel(const char panelIndicator, const exp_placeholder *pl, Expander &exp) 0943 { 0944 switch (panelIndicator) { 0945 case 'a': 0946 return ACTIVE_PANEL; 0947 case 'o': 0948 return OTHER_PANEL; 0949 case 'l': 0950 return LEFT_PANEL; 0951 case 'r': 0952 return RIGHT_PANEL; 0953 case '_': 0954 return nullptr; 0955 default: 0956 exp.setError( 0957 Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: Bad panel specifier %1 in placeholder %2", panelIndicator, pl->description()))); 0958 return nullptr; 0959 } 0960 } 0961 0962 void Expander::expand(const QString &stringToExpand, bool useUrl) 0963 { 0964 TagString result = expandCurrent(stringToExpand, useUrl); 0965 if (error()) 0966 return; 0967 0968 if (!result.isSimple()) 0969 resultList = splitEach(result); 0970 else 0971 resultList.append(result.string()); 0972 0973 // qWarning() << resultList[0]; 0974 } 0975 0976 TagString Expander::expandCurrent(const QString &stringToExpand, bool useUrl) 0977 { 0978 TagString result; 0979 QString exp; 0980 TagString tmpResult; 0981 int begin, end, i; 0982 // int brackets = 0; 0983 // bool inQuotes = false; 0984 int idx = 0; 0985 while (idx < stringToExpand.length()) { 0986 if ((begin = stringToExpand.indexOf('%', idx)) == -1) 0987 break; 0988 if ((end = findEnd(stringToExpand, begin)) == -1) { 0989 // xgettext:no-c-format 0990 setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: unterminated % in Expander"))); 0991 return QString(); 0992 } 0993 0994 result += stringToExpand.mid(idx, begin - idx); // copy until the start of %exp% 0995 0996 // get the expression, and expand it using the correct expander function 0997 exp = stringToExpand.mid(begin + 1, end - begin - 1); 0998 // qDebug() << "------------- exp: '" << exp << "'"; 0999 if (exp.isEmpty()) 1000 result += QString(QChar('%')); 1001 else { 1002 TagStringList parameter = separateParameter(&exp, useUrl); 1003 if (error()) 1004 return QString(); 1005 char panelIndicator = exp.toLower()[0].toLatin1(); 1006 exp.replace(0, 1, ""); 1007 for (i = 0; i < placeholderCount(); ++i) 1008 if (exp == placeholder(i)->expression()) { 1009 // qDebug() << "---------------------------------------"; 1010 tmpResult = placeholder(i)->expFunc(getPanel(panelIndicator, placeholder(i), *this), parameter, useUrl, *this); 1011 if (error()) { 1012 return QString(); 1013 } else 1014 result += tmpResult; 1015 // qDebug() << "---------------------------------------"; 1016 break; 1017 } 1018 if (i == placeholderCount()) { // didn't find an expander 1019 setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: unrecognized %%%1%2%% in Expander", panelIndicator, exp))); 1020 return QString(); 1021 } 1022 } // else 1023 idx = end + 1; 1024 } 1025 // copy the rest of the string 1026 result += stringToExpand.mid(idx); 1027 // qDebug() << "============== result '" << result << "'"; 1028 return result; 1029 } 1030 1031 QStringList Expander::splitEach(TagString stringToSplit) 1032 { 1033 if (stringToSplit.isSimple()) { 1034 // qWarning() << stringToSplit.string(); 1035 QStringList l; 1036 l << stringToSplit.string(); 1037 return l; 1038 } 1039 pair<uint, QStringList> pl = *stringToSplit.tagsBegin(); 1040 stringToSplit.eraseTag(stringToSplit.tagsBegin()); 1041 QStringList ret; 1042 for (QStringList::const_iterator it = pl.second.constBegin(), end = pl.second.constEnd(); it != end; ++it) { 1043 TagString s = stringToSplit; 1044 s.insert(pl.first, *it); 1045 ret += splitEach(s); 1046 } 1047 return ret; 1048 // qDebug() << "stringToSplit: " << stringToSplit; 1049 } 1050 1051 TagStringList Expander::separateParameter(QString *const exp, bool useUrl) 1052 { 1053 TagStringList parameter; 1054 QStringList parameter1; 1055 QString result; 1056 int begin, end; 1057 if ((begin = exp->indexOf('(')) != -1) { 1058 if ((end = exp->lastIndexOf(')')) == -1) { 1059 setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: missing ')' in Expander"))); 1060 return TagStringList(); 1061 } 1062 result = exp->mid(begin + 1, end - begin - 1); 1063 *exp = exp->left(begin); 1064 1065 bool inQuotes = false; 1066 int idx = 0; 1067 begin = 0; 1068 while (idx < result.length()) { 1069 if (result[idx].toLatin1() == '\\') { 1070 if (result[idx + 1].toLatin1() == '"') 1071 result.replace(idx, 1, ""); 1072 } 1073 if (result[idx].toLatin1() == '"') 1074 inQuotes = !inQuotes; 1075 if (result[idx].toLatin1() == ',' && !inQuotes) { 1076 parameter1.append(result.mid(begin, idx - begin)); 1077 begin = idx + 1; 1078 // qWarning() << " ---- parameter: " << parameter.join(";"); 1079 } 1080 idx++; 1081 } 1082 parameter1.append(result.mid(begin, idx - begin)); // don't forget the last one 1083 1084 for (auto &it : parameter1) { 1085 it = it.trimmed(); 1086 if (it.left(1) == "\"") 1087 it = it.mid(1, it.length() - 2); 1088 parameter.push_back(expandCurrent(it, useUrl)); 1089 if (error()) 1090 return TagStringList(); 1091 } 1092 } 1093 1094 // qWarning() << "------- exp: " << *exp << " ---- parameter: " << parameter.join(";"); 1095 return parameter; 1096 } 1097 1098 int Expander::findEnd(const QString &str, int start) 1099 { 1100 int end = str.indexOf('%', start + 1); 1101 if (end == -1) 1102 return end; 1103 int bracket = str.indexOf('(', start + 1); 1104 if (end < bracket || bracket == -1) 1105 return end; 1106 1107 int idx = bracket + 1; 1108 bool inQuotes = false; 1109 int depth = 1; 1110 while (idx < str.length()) { 1111 switch (str[idx].toLatin1()) { 1112 case '\\': 1113 idx++; 1114 break; 1115 case '"': 1116 inQuotes = !inQuotes; 1117 break; 1118 case '(': 1119 if (!inQuotes) 1120 depth++; 1121 break; 1122 case ')': 1123 if (!inQuotes) 1124 --depth; 1125 break; 1126 case '%': 1127 if (depth == 0) 1128 return idx; 1129 } // switch 1130 idx++; 1131 } // while 1132 // failsafe 1133 return -1; 1134 } 1135 1136 QList<const exp_placeholder *> &Expander::_placeholder() 1137 { 1138 static QList<const exp_placeholder *> ret; 1139 if (!ret.count()) { 1140 ret << new exp_View; 1141 ret << new exp_PanelSize; 1142 ret << new exp_ColSort; 1143 ret << new exp_Each; 1144 ret << new exp_Profile; 1145 ret << new exp_NewSearch; 1146 #ifdef SYNCHRONIZER_ENABLED 1147 ret << new exp_Sync; 1148 #endif 1149 ret << new exp_Move; 1150 ret << new exp_Copy; 1151 ret << new exp_Goto; 1152 ret << new exp_Select; 1153 ret << new exp_Clipboard; 1154 ret << new exp_Ask; 1155 ret << new exp_ListFile; 1156 ret << new exp_List; 1157 ret << new exp_Current; 1158 ret << new exp_Filter; 1159 ret << new exp_Count; 1160 ret << new exp_Path; 1161 } 1162 return ret; 1163 }