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 }