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 }