File indexing completed on 2024-05-05 17:56:45

0001 /*
0002     SPDX-FileCopyrightText: 2006 Shie Erlich <erlich@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2006 Rafi Yanai <yanai@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2006-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "useractionpage.h"
0010 
0011 // QtWidgets
0012 #include <QFileDialog>
0013 #include <QHBoxLayout>
0014 #include <QLayout>
0015 #include <QSplitter>
0016 #include <QToolButton>
0017 #include <QVBoxLayout>
0018 // QtGui
0019 #include <QClipboard>
0020 // QtXml
0021 #include <QDomDocumentType>
0022 
0023 #include <KCompletion/KLineEdit>
0024 #include <KI18n/KLocalizedString>
0025 #include <KWidgetsAddons/KMessageBox>
0026 #include <KWidgetsAddons/KStandardGuiItem>
0027 
0028 #include "../UserAction/kraction.h"
0029 #include "../UserAction/useraction.h"
0030 #include "../icon.h"
0031 #include "../krglobal.h"
0032 #include "../krusader.h"
0033 #include "actionproperty.h"
0034 #include "useractionlistview.h"
0035 
0036 // This is the filter in the QFileDialog of Import/Export:
0037 static const char *FILE_FILTER = I18N_NOOP("*.xml|XML files\n*|All files");
0038 
0039 UserActionPage::UserActionPage(QWidget *parent)
0040     : QWidget(parent)
0041 {
0042     auto *layout = new QVBoxLayout(this);
0043     layout->setContentsMargins(0, 0, 0, 0);
0044     layout->setSpacing(6); // 0px margin, 6px item-spacing
0045 
0046     // ======== pseudo-toolbar start ========
0047     auto *toolbarLayout = new QHBoxLayout; // neither margin nor spacing for the toolbar with autoRaise
0048     toolbarLayout->setSpacing(0);
0049     toolbarLayout->setContentsMargins(0, 0, 0, 0);
0050 
0051     newButton = new QToolButton(this);
0052     newButton->setIcon(Icon("document-new"));
0053     newButton->setAutoRaise(true);
0054     newButton->setToolTip(i18n("Create new useraction"));
0055 
0056     importButton = new QToolButton(this);
0057     importButton->setIcon(Icon("document-import"));
0058     importButton->setAutoRaise(true);
0059     importButton->setToolTip(i18n("Import useractions"));
0060 
0061     exportButton = new QToolButton(this);
0062     exportButton->setIcon(Icon("document-export"));
0063     exportButton->setAutoRaise(true);
0064     exportButton->setToolTip(i18n("Export useractions"));
0065 
0066     copyButton = new QToolButton(this);
0067     copyButton->setIcon(Icon("edit-copy"));
0068     copyButton->setAutoRaise(true);
0069     copyButton->setToolTip(i18n("Copy useractions to clipboard"));
0070 
0071     pasteButton = new QToolButton(this);
0072     pasteButton->setIcon(Icon("edit-paste"));
0073     pasteButton->setAutoRaise(true);
0074     pasteButton->setToolTip(i18n("Paste useractions from clipboard"));
0075 
0076     removeButton = new QToolButton(this);
0077     removeButton->setIcon(Icon("edit-delete"));
0078     removeButton->setAutoRaise(true);
0079     removeButton->setToolTip(i18n("Delete selected useractions"));
0080 
0081     toolbarLayout->addWidget(newButton);
0082     toolbarLayout->addWidget(importButton);
0083     toolbarLayout->addWidget(exportButton);
0084     toolbarLayout->addWidget(copyButton);
0085     toolbarLayout->addWidget(pasteButton);
0086     toolbarLayout->addSpacing(6); // 6 pixel nothing
0087     toolbarLayout->addWidget(removeButton);
0088     toolbarLayout->addStretch(1000); // some very large stretch-factor
0089     // ======== pseudo-toolbar end ========
0090     /* This seems obsolete now!
0091        // Display some help
0092        KMessageBox::information( this, // parent
0093          i18n( "When you apply changes to an action, the modifications "
0094           "become available in the current session immediately.\n"
0095           "When closing ActionMan, you will be asked to save the changes permanently."
0096          ),
0097         QString(), // caption
0098         "show UserAction help" //dontShowAgainName for the config
0099        );
0100     */
0101     layout->addLayout(toolbarLayout);
0102     auto *split = new QSplitter(this);
0103     layout->addWidget(split, 1000); // again a very large stretch-factor to fix the height of the toolbar
0104 
0105     actionTree = new UserActionListView(split);
0106     actionTree->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0107     actionProperties = new ActionProperty(split);
0108     actionProperties->setEnabled(false); // if there are any actions in the list, the first is displayed and this widget is enabled
0109 
0110     connect(actionTree, &UserActionListView::currentItemChanged, this, &UserActionPage::slotChangeCurrent);
0111     connect(newButton, &QToolButton::clicked, this, &UserActionPage::slotNewAction);
0112     connect(removeButton, &QToolButton::clicked, this, &UserActionPage::slotRemoveAction);
0113     connect(importButton, &QToolButton::clicked, this, &UserActionPage::slotImport);
0114     connect(exportButton, &QToolButton::clicked, this, &UserActionPage::slotExport);
0115     connect(copyButton, &QToolButton::clicked, this, &UserActionPage::slotToClip);
0116     connect(pasteButton, &QToolButton::clicked, this, &UserActionPage::slotFromClip);
0117 
0118     // forwards the changed signal of the properties
0119     connect(actionProperties, SIGNAL(changed()), SIGNAL(changed()));
0120 
0121     actionTree->setFirstActionCurrent();
0122     actionTree->setFocus();
0123 }
0124 
0125 UserActionPage::~UserActionPage() = default;
0126 
0127 bool UserActionPage::continueInSpiteOfChanges()
0128 {
0129     if (!actionProperties->isModified())
0130         return true;
0131 
0132     int answer = KMessageBox::questionYesNoCancel(this, i18n("The current action has been modified. Do you want to apply these changes?"));
0133     if (answer == KMessageBox::Cancel) {
0134         disconnect(actionTree, &UserActionListView::currentItemChanged, this, &UserActionPage::slotChangeCurrent);
0135         actionTree->setCurrentAction(actionProperties->action());
0136         connect(actionTree, &UserActionListView::currentItemChanged, this, &UserActionPage::slotChangeCurrent);
0137         return false;
0138     }
0139     if (answer == KMessageBox::Yes) {
0140         if (!actionProperties->validProperties()) {
0141             disconnect(actionTree, &UserActionListView::currentItemChanged, this, &UserActionPage::slotChangeCurrent);
0142             actionTree->setCurrentAction(actionProperties->action());
0143             connect(actionTree, &UserActionListView::currentItemChanged, this, &UserActionPage::slotChangeCurrent);
0144             return false;
0145         }
0146         slotUpdateAction();
0147     } // if Yes
0148     return true;
0149 }
0150 
0151 void UserActionPage::slotChangeCurrent()
0152 {
0153     if (!continueInSpiteOfChanges())
0154         return;
0155 
0156     KrAction *action = actionTree->currentAction();
0157     if (action) {
0158         actionProperties->setEnabled(true);
0159         // the distinct name is used as ID it is not allowed to change it afterwards because it is may referenced anywhere else
0160         actionProperties->leDistinctName->setEnabled(false);
0161         actionProperties->updateGUI(action);
0162     } else {
0163         // If the current item in the tree is no action (i.e. a category), disable the properties
0164         actionProperties->clear();
0165         actionProperties->setEnabled(false);
0166     }
0167     emit applied(); // to disable the apply-button
0168 }
0169 
0170 void UserActionPage::slotUpdateAction()
0171 {
0172     // check that we have a command line, title and a name
0173     if (!actionProperties->validProperties())
0174         return;
0175 
0176     if (actionProperties->leDistinctName->isEnabled()) {
0177         // := new entry
0178         KrAction *action = new KrAction(krApp->actionCollection(), actionProperties->leDistinctName->text());
0179         krUserAction->addKrAction(action);
0180         actionProperties->updateAction(action);
0181         UserActionListViewItem *item = actionTree->insertAction(action);
0182         actionTree->setCurrentItem(item);
0183     } else { // := edit an existing
0184         actionProperties->updateAction();
0185         actionTree->update(actionProperties->action()); // update the listviewitem as well...
0186     }
0187     apply();
0188 }
0189 
0190 void UserActionPage::slotNewAction()
0191 {
0192     if (continueInSpiteOfChanges()) {
0193         actionTree->clearSelection(); // else the user may think that he is overwriting the selected action
0194         actionProperties->clear();
0195         actionProperties->setEnabled(true); // it may be disabled because the tree has the focus on a category
0196         actionProperties->leDistinctName->setEnabled(true);
0197         actionProperties->leDistinctName->setFocus();
0198     }
0199 }
0200 
0201 void UserActionPage::slotRemoveAction()
0202 {
0203     if (!dynamic_cast<UserActionListViewItem *>(actionTree->currentItem()))
0204         return;
0205 
0206     int messageDelete = KMessageBox::warningContinueCancel(this, // parent
0207                                                            i18n("Are you sure that you want to remove all selected actions?"), // text
0208                                                            i18n("Remove Selected Actions?"), // caption
0209                                                            KStandardGuiItem::remove(), // Label for the continue-button
0210                                                            KStandardGuiItem::cancel(),
0211                                                            "Confirm Remove UserAction", // dontAskAgainName (for the config-file)
0212                                                            KMessageBox::Dangerous | KMessageBox::Notify);
0213 
0214     if (messageDelete != KMessageBox::Continue)
0215         return;
0216 
0217     actionProperties->clear();
0218     actionProperties->setEnabled(false);
0219 
0220     actionTree->removeSelectedActions();
0221 
0222     apply();
0223 }
0224 
0225 void UserActionPage::slotImport()
0226 {
0227     QString filename = QFileDialog::getOpenFileName(this, QString(), QString(), i18n(FILE_FILTER));
0228     if (filename.isEmpty())
0229         return;
0230 
0231     UserAction::KrActionList newActions;
0232     krUserAction->readFromFile(filename, UserAction::renameDoublicated, &newActions);
0233 
0234     QListIterator<KrAction *> it(newActions);
0235     while (it.hasNext())
0236         actionTree->insertAction(it.next());
0237 
0238     if (newActions.count() > 0) {
0239         apply();
0240     }
0241 }
0242 
0243 void UserActionPage::slotExport()
0244 {
0245     if (!dynamic_cast<UserActionListViewItem *>(actionTree->currentItem()))
0246         return;
0247 
0248     QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), i18n(FILE_FILTER));
0249     if (filename.isEmpty())
0250         return;
0251 
0252     QDomDocument doc = QDomDocument(ACTION_DOCTYPE);
0253     QFile file(filename);
0254     int answer = 0;
0255     if (file.open(QIODevice::ReadOnly)) { // getting here, means the file already exists an can be read
0256         if (doc.setContent(&file)) // getting here means the file exists and already contains an UserAction-XML-tree
0257             answer = KMessageBox::warningYesNoCancel(
0258                 this, // parent
0259                 i18n("This file already contains some useractions.\nDo you want to overwrite it or should it be merged with the selected actions?"), // text
0260                 i18n("Overwrite or Merge?"), // caption
0261                 KStandardGuiItem::overwrite(), // label for Yes-Button
0262                 KGuiItem(i18n("Merge")) // label for No-Button
0263             );
0264         file.close();
0265     }
0266     if (answer == 0 && file.exists())
0267         answer = KMessageBox::warningContinueCancel(this, // parent
0268                                                     i18n("This file already exists. Do you want to overwrite it?"), // text
0269                                                     i18n("Overwrite Existing File?"), // caption
0270                                                     KStandardGuiItem::overwrite() // label for Continue-Button
0271         );
0272 
0273     if (answer == KMessageBox::Cancel)
0274         return;
0275 
0276     if (answer == KMessageBox::No) // that means the merge-button
0277         doc = actionTree->dumpSelectedActions(&doc); // merge
0278     else // Yes or Continue means overwrite
0279         doc = actionTree->dumpSelectedActions();
0280 
0281     bool success = UserAction::writeToFile(doc, filename);
0282     if (!success)
0283         KMessageBox::error(this, i18n("Cannot open %1 for writing.\nNothing exported.", filename), i18n("Export Failed"));
0284 }
0285 
0286 void UserActionPage::slotToClip()
0287 {
0288     if (!dynamic_cast<UserActionListViewItem *>(actionTree->currentItem()))
0289         return;
0290 
0291     QDomDocument doc = actionTree->dumpSelectedActions();
0292     QApplication::clipboard()->setText(doc.toString());
0293 }
0294 
0295 void UserActionPage::slotFromClip()
0296 {
0297     QDomDocument doc(ACTION_DOCTYPE);
0298     if (doc.setContent(QApplication::clipboard()->text())) {
0299         QDomElement root = doc.documentElement();
0300         UserAction::KrActionList newActions;
0301         krUserAction->readFromElement(root, UserAction::renameDoublicated, &newActions);
0302 
0303         QListIterator<KrAction *> it(newActions);
0304         while (it.hasNext())
0305             actionTree->insertAction(it.next());
0306 
0307         if (newActions.count() > 0) {
0308             apply();
0309         }
0310     } // if ( doc.setContent )
0311 }
0312 
0313 bool UserActionPage::readyToQuit()
0314 {
0315     // Check if the current UserAction has changed
0316     if (!continueInSpiteOfChanges())
0317         return false;
0318 
0319     krUserAction->writeActionFile();
0320     return true;
0321 }
0322 
0323 void UserActionPage::apply()
0324 {
0325     krUserAction->writeActionFile();
0326     emit applied();
0327 }
0328 
0329 void UserActionPage::applyChanges()
0330 {
0331     slotUpdateAction();
0332 }