File indexing completed on 2025-03-09 04:10:27

0001 /*
0002  * SPDX-FileCopyrightText: 2017 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "ToolReferenceImages.h"
0008 
0009 #include <QDesktopServices>
0010 #include <QFile>
0011 #include <QLayout>
0012 #include <QMenu>
0013 #include <QMessageBox>
0014 #include <QVector>
0015 #include <QAction>
0016 #include <QApplication>
0017 
0018 #include <KoSelection.h>
0019 #include <KoShapeRegistry.h>
0020 #include <KoShapeManager.h>
0021 #include <KoShapeController.h>
0022 #include <KoFileDialog.h>
0023 #include "KisMimeDatabase.h"
0024 
0025 #include <kis_action_registry.h>
0026 #include <kis_canvas2.h>
0027 #include <kis_canvas_resource_provider.h>
0028 #include <KisViewManager.h>
0029 #include <KisDocument.h>
0030 #include <KisReferenceImagesLayer.h>
0031 #include <kis_image.h>
0032 #include "QClipboard"
0033 #include "kis_action.h"
0034 #include <KisCursorOverrideLock.h>
0035 
0036 #include "ToolReferenceImagesWidget.h"
0037 #include "KisReferenceImageCollection.h"
0038 
0039 ToolReferenceImages::ToolReferenceImages(KoCanvasBase * canvas)
0040     : DefaultTool(canvas, false)
0041 {
0042     setObjectName("ToolReferenceImages");
0043 }
0044 
0045 ToolReferenceImages::~ToolReferenceImages()
0046 {
0047 }
0048 
0049 void ToolReferenceImages::activate(const QSet<KoShape*> &shapes)
0050 {
0051     DefaultTool::activate(shapes);
0052 
0053     auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0054     KIS_ASSERT(kisCanvas);
0055     connect(kisCanvas->image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), this, SLOT(slotNodeAdded(KisNodeSP)));
0056     connect(kisCanvas->imageView()->document(), &KisDocument::sigReferenceImagesLayerChanged, this, &ToolReferenceImages::slotNodeAdded);
0057 
0058     auto referenceImageLayer = document()->referenceImagesLayer();
0059     if (referenceImageLayer) {
0060         setReferenceImageLayer(referenceImageLayer);
0061     }
0062 }
0063 
0064 void ToolReferenceImages::deactivate()
0065 {
0066     DefaultTool::deactivate();
0067 }
0068 
0069 void ToolReferenceImages::slotNodeAdded(KisNodeSP node)
0070 {
0071     auto *referenceImagesLayer = dynamic_cast<KisReferenceImagesLayer*>(node.data());
0072 
0073     if (referenceImagesLayer) {
0074         setReferenceImageLayer(referenceImagesLayer);
0075     }
0076 }
0077 
0078 void ToolReferenceImages::setReferenceImageLayer(KisSharedPtr<KisReferenceImagesLayer> layer)
0079 {
0080     m_layer = layer;
0081     connect(layer.data(), SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
0082     connect(layer->shapeManager(), SIGNAL(selectionChanged()), this, SLOT(repaintDecorations()));
0083     connect(layer->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(repaintDecorations()));
0084 }
0085 
0086 bool ToolReferenceImages::hasSelection()
0087 {
0088     const KoShapeManager *manager = shapeManager();
0089     return manager && manager->selection()->count() != 0;
0090 }
0091 
0092 void ToolReferenceImages::addReferenceImage()
0093 {
0094     auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0095     KIS_ASSERT_RECOVER_RETURN(kisCanvas);
0096 
0097             KoFileDialog dialog(kisCanvas->viewManager()->mainWindowAsQWidget(), KoFileDialog::OpenFile, "OpenReferenceImage");
0098     dialog.setCaption(i18n("Select a Reference Image"));
0099 
0100     QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
0101     if (!locations.isEmpty()) {
0102         dialog.setDefaultDir(locations.first());
0103     }
0104 
0105     QString filename = dialog.filename();
0106     if (filename.isEmpty()) return;
0107     if (!QFileInfo(filename).exists()) return;
0108 
0109     auto *reference = KisReferenceImage::fromFile(filename, *kisCanvas->coordinatesConverter(), canvas()->canvasWidget());
0110     if (reference) {
0111         if (document()->referenceImagesLayer()) {
0112             reference->setZIndex(document()->referenceImagesLayer()->shapes().size());
0113         }
0114         canvas()->addCommand(KisReferenceImagesLayer::addReferenceImages(document(), {reference}));
0115     }
0116 }
0117 
0118 void ToolReferenceImages::pasteReferenceImage()
0119 {
0120     KisCanvas2* kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0121     KIS_ASSERT_RECOVER_RETURN(kisCanvas);
0122 
0123     KisReferenceImage* reference = KisReferenceImage::fromClipboard(*kisCanvas->coordinatesConverter());
0124     if (reference) {
0125         if (document()->referenceImagesLayer()) {
0126             reference->setZIndex(document()->referenceImagesLayer()->shapes().size());
0127         }
0128         canvas()->addCommand(KisReferenceImagesLayer::addReferenceImages(document(), {reference}));
0129     } else {
0130         if (canvas()->canvasWidget()) {
0131             QMessageBox::critical(canvas()->canvasWidget(), i18nc("@title:window", "Krita"), i18n("Could not load reference image from clipboard"));
0132         }
0133     }
0134 }
0135 
0136 
0137 
0138 void ToolReferenceImages::removeAllReferenceImages()
0139 {
0140     auto layer = m_layer.toStrongRef();
0141     if (!layer) return;
0142 
0143     canvas()->addCommand(layer->removeReferenceImages(document(), layer->shapes()));
0144 }
0145 
0146 void ToolReferenceImages::loadReferenceImages()
0147 {
0148     auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0149     KIS_ASSERT_RECOVER_RETURN(kisCanvas);
0150 
0151             KoFileDialog dialog(kisCanvas->viewManager()->mainWindowAsQWidget(), KoFileDialog::OpenFile, "OpenReferenceImageCollection");
0152     dialog.setMimeTypeFilters(QStringList() << "application/x-krita-reference-images");
0153     dialog.setCaption(i18n("Load Reference Images"));
0154 
0155     QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
0156     if (!locations.isEmpty()) {
0157         dialog.setDefaultDir(locations.first());
0158     }
0159 
0160     QString filename = dialog.filename();
0161     if (filename.isEmpty()) return;
0162     if (!QFileInfo(filename).exists()) return;
0163 
0164     QFile file(filename);
0165     if (!file.open(QIODevice::ReadOnly)) {
0166         QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not open '%1'.", filename));
0167         return;
0168     }
0169 
0170     KisReferenceImageCollection collection;
0171 
0172     int currentZIndex = 0;
0173     if (document()->referenceImagesLayer()) {
0174         currentZIndex = document()->referenceImagesLayer()->shapes().size();
0175     }
0176 
0177     if (collection.load(&file)) {
0178         QList<KoShape*> shapes;
0179         Q_FOREACH(auto *reference, collection.referenceImages()) {
0180             reference->setZIndex(currentZIndex);
0181             shapes.append(reference);
0182             currentZIndex += 1;
0183         }
0184 
0185         canvas()->addCommand(KisReferenceImagesLayer::addReferenceImages(document(), shapes));
0186     } else {
0187         QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not load reference images from '%1'.", filename));
0188     }
0189     file.close();
0190 }
0191 
0192 void ToolReferenceImages::saveReferenceImages()
0193 {
0194     KisCursorOverrideLock cursorLock(Qt::BusyCursor);
0195 
0196     auto layer = m_layer.toStrongRef();
0197     if (!layer || layer->shapeCount() == 0) return;
0198 
0199     auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0200     KIS_ASSERT_RECOVER_RETURN(kisCanvas);
0201 
0202             KoFileDialog dialog(kisCanvas->viewManager()->mainWindowAsQWidget(), KoFileDialog::SaveFile, "SaveReferenceImageCollection");
0203     dialog.setMimeTypeFilters(QStringList() << "application/x-krita-reference-images");
0204     dialog.setCaption(i18n("Save Reference Images"));
0205 
0206     QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
0207     if (!locations.isEmpty()) {
0208         dialog.setDefaultDir(locations.first());
0209     }
0210 
0211     QString filename = dialog.filename();
0212     if (filename.isEmpty()) return;
0213 
0214     QString fileMime = KisMimeDatabase::mimeTypeForFile(filename, false);
0215     if (fileMime != "application/x-krita-reference-images") {
0216         filename.append(filename.endsWith(".") ? "krf" : ".krf");
0217     }
0218 
0219     QFile file(filename);
0220     if (!file.open(QIODevice::WriteOnly)) {
0221         QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not open '%1' for saving.", filename));
0222         return;
0223     }
0224 
0225     KisReferenceImageCollection collection(layer->referenceImages());
0226     bool ok = collection.save(&file);
0227     file.close();
0228 
0229     if (!ok) {
0230         QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Failed to save reference images."));
0231     }
0232 }
0233 
0234 void ToolReferenceImages::slotSelectionChanged()
0235 {
0236     auto layer = m_layer.toStrongRef();
0237     if (!layer) return;
0238 
0239     m_optionsWidget->selectionChanged(layer->shapeManager()->selection());
0240     updateActions();
0241 }
0242 
0243 QList<QPointer<QWidget>> ToolReferenceImages::createOptionWidgets()
0244 {
0245     // Instead of inheriting DefaultTool's multi-tab implementation, inherit straight from KoToolBase
0246     return KoToolBase::createOptionWidgets();
0247 }
0248 
0249 QWidget *ToolReferenceImages::createOptionWidget()
0250 {
0251     if (!m_optionsWidget) {
0252         m_optionsWidget = new ToolReferenceImagesWidget(this);
0253         // See https://bugs.kde.org/show_bug.cgi?id=316896
0254         QWidget *specialSpacer = new QWidget(m_optionsWidget);
0255         specialSpacer->setObjectName("SpecialSpacer");
0256         specialSpacer->setFixedSize(0, 0);
0257         m_optionsWidget->layout()->addWidget(specialSpacer);
0258     }
0259     return m_optionsWidget;
0260 }
0261 
0262 bool ToolReferenceImages::isValidForCurrentLayer() const
0263 {
0264     return true;
0265 }
0266 
0267 KoShapeManager *ToolReferenceImages::shapeManager() const
0268 {
0269     auto layer = m_layer.toStrongRef();
0270     return layer ? layer->shapeManager() : nullptr;
0271 }
0272 
0273 KoSelection *ToolReferenceImages::koSelection() const
0274 {
0275     auto manager = shapeManager();
0276     return manager ? manager->selection() : nullptr;
0277 }
0278 
0279 void ToolReferenceImages::updateDistinctiveActions(const QList<KoShape*> &)
0280 {
0281     action("object_group")->setEnabled(false);
0282     action("object_unite")->setEnabled(false);
0283     action("object_intersect")->setEnabled(false);
0284     action("object_subtract")->setEnabled(false);
0285     action("object_split")->setEnabled(false);
0286     action("object_ungroup")->setEnabled(false);
0287 }
0288 
0289 void ToolReferenceImages::deleteSelection()
0290 {
0291     auto layer = m_layer.toStrongRef();
0292     if (!layer) return;
0293 
0294     QList<KoShape *> shapes = koSelection()->selectedShapes();
0295 
0296     if (!shapes.empty()) {
0297         canvas()->addCommand(layer->removeReferenceImages(document(), shapes));
0298     }
0299 }
0300 
0301 QMenu* ToolReferenceImages::popupActionsMenu()
0302 {
0303     if (m_contextMenu) {
0304         m_contextMenu->clear();
0305         m_contextMenu->addSection(i18n("Reference Image Actions"));
0306         m_contextMenu->addSeparator();
0307 
0308         QMenu *transform = m_contextMenu->addMenu(i18n("Transform"));
0309 
0310         transform->addAction(action("object_transform_rotate_90_cw"));
0311         transform->addAction(action("object_transform_rotate_90_ccw"));
0312         transform->addAction(action("object_transform_rotate_180"));
0313         transform->addSeparator();
0314         transform->addAction(action("object_transform_mirror_horizontally"));
0315         transform->addAction(action("object_transform_mirror_vertically"));
0316         transform->addSeparator();
0317         transform->addAction(action("object_transform_reset"));
0318 
0319         m_contextMenu->addSeparator();
0320 
0321         m_contextMenu->addAction(action("edit_cut"));
0322         m_contextMenu->addAction(action("edit_copy"));
0323         m_contextMenu->addAction(action("edit_paste"));
0324 
0325         m_contextMenu->addSeparator();
0326 
0327         m_contextMenu->addAction(action("object_order_front"));
0328         m_contextMenu->addAction(action("object_order_raise"));
0329         m_contextMenu->addAction(action("object_order_lower"));
0330         m_contextMenu->addAction(action("object_order_back"));
0331     }
0332 
0333     return m_contextMenu.data();
0334 }
0335 
0336 void ToolReferenceImages::cut()
0337 {
0338     copy();
0339     deleteSelection();
0340 }
0341 
0342 void ToolReferenceImages::copy() const
0343 {
0344     QList<KoShape *> shapes = koSelection()->selectedShapes();
0345     if (!shapes.isEmpty()) {
0346         KoShape* shape = shapes.at(0);
0347         KisReferenceImage *reference = dynamic_cast<KisReferenceImage*>(shape);
0348         KIS_SAFE_ASSERT_RECOVER_RETURN(reference);
0349         QClipboard *cb = QApplication::clipboard();
0350         cb->setImage(reference->getImage());
0351     }
0352 }
0353 
0354 bool ToolReferenceImages::paste()
0355 {
0356     pasteReferenceImage();
0357     return true;
0358 }
0359 
0360 bool ToolReferenceImages::selectAll()
0361 {
0362     Q_FOREACH(KoShape *shape, shapeManager()->shapes()) {
0363         if (!shape->isSelectable()) continue;
0364         koSelection()->select(shape);
0365     }
0366     repaintDecorations();
0367 
0368     return true;
0369 }
0370 
0371 void ToolReferenceImages::deselect()
0372 {
0373     koSelection()->deselectAll();
0374     repaintDecorations();
0375 }
0376 
0377 KisDocument *ToolReferenceImages::document() const
0378 {
0379     auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0380     KIS_ASSERT(kisCanvas);
0381     return kisCanvas->imageView()->document();
0382 }
0383 
0384 QList<QAction *> ToolReferenceImagesFactory::createActionsImpl()
0385 {
0386     QList<QAction *> defaultActions = DefaultToolFactory::createActionsImpl();
0387     QList<QAction *> actions;
0388 
0389     QStringList actionNames;
0390     actionNames << "object_order_front"
0391                 << "object_order_raise"
0392                 << "object_order_lower"
0393                 << "object_order_back"
0394                 << "object_transform_rotate_90_cw"
0395                 << "object_transform_rotate_90_ccw"
0396                 << "object_transform_rotate_180"
0397                 << "object_transform_mirror_horizontally"
0398                 << "object_transform_mirror_vertically"
0399                 << "object_transform_reset";
0400 
0401     Q_FOREACH(QAction *action, defaultActions) {
0402         if (actionNames.contains(action->objectName())) {
0403             actions << action;
0404         } else {
0405             delete action;
0406         }
0407     }
0408     return actions;
0409 }