diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp index 08d7c4d1b2..7bf3be91a4 100644 --- a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp +++ b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp @@ -1,792 +1,795 @@ /* * kis_layer_box.cc - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * Copyright (c) 2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_box.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action.h" #include "kis_action_manager.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_slider_spin_box.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_node_model.h" #include "canvas/kis_canvas2.h" #include "KisDocument.h" #include "kis_dummies_facade_base.h" #include "kis_shape_controller.h" #include "kis_selection_mask.h" #include "kis_config.h" #include "KisView.h" #include "ui_wdglayerbox.h" class ButtonAction : public KisAction { public: ButtonAction(QAbstractButton* button, const KIcon& icon, const QString& text, QObject* parent) : KisAction(icon, text, parent) , m_button(button) { connect(m_button, SIGNAL(clicked()), this, SLOT(trigger())); } ButtonAction(QAbstractButton* button, QObject* parent) : KisAction(parent) , m_button(button) { connect(m_button, SIGNAL(clicked()), this, SLOT(trigger())); } virtual void setActionEnabled(bool enabled) { KisAction::setActionEnabled(enabled); m_button->setEnabled(enabled); } private: QAbstractButton* m_button; }; inline void KisLayerBox::connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id) { Q_ASSERT(view); KisAction *action = view->actionManager()->actionByName(id); connect(button, SIGNAL(clicked()), action, SLOT(trigger())); connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool))); } inline void KisLayerBox::addActionToMenu(QMenu *menu, const QString &id) { Q_ASSERT(m_canvas); menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id)); } KisLayerBox::KisLayerBox() : QDockWidget(i18n("Layers")) , m_canvas(0) , m_wdgLayerBox(new Ui_WdgLayerBox) { KisConfig cfg; setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_opacityDelayTimer.setSingleShot(true); m_wdgLayerBox->setupUi(mainWidget); m_wdgLayerBox->listLayers->setDefaultDropAction(Qt::MoveAction); m_wdgLayerBox->listLayers->setSelectionMode(QAbstractItemView::ExtendedSelection); m_wdgLayerBox->listLayers->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_wdgLayerBox->listLayers->setSelectionBehavior(QAbstractItemView::SelectRows); connect(m_wdgLayerBox->listLayers, SIGNAL(contextMenuRequested(const QPoint&, const QModelIndex&)), this, SLOT(slotContextMenuRequested(const QPoint&, const QModelIndex&))); connect(m_wdgLayerBox->listLayers, SIGNAL(collapsed(const QModelIndex&)), SLOT(slotCollapsed(const QModelIndex &))); connect(m_wdgLayerBox->listLayers, SIGNAL(expanded(const QModelIndex&)), SLOT(slotExpanded(const QModelIndex &))); connect(m_wdgLayerBox->listLayers, SIGNAL(selectionChanged(const QModelIndexList&)), SLOT(selectionChanged(const QModelIndexList&))); m_viewModeMenu = new KMenu(this); QActionGroup *group = new QActionGroup(this); QList actions; actions << m_viewModeMenu->addAction(koIcon("view-list-text"), i18n("Minimal View"), this, SLOT(slotMinimalView())); actions << m_viewModeMenu->addAction(koIcon("view-list-details"), i18n("Detailed View"), this, SLOT(slotDetailedView())); actions << m_viewModeMenu->addAction(koIcon("view-preview"), i18n("Thumbnail View"), this, SLOT(slotThumbnailView())); for (int i = 0, n = actions.count(); i < n; ++i) { actions[i]->setCheckable(true); actions[i]->setActionGroup(group); } m_wdgLayerBox->bnAdd->setIcon(themedIcon("addlayer")); m_wdgLayerBox->bnViewMode->setMenu(m_viewModeMenu); m_wdgLayerBox->bnViewMode->setPopupMode(QToolButton::InstantPopup); m_wdgLayerBox->bnViewMode->setIcon(koIcon("view-choose")); m_wdgLayerBox->bnViewMode->setText(i18n("View mode")); m_wdgLayerBox->bnDelete->setIcon(themedIcon("deletelayer")); m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnRaise->setEnabled(false); m_wdgLayerBox->bnRaise->setIcon(themedIcon("arrowupblr")); m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setEnabled(false); m_wdgLayerBox->bnLower->setIcon(themedIcon("arrowdown")); m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLeft->setEnabled(true); m_wdgLayerBox->bnLeft->setIcon(themedIcon("removefromfolder")); m_wdgLayerBox->bnLeft->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnRight->setEnabled(true); m_wdgLayerBox->bnRight->setIcon(themedIcon("addtofolder")); m_wdgLayerBox->bnRight->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnProperties->setIcon(themedIcon("properties")); m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnDuplicate->setIcon(themedIcon("duplicatelayer")); m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22)); m_removeAction = new ButtonAction(m_wdgLayerBox->bnDelete, themedIcon("deletelayer"), i18n("&Remove Layer"), this); m_removeAction->setActivationFlags(KisAction::ACTIVE_NODE); m_removeAction->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE); m_removeAction->setObjectName("remove_layer"); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked())); m_actions.append(m_removeAction); KisAction* action = new ButtonAction(m_wdgLayerBox->bnLeft, this); action->setText(i18n("Move Layer Left")); action->setActivationFlags(KisAction::ACTIVE_NODE); action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE); action->setObjectName("move_layer_left"); connect(action, SIGNAL(triggered()), this, SLOT(slotLeftClicked())); m_actions.append(action); action = new ButtonAction(m_wdgLayerBox->bnRight, this); action->setText(i18n("Move Layer Right")); action->setActivationFlags(KisAction::ACTIVE_NODE); action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE); action->setObjectName("move_layer_right"); connect(action, SIGNAL(triggered()), this, SLOT(slotRightClicked())); m_actions.append(action); m_propertiesAction = new ButtonAction(m_wdgLayerBox->bnProperties, themedIcon("properties"), i18n("&Properties..."),this); m_propertiesAction->setActivationFlags(KisAction::ACTIVE_NODE); m_propertiesAction->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE); m_propertiesAction->setObjectName("layer_properties"); connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked())); m_actions.append(m_propertiesAction); // NOTE: this is _not_ a mistake. The layerbox shows the layers in the reverse order connect(m_wdgLayerBox->bnRaise, SIGNAL(clicked()), SLOT(slotLowerClicked())); connect(m_wdgLayerBox->bnLower, SIGNAL(clicked()), SLOT(slotRaiseClicked())); // END NOTE if (cfg.sliderLabels()) { m_wdgLayerBox->opacityLabel->hide(); m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity"))); } m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0); m_wdgLayerBox->doubleOpacity->setSuffix("%"); connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal))); connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged())); connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int))); m_selectOpaque = new KisAction(i18n("&Select Opaque"), this); m_selectOpaque->setActivationFlags(KisAction::ACTIVE_DEVICE); m_selectOpaque->setActivationConditions(KisAction::SELECTION_EDITABLE); m_selectOpaque->setObjectName("select_opaque"); connect(m_selectOpaque, SIGNAL(triggered(bool)), this, SLOT(slotSelectOpaque())); m_actions.append(m_selectOpaque); m_newLayerMenu = new KMenu(this); m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu); m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup); m_nodeModel = new KisNodeModel(this); /** * Connect model updateUI() to enable/disable controls. * Note: nodeActivated() is connected separately in setImage(), because * it needs particular order of calls: first the connection to the * node manager should be called, then updateUI() */ connect(m_nodeModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(updateUI())); KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this); showGlobalSelectionMask->setObjectName("show-global-selection-mask"); showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in Layers docker")); showGlobalSelectionMask->setCheckable(true); connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool))); m_actions.append(showGlobalSelectionMask); showGlobalSelectionMask->setChecked(cfg.showGlobalSelection()); m_wdgLayerBox->listLayers->setModel(m_nodeModel); } KisLayerBox::~KisLayerBox() { delete m_wdgLayerBox; } void expandNodesRecursively(KisNodeSP root, QPointer nodeModel, KisDocumentSectionView *sectionView) { if (!root) return; if (nodeModel.isNull()) return; if (!sectionView) return; sectionView->blockSignals(true); KisNodeSP node = root->firstChild(); while (node) { QModelIndex idx = nodeModel->indexFromNode(node); if (idx.isValid()) { if (node->collapsed()) { sectionView->collapse(idx); } } if (node->childCount() > 0) { expandNodesRecursively(node, nodeModel, sectionView); } node = node->nextSibling(); } sectionView->blockSignals(false); } void KisLayerBox::setMainWindow(KisViewManager* kisview) { m_nodeManager = kisview->nodeManager(); foreach(KisAction *action, m_actions) { kisview->actionManager()-> addAction(action->objectName(), action); } connectActionToButton(kisview, m_wdgLayerBox->bnAdd, "add_new_paint_layer"); connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer"); } void KisLayerBox::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_nodeModel->setDummiesFacade(0, 0, 0); disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); disconnect(m_nodeModel, SIGNAL(nodeActivated(KisNodeSP)), this, SLOT(updateUI())); } m_canvas = dynamic_cast(canvas); if (m_canvas) { m_image = m_canvas->image(); connect(m_image, SIGNAL(sigImageUpdated(QRect)), SLOT(updateThumbnail())); KisDocument* doc = static_cast(m_canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); KisDummiesFacadeBase *kritaDummiesFacade = static_cast(kritaShapeController); m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController); connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged())); // cold start if (m_nodeManager) { setCurrentNode(m_nodeManager->activeNode()); } else { setCurrentNode(m_canvas->imageView()->currentNode()); } // Connection KisNodeManager -> KisLayerBox connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(setCurrentNode(KisNodeSP))); // Connection KisLayerBox -> KisNodeManager // The order of these connections is important! See comment in the ctor connect(m_nodeModel, SIGNAL(nodeActivated(KisNodeSP)), m_nodeManager, SLOT(slotUiActivatedNode(KisNodeSP))); connect(m_nodeModel, SIGNAL(nodeActivated(KisNodeSP)), SLOT(updateUI())); // Connection KisLayerBox -> KisNodeManager (isolate layer) connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()), m_nodeManager, SLOT(toggleIsolateActiveNode())); // Node manipulation methods are forwarded to the node manager connect(m_nodeModel, SIGNAL(requestAddNode(KisNodeSP, KisNodeSP, KisNodeSP)), m_nodeManager, SLOT(addNodeDirect(KisNodeSP, KisNodeSP, KisNodeSP))); connect(m_nodeModel, SIGNAL(requestMoveNode(KisNodeSP, KisNodeSP, KisNodeSP)), m_nodeManager, SLOT(moveNodeDirect(KisNodeSP, KisNodeSP, KisNodeSP))); m_wdgLayerBox->listLayers->expandAll(); expandNodesRecursively(m_image->rootLayer(), m_nodeModel, m_wdgLayerBox->listLayers); m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex()); addActionToMenu(m_newLayerMenu, "add_new_paint_layer"); addActionToMenu(m_newLayerMenu, "add_new_group_layer"); addActionToMenu(m_newLayerMenu, "add_new_clone_layer"); addActionToMenu(m_newLayerMenu, "add_new_shape_layer"); addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer"); addActionToMenu(m_newLayerMenu, "add_new_fill_layer"); addActionToMenu(m_newLayerMenu, "add_new_file_layer"); m_newLayerMenu->addSeparator(); addActionToMenu(m_newLayerMenu, "add_new_transparency_mask"); addActionToMenu(m_newLayerMenu, "add_new_filter_mask"); addActionToMenu(m_newLayerMenu, "add_new_transform_mask"); addActionToMenu(m_newLayerMenu, "add_new_selection_mask"); } } void KisLayerBox::unsetCanvas() { setEnabled(false); if (m_canvas) { m_newLayerMenu->clear(); } setCanvas(0); m_nodeManager->setSelectedNodes(QList()); } void KisLayerBox::notifyImageDeleted() { setCanvas(0); m_nodeManager->setSelectedNodes(QList()); } void KisLayerBox::updateUI() { if (!m_canvas) return; if (!m_nodeManager) return; KisNodeSP activeNode = m_nodeManager->activeNode(); m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false)); if (activeNode) { if (m_nodeManager->activePaintDevice()) { slotFillCompositeOps(m_nodeManager->activeColorSpace()); } else { slotFillCompositeOps(m_image->colorSpace()); } if (activeNode->inherits("KisMask")) { m_wdgLayerBox->cmbComposite->setEnabled(false); m_wdgLayerBox->doubleOpacity->setEnabled(false); } else if (activeNode->inherits("KisLayer")) { m_wdgLayerBox->doubleOpacity->setEnabled(true); KisLayerSP l = qobject_cast(activeNode.data()); slotSetOpacity(l->opacity() * 100.0 / 255); const KoCompositeOp* compositeOp = l->compositeOp(); if (compositeOp) { slotSetCompositeOp(compositeOp); } else { m_wdgLayerBox->cmbComposite->setEnabled(false); } const KisGroupLayer *group = qobject_cast(activeNode.data()); bool compositeSelectionActive = !(group && group->passThroughMode()); m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive); } } } /** * This method is callen *only* when non-GUI code requested the * change of the current node */ void KisLayerBox::setCurrentNode(KisNodeSP node) { QModelIndex index = node ? m_nodeModel->indexFromNode(node) : QModelIndex(); m_wdgLayerBox->listLayers->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); updateUI(); } void KisLayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp) { KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id()); m_wdgLayerBox->cmbComposite->blockSignals(true); m_wdgLayerBox->cmbComposite->selectCompositeOp(opId); m_wdgLayerBox->cmbComposite->blockSignals(false); } void KisLayerBox::slotFillCompositeOps(const KoColorSpace* colorSpace) { m_wdgLayerBox->cmbComposite->validate(colorSpace); } // range: 0-100 void KisLayerBox::slotSetOpacity(double opacity) { Q_ASSERT(opacity >= 0 && opacity <= 100); m_wdgLayerBox->doubleOpacity->blockSignals(true); m_wdgLayerBox->doubleOpacity->setValue(opacity); m_wdgLayerBox->doubleOpacity->blockSignals(false); } void KisLayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index) { QMenu menu; if (index.isValid()) { menu.addAction(m_propertiesAction); menu.addSeparator(); menu.addAction(m_removeAction); addActionToMenu(&menu, "duplicatelayer"); addActionToMenu(&menu, "flatten_image"); addActionToMenu(&menu, "flatten_layer"); // TODO: missing icon "edit-merge" QAction* mergeLayerDown = menu.addAction(i18n("&Merge with Layer Below"), this, SLOT(slotMergeLayer())); if (!index.sibling(index.row() + 1, 0).isValid()) mergeLayerDown->setEnabled(false); menu.addSeparator(); QMenu *convertToMenu = menu.addMenu(i18n("&Convert")); addActionToMenu(convertToMenu, "convert_to_paint_layer"); addActionToMenu(convertToMenu, "convert_to_transparency_mask"); addActionToMenu(convertToMenu, "convert_to_filter_mask"); addActionToMenu(convertToMenu, "convert_to_transform_mask"); addActionToMenu(convertToMenu, "convert_to_selection_mask"); QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha")); addActionToMenu(splitAlphaMenu, "split_alpha_into_mask"); addActionToMenu(splitAlphaMenu, "split_alpha_write"); addActionToMenu(splitAlphaMenu, "split_alpha_save_merged"); - addActionToMenu(&menu, "isolate_layer"); + KisNodeSP node = m_nodeModel->nodeFromIndex(index); + if (node && !node->inherits("KisTransformMask")) { + addActionToMenu(&menu, "isolate_layer"); + } } menu.addSeparator(); addActionToMenu(&menu, "add_new_transparency_mask"); addActionToMenu(&menu, "add_new_filter_mask"); addActionToMenu(&menu, "add_new_transform_mask"); addActionToMenu(&menu, "add_new_selection_mask"); menu.addSeparator(); menu.addAction(m_selectOpaque); menu.exec(pos); } void KisLayerBox::slotMergeLayer() { if (!m_canvas) return; m_nodeManager->mergeLayerDown(); } void KisLayerBox::slotMinimalView() { m_wdgLayerBox->listLayers->setDisplayMode(KisDocumentSectionView::MinimalMode); } void KisLayerBox::slotDetailedView() { m_wdgLayerBox->listLayers->setDisplayMode(KisDocumentSectionView::DetailedMode); } void KisLayerBox::slotThumbnailView() { m_wdgLayerBox->listLayers->setDisplayMode(KisDocumentSectionView::ThumbnailMode); } void KisLayerBox::slotRmClicked() { if (!m_canvas) return; m_nodeManager->removeNode(); } void KisLayerBox::slotRaiseClicked() { if (!m_canvas) return; KisNodeSP node = m_nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); if (!m_nodeManager->activeNode()->prevSibling()) { if (!grandParent) return; if (!grandParent->parent() && node->inherits("KisMask")) return; m_nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent)); } else { m_nodeManager->raiseNode(); } } void KisLayerBox::slotLowerClicked() { if (!m_canvas) return; KisNodeSP node = m_nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); if (!m_nodeManager->activeNode()->nextSibling()) { if (!grandParent) return; if (!grandParent->parent() && node->inherits("KisMask")) return; m_nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1); } else { m_nodeManager->lowerNode(); } } void KisLayerBox::slotLeftClicked() { if (!m_canvas) return; foreach(KisNodeSP node, m_nodeManager->selectedNodes()) { KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); quint16 nodeIndex = parent->index(node); if (!grandParent) continue; if (!grandParent->parent() && node->inherits("KisMask")) continue; if (nodeIndex <= parent->childCount() / 2) { m_nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent)); } else { m_nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1); } } } void KisLayerBox::slotRightClicked() { if (!m_canvas) return; foreach(KisNodeSP node, m_nodeManager->selectedNodes()) { KisNodeSP parent = m_nodeManager->activeNode()->parent(); KisNodeSP newParent; int nodeIndex = parent->index(node); int indexAbove = nodeIndex + 1; int indexBelow = nodeIndex - 1; if (parent->at(indexBelow) && parent->at(indexBelow)->allowAsChild(node)) { newParent = parent->at(indexBelow); m_nodeManager->moveNodeAt(node, newParent, newParent->childCount()); } else if (parent->at(indexAbove) && parent->at(indexAbove)->allowAsChild(node)) { newParent = parent->at(indexAbove); m_nodeManager->moveNodeAt(node, newParent, 0); } } } void KisLayerBox::slotPropertiesClicked() { if (!m_canvas) return; if (KisNodeSP active = m_nodeManager->activeNode()) { m_nodeManager->nodeProperties(active); } } void KisLayerBox::slotCompositeOpChanged(int index) { Q_UNUSED(index); if (!m_canvas) return; QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id(); m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp)); } void KisLayerBox::slotOpacityChanged() { if (!m_canvas) return; m_nodeManager->nodeOpacityChanged(m_newOpacity, true); } void KisLayerBox::slotOpacitySliderMoved(qreal opacity) { m_newOpacity = opacity; m_opacityDelayTimer.start(200); } void KisLayerBox::slotCollapsed(const QModelIndex &index) { KisNodeSP node = m_nodeModel->nodeFromIndex(index); if (node) { node->setCollapsed(true); } } void KisLayerBox::slotExpanded(const QModelIndex &index) { KisNodeSP node = m_nodeModel->nodeFromIndex(index); if (node) { node->setCollapsed(false); } } void KisLayerBox::slotSelectOpaque() { if (!m_canvas) return; QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque"); if (action) { action->trigger(); } } void KisLayerBox::slotNodeCollapsedChanged() { m_wdgLayerBox->listLayers->expandAll(); expandNodesRecursively(m_image->rootLayer(), m_nodeModel, m_wdgLayerBox->listLayers); } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } KisNodeSP KisLayerBox::findNonHidableNode(KisNodeSP startNode) { if (isSelectionMask(startNode) && startNode->parent() && !startNode->parent()->parent()) { KisNodeSP node = startNode->prevSibling(); while (node && isSelectionMask(node)) { node = node->prevSibling(); } if (!node) { node = startNode->nextSibling(); while (node && isSelectionMask(node)) { node = node->nextSibling(); } } if (!node) { node = m_image->root()->lastChild(); while (node && isSelectionMask(node)) { node = node->prevSibling(); } } KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!"); startNode = node; } return startNode; } void KisLayerBox::slotEditGlobalSelection(bool showSelections) { KisNodeSP lastActiveNode = m_nodeManager->activeNode(); KisNodeSP activateNode = lastActiveNode; if (!showSelections) { activateNode = findNonHidableNode(activateNode); } m_nodeModel->setShowGlobalSelection(showSelections); if (showSelections) { KisNodeSP newMask = m_image->rootLayer()->selectionMask(); if (newMask) { activateNode = newMask; } } if (activateNode) { if (lastActiveNode != activateNode) { m_nodeManager->slotNonUiActivatedNode(activateNode); } else { setCurrentNode(lastActiveNode); } } } void KisLayerBox::selectionChanged(const QModelIndexList selection) { if (!m_nodeManager) return; if (selection.isEmpty() && m_nodeManager->activeNode()) { m_wdgLayerBox->listLayers->selectionModel()-> setCurrentIndex( m_nodeModel->indexFromNode(m_nodeManager->activeNode()), QItemSelectionModel::ClearAndSelect); return; } QList selectedNodes; foreach(const QModelIndex &idx, selection) { selectedNodes << m_nodeModel->nodeFromIndex(idx); } m_nodeManager->setSelectedNodes(selectedNodes); updateUI(); } void KisLayerBox::updateThumbnail() { m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex()); } #include "kis_layer_box.moc" diff --git a/krita/ui/kis_node_manager.cpp b/krita/ui/kis_node_manager.cpp index 916b0ab377..9733f2d557 100644 --- a/krita/ui/kis_node_manager.cpp +++ b/krita/ui/kis_node_manager.cpp @@ -1,1236 +1,1238 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPart.h" #include "canvas/kis_canvas2.h" #include "kis_shape_controller.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_mask_manager.h" #include "kis_group_layer.h" #include "kis_layer_manager.h" #include "kis_selection_manager.h" #include "kis_node_commands_adapter.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_processing_applicator.h" #include "kis_sequential_iterator.h" #include "kis_transaction.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" struct KisNodeManager::Private { Private(KisNodeManager *_q) : q(_q) , view(0) , imageView(0) , layerManager(0) , maskManager(0) , self(0) , commandsAdapter(0) { } ~Private() { delete layerManager; delete maskManager; } KisNodeManager *q; KisViewManager * view; QPointerimageView; KisLayerManager * layerManager; KisMaskManager * maskManager; KisNodeManager* self; KisNodeCommandsAdapter* commandsAdapter; KisAction *mergeSelectedLayers; QList selectedNodes; bool activateNodeImpl(KisNodeSP node); QSignalMapper nodeCreationSignalMapper; QSignalMapper nodeConversionSignalMapper; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == self->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager->activateMask(0); layerManager->activateLayer(0); } else { KoShape * shape = view->document()->shapeForNode(node); Q_ASSERT(shape); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); Q_ASSERT(shapeLayer); // shapeLayer->setGeometryProtected(node->userLocked()); // shapeLayer->setVisible(node->visible()); selection->setActiveLayer(shapeLayer); imageView->setCurrentNode(node); if (KisLayerSP layer = dynamic_cast(node.data())) { maskManager->activateMask(0); layerManager->activateLayer(layer); } else if (KisMaskSP mask = dynamic_cast(node.data())) { maskManager->activateMask(mask); // XXX_NODE: for now, masks cannot be nested. layerManager->activateLayer(static_cast(node->parent().data())); } } return true; } KisNodeManager::KisNodeManager(KisViewManager *view) : m_d(new Private(this)) { m_d->view = view; m_d->layerManager = new KisLayerManager(view); m_d->maskManager = new KisMaskManager(view); m_d->self = this; m_d->commandsAdapter = new KisNodeCommandsAdapter(view); connect(m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP))); m_d->mergeSelectedLayers = new KisAction(i18n("&Merge Selected Layers"), this); m_d->mergeSelectedLayers->setActivationFlags(KisAction::ACTIVE_LAYER); view->actionManager()->addAction("merge_selected_layers", m_d->mergeSelectedLayers); connect(m_d->mergeSelectedLayers, SIGNAL(triggered()), this, SLOT(mergeLayerDown())); } KisNodeManager::~KisNodeManager() { delete m_d->commandsAdapter; delete m_d; } void KisNodeManager::setView(QPointerimageView) { m_d->maskManager->setView(imageView); m_d->layerManager->setView(imageView); if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->view->document()->shapeController()); Q_ASSERT(shapeController); shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this); m_d->imageView->image()->disconnect(this); } m_d->imageView = imageView; if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->view->document()->shapeController()); Q_ASSERT(shapeController); connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP))); connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction())); m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode()); } } #define NEW_LAYER_ACTION(id, text, layerType, icon) \ { \ action = new KisAction(icon, text, this); \ action->setActivationFlags(KisAction::ACTIVE_NODE); \ actionManager->addAction(id, action); \ m_d->nodeCreationSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeCreationSignalMapper, SLOT(map())); \ } #define NEW_LAYER_ACTION_KEY(id, text, layerType, icon, shortcut) \ { \ NEW_LAYER_ACTION(id, text, layerType, icon); \ action->setShortcut(KShortcut(shortcut)); \ } #define NEW_MASK_ACTION(id, text, layerType, icon) \ { \ NEW_LAYER_ACTION(id, text, layerType, icon); \ action->setActivationFlags(KisAction::ACTIVE_LAYER); \ } #define CONVERT_NODE_ACTION(id, text, layerType, icon) \ { \ action = new KisAction(icon, text, this); \ action->setActivationFlags(KisAction::ACTIVE_NODE); \ action->setExcludedNodeTypes(QStringList(layerType)); \ actionManager->addAction(id, action); \ m_d->nodeConversionSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeConversionSignalMapper, SLOT(map())); \ } void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager) { m_d->layerManager->setup(actionManager); m_d->maskManager->setup(actionCollection, actionManager); KisAction * action = new KisAction(koIcon("object-flip-horizontal"), i18n("Mirror Layer Horizontally"), this); action->setActivationFlags(KisAction::ACTIVE_NODE); action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE); actionManager->addAction("mirrorNodeX", action); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX())); action = new KisAction(koIcon("object-flip-vertical"), i18n("Mirror Layer Vertically"), this); action->setActivationFlags(KisAction::ACTIVE_NODE); action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE); actionManager->addAction("mirrorNodeY", action); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY())); action = new KisAction(i18n("Activate next layer"), this); action->setActivationFlags(KisAction::ACTIVE_LAYER); action->setShortcut(KShortcut(Qt::Key_PageUp)); actionManager->addAction("activateNextLayer", action); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = new KisAction(i18n("Activate previous layer"), this); action->setActivationFlags(KisAction::ACTIVE_LAYER); action->setShortcut(KShortcut(Qt::Key_PageDown)); actionManager->addAction("activatePreviousLayer", action); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = new KisAction(koIcon("document-save"), i18n("Save Layer/Mask..."), this); action->setActivationFlags(KisAction::ACTIVE_NODE); actionManager->addAction("save_node_as_image", action); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = new KisAction(koIcon("edit-copy"), i18n("&Duplicate Layer or Mask"), this); action->setActivationFlags(KisAction::ACTIVE_NODE); action->setShortcut(KShortcut(Qt::ControlModifier + Qt::Key_J)); actionManager->addAction("duplicatelayer", action); connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode())); NEW_LAYER_ACTION_KEY("add_new_paint_layer", i18n("&Paint Layer"), "KisPaintLayer", koIcon("document-new"), Qt::Key_Insert); NEW_LAYER_ACTION("add_new_group_layer", i18n("&Group Layer"), "KisGroupLayer", koIcon("folder-new")); NEW_LAYER_ACTION("add_new_clone_layer", i18n("&Clone Layer"), "KisCloneLayer", koIcon("edit-copy")); NEW_LAYER_ACTION("add_new_shape_layer", i18n("&Vector Layer"), "KisShapeLayer", koIcon("bookmark-new")); NEW_LAYER_ACTION("add_new_adjustment_layer", i18n("&Filter Layer..."), "KisAdjustmentLayer", koIcon("view-filter")); NEW_LAYER_ACTION("add_new_fill_layer", i18n("&Fill Layer..."), "KisGeneratorLayer", koIcon("krita_tool_color_fill")); NEW_LAYER_ACTION("add_new_file_layer", i18n("&File Layer..."), "KisFileLayer", koIcon("document-open")); NEW_MASK_ACTION("add_new_transparency_mask", i18n("&Transparency Mask"), "KisTransparencyMask", koIcon("edit-copy")); NEW_MASK_ACTION("add_new_filter_mask", i18n("&Filter Mask..."), "KisFilterMask", koIcon("bookmarks")); NEW_MASK_ACTION("add_new_transform_mask", i18n("&Transform Mask..."), "KisTransformMask", koIcon("bookmarks")); NEW_MASK_ACTION("add_new_selection_mask", i18n("&Local Selection"), "KisSelectionMask", koIcon("edit-paste")); connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(createNode(const QString &))); CONVERT_NODE_ACTION("convert_to_paint_layer", i18n("to &Paint Layer"), "KisPaintLayer", koIcon("document-new")); CONVERT_NODE_ACTION("convert_to_selection_mask", i18n("to &Selection Mask"), "KisSelectionMask", koIcon("edit-paste")); CONVERT_NODE_ACTION("convert_to_filter_mask", i18n("to &Filter Mask..."), "KisFilterMask", koIcon("bookmarks")); CONVERT_NODE_ACTION("convert_to_transparency_mask", i18n("to &Transparency Mask"), "KisTransparencyMask", koIcon("edit-copy")); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(convertNode(const QString &))); action = new KisAction(koIcon("view-filter"), i18n("&Isolate Layer"), this); action->setCheckable(true); action->setActivationFlags(KisAction::ACTIVE_NODE); actionManager->addAction("isolate_layer", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); action = new KisAction(koIcon("edit-copy"), i18n("Alpha into Mask"), this); action->setActivationFlags(KisAction::ACTIVE_LAYER); action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE); actionManager->addAction("split_alpha_into_mask", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask())); action = new KisAction(koIcon("transparency-enabled"), i18n("Write as Alpha"), this); action->setActivationFlags(KisAction::ACTIVE_TRANSPARENCY_MASK); action->setActivationConditions(KisAction::ACTIVE_NODE_EDITABLE); actionManager->addAction("split_alpha_write", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite())); action = new KisAction(koIcon("document-save"), i18n("Save Merged..."), this); action->setActivationFlags(KisAction::ACTIVE_TRANSPARENCY_MASK); // HINT: we can save even when the nodes are not editable actionManager->addAction("split_alpha_save_merged", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryFinishIsolatedMode())); } void KisNodeManager::updateGUI() { // enable/disable all relevant actions m_d->layerManager->updateGUI(); m_d->maskManager->updateGUI(); } KisNodeSP KisNodeManager::activeNode() { if (m_d->imageView) { return m_d->imageView->currentNode(); } return 0; } KisLayerSP KisNodeManager::activeLayer() { return m_d->layerManager->activeLayer(); } const KoColorSpace* KisNodeManager::activeColorSpace() { Q_ASSERT(m_d->maskManager); if (m_d->maskManager->activeDevice()) { // Q_ASSERT(m_d->maskManager->activeDevice()); return m_d->maskManager->activeDevice()->colorSpace(); } else { Q_ASSERT(m_d->layerManager); Q_ASSERT(m_d->layerManager->activeLayer()); if (m_d->layerManager->activeLayer()->parentLayer()) return m_d->layerManager->activeLayer()->parentLayer()->colorSpace(); else return m_d->view->image()->colorSpace(); } } void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index) { if (parent->allowAsChild(node)) { if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) { KisSelectionMask *m = dynamic_cast(node.data()); KisLayer *l = dynamic_cast(parent.data()); KisSelectionMaskSP selMask = l->selectionMask(); if (m && m->active() && l && l->selectionMask()) selMask->setActive(false); } m_d->commandsAdapter->moveNode(node, parent, index); } } void KisNodeManager::addNodeDirect(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) { Q_ASSERT(parent->allowAsChild(node)); m_d->commandsAdapter->addNode(node, parent, aboveThis); } void KisNodeManager::moveNodeDirect(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) { Q_ASSERT(parent->allowAsChild(node)); m_d->commandsAdapter->moveNode(node, parent, aboveThis); } void KisNodeManager::toggleIsolateActiveNode() { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode == image->isolatedModeRoot()) { toggleIsolateMode(false); } else { toggleIsolateMode(true); } } void KisNodeManager::toggleIsolateMode(bool checked) { KisImageWSP image = m_d->view->image(); if (checked) { KisNodeSP activeNode = this->activeNode(); + // Transform masks don't have pixel data... + if (activeNode->inherits("KisTransformMask")) return; KIS_ASSERT_RECOVER_RETURN(activeNode); image->startIsolatedMode(activeNode); } else { image->stopIsolatedMode(); } } void KisNodeManager::slotUpdateIsolateModeAction() { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); Q_ASSERT(action); KisNodeSP activeNode = this->activeNode(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); action->setChecked(isolatedRootNode && isolatedRootNode == activeNode); } void KisNodeManager::slotTryFinishIsolatedMode() { KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode) return; bool belongsToIsolatedGroup = false; KisNodeSP node = this->activeNode(); while(node) { if (node == isolatedRootNode) { belongsToIsolatedGroup = true; break; } node = node->parent(); } if (!belongsToIsolatedGroup) { m_d->view->image()->stopIsolatedMode(); } } void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode->systemLocked()) { return; } // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { m_d->layerManager->addLayer(activeNode); } else if (nodeType == "KisGroupLayer") { m_d->layerManager->addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { m_d->layerManager->addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { m_d->layerManager->addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { m_d->layerManager->addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { m_d->layerManager->addCloneLayer(activeNode); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager->createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { m_d->maskManager->createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisTransformMask") { m_d->maskManager->createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { m_d->maskManager->createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { m_d->layerManager->addFileLayer(activeNode); } } void KisNodeManager::convertNode(const QString &nodeType) { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; if (nodeType == "KisPaintLayer") { m_d->layerManager->convertNodeToPaintLayer(activeNode); } else if (nodeType == "KisSelectionMask" || nodeType == "KisFilterMask" || nodeType == "KisTransparencyMask") { KisPaintDeviceSP copyFrom = activeNode->paintDevice() ? activeNode->paintDevice() : activeNode->projection(); m_d->commandsAdapter->beginMacro(kundo2_i18n("Convert to a Selection Mask")); if (nodeType == "KisSelectionMask") { m_d->maskManager->createSelectionMask(activeNode, copyFrom, true); } else if (nodeType == "KisFilterMask") { m_d->maskManager->createFilterMask(activeNode, copyFrom, false, true); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager->createTransparencyMask(activeNode, copyFrom, true); } m_d->commandsAdapter->removeNode(activeNode); m_d->commandsAdapter->endMacro(); } else { qWarning() << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node) { if (m_d->activateNodeImpl(node)) { emit sigUiNeedChangeActiveNode(node); emit sigNodeActivated(node); nodesUpdated(); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_just_the_canvas")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } } void KisNodeManager::slotUiActivatedNode(KisNodeSP node) { if (m_d->activateNodeImpl(node)) { emit sigNodeActivated(node); nodesUpdated(); } if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathToolFactoryId"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; if (node->inherits("KisShapeLayer")) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } } void KisNodeManager::nodesUpdated() { KisNodeSP node = activeNode(); if (!node) return; m_d->layerManager->layersUpdated(); m_d->maskManager->masksUpdated(); m_d->view->updateGUI(); m_d->view->selectionManager()->selectionChanged(); } KisPaintDeviceSP KisNodeManager::activePaintDevice() { return m_d->maskManager->activeMask() ? m_d->maskManager->activeDevice() : m_d->layerManager->activeDevice(); } void KisNodeManager::nodeProperties(KisNodeSP node) { if (node->inherits("KisLayer")) { m_d->layerManager->layerProperties(); } else if (node->inherits("KisMask")) { m_d->maskManager->maskProperties(); } } qint32 KisNodeManager::convertOpacityToInt(qreal opacity) { /** * Scales opacity from the range 0...100 * to the integer range 0...255 */ return qMin(255, int(opacity * 2.55 + 0.5)); } void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity, bool finalChange) { if (!node) return; if (node->opacity() == opacity) return; if (!finalChange) { node->setOpacity(opacity); node->setDirty(); } else { m_d->commandsAdapter->setOpacity(node, opacity); } } void KisNodeManager::setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp) { if (!node) return; if (node->compositeOp() == compositeOp) return; m_d->commandsAdapter->setCompositeOp(node, compositeOp); } void KisNodeManager::setSelectedNodes(QList nodes) { m_d->selectedNodes = nodes; } QList KisNodeManager::selectedNodes() { return m_d->selectedNodes; } void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange) { KisNodeSP node = activeNode(); setNodeOpacity(node, convertOpacityToInt(opacity), finalChange); } void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op) { KisNodeSP node = activeNode(); setNodeCompositeOp(node, op); } void KisNodeManager::duplicateActiveNode() { KisNodeSP node = activeNode(); // FIXME: can't imagine how it may happen Q_ASSERT(node); if (node->inherits("KisLayer")) { m_d->layerManager->layerDuplicate(); } else if (node->inherits("KisMask")) { m_d->maskManager->duplicateMask(); } } void KisNodeManager::raiseNode() { // The user sees the layer stack topsy-turvy, as a tree with the // root at the bottom instead of on top. KisNodeSP node = activeNode(); if (node->inherits("KisLayer")) { m_d->layerManager->layerLower(); } else if (node->inherits("KisMask")) { m_d->maskManager->lowerMask(); } } void KisNodeManager::lowerNode() { // The user sees the layer stack topsy-turvy, as a tree with the // root at the bottom instead of on top. KisNodeSP node = activeNode(); if (node->inherits("KisLayer")) { m_d->layerManager->layerRaise(); } else if (node->inherits("KisMask")) { m_d->maskManager->raiseMask(); } } void KisNodeManager::nodeToTop() { KisNodeSP node = activeNode(); if (node->inherits("KisLayer")) { m_d->layerManager->layerBack(); } else if (node->inherits("KisMask")) { m_d->maskManager->maskToBottom(); } } void KisNodeManager::nodeToBottom() { KisNodeSP node = activeNode(); if (node->inherits("KisLayer")) { m_d->layerManager->layerLower(); } else if (node->inherits("KisMask")) { m_d->maskManager->maskToTop(); } } bool scanForLastLayer(KisImageWSP image, KisNodeSP nodeToRemove) { if (!dynamic_cast(nodeToRemove.data())) { return false; } bool lastLayer = true; KisNodeSP node = image->root()->firstChild(); while (node) { if (node != nodeToRemove && dynamic_cast(node.data())) { lastLayer = false; break; } node = node->nextSibling(); } return lastLayer; } /// Scan whether the node has a parent in the list of nodes bool scanForParent(QList nodeList, KisNodeSP node) { KisNodeSP parent = node->parent(); while (parent) { if (nodeList.contains(parent)) { return true; } parent = parent->parent(); } return false; } void KisNodeManager::removeSingleNode(KisNodeSP node) { if (!node || !node->parent()) { return; } if (scanForLastLayer(m_d->view->image(), node)) { m_d->commandsAdapter->beginMacro(kundo2_i18n("Remove Last Layer")); m_d->commandsAdapter->removeNode(node); // An oddity, but this is required as for some reason, we can end up in a situation // where our active node is still set to one of the layers removed above. activeNode().clear(); createNode("KisPaintLayer"); m_d->commandsAdapter->endMacro(); } else { m_d->commandsAdapter->removeNode(node); } } void KisNodeManager::removeSelectedNodes(QList selectedNodes) { m_d->commandsAdapter->beginMacro(kundo2_i18n("Remove Multiple Layers and Masks")); foreach(KisNodeSP node, selectedNodes) { if (!scanForParent(selectedNodes, node)) { removeSingleNode(node); } } m_d->commandsAdapter->endMacro(); } void KisNodeManager::removeNode() { //do not delete root layer if (m_d->selectedNodes.count() > 1) { removeSelectedNodes(m_d->selectedNodes); } else { removeSingleNode(activeNode()); } } void KisNodeManager::mirrorNodeX() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer X"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask X"); } mirrorNode(node, commandName, Qt::Horizontal); } void KisNodeManager::mirrorNodeY() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer Y"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask Y"); } mirrorNode(node, commandName, Qt::Vertical); } inline bool checkForGlobalSelection(KisNodeSP node) { return dynamic_cast(node.data()) && node->parent() && !node->parent()->parent(); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } while(node && checkForGlobalSelection(node)) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->prevSibling(); if (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); } while(node && checkForGlobalSelection(node)) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } QList hideLayers(KisNodeSP root, QList selectedNodes) { QList alreadyHidden; foreach(KisNodeSP node, root->childNodes(QStringList(), KoProperties())) { if (!selectedNodes.contains(node)) { if (node->visible()) { node->setVisible(false); } else { alreadyHidden << node; } } if (node->childCount() > 0) { hideLayers(node, selectedNodes); } } return alreadyHidden; } void showNodes(KisNodeSP root, QList keepHiddenNodes) { foreach(KisNodeSP node, root->childNodes(QStringList(), KoProperties())) { if (!keepHiddenNodes.contains(node)) { node->setVisible(true); } if (node->childCount() > 0) { showNodes(node, keepHiddenNodes); } } } void KisNodeManager::mergeLayerDown() { if (m_d->selectedNodes.size() > 1) { QList selectedNodes = m_d->selectedNodes; KisLayerSP l = activeLayer(); // hide every layer that's not in the list of selected nodes QList alreadyHiddenNodes = hideLayers(m_d->imageView->image()->root(), selectedNodes); // render and copy the projection m_d->imageView->image()->refreshGraph(); m_d->imageView->image()->waitForDone(); // Copy the projections KisPaintDeviceSP dev = new KisPaintDevice(*m_d->imageView->image()->projection().data()); // place the projection in a layer KisPaintLayerSP flattenLayer = new KisPaintLayer(m_d->imageView->image(), i18n("Merged"), OPACITY_OPAQUE_U8, dev); // start a big macro m_d->commandsAdapter->beginMacro(kundo2_i18n("Merge Selected Nodes")); // Add the new merged node on top of the active node -- checking whether the parent is in the selection KisNodeSP parent = l->parent(); while (selectedNodes.contains(parent)) { parent = parent->parent(); } if (parent == l->parent()) { m_d->commandsAdapter->addNode(flattenLayer, parent, l); } else { m_d->commandsAdapter->addNode(flattenLayer, parent, parent->lastChild()); } // remove all nodes in the selection but the active node removeSelectedNodes(selectedNodes); m_d->commandsAdapter->endMacro(); // And unhide showNodes(m_d->imageView->image()->root(), alreadyHiddenNodes); m_d->imageView->image()->refreshGraph(); m_d->imageView->image()->waitForDone(); m_d->imageView->image()->notifyLayersChanged(); m_d->imageView->image()->setModified(); slotNonUiActivatedNode(flattenLayer); } else { m_d->layerManager->mergeLayer(); } } void KisNodeManager::rotate(double radians) { // XXX: implement rotation for masks as well m_d->layerManager->rotateLayer(radians); } void KisNodeManager::rotate180() { rotate(M_PI); } void KisNodeManager::rotateLeft90() { rotate(-M_PI / 2); } void KisNodeManager::rotateRight90() { rotate(M_PI / 2); } void KisNodeManager::shear(double angleX, double angleY) { // XXX: implement shear for masks as well m_d->layerManager->shearLayer(angleX, angleY); } void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy) { KisNodeSP node = activeNode(); KIS_ASSERT_RECOVER_RETURN(node); m_d->view->image()->scaleNode(node, sx, sy, filterStrategy); nodesUpdated(); } void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(m_d->view->image(), node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); nodesUpdated(); } void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity) { KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "krita/savenodeasimage"); dialog.setCaption(i18n("Export \"%1\"", defaultName)); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter("application/x-krita", KisImportExportManager::Export)); QString filename = dialog.url(); if (filename.isEmpty()) return; KUrl url = KUrl::fromLocalFile(filename); if (url.isEmpty()) return; KMimeType::Ptr mime = KMimeType::findByUrl(url); QString mimefilter = mime->name(); QScopedPointer d(KisPart::instance()->createDocument()); d->prepareForImport(); KisImageSP dst = new KisImage(d->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); d->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); d->setOutputMimeType(mimefilter.toLatin1()); d->exportDocument(url); } void KisNodeManager::saveNodeAsImage() { KisNodeSP node = activeNode(); if (!node) { qWarning() << "BUG: Save Node As Image was called without any node selected"; return; } KisImageWSP image = m_d->view->image(); QRect saveRect = image->bounds() | node->exactBounds(); KisPaintDeviceSP device = node->paintDevice(); if (!device) { device = node->projection(); } m_d->saveDeviceAsImage(device, node->name(), saveRect, image->xRes(), image->yRes(), node->opacity()); } void KisNodeManager::slotSplitAlphaIntoMask() { KisNodeSP node = activeNode(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice()); KisPaintDeviceSP srcDevice = node->paintDevice(); const KoColorSpace *srcCS = srcDevice->colorSpace(); const QRect processRect = srcDevice->exactBounds() | srcDevice->defaultBounds()->bounds(); KisPaintDeviceSP selectionDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); m_d->commandsAdapter->beginMacro(kundo2_i18n("Split Alpha into a Mask")); KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice); KisSequentialIterator srcIt(srcDevice, processRect); KisSequentialIterator dstIt(selectionDevice, processRect); do { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } while (srcIt.nextPixel() && dstIt.nextPixel()); m_d->commandsAdapter->addExtraCommand(transaction.endAndTake()); createNode("KisTransparencyMask", false, selectionDevice); m_d->commandsAdapter->endMacro(); } void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers) { KisNodeSP node = q->activeNode(); KisNodeSP parentNode = node->parent(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask")); if (writeToLayers && !parentNode->hasEditablePaintDevice()) { QMessageBox::information(view->mainWindow(), i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.").arg(parentNode->name())); return; } KisPaintDeviceSP dstDevice; if (writeToLayers) { KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice()); dstDevice = parentNode->paintDevice(); } else { KisPaintDeviceSP copyDevice = parentNode->paintDevice(); if (!copyDevice) { copyDevice = parentNode->original(); } dstDevice = new KisPaintDevice(*copyDevice); } const KoColorSpace *dstCS = dstDevice->colorSpace(); KisPaintDeviceSP selectionDevice = node->paintDevice(); KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1); const QRect processRect = selectionDevice->exactBounds() | dstDevice->exactBounds() | selectionDevice->defaultBounds()->bounds(); QScopedPointer transaction; if (writeToLayers) { commandsAdapter->beginMacro(kundo2_i18n("Write Alpha into a Layer")); transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice)); } KisSequentialIterator srcIt(selectionDevice, processRect); KisSequentialIterator dstIt(dstDevice, processRect); do { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } while (srcIt.nextPixel() && dstIt.nextPixel()); if (writeToLayers) { commandsAdapter->addExtraCommand(transaction->endAndTake()); commandsAdapter->removeNode(node); commandsAdapter->endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } #include "kis_node_manager.moc"