diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro index ec831b0d9565e301ee2844ea3b1cccf6e98d0032..432a1f62db583146ec0fa0cdfd034629e1d63b8b 100644 --- a/Grinder/Grinder.pro +++ b/Grinder/Grinder.pro @@ -496,7 +496,8 @@ SOURCES += \ ui/ml/rf/RandomForestFeaturesDialog.cpp \ ml/blocks/MergeInferenceBlock.cpp \ ml/processors/MergeInferenceProcessor.cpp \ - engine/EngineUtils.cpp + engine/EngineUtils.cpp \ + ui/image/PasteNodesDialog.cpp HEADERS += \ ui/mainwnd/GrinderWindow.h \ @@ -1086,7 +1087,8 @@ HEADERS += \ ui/ml/rf/RandomForestFeaturesDialog.h \ ml/blocks/MergeInferenceBlock.h \ ml/processors/MergeInferenceProcessor.h \ - engine/EngineUtils.h + engine/EngineUtils.h \ + ui/image/PasteNodesDialog.h FORMS += \ ui/mainwnd/GrinderWindow.ui \ @@ -1107,7 +1109,8 @@ FORMS += \ ui/cmd/tasks/CommandInterfaceTestTaskWidget.ui \ ui/dlg/BrowseDialog.ui \ ui/image/LayerSettingsDialog.ui \ - ui/ml/rf/RandomForestFeaturesDialog.ui + ui/ml/rf/RandomForestFeaturesDialog.ui \ + ui/image/PasteNodesDialog.ui RESOURCES += \ res/Grinder.qrc diff --git a/Grinder/Version.h b/Grinder/Version.h index 30111b3a55c4b36160ffe1f99f25ebb9c8dd1444..4c5e8d43c17eb8f05f9018b9bd1c3fe19a2715c5 100644 --- a/Grinder/Version.h +++ b/Grinder/Version.h @@ -10,14 +10,14 @@ #define GRNDR_INFO_TITLE "Grinder" #define GRNDR_INFO_COPYRIGHT "Copyright (c) WWU Muenster" -#define GRNDR_INFO_DATE "10.02.2020" +#define GRNDR_INFO_DATE "26.02.2020" #define GRNDR_INFO_COMPANY "WWU Muenster" #define GRNDR_INFO_WEBSITE "http://www.uni-muenster.de" #define GRNDR_VERSION_MAJOR 0 #define GRNDR_VERSION_MINOR 17 #define GRNDR_VERSION_REVISION 0 -#define GRNDR_VERSION_BUILD 431 +#define GRNDR_VERSION_BUILD 433 namespace grndr { diff --git a/Grinder/controller/ImageEditorController.cpp b/Grinder/controller/ImageEditorController.cpp index 7a3feea81efe53aab93452e13b1518d257f9e6de..f836a24a6d487aa4a27689ea4abb0816065fccfa 100644 --- a/Grinder/controller/ImageEditorController.cpp +++ b/Grinder/controller/ImageEditorController.cpp @@ -839,6 +839,56 @@ void ImageEditorController::pasteSelectedNodes() }); } +void ImageEditorController::pasteSelectedNodesTo(std::vector<const ImageReference*> targetImageReferences, QString layerName) +{ + callControllerFunction("Pasting draft items", [this](std::vector<const ImageReference*> targetImageReferences, QString layerName) { + _undoStack.beginMerging(); + + LongOperation opPasteNodes{"Pasting draft items", static_cast<unsigned int>(targetImageReferences.size()), true}; + + // Paste the items onto each target image reference + for (auto imageRef : targetImageReferences) + { + LongOperationStep opPasteToImage{QString{"Pasting to '%1'"}.arg(imageRef->getImageFileName())}; + + if (const auto activeLabel = grinder()->projectController().activeLabel()) + { + // Ensure that the image build exists + grinder()->engineController().executeLabel(nullptr, imageRef, Engine::ExecutionMode::Execute); + + // Get the target image build for pasting + if (auto targetImageBuild = activeLabel->imageBuildPool().imageBuild(_activeImageBuild->block(), imageRef)) + { + QApplication::processEvents(); + + // Create the target layer if necessary + auto layer = targetImageBuild->layers().selectByName(layerName); + + if (!layer) + layer = targetImageBuild->createLayer(layerName); + + if (layer && checkLayerEditability(layer.get(), false)) + { + // Create draft items for each object in the clipboard + grinder()->clipboardManager().deserialize<DraftItem>(DraftItemVector::Serialization_Element, [this, &layer](const SettingsContainer& settings) { + DraftItemType type = settings(DraftItem::Serialization_Value_Type, DraftItemType::Undefined).toString(); + + return createDraftItem(type, layer.get(), false).get(); + }); + } + } + } + + if (opPasteNodes.wasCanceled()) + break; + } + + _undoStack.endMerging(); + + return true; + }, targetImageReferences, layerName); +} + void ImageEditorController::cutSelectedNodes() const { copySelectedNodes(); diff --git a/Grinder/controller/ImageEditorController.h b/Grinder/controller/ImageEditorController.h index 11ef383a1e3a457c0cd4de828508e58a9799c278..7e0e55fe11673c7f9bfb6eb056d315f7ca070f2b 100644 --- a/Grinder/controller/ImageEditorController.h +++ b/Grinder/controller/ImageEditorController.h @@ -92,6 +92,7 @@ namespace grndr void copySelectedNodes() const; void pasteSelectedNodes(); + void pasteSelectedNodesTo(std::vector<const ImageReference*> targetImageReferences, QString layerName); void cutSelectedNodes() const; void removeSelectedNodes() const; diff --git a/Grinder/ui/image/ImageEditorView.cpp b/Grinder/ui/image/ImageEditorView.cpp index a2d7dac8075a4021b5f7105e76abe7fe87c84edc..1d18ef7ec474b4df54d4e1d1791b225793922ab1 100644 --- a/Grinder/ui/image/ImageEditorView.cpp +++ b/Grinder/ui/image/ImageEditorView.cpp @@ -10,6 +10,7 @@ #include "DraftItemNode.h" #include "core/GrinderApplication.h" #include "controller/ImageEditorController.h" +#include "ui/image/PasteNodesDialog.h" #include "ui/UIUtils.h" #include "res/Resources.h" @@ -48,6 +49,7 @@ ImageEditorView::ImageEditorView(QWidget* parent) : VisualSceneView(parent) _copyDraftItems->setShortcut(QKeySequence{QKeySequence::Copy}); _pasteDraftItems = UIUtils::createAction(this, "&Paste item(s)", FILE_ICON_PASTE, SLOT(pasteDraftItems()), "Paste items from the clipboard"); _pasteDraftItems->setShortcut(QKeySequence{QKeySequence::Paste}); + _pasteDraftItemsTo = UIUtils::createAction(this, "Paste item(s) &to...", FILE_ICON_PASTE, SLOT(pasteDraftItemsTo()), "Paste items from the clipboard to one or more images", "Ctrl+Shift+V"); _cutDraftItems = UIUtils::createAction(this, "Cu&t item(s)", FILE_ICON_CUT, SLOT(cutDraftItems()), "Copy the selected items to the clipboard and remove them afterwards"); _cutDraftItems->setShortcut(QKeySequence{QKeySequence::Cut}); _convertDraftItemsAction = UIUtils::createAction(this, "&Convert to pixels", FILE_ICON_EDITOR_CONVERTTOPIXELS, SLOT(convertDraftItems()), "Converts the selected items to pixels", "Return"); @@ -116,6 +118,7 @@ void ImageEditorView::prepareNodeContextMenu(QMenu& menu) const menu.insertAction(firstAction, _cutDraftItems); menu.insertAction(firstAction, _copyDraftItems); menu.insertAction(firstAction, _pasteDraftItems); + menu.insertAction(firstAction, _pasteDraftItemsTo); menu.insertSeparator(firstAction); menu.insertAction(firstAction, _convertDraftItemsAction); } @@ -134,6 +137,7 @@ std::vector<QAction*> ImageEditorView::getActions(AddActionsMode mode) const actions.push_back(_cutDraftItems); actions.push_back(_copyDraftItems); actions.push_back(_pasteDraftItems); + actions.push_back(_pasteDraftItemsTo); actions.push_back(nullptr); actions.push_back(_selectAllAction); @@ -242,6 +246,7 @@ void ImageEditorView::updateActions() _copyDraftItems->setEnabled(_scene && draftItemSelected); _pasteDraftItems->setEnabled(_scene && grinder()->clipboardManager().hasData(DraftItemVector::Serialization_Element) && activeLayerEditable); + _pasteDraftItemsTo->setEnabled(_scene && grinder()->clipboardManager().hasData(DraftItemVector::Serialization_Element)); _cutDraftItems->setEnabled(_scene && draftItemSelected && editableItemSelected); _convertDraftItemsAction->setEnabled(_scene && draftItemSelected && editableItemSelected); _deleteSelectedAction->setEnabled(_deleteSelectedAction->isEnabled() && draftItemSelected && editableItemSelected); @@ -405,6 +410,20 @@ void ImageEditorView::pasteDraftItems() const _editorScene->imageEditor()->controller().pasteSelectedNodes(); } +void ImageEditorView::pasteDraftItemsTo() const +{ + if (_editorScene) + { + if (auto layer = _editorScene->imageEditor()->controller().activeLayer()) + { + PasteNodesDialog dlg{layer->getName()}; + + if (dlg.exec() == QDialog::Accepted) + _editorScene->imageEditor()->controller().pasteSelectedNodesTo(dlg.getImageReferences().imageReferences(), dlg.getLayerName()); + } + } +} + void ImageEditorView::cutDraftItems() const { if (_editorScene) diff --git a/Grinder/ui/image/ImageEditorView.h b/Grinder/ui/image/ImageEditorView.h index f77e39d1dc1b44ac9aaf5c6881c243a6caeceed1..eeb0aaac91e6b48d2b722701217c42eefc07de05 100644 --- a/Grinder/ui/image/ImageEditorView.h +++ b/Grinder/ui/image/ImageEditorView.h @@ -77,6 +77,7 @@ namespace grndr void copyDraftItems() const; void pasteDraftItems() const; + void pasteDraftItemsTo() const; void cutDraftItems() const; void convertDraftItems() const; @@ -96,6 +97,7 @@ namespace grndr QAction* _copyDraftItems{nullptr}; QAction* _pasteDraftItems{nullptr}; + QAction* _pasteDraftItemsTo{nullptr}; QAction* _cutDraftItems{nullptr}; QAction* _convertDraftItemsAction{nullptr}; }; diff --git a/Grinder/ui/image/PasteNodesDialog.cpp b/Grinder/ui/image/PasteNodesDialog.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d11332d39e832a58e6a85ace9c598a51982a21ba --- /dev/null +++ b/Grinder/ui/image/PasteNodesDialog.cpp @@ -0,0 +1,68 @@ +/****************************************************************************** + * File: PasteNodesDialog.cpp + * Date: 26.2.2020 + *****************************************************************************/ + +#include "Grinder.h" +#include "PasteNodesDialog.h" +#include "ui_PasteNodesDialog.h" +#include "core/GrinderApplication.h" + +PasteNodesDialog::PasteNodesDialog(QString layerName, QWidget *parent) : QDialog(parent, Qt::Dialog|Qt::WindowTitleHint|Qt::WindowCloseButtonHint), + ui{new Ui::PasteNodesDialog} +{ + setupUi(layerName); + + fillImageReferences(grinder()->project().imageReferences()); +} + +PasteNodesDialog::~PasteNodesDialog() +{ + delete ui; +} + +ImageReferenceSelection PasteNodesDialog::getImageReferences() const +{ + return ui->lstImageReferences->getSelectedImageReferences(); +} + +QString PasteNodesDialog::getLayerName() const +{ + return ui->txtLayerName->text(); +} + +void PasteNodesDialog::accept() +{ + // Make sure that a layer name was entered + if (ui->txtLayerName->text().isEmpty()) + { + QMessageBox::warning(nullptr, "Paste item(s) to...", "Please enter a layer name."); + ui->txtLayerName->setFocus(); + return; + } + + QDialog::accept(); +} + +void PasteNodesDialog::setupUi(QString layerName) +{ + ui->setupUi(this); + + ui->txtLayerName->setText(layerName); + + // The OK button should only be enabled if at least one image reference is checked + connect(ui->lstImageReferences, &QListWidget::itemChanged, this, &PasteNodesDialog::imageReferencesListItemChanged); + + updateUi(); +} + +void PasteNodesDialog::updateUi() +{ + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ui->lstImageReferences->getCheckedObjects().size() > 0); +} + +void PasteNodesDialog::fillImageReferences(const ImageReferenceVector& imageRefs) +{ + ui->lstImageReferences->populate(imageRefs); + ui->lstImageReferences->checkObject(grinder()->projectController().activeImageReference(), false); +} diff --git a/Grinder/ui/image/PasteNodesDialog.h b/Grinder/ui/image/PasteNodesDialog.h new file mode 100644 index 0000000000000000000000000000000000000000..f7a6cca7a6d0543143ec6c01bae1f73f44e444b8 --- /dev/null +++ b/Grinder/ui/image/PasteNodesDialog.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * File: PasteNodesDialog.h + * Date: 26.2.2020 + *****************************************************************************/ + +#ifndef PASTENODESDIALOG_H +#define PASTENODESDIALOG_H + +#include <QDialog> + +#include "project/ImageReference.h" +#include "ui/widgets/project/ImageReferencesCheckListWidget.h" +#include "ui/mainwnd/ImageReferencesListItem.h" + +namespace Ui +{ + class PasteNodesDialog; +} + +namespace grndr +{ + class Project; + class ImageReferenceVector; + + class PasteNodesDialog : public QDialog + { + Q_OBJECT + + public: + PasteNodesDialog(QString layerName = "", QWidget *parent = nullptr); + ~PasteNodesDialog(); + + public: + ImageReferenceSelection getImageReferences() const; + QString getLayerName() const; + + public slots: + virtual void accept() override; + + private: + Ui::PasteNodesDialog *ui; + void setupUi(QString layerName); + void updateUi(); + + private slots: + void imageReferencesListItemChanged(QListWidgetItem* item) { Q_UNUSED(item); updateUi(); } + + private: + void fillImageReferences(const ImageReferenceVector& imageRefs); + }; +} + +#endif diff --git a/Grinder/ui/image/PasteNodesDialog.ui b/Grinder/ui/image/PasteNodesDialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..2f65508d1252a96ed921ce85e8acc3bcb3db3ed6 --- /dev/null +++ b/Grinder/ui/image/PasteNodesDialog.ui @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PasteNodesDialog</class> + <widget class="QDialog" name="PasteNodesDialog"> + <property name="windowModality"> + <enum>Qt::ApplicationModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>376</width> + <height>499</height> + </rect> + </property> + <property name="windowTitle"> + <string>Paste item(s) to...</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Images</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="ImageReferencesCheckListWidget" name="lstImageReferences"> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Layer</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QLineEdit" name="txtLayerName"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>&Name:</string> + </property> + <property name="buddy"> + <cstring>txtLayerName</cstring> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>200</width> + <height>17</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ImageReferencesCheckListWidget</class> + <extends>QListWidget</extends> + <header>ui/widgets/project/ImageReferencesCheckListWidget.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>PasteNodesDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>PasteNodesDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/Grinder/ui/widgets/CheckListWidget.h b/Grinder/ui/widgets/CheckListWidget.h index 658d8aa7f141f08885ee7cb12a864a021880041f..7701a8751f42288d556b23b177ac41e1f9b099cc 100644 --- a/Grinder/ui/widgets/CheckListWidget.h +++ b/Grinder/ui/widgets/CheckListWidget.h @@ -28,6 +28,8 @@ namespace grndr template<typename ObjectReturnType = ObjectType> void setCheckedObjects(const std::vector<ObjectReturnType*>& checkedObjects); + void checkObject(const ObjectType* obj, bool check = true); + protected: virtual void contextMenuEvent(QContextMenuEvent* event) override; diff --git a/Grinder/ui/widgets/CheckListWidget.impl.h b/Grinder/ui/widgets/CheckListWidget.impl.h index ff78e7e92b7727b44fbb1130d7f269d82e415e38..3df3e450394d65fd276a7b96b217b8fbb14dd76a 100644 --- a/Grinder/ui/widgets/CheckListWidget.impl.h +++ b/Grinder/ui/widgets/CheckListWidget.impl.h @@ -71,6 +71,22 @@ void CheckListWidget<ObjectType, ItemType>::setCheckedObjects(const std::vector< } } +template<typename ObjectType, typename ItemType> +void CheckListWidget<ObjectType, ItemType>::checkObject(const ObjectType* obj, bool check) +{ + for (int i = 0; i < this->count(); ++i) + { + if (auto item = dynamic_cast<ItemType*>(this->item(i))) + { + if (static_cast<const ObjectType*>(item->object()) == obj) + { + item->setCheckState(check ? Qt::Checked : Qt::Unchecked); + break; + } + } + } +} + template<typename ObjectType, typename ItemType> void CheckListWidget<ObjectType, ItemType>::contextMenuEvent(QContextMenuEvent* event) {