File indexing completed on 2024-05-19 04:28:51

0001 /*
0002  *  SPDX-FileCopyrightText: 2013 Sven Langkamp <sven.langkamp@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_action_manager.h"
0008 
0009 #include <QList>
0010 #include <kactioncollection.h>
0011 
0012 #include <kis_icon.h>
0013 #include "KisPart.h"
0014 #include "kis_action.h"
0015 #include "KisViewManager.h"
0016 #include "kis_selection_manager.h"
0017 #include "operations/kis_operation_ui_factory.h"
0018 #include "operations/kis_operation_registry.h"
0019 #include "operations/kis_operation.h"
0020 #include "kis_layer.h"
0021 #include "KisDocument.h"
0022 #include "kis_clipboard.h"
0023 #include <kis_image_animation_interface.h>
0024 #include "kis_config.h"
0025 
0026 #include <QMenu>
0027 #include "QFile"
0028 #include <QDomDocument>
0029 #include <QDomElement>
0030 
0031 class Q_DECL_HIDDEN KisActionManager::Private {
0032 
0033 public:
0034     Private() {}
0035 
0036     ~Private()
0037     {
0038         qDeleteAll(uiRegistry.values());
0039     }
0040 
0041     KisViewManager* viewManager {nullptr};
0042     KisKActionCollection *actionCollection {nullptr};
0043 
0044     QList<QPointer<KisAction>> actions;
0045     KoGenericRegistry<KisOperationUIFactory*> uiRegistry;
0046     KisOperationRegistry operationRegistry;
0047 
0048 };
0049 
0050 KisActionManager::KisActionManager(KisViewManager* viewManager, KisKActionCollection *actionCollection)
0051     : d(new Private)
0052 {
0053     d->viewManager = viewManager;
0054     d->actionCollection = actionCollection;
0055 
0056     connect(d->actionCollection,
0057             SIGNAL(inserted(QAction*)), SLOT(slotActionAddedToCollection(QAction*)));
0058 }
0059 
0060 KisActionManager::~KisActionManager()
0061 {
0062 
0063 #if 0
0064   if ((d->actions.size() > 0)) {
0065 
0066        QDomDocument doc;
0067        QDomElement e = doc.createElement("Actions");
0068        e.setAttribute("version", "2");
0069        doc.appendChild(e);
0070 
0071        Q_FOREACH (KisAction *action, d->actions) {
0072            QDomElement a = doc.createElement("Action");
0073            a.setAttribute("name", action->objectName());
0074 
0075            // But seriously, XML is the worst format ever designed
0076            auto addElement = [&](QString title, QString content) {
0077                QDomElement newNode = doc.createElement(title);
0078                QDomText    newText = doc.createTextNode(content);
0079                newNode.appendChild(newText);
0080                a.appendChild(newNode);
0081            };
0082 
0083            addElement("icon", action->icon().name());
0084            addElement("text", action->text());
0085            addElement("whatsThis" , action->whatsThis());
0086            addElement("toolTip" , action->toolTip());
0087            addElement("iconText" , action->iconText());
0088            addElement("shortcut" , action->shortcut().toString());
0089            addElement("activationFlags" , QString::number(action->activationFlags(),2));
0090            addElement("activationConditions" , QString::number(action->activationConditions(),2));
0091            addElement("defaultShortcut" , action->defaultShortcut().toString());
0092            addElement("isCheckable" , QString((action->isChecked() ? "true" : "false")));
0093            addElement("statusTip", action->statusTip());
0094            e.appendChild(a);
0095        }
0096        QFile f("ActionManager.action");
0097        f.open(QFile::WriteOnly);
0098        f.write(doc.toString().toUtf8());
0099        f.close();
0100 
0101    }
0102 #endif
0103     delete d;
0104 }
0105 
0106 void KisActionManager::setView(QPointer<KisView> imageView)
0107 {
0108     Q_UNUSED(imageView);
0109 }
0110 
0111 void KisActionManager::slotActionAddedToCollection(QAction *action)
0112 {
0113     /**
0114      * Small hack alert: not all the actions are still created by the manager and
0115      * immediately added to the action collection. Some plugins add actions
0116      * directly to the action collection when a document is created. Here we
0117      * catch these cases
0118      */
0119     KisActionRegistry::instance()->updateShortcut(action->objectName(), action);
0120 }
0121 
0122 void KisActionManager::addAction(const QString& name, KisAction* action)
0123 {
0124     Q_ASSERT(!name.isEmpty());
0125     Q_ASSERT(action);
0126     Q_ASSERT(d->viewManager);
0127     Q_ASSERT(d->actionCollection);
0128 
0129     d->actionCollection->addAction(name, action);
0130     action->setParent(d->actionCollection);
0131 
0132     d->actions.append(action);
0133     action->setActionManager(this);
0134 }
0135 
0136 void KisActionManager::takeAction(KisAction* action)
0137 {
0138     d->actions.removeOne(action);
0139 
0140     if (!action->objectName().isEmpty()) {
0141         KIS_ASSERT_RECOVER_RETURN(d->actionCollection);
0142         d->actionCollection->takeAction(action);
0143     }
0144 }
0145 
0146 KisAction *KisActionManager::actionByName(const QString &name) const
0147 {
0148     Q_FOREACH (KisAction *action, d->actions) {
0149         if (action->objectName() == name) {
0150             return action;
0151         }
0152     }
0153     return 0;
0154 }
0155 
0156 
0157 KisAction *KisActionManager::createAction(const QString &name)
0158 {
0159     KisAction *a = actionByName(name); // Check if the action already exists
0160 
0161     if (a) {
0162         dbgAction << name << "already exists";
0163         return a;
0164     }
0165 
0166     // There is some tension here. KisActionManager is supposed to be in control
0167     // of global actions, but these actions are supposed to be duplicated. We
0168     // will add them to the KisActionRegistry for the time being so we can get
0169     // properly categorized shortcuts.
0170     a = new KisAction();
0171 
0172     KisActionRegistry *actionRegistry = KisActionRegistry::instance();
0173 
0174     // Add extra properties
0175     actionRegistry->propertizeAction(name, a);
0176     bool ok; // We will skip this check
0177     int activationFlags = actionRegistry->getActionProperty(name, "activationFlags").toInt(&ok, 2);
0178     int activationConditions = actionRegistry->getActionProperty(name, "activationConditions").toInt(&ok, 2);
0179     a->setActivationFlags((KisAction::ActivationFlags) activationFlags);
0180     a->setActivationConditions((KisAction::ActivationConditions) activationConditions);
0181 
0182     addAction(name, a);
0183     return a;
0184 }
0185 
0186 void KisActionManager::updateGUI()
0187 {
0188     //TODO other flags
0189     KisAction::ActivationFlags flags;
0190 
0191     KisImageWSP image;
0192     KisNodeSP node;
0193     KisLayerSP layer;
0194     KisSelectionManager *selectionManager = 0;
0195 
0196     KisAction::ActivationConditions conditions = KisAction::NO_CONDITION;
0197 
0198     if (d->viewManager) {
0199         node = d->viewManager->activeNode();
0200         selectionManager = d->viewManager->selectionManager();
0201 
0202         // if there are no views, that means no document is open.
0203         // we cannot have nodes (selections), devices, or documents without a view
0204         if ( d->viewManager->viewCount() > 0 )
0205         {
0206             image = d->viewManager->image();
0207             flags |= KisAction::ACTIVE_IMAGE;
0208 
0209             if (image && image->animationInterface()->hasAnimation()) {
0210                 flags |= KisAction::IMAGE_HAS_ANIMATION;
0211             }
0212 
0213             if (d->viewManager->viewCount() > 1) {
0214                 flags |= KisAction::MULTIPLE_IMAGES;
0215             }
0216 
0217             if (d->viewManager->document() && d->viewManager->document()->isModified()) {
0218                 flags |= KisAction::CURRENT_IMAGE_MODIFIED;
0219             }
0220 
0221             if (d->viewManager->activeDevice()) {
0222                 flags |= KisAction::ACTIVE_DEVICE;
0223             }
0224         }
0225 
0226         if (d->viewManager->selectionEditable()) {
0227             conditions |= KisAction::SELECTION_EDITABLE;
0228         }
0229 
0230     }
0231 
0232     // is there a selection/mask?
0233     // you have to have at least one view (document) open for this to be true
0234     if (node) {
0235 
0236         // if a node exists, we know there is an active layer as well
0237         flags |= KisAction::ACTIVE_NODE;
0238 
0239         layer = qobject_cast<KisLayer*>(node.data());
0240         if (layer) {
0241             flags |= KisAction::ACTIVE_LAYER;
0242         }
0243 
0244         if (node->inherits("KisTransparencyMask")) {
0245             flags |= KisAction::ACTIVE_TRANSPARENCY_MASK;
0246         }
0247 
0248 
0249         if (layer && layer->inherits("KisShapeLayer")) {
0250             flags |= KisAction::ACTIVE_SHAPE_LAYER;
0251         }
0252 
0253         if (KisClipboard::instance()->hasLayers()) {
0254             flags |= KisAction::LAYERS_IN_CLIPBOARD;
0255         }
0256 
0257         if (selectionManager) {
0258 
0259             if (selectionManager->canReselectDeactivatedSelection()) {
0260                 flags |= KisAction::IMAGE_CAN_RESELECT;
0261             }
0262 
0263             if (selectionManager->havePixelsSelected()) {
0264                 flags |= KisAction::PIXELS_SELECTED;
0265             }
0266 
0267             if (selectionManager->haveShapesSelected()) {
0268                 flags |= KisAction::SHAPES_SELECTED;
0269             }
0270 
0271             if (selectionManager->haveAnySelectionWithPixels()) {
0272                 flags |= KisAction::ANY_SELECTION_WITH_PIXELS;
0273             }
0274 
0275             if (selectionManager->haveShapeSelectionWithShapes()) {
0276                 flags |= KisAction::SHAPE_SELECTION_WITH_SHAPES;
0277             }
0278 
0279             if (selectionManager->haveRasterSelectionWithPixels()) {
0280                 flags |= KisAction::PIXEL_SELECTION_WITH_PIXELS;
0281             }
0282 
0283             if (selectionManager->havePixelsInClipboard()) {
0284                 flags |= KisAction::PIXELS_IN_CLIPBOARD;
0285             }
0286 
0287             if (selectionManager->haveShapesInClipboard()) {
0288                 flags |= KisAction::SHAPES_IN_CLIPBOARD;
0289             }
0290         }
0291 
0292         if (node->isEditable(false)) {
0293             conditions |= KisAction::ACTIVE_NODE_EDITABLE;
0294         }
0295 
0296         if (node->hasEditablePaintDevice()) {
0297             conditions |= KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE;
0298         }
0299     }
0300 
0301     KisConfig cfg(true);
0302     if (cfg.useOpenGL()) {
0303         conditions |= KisAction::OPENGL_ENABLED;
0304     }
0305 
0306     // loop through all actions in action manager and determine what should be enabled
0307     Q_FOREACH (QPointer<KisAction> action, d->actions) {
0308         bool enable;
0309         if (action) {
0310             if (action->activationFlags() == KisAction::NONE) {
0311                 enable = true;
0312             }
0313             else {
0314                 enable = action->activationFlags() & flags; // combine action flags with updateGUI flags
0315             }
0316 
0317             enable = enable && (int)(action->activationConditions() & conditions) == (int)action->activationConditions();
0318 
0319             if (node && enable) {
0320                 Q_FOREACH (const QString &type, action->excludedNodeTypes()) {
0321                     if (node->inherits(type.toLatin1())) {
0322                         enable = false;
0323                         break;
0324                     }
0325                 }
0326             }
0327 
0328             action->setActionEnabled(enable);
0329         }
0330     }
0331 }
0332 
0333 KisAction *KisActionManager::createStandardAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member)
0334 {
0335     QAction *standardAction = KStandardAction::create(actionType, receiver, member, 0);
0336     KisAction *action = new KisAction(standardAction->icon(), standardAction->text());
0337 
0338     const QList<QKeySequence> defaultShortcuts = standardAction->property("defaultShortcuts").value<QList<QKeySequence> >();
0339     const QKeySequence defaultShortcut = defaultShortcuts.isEmpty() ? QKeySequence() : defaultShortcuts.at(0);
0340     action->setDefaultShortcut(standardAction->shortcut());
0341 #ifdef Q_OS_WIN
0342     if (actionType == KStandardAction::SaveAs && defaultShortcuts.isEmpty()) {
0343         action->setShortcut(QKeySequence("CTRL+SHIFT+S"));
0344     }
0345 #endif
0346     action->setCheckable(standardAction->isCheckable());
0347     if (action->isCheckable()) {
0348         action->setChecked(standardAction->isChecked());
0349     }
0350     action->setMenuRole(standardAction->menuRole());
0351     action->setText(standardAction->text());
0352     action->setToolTip(standardAction->toolTip());
0353 
0354     if (receiver && member) {
0355         if (actionType == KStandardAction::OpenRecent) {
0356             QObject::connect(action, SIGNAL(urlSelected(QUrl)), receiver, member);
0357         }
0358         else if (actionType == KStandardAction::ConfigureToolbars) {
0359             QObject::connect(action, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection);
0360         }
0361         else {
0362             QObject::connect(action, SIGNAL(triggered(bool)), receiver, member);
0363         }
0364     }
0365 
0366     KisActionRegistry *actionRegistry = KisActionRegistry::instance();
0367     actionRegistry->propertizeAction(standardAction->objectName(), action);
0368 
0369     addAction(standardAction->objectName(), action);
0370     delete standardAction;
0371     return action;
0372 }
0373 
0374 void KisActionManager::safePopulateMenu(QMenu *menu, const QString &actionId, KisActionManager *actionManager)
0375 {
0376     KIS_SAFE_ASSERT_RECOVER_RETURN(actionManager);
0377 
0378     KisAction *action = actionManager->actionByName(actionId);
0379     KIS_SAFE_ASSERT_RECOVER_RETURN(action);
0380 
0381     menu->addAction(action);
0382 }
0383 
0384 void KisActionManager::registerOperationUIFactory(KisOperationUIFactory* factory)
0385 {
0386     d->uiRegistry.add(factory);
0387 }
0388 
0389 void KisActionManager::registerOperation(KisOperation* operation)
0390 {
0391     d->operationRegistry.add(operation);
0392 }
0393 
0394 void KisActionManager::runOperation(const QString& id)
0395 {
0396     KisOperationConfigurationSP config = new KisOperationConfiguration(id);
0397 
0398     KisOperationUIFactory* uiFactory = d->uiRegistry.get(id);
0399     if (uiFactory) {
0400         bool gotConfig = uiFactory->fetchConfiguration(d->viewManager, config);
0401         if (!gotConfig) {
0402             return;
0403         }
0404     }
0405 
0406     runOperationFromConfiguration(config);
0407 }
0408 
0409 void KisActionManager::runOperationFromConfiguration(KisOperationConfigurationSP config)
0410 {
0411     KisOperation* operation = d->operationRegistry.get(config->id());
0412     Q_ASSERT(operation);
0413     operation->runFromXML(d->viewManager, *config);
0414 }
0415 
0416 void KisActionManager::dumpActionFlags()
0417 {
0418     QFile data("actions.txt");
0419     if (data.open(QFile::WriteOnly | QFile::Truncate)) {
0420         QTextStream out(&data);
0421         out.setCodec("UTF-8");
0422 
0423         Q_FOREACH (KisAction* action, d->actions) {
0424             KisAction::ActivationFlags flags = action->activationFlags();
0425             out << "-------- " << action->text() << " --------\n";
0426             out << "Action will activate on: \n";
0427 
0428             if (flags & KisAction::ACTIVE_IMAGE) {
0429                 out << "    Active image\n";
0430             }
0431             if (flags & KisAction::MULTIPLE_IMAGES) {
0432                 out << "    More than one image open\n";
0433             }
0434             if (flags & KisAction::CURRENT_IMAGE_MODIFIED) {
0435                 out << "    Active image modified\n";
0436             }
0437             if (flags & KisAction::ACTIVE_DEVICE) {
0438                 out << "    Active device\n";
0439             }
0440             if (flags & KisAction::ACTIVE_LAYER) {
0441                 out << "    Active layer\n";
0442             }
0443             if (flags & KisAction::ACTIVE_TRANSPARENCY_MASK) {
0444                 out << "    Active transparency mask\n";
0445             }
0446             if (flags & KisAction::ACTIVE_NODE) {
0447                 out << "    Active node\n";
0448             }
0449             if (flags & KisAction::ACTIVE_SHAPE_LAYER) {
0450                 out << "    Active shape layer\n";
0451             }
0452             if (flags & KisAction::PIXELS_SELECTED) {
0453                 out << "    Pixels selected\n";
0454             }
0455             if (flags & KisAction::SHAPES_SELECTED) {
0456                 out << "    Shapes selected\n";
0457             }
0458             if (flags & KisAction::ANY_SELECTION_WITH_PIXELS) {
0459                 out << "    Any selection with pixels\n";
0460             }
0461             if (flags & KisAction::PIXELS_IN_CLIPBOARD) {
0462                 out << "    Pixels in clipboard\n";
0463             }
0464             if (flags & KisAction::SHAPES_IN_CLIPBOARD) {
0465                 out << "    Shape in clipboard\n";
0466             }
0467             if (flags & KisAction::IMAGE_HAS_ANIMATION) {
0468                 out << "    Image has animation\n";
0469             }
0470 
0471             out << "\n\n";
0472             out << "Action will only activate if the following conditions are met: \n";
0473             KisAction::ActivationConditions conditions = action->activationConditions();
0474             if ((int)conditions == 0) {
0475                 out << "    -\n";
0476             }
0477             if (conditions & KisAction::ACTIVE_NODE_EDITABLE) {
0478                 out << "    Active Node editable\n";
0479             }
0480             if (conditions & KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE) {
0481                 out << "    Active Node has editable paint device\n";
0482             }
0483             if (conditions & KisAction::SELECTION_EDITABLE) {
0484                 out << "    Selection is editable\n";
0485             }
0486             if (conditions & KisAction::OPENGL_ENABLED) {
0487                 out << "    OpenGL is enabled\n";
0488             }
0489             out << "\n\n";
0490         }
0491     }
0492 }