From 02657d41ccfe35e43e71dc75c04a3a73fa2343b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= <d_muel20@uni-muenster.de> Date: Fri, 4 May 2018 20:20:38 +0200 Subject: [PATCH] * Image tags can now be assigned to image builds and draft items --- Grinder/Grinder.pro | 10 +- Grinder/Version.h | 4 +- Grinder/common/ObjectVector.h | 8 +- Grinder/common/ObjectVector.impl.h | 10 +- Grinder/common/properties/ObjectProperty.h | 3 + .../common/properties/ObjectProperty.impl.h | 20 ++++ Grinder/common/properties/PropertyBase.h | 2 + Grinder/common/properties/PropertyID.cpp | 1 + Grinder/common/properties/PropertyID.h | 1 + Grinder/engine/ProcessorBase.cpp | 24 ++-- Grinder/image/DraftItem.cpp | 4 + Grinder/image/DraftItem.h | 4 + Grinder/image/ImageBuild.cpp | 33 ++++++ Grinder/image/ImageBuild.h | 29 ++++- Grinder/image/ImageBuildPool.cpp | 7 ++ Grinder/image/tags/ImageTags.cpp | 5 - Grinder/image/tags/ImageTags.h | 3 - Grinder/image/tags/ImageTagsAllotment.cpp | 103 ++++++++++++++++++ Grinder/image/tags/ImageTagsAllotment.h | 66 +++++++++++ .../image/tags/ImageTagsAllotmentProperty.cpp | 35 ++++++ .../image/tags/ImageTagsAllotmentProperty.h | 32 ++++++ Grinder/image/tags/ImageTagsProperty.cpp | 18 --- Grinder/image/tags/ImageTagsProperty.h | 3 - Grinder/pipeline/Block.cpp | 31 ++++++ Grinder/pipeline/Block.h | 14 ++- Grinder/pipeline/Block.impl.h | 14 +-- Grinder/res/Grinder.qrc | 1 + Grinder/res/Resources.h | 1 + Grinder/res/css/controlBar.css | 103 +----------------- Grinder/ui/dlg/CheckListDialog.cpp | 21 +++- Grinder/ui/dlg/CheckListDialog.h | 10 +- Grinder/ui/dlg/CheckListDialog.impl.h | 31 ++---- .../ui/image/ImageEditorPropertyWidget.cpp | 2 +- Grinder/ui/image/ImageEditorWidget.cpp | 23 +++- Grinder/ui/image/ImageEditorWidget.h | 4 + Grinder/ui/image/tags/ImageTagsListWidget.cpp | 24 +--- Grinder/ui/image/tools/DraftItemTool.cpp | 12 ++ Grinder/ui/image/tools/DraftItemTool.h | 7 ++ .../ui/properties/PropertyTreeItemDelegate.h | 2 +- .../ui/properties/ValuePropertyTreeItem.cpp | 1 - .../properties/editors/DialogPropertyEditor.h | 5 +- .../editors/DialogPropertyEditor.impl.h | 13 ++- .../ImageTagsAllotmentPropertyEditor.cpp | 41 +++++++ .../ImageTagsAllotmentPropertyEditor.h | 26 +++++ .../editors/ImageTagsPropertyEditor.h | 2 +- Grinder/ui/widgets/CheckListWidget.h | 3 + Grinder/ui/widgets/CheckListWidget.impl.h | 32 ++++++ Grinder/ui/widgets/ObjectListWidget.h | 2 +- 48 files changed, 620 insertions(+), 230 deletions(-) create mode 100644 Grinder/image/tags/ImageTagsAllotment.cpp create mode 100644 Grinder/image/tags/ImageTagsAllotment.h create mode 100644 Grinder/image/tags/ImageTagsAllotmentProperty.cpp create mode 100644 Grinder/image/tags/ImageTagsAllotmentProperty.h create mode 100644 Grinder/ui/properties/editors/ImageTagsAllotmentPropertyEditor.cpp create mode 100644 Grinder/ui/properties/editors/ImageTagsAllotmentPropertyEditor.h diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro index 2351804..0ccdc20 100644 --- a/Grinder/Grinder.pro +++ b/Grinder/Grinder.pro @@ -211,7 +211,10 @@ SOURCES += \ ui/image/tags/ImageTagsDialog.cpp \ ui/properties/editors/ImageTagsPropertyEditor.cpp \ ui/image/tags/EditableImageTagsListWidget.cpp \ - ui/image/tags/ImageTagsListWidget.cpp + ui/image/tags/ImageTagsListWidget.cpp \ + image/tags/ImageTagsAllotment.cpp \ + image/tags/ImageTagsAllotmentProperty.cpp \ + ui/properties/editors/ImageTagsAllotmentPropertyEditor.cpp HEADERS += \ ui/mainwnd/GrinderWindow.h \ @@ -455,7 +458,10 @@ HEADERS += \ ui/image/tags/ImageTagsDialog.h \ ui/properties/editors/ImageTagsPropertyEditor.h \ ui/image/tags/EditableImageTagsListWidget.h \ - ui/image/tags/ImageTagsListWidget.h + ui/image/tags/ImageTagsListWidget.h \ + image/tags/ImageTagsAllotment.h \ + image/tags/ImageTagsAllotmentProperty.h \ + ui/properties/editors/ImageTagsAllotmentPropertyEditor.h FORMS += \ ui/mainwnd/GrinderWindow.ui \ diff --git a/Grinder/Version.h b/Grinder/Version.h index d09cac3..443017f 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 "03.05.2018" +#define GRNDR_INFO_DATE "04.05.2018" #define GRNDR_INFO_COMPANY "WWU Muenster" #define GRNDR_INFO_WEBSITE "http://www.uni-muenster.de" #define GRNDR_VERSION_MAJOR 0 #define GRNDR_VERSION_MINOR 3 #define GRNDR_VERSION_REVISION 0 -#define GRNDR_VERSION_BUILD 149 +#define GRNDR_VERSION_BUILD 154 namespace grndr { diff --git a/Grinder/common/ObjectVector.h b/Grinder/common/ObjectVector.h index cb0ef7f..8d004e1 100644 --- a/Grinder/common/ObjectVector.h +++ b/Grinder/common/ObjectVector.h @@ -36,6 +36,10 @@ namespace grndr template<typename T = vector_type> void deepCopy(const std::enable_if_t<std::is_copy_constructible<ObjType>::value, T>& src); + public: + using std::vector<std::shared_ptr<ObjType>>::erase; + void erase(const object_type* obj); + public: vector_type sorted() const; @@ -48,9 +52,9 @@ namespace grndr auto find(std::function<bool(const object_type*)> pred) const; auto indexOf(const pointer_type& obj) const; - auto indexOf(const ObjType* obj) const; + auto indexOf(const object_type* obj) const; bool contains(const pointer_type& obj) const { return indexOf(obj) != -1; } - bool contains(const ObjType* obj) const { return indexOf(obj) != -1; } + bool contains(const object_type* obj) const { return indexOf(obj) != -1; } public: void serialize(QString elemName, SerializationContext& ctx, std::function<bool(const pointer_type&)> predicate = nullptr) const; diff --git a/Grinder/common/ObjectVector.impl.h b/Grinder/common/ObjectVector.impl.h index 40037bd..b22551f 100644 --- a/Grinder/common/ObjectVector.impl.h +++ b/Grinder/common/ObjectVector.impl.h @@ -27,6 +27,14 @@ void ObjectVector<ObjType>::deepCopy(const std::enable_if_t<std::is_copy_constru } } +template<typename ObjType> +void ObjectVector<ObjType>::erase(const object_type* obj) +{ + auto it = find(obj); + + if (it != this->cend()) + this->erase(it); +} template<typename ObjType> typename ObjectVector<ObjType>::vector_type ObjectVector<ObjType>::sorted() const @@ -80,7 +88,7 @@ auto ObjectVector<ObjType>::find(std::function<bool(const object_type*)> pred) c } template<typename ObjType> -auto ObjectVector<ObjType>::indexOf(const ObjType* obj) const +auto ObjectVector<ObjType>::indexOf(const object_type* obj) const { auto it = find(obj); diff --git a/Grinder/common/properties/ObjectProperty.h b/Grinder/common/properties/ObjectProperty.h index b2b2cf5..b26dc57 100644 --- a/Grinder/common/properties/ObjectProperty.h +++ b/Grinder/common/properties/ObjectProperty.h @@ -39,6 +39,9 @@ namespace grndr virtual QString toString() const override { return ""; } virtual void fromString(const QString& data) override { Q_UNUSED(data); } + virtual void serialize(SerializationContext& ctx) const override; + virtual void deserialize(DeserializationContext& ctx) override; + protected: object_type _object{}; }; diff --git a/Grinder/common/properties/ObjectProperty.impl.h b/Grinder/common/properties/ObjectProperty.impl.h index 068a546..6824698 100644 --- a/Grinder/common/properties/ObjectProperty.impl.h +++ b/Grinder/common/properties/ObjectProperty.impl.h @@ -24,3 +24,23 @@ void ObjectProperty<ObjType>::copyValue(const PropertyBase* property) } } } + +template<typename ObjType> +void ObjectProperty<ObjType>::serialize(SerializationContext& ctx) const +{ + PropertyBase::serialize(ctx); + + // Serialize object + if (!hasFlag(Flag::ReadOnly)) + _object.serialize(ctx); +} + +template<typename ObjType> +void ObjectProperty<ObjType>::deserialize(DeserializationContext& ctx) +{ + PropertyBase::deserialize(ctx); + + // Deserialize object + if (!hasFlag(Flag::ReadOnly)) + _object.deserialize(ctx); +} diff --git a/Grinder/common/properties/PropertyBase.h b/Grinder/common/properties/PropertyBase.h index 6e3d54f..2ef5a4b 100644 --- a/Grinder/common/properties/PropertyBase.h +++ b/Grinder/common/properties/PropertyBase.h @@ -38,6 +38,8 @@ namespace grndr PropertyBase& operator =(const PropertyBase& src) = default; PropertyBase& operator =(PropertyBase&& src) = default; + bool operator <(const PropertyBase& prop) { return _name < prop._name; } + public: virtual void initProperty() { } diff --git a/Grinder/common/properties/PropertyID.cpp b/Grinder/common/properties/PropertyID.cpp index 7c13bb2..8dd58f9 100644 --- a/Grinder/common/properties/PropertyID.cpp +++ b/Grinder/common/properties/PropertyID.cpp @@ -24,3 +24,4 @@ const char* PropertyID::Size = "Size"; const char* PropertyID::LineWidth = "LineWidth"; const char* PropertyID::HasDirection = "HasDirection"; const char* PropertyID::Direction = "Direction"; +const char* PropertyID::ImageTagsAllotment = "ImageTagsAllotment"; diff --git a/Grinder/common/properties/PropertyID.h b/Grinder/common/properties/PropertyID.h index a08f711..fc95e5e 100644 --- a/Grinder/common/properties/PropertyID.h +++ b/Grinder/common/properties/PropertyID.h @@ -31,6 +31,7 @@ namespace grndr static const char* LineWidth; static const char* HasDirection; static const char* Direction; + static const char* ImageTagsAllotment; public: using QString::QString; diff --git a/Grinder/engine/ProcessorBase.cpp b/Grinder/engine/ProcessorBase.cpp index 13dedae..479bcc2 100644 --- a/Grinder/engine/ProcessorBase.cpp +++ b/Grinder/engine/ProcessorBase.cpp @@ -7,6 +7,7 @@ #include "ProcessorBase.h" #include "EngineExceptions.h" #include "pipeline/Block.h" +#include "pipeline/PipelineExceptions.h" ProcessorBase::ProcessorBase(const Block* block) : _block{block} @@ -70,22 +71,17 @@ void ProcessorBase::throwProcessorException(QString what) const const Port* ProcessorBase::resolveDataPort(const Port* port, bool required) const { - auto outPort = port; + try { + auto dataPort = _block->dataPort(port); - // If the given port is an in-port, find the out-port it is connected to - if (port->isIn()) - { - outPort = nullptr; - - auto connections = port->getConnections(Port::Direction::In); - - if (connections.size() == 1) - outPort = connections.at(0)->sourcePort(); - else if (connections.size() == 0 && required) + if (!dataPort && required) throwProcessorException(QString{"Port '%1' is not connected to a source"}.arg(port->getName())); - else if (connections.size() > 1) - throwProcessorException(QString{"Port '%1' is connected to more than one source"}.arg(port->getName())); // Should never happen + + return dataPort; + } catch (BlockException& e) { + // Re-throw the exception + throwProcessorException(GetExceptionMessage(e.what())); } - return outPort; + return nullptr; } diff --git a/Grinder/image/DraftItem.cpp b/Grinder/image/DraftItem.cpp index aaded77..db9c61b 100644 --- a/Grinder/image/DraftItem.cpp +++ b/Grinder/image/DraftItem.cpp @@ -56,4 +56,8 @@ void DraftItem::createProperties() _direction = createProperty<AngleProperty>(PropertyID::Direction, "Direction", 0.0); direction()->createConstraint<RangeConstraint>(0, 360); direction()->setDescription("The direction/orientation of the box contents."); + + _imageTagsAllotment = createProperty<ImageTagsAllotmentProperty>(PropertyID::ImageTagsAllotment, "Image tags"); + imageTagsAllotment()->setDescription("The assigned image tags."); + imageTagsAllotment()->object().setImageBuild(_layer->imageBuild()); } diff --git a/Grinder/image/DraftItem.h b/Grinder/image/DraftItem.h index a16ab4f..46dfa05 100644 --- a/Grinder/image/DraftItem.h +++ b/Grinder/image/DraftItem.h @@ -9,6 +9,7 @@ #include "common/properties/PropertyObject.h" #include "DraftItemType.h" #include "DraftItemRendererBase.h" +#include "tags/ImageTagsAllotmentProperty.h" namespace grndr { @@ -45,6 +46,8 @@ namespace grndr auto hasDirection() const { return dynamic_cast<BoolProperty*>(_hasDirection.get()); } auto direction() { return dynamic_cast<AngleProperty*>(_direction.get()); } auto direction() const { return dynamic_cast<AngleProperty*>(_direction.get()); } + auto imageTagsAllotment() { return dynamic_cast<ImageTagsAllotmentProperty*>(_imageTagsAllotment.get()); } + auto imageTagsAllotment() const { return dynamic_cast<ImageTagsAllotmentProperty*>(_imageTagsAllotment.get()); } public: virtual void setDefaultPropertyValues() { } @@ -68,6 +71,7 @@ namespace grndr std::shared_ptr<PropertyBase> _position; std::shared_ptr<PropertyBase> _hasDirection; std::shared_ptr<PropertyBase> _direction; + std::shared_ptr<PropertyBase> _imageTagsAllotment; }; } diff --git a/Grinder/image/ImageBuild.cpp b/Grinder/image/ImageBuild.cpp index 7f6ebf1..2c74c1b 100644 --- a/Grinder/image/ImageBuild.cpp +++ b/Grinder/image/ImageBuild.cpp @@ -6,6 +6,8 @@ #include "Grinder.h" #include "ImageBuild.h" #include "ImageExceptions.h" +#include "pipeline/Block.h" +#include "image/tags/ImageTagsProperty.h" const char* ImageBuild::Serialization_Value_ImageReferences = "ImageReference"; // Not called ImageReferences due to backwards compatibility @@ -17,6 +19,18 @@ ImageBuild::ImageBuild(const Block* block, const std::vector<const ImageReferenc if (imageReferences.empty()) throw std::invalid_argument{_EXCPT("imageReferences may not be empty")}; + + // Listen for connection events on the image tags in-port + if (auto imageTagsInPort = _block->ports().selectByType(PortType::ImageTagsIn)) + { + connect(imageTagsInPort.get(), SIGNAL(portConnected(const Connection*)), this, SLOT(imageTagsConnectionChanged()), Qt::QueuedConnection); + connect(imageTagsInPort.get(), SIGNAL(portDisconnected(const Connection*)), this, SLOT(imageTagsConnectionChanged()), Qt::QueuedConnection); + } +} + +void ImageBuild::initImageBuild() +{ + createProperties(); } std::shared_ptr<Layer> ImageBuild::createLayer(QString name) @@ -88,8 +102,18 @@ void ImageBuild::moveLayer(const Layer* layer, bool up) throw ImageBuildException{this, _EXCPT("Tried to move a layer not belonging to this image build")}; } +ImageTags* ImageBuild::inputImageTags() const +{ + if (auto imageTagsProperty = _block->portProperty<ImageTagsProperty>(PortType::ImageTagsIn, PropertyID::ImageTags)) + return &imageTagsProperty->object(); + else + return nullptr; +} + void ImageBuild::serialize(SerializationContext& ctx) const { + PropertyObject::serialize(ctx); + // Serialize image references if (ctx.getMode() == SerializationContext::Mode::ProjectSerialization) { @@ -109,6 +133,8 @@ void ImageBuild::serialize(SerializationContext& ctx) const void ImageBuild::deserialize(DeserializationContext& ctx) { + PropertyObject::deserialize(ctx); + // Deserialize all layers if (ctx.beginGroup(LayerVector::Serialization_Group)) { @@ -120,3 +146,10 @@ void ImageBuild::deserialize(DeserializationContext& ctx) ctx.endGroup(); } } + +void ImageBuild::createProperties() +{ + _imageTagsAllotment = createProperty<ImageTagsAllotmentProperty>(PropertyID::ImageTagsAllotment, "Image tags"); + imageTagsAllotment()->setDescription("The assigned image tags."); + imageTagsAllotment()->object().setImageBuild(this); +} diff --git a/Grinder/image/ImageBuild.h b/Grinder/image/ImageBuild.h index 650a00a..45b66f9 100644 --- a/Grinder/image/ImageBuild.h +++ b/Grinder/image/ImageBuild.h @@ -8,13 +8,16 @@ #include <opencv2/core.hpp> +#include "common/properties/PropertyObject.h" #include "LayerVector.h" +#include "tags/ImageTagsAllotmentProperty.h" namespace grndr { class ImageReference; + class ImageTags; - class ImageBuild : public QObject + class ImageBuild : public PropertyObject { Q_OBJECT @@ -24,6 +27,9 @@ namespace grndr public: ImageBuild(const Block* block, const std::vector<const ImageReference*>& imageReferences); + public: + void initImageBuild(); + public: std::shared_ptr<Layer> createLayer(QString name = ""); void removeLayer(const Layer* layer); @@ -41,16 +47,29 @@ namespace grndr const LayerVector& layers() const { return _layers; } + ImageTags* inputImageTags() const; + + auto imageTagsAllotment() { return dynamic_cast<ImageTagsAllotmentProperty*>(_imageTagsAllotment.get()); } + auto imageTagsAllotment() const { return dynamic_cast<ImageTagsAllotmentProperty*>(_imageTagsAllotment.get()); } + public: - void serialize(SerializationContext& ctx) const; - void deserialize(DeserializationContext& ctx); + void serialize(SerializationContext& ctx) const override; + void deserialize(DeserializationContext& ctx) override; signals: void layerCreated(const std::shared_ptr<Layer>&); void layerRemoved(const std::shared_ptr<Layer>&); void layerMoved(const std::shared_ptr<Layer>&, int, int); - void imageDataChanged(); + void imageDataChanged(); + + void inputImageTagsChanged(const ImageTags*) const; + + protected: + virtual void createProperties() override; + + private slots: + void imageTagsConnectionChanged() const { emit inputImageTagsChanged(inputImageTags()); } private: const Block* _block{nullptr}; @@ -58,6 +77,8 @@ namespace grndr cv::Mat _imageData; LayerVector _layers; + + std::shared_ptr<PropertyBase> _imageTagsAllotment; }; } diff --git a/Grinder/image/ImageBuildPool.cpp b/Grinder/image/ImageBuildPool.cpp index 477e6c9..527c355 100644 --- a/Grinder/image/ImageBuildPool.cpp +++ b/Grinder/image/ImageBuildPool.cpp @@ -171,6 +171,13 @@ std::shared_ptr<ImageBuild> ImageBuildPool::imageBuild(std::shared_ptr<ImageBuil { auto build = std::make_shared<ImageBuild>(block, imageReferences); + try { // Propagate initialization errors to the caller + build->initImageBuild(); + } + catch (...) { + throw; + } + builds->push_back(build); emit imageBuildCreated(build); diff --git a/Grinder/image/tags/ImageTags.cpp b/Grinder/image/tags/ImageTags.cpp index d811d3a..d3cafde 100644 --- a/Grinder/image/tags/ImageTags.cpp +++ b/Grinder/image/tags/ImageTags.cpp @@ -7,11 +7,6 @@ #include "ImageTags.h" #include "image/ImageExceptions.h" -ImageTags::ImageTags(const ImageTags& imageTags) : QObject() -{ - _tags.deepCopy(imageTags._tags); -} - ImageTags& ImageTags::operator =(const ImageTags& imageTags) { _tags.deepCopy(imageTags._tags); diff --git a/Grinder/image/tags/ImageTags.h b/Grinder/image/tags/ImageTags.h index 9c9e6e1..3202331 100644 --- a/Grinder/image/tags/ImageTags.h +++ b/Grinder/image/tags/ImageTags.h @@ -21,9 +21,6 @@ namespace grndr static const char* Serialization_Element; public: - ImageTags() { } - ImageTags(const ImageTags& imageTags); - ImageTags& operator =(const ImageTags& imageTags); public: diff --git a/Grinder/image/tags/ImageTagsAllotment.cpp b/Grinder/image/tags/ImageTagsAllotment.cpp new file mode 100644 index 0000000..6845e22 --- /dev/null +++ b/Grinder/image/tags/ImageTagsAllotment.cpp @@ -0,0 +1,103 @@ +/****************************************************************************** + * File: ImageTagsAllotment.cpp + * Date: 03.5.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "ImageTagsAllotment.h" +#include "ImageTags.h" +#include "image/ImageBuild.h" + +const char* ImageTagsAllotment::Serialization_Value_AssignedTags = "AssignedTags"; + +ImageTagsAllotment& ImageTagsAllotment::operator =(const ImageTagsAllotment& imageTagsAllotment) +{ + unassignAllTags(); + + // Only copy the assigned tags if both allotments use the same input image tags + if (_inputImageTags == imageTagsAllotment._inputImageTags) + { + _assignedTags = imageTagsAllotment._assignedTags; + emit allotmentChanged(); + } + + return *this; +} + +void ImageTagsAllotment::setImageBuild(const ImageBuild* imageBuild) +{ + // Disconnect any previously connected signals from the image build + if (_imageBuild) + disconnect(_imageBuild, nullptr, this, nullptr); + + _imageBuild = imageBuild; + + setInputImageTags(_imageBuild->inputImageTags()); + + // Listen for connection signals from the block's image tags in-port + connect(_imageBuild, &ImageBuild::inputImageTagsChanged, this, &ImageTagsAllotment::inputImageTagsChanged); +} + +void ImageTagsAllotment::assignTag(ImageTag* imageTag) +{ + if (_inputImageTags && _inputImageTags->tags().contains(imageTag)) + { + _assignedTags.emplace(imageTag); + emit allotmentChanged(); + } +} + +void ImageTagsAllotment::unassignTag(ImageTag* imageTag) +{ + _assignedTags.erase(imageTag); + emit allotmentChanged(); +} + +void ImageTagsAllotment::serialize(SerializationContext& ctx) const +{ + // Store the names of all assigned image tags; since tag names must be unique, this can be used to identify each tag + QStringList assignedTags; + + for (const auto& imageTag : _assignedTags) + assignedTags << imageTag->getName(); + + ctx.settings()[Serialization_Value_AssignedTags] = assignedTags.join(","); +} + +void ImageTagsAllotment::deserialize(DeserializationContext& ctx) +{ + unassignAllTags(); + + if (_inputImageTags) + { + // Search for each tag name in the current input image tags + for (auto tagName : ctx.settings()[Serialization_Value_AssignedTags].toString().split(",")) + { + if (auto imageTag = _inputImageTags->tags().selectByName(tagName)) + assignTag(imageTag.get()); + } + } +} + +void ImageTagsAllotment::setInputImageTags(const ImageTags* imageTags) +{ + if (imageTags == _inputImageTags) + return; + + if (_inputImageTags) + disconnect(_inputImageTags, nullptr, this, nullptr); + + // The input image tags have changed, so clear any assigned tags + unassignAllTags(); + + _inputImageTags = imageTags; + + // If a tag has been removed, remove it from the allotment as well + if (_inputImageTags) + connect(_inputImageTags, &ImageTags::tagRemoved, this, &ImageTagsAllotment::imageTagRemoved); +} + +void ImageTagsAllotment::imageTagRemoved(const std::shared_ptr<ImageTag>& imageTag) +{ + unassignTag(imageTag.get()); +} diff --git a/Grinder/image/tags/ImageTagsAllotment.h b/Grinder/image/tags/ImageTagsAllotment.h new file mode 100644 index 0000000..1d87821 --- /dev/null +++ b/Grinder/image/tags/ImageTagsAllotment.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * File: ImageTagsAllotment.h + * Date: 03.5.2018 + *****************************************************************************/ + +#ifndef IMAGETAGSALLOTMENT_H +#define IMAGETAGSALLOTMENT_H + +#include <set> + +#include "common/serialization/SerializationContext.h" +#include "common/serialization/DeserializationContext.h" + +namespace grndr +{ + class ImageBuild; + class ImageTag; + class ImageTags; + + class ImageTagsAllotment : public QObject + { + Q_OBJECT + + public: + static const char* Serialization_Value_AssignedTags; + + public: + ImageTagsAllotment& operator =(const ImageTagsAllotment& imageTagsAllotment); + + public: + void setImageBuild(const ImageBuild* imageBuild); + + public: + const ImageTags* inputImageTags() const { return _inputImageTags; } + + public: + void assignTag(ImageTag* imageTag); + void unassignTag(ImageTag* imageTag); + void unassignAllTags() { _assignedTags.clear(); emit allotmentChanged(); } + + const std::set<ImageTag*>& assignedTags() const { return _assignedTags; } + + public: + void serialize(SerializationContext& ctx) const; + void deserialize(DeserializationContext& ctx); + + signals: + void allotmentChanged(); + + private: + void setInputImageTags(const ImageTags* imageTags); + + private slots: + void inputImageTagsChanged(const ImageTags* imageTags) { setInputImageTags(imageTags); } + + void imageTagRemoved(const std::shared_ptr<ImageTag>& imageTag); + + private: + const ImageBuild* _imageBuild{nullptr}; + + const ImageTags* _inputImageTags{nullptr}; + std::set<ImageTag*> _assignedTags; + }; +} + +#endif diff --git a/Grinder/image/tags/ImageTagsAllotmentProperty.cpp b/Grinder/image/tags/ImageTagsAllotmentProperty.cpp new file mode 100644 index 0000000..677203b --- /dev/null +++ b/Grinder/image/tags/ImageTagsAllotmentProperty.cpp @@ -0,0 +1,35 @@ +/****************************************************************************** + * File: ImageTagsAllotmentProperty.cpp + * Date: 3.5.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "ImageTagsAllotmentProperty.h" +#include "image/tags/ImageTag.h" +#include "ui/properties/editors/ImageTagsAllotmentPropertyEditor.h" + +void ImageTagsAllotmentProperty::initProperty() +{ + ObjectProperty::initProperty(); + + // Whenever the allotment has changed, notify the property about this + connect(&_object, &ImageTagsAllotment::allotmentChanged, this, &ImageTagsAllotmentProperty::objectModified); +} + +QWidget* ImageTagsAllotmentProperty::createEditor(QWidget* parent) +{ + return new ImageTagsAllotmentPropertyEditor{this, parent}; +} + +QString ImageTagsAllotmentProperty::toString() const +{ + QStringList tagNames; + + for (const auto& imageTag : _object.assignedTags()) + tagNames << imageTag->getName(); + + if (!tagNames.isEmpty()) + return tagNames.join(", "); + else + return "No tags"; +} diff --git a/Grinder/image/tags/ImageTagsAllotmentProperty.h b/Grinder/image/tags/ImageTagsAllotmentProperty.h new file mode 100644 index 0000000..650089f --- /dev/null +++ b/Grinder/image/tags/ImageTagsAllotmentProperty.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * File: ImageTagsAllotmentProperty.h + * Date: 3.5.2018 + *****************************************************************************/ + +#ifndef IMAGETAGSALLOTMENTPROPERTY_H +#define IMAGETAGSALLOTMENTPROPERTY_H + +#include "common/properties/ObjectProperty.h" +#include "image/tags/ImageTagsAllotment.h" + +namespace grndr +{ + class ImageTagsAllotmentProperty : public ObjectProperty<ImageTagsAllotment> + { + Q_OBJECT + + public: + using ObjectProperty::ObjectProperty; + + public: + virtual void initProperty() override; + + public: + virtual QWidget* createEditor(QWidget* parent) override; + + public: + virtual QString toString() const override; + }; +} + +#endif diff --git a/Grinder/image/tags/ImageTagsProperty.cpp b/Grinder/image/tags/ImageTagsProperty.cpp index a858e8a..7113e0d 100644 --- a/Grinder/image/tags/ImageTagsProperty.cpp +++ b/Grinder/image/tags/ImageTagsProperty.cpp @@ -16,21 +16,3 @@ QString ImageTagsProperty::toString() const { return QString{"%1 tag(s)"}.arg(_object.tags().size()); } - -void ImageTagsProperty::serialize(SerializationContext& ctx) const -{ - ObjectProperty::serialize(ctx); - - // Serialize tags - if (!hasFlag(Flag::ReadOnly)) - _object.serialize(ctx); -} - -void ImageTagsProperty::deserialize(DeserializationContext& ctx) -{ - ObjectProperty::deserialize(ctx); - - // Deserialize tags - if (!hasFlag(Flag::ReadOnly)) - _object.deserialize(ctx); -} diff --git a/Grinder/image/tags/ImageTagsProperty.h b/Grinder/image/tags/ImageTagsProperty.h index c1d6fe8..8abd177 100644 --- a/Grinder/image/tags/ImageTagsProperty.h +++ b/Grinder/image/tags/ImageTagsProperty.h @@ -23,9 +23,6 @@ namespace grndr public: virtual QString toString() const override; - - virtual void serialize(SerializationContext& ctx) const override; - virtual void deserialize(DeserializationContext& ctx) override; }; } diff --git a/Grinder/pipeline/Block.cpp b/Grinder/pipeline/Block.cpp index 6bcc55e..ee20b2f 100644 --- a/Grinder/pipeline/Block.cpp +++ b/Grinder/pipeline/Block.cpp @@ -75,6 +75,37 @@ std::set<const ImageReference*> Block::assembleInputImageReferences(const ImageR return imageReferences; } +const Port* Block::dataPort(PortType portType) const +{ + if (auto port = _ports.selectByType(portType)) + return dataPort(port.get()); + else + return nullptr; +} + +const Port* Block::dataPort(const Port* port) const +{ + if (!_ports.contains(port)) + throw BlockException{this, _EXCPT("Tried to retrieve the data port for a port not belonging to this block")}; + + auto outPort = port; + + // If the given port is an in-port, find the out-port it is connected to + if (port->isIn()) + { + outPort = nullptr; + + auto connections = port->getConnections(Port::Direction::In); + + if (connections.size() == 1) + outPort = connections.at(0)->sourcePort(); + else if (connections.size() > 1) + throw BlockException{this, QString{"Port '%1' is connected to more than one source"}.arg(port->getName())}; // Should never happen + } + + return outPort; +} + QString Block::getFormattedName() const { return QString{"%1::%2"}.arg(_pipeline->getName()).arg(_name); diff --git a/Grinder/pipeline/Block.h b/Grinder/pipeline/Block.h index d11ebaa..9dd96cc 100644 --- a/Grinder/pipeline/Block.h +++ b/Grinder/pipeline/Block.h @@ -42,17 +42,21 @@ namespace grndr std::set<const ImageReference*> assembleInputImageReferences(const ImageReference* activeImageRef) const; public: - QString getFormattedName() const; - BlockType getType() const { return _type; } - BlockCategory getCategory() const { return _category; } - - const PortVector& ports() const { return _ports; } + const Port* dataPort(PortType portType) const; + const Port* dataPort(const Port* port) const; template<typename PropType> PropType* portProperty(PortType portType, PropertyID propertyID) const; template<typename PropType> PropType* portProperty(const Port* port, PropertyID propertyID) const; + public: + QString getFormattedName() const; + BlockType getType() const { return _type; } + BlockCategory getCategory() const { return _category; } + + const PortVector& ports() const { return _ports; } + public: virtual void serialize(SerializationContext& ctx) const override; virtual void deserialize(DeserializationContext& ctx) override; diff --git a/Grinder/pipeline/Block.impl.h b/Grinder/pipeline/Block.impl.h index 0fdc9c1..e30301c 100644 --- a/Grinder/pipeline/Block.impl.h +++ b/Grinder/pipeline/Block.impl.h @@ -18,19 +18,7 @@ PropType* Block::portProperty(PortType portType, PropertyID propertyID) const template<typename PropType> PropType* Block::portProperty(const Port* port, PropertyID propertyID) const { - auto outPort = port; - - if (port->isIn()) // Get property from the connected block (if any) - { - outPort = nullptr; - - auto connections = port->getConnections(Port::Direction::In); - - if (connections.size() == 1) - outPort = connections.at(0)->sourcePort(); - } - - if (outPort) + if (auto outPort = dataPort(port)) return outPort->block()->properties().property<PropType>(propertyID); else return nullptr; diff --git a/Grinder/res/Grinder.qrc b/Grinder/res/Grinder.qrc index 3153946..9730a49 100644 --- a/Grinder/res/Grinder.qrc +++ b/Grinder/res/Grinder.qrc @@ -42,6 +42,7 @@ <file>icons/tag-black.png</file> <file>icons/check-mark-black-outline.png</file> <file>icons/multi_checks.png</file> + <file>icons/tags-black-couple-with-rings.png</file> </qresource> <qresource prefix="/"> <file>css/global.css</file> diff --git a/Grinder/res/Resources.h b/Grinder/res/Resources.h index 34b9d9a..1937fb6 100644 --- a/Grinder/res/Resources.h +++ b/Grinder/res/Resources.h @@ -24,6 +24,7 @@ #define FILE_ICON_IMAGEREFERENCE ":/icons/icons/map-of-roads.png" #define FILE_ICON_LAYER ":/icons/icons/documents-empty.png" #define FILE_ICON_TAG ":/icons/icons/tag-black.png" +#define FILE_ICON_TAGS ":/icons/icons/tags-black-couple-with-rings.png" #define FILE_ICON_ADD ":/icons/icons/plus-sign.png" #define FILE_ICON_EDIT ":/icons/icons/edit.png" diff --git a/Grinder/res/css/controlBar.css b/Grinder/res/css/controlBar.css index a17f614..bd12c80 100644 --- a/Grinder/res/css/controlBar.css +++ b/Grinder/res/css/controlBar.css @@ -1,4 +1,4 @@ -QFrame +grndr--ControlBar { background: %LIGHTBG%; border: 1px solid %BORDER%; @@ -6,116 +6,23 @@ QFrame margin: 0px; } -QFrame QFrame +grndr--ControlBar QFrame { border: none; border-right: 1px solid %LIGHTGRAY%; } -QFrame QToolButton +grndr--ControlBar QToolButton { margin-bottom: -1px; /** !win32 **/ } -QFrame QToolButton[popupMode="1"] +grndr--ControlBar QToolButton[popupMode="1"] { padding-right: 16px; } -QFrame QLabel -{ - border: none; -} - -/* ControlBar inside a splitter */ - -QSplitter QFrame -{ - background: %LIGHTBG%; - border: 1px solid %BORDER%; - padding: 0px; - margin: 0px; -} - -QSplitter QFrame QFrame -{ - border: none; - border-right: 1px solid %LIGHTGRAY%; -} - -QSpitter QFrame QToolButton -{ - margin-bottom: -1px; /** !win32 **/ -} - -QSplitter QFrame QToolButton[popupMode="1"] -{ - padding-right: 16px; -} - -QSplitter QFrame QLabel -{ - border: none; -} - -/* ControlBar inside two splitters */ - -QSplitter QSplitter QFrame -{ - background: %LIGHTBG%; - border: 1px solid %BORDER%; - padding: 0px; - margin: 0px; -} - -QSplitter QSplitter QFrame QFrame -{ - border: none; - border-right: 1px solid %LIGHTGRAY%; -} - -QSplitter QSpitter QFrame QToolButton -{ - margin-bottom: -1px; /** !win32 **/ -} - -QSplitter QSplitter QFrame QToolButton[popupMode="1"] -{ - padding-right: 16px; -} - -QSplitter QSplitter QFrame QLabel -{ - border: none; -} - -/* ControlBar inside a dialog */ - -QDialog QFrame -{ - background: %LIGHTBG%; - border: 1px solid %BORDER%; - padding: 0px; - margin: 0px; -} - -QDialog QFrame QFrame -{ - border: none; - border-right: 1px solid %LIGHTGRAY%; -} - -QDialog QFrame QToolButton -{ - margin-bottom: -1px; /** !win32 **/ -} - -QDialog QFrame QToolButton[popupMode="1"] -{ - padding-right: 16px; -} - -QDialog QFrame QLabel +grndr--ControlBar QLabel { border: none; } diff --git a/Grinder/ui/dlg/CheckListDialog.cpp b/Grinder/ui/dlg/CheckListDialog.cpp index 2d46903..5fceeec 100644 --- a/Grinder/ui/dlg/CheckListDialog.cpp +++ b/Grinder/ui/dlg/CheckListDialog.cpp @@ -7,8 +7,8 @@ #include "CheckListDialog.h" #include "ui_CheckListDialog.h" -CheckListDialog::CheckListDialog(QString dialogTitle, QWidget *parent) : QDialog(parent, Qt::Dialog|Qt::WindowTitleHint|Qt::WindowCloseButtonHint), - ui{new Ui::CheckListDialog} +CheckListDialog::CheckListDialog(QString dialogTitle, bool acceptIfNoItemsChecked, QWidget *parent) : QDialog(parent, Qt::Dialog|Qt::WindowTitleHint|Qt::WindowCloseButtonHint), + ui{new Ui::CheckListDialog}, _acceptIfNoItemsChecked{acceptIfNoItemsChecked} { ui->setupUi(this); @@ -47,5 +47,20 @@ void CheckListDialog::setCheckListWidget(QListWidget* listWidget) void CheckListDialog::checkListItemChanged(QListWidgetItem* item) { Q_UNUSED(item); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!getCheckedItems().empty()); + + bool anyItemChecked = false; + + if (!_acceptIfNoItemsChecked) + { + for (int i = 0; i < _checkListWidget->count(); ++i) + { + if (_checkListWidget->item(i)->checkState() == Qt::Checked) + { + anyItemChecked = true; + break; + } + } + } + + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(anyItemChecked || _acceptIfNoItemsChecked); } diff --git a/Grinder/ui/dlg/CheckListDialog.h b/Grinder/ui/dlg/CheckListDialog.h index b87a67b..447990f 100644 --- a/Grinder/ui/dlg/CheckListDialog.h +++ b/Grinder/ui/dlg/CheckListDialog.h @@ -21,12 +21,12 @@ namespace grndr Q_OBJECT public: - CheckListDialog(QString dialogTitle = "", QWidget* parent = nullptr); + CheckListDialog(QString dialogTitle = "", bool acceptIfNoItemsChecked = false, QWidget* parent = nullptr); ~CheckListDialog(); public: template<typename ObjectType, typename ItemType, typename ContainerType> - std::vector<ObjectType*> exec(const ContainerType& container); + std::vector<ObjectType*> exec(const ContainerType& container, const std::vector<ObjectType*>& checkedObjects = {}, bool* accepted = nullptr); private: void setupUi(); @@ -37,11 +37,11 @@ namespace grndr private: void setCheckListWidget(QListWidget* listWidget); - template<typename ItemType = QListWidgetItem> - std::vector<ItemType*> getCheckedItems() const; - private slots: void checkListItemChanged(QListWidgetItem* item); + + private: + bool _acceptIfNoItemsChecked{false}; }; } diff --git a/Grinder/ui/dlg/CheckListDialog.impl.h b/Grinder/ui/dlg/CheckListDialog.impl.h index c650311..deaee4d 100644 --- a/Grinder/ui/dlg/CheckListDialog.impl.h +++ b/Grinder/ui/dlg/CheckListDialog.impl.h @@ -8,40 +8,25 @@ #include "ui/widgets/CheckListWidget.h" template<typename ObjectType, typename ItemType, typename ContainerType> -std::vector<ObjectType*> CheckListDialog::exec(const ContainerType& container) +std::vector<ObjectType*> CheckListDialog::exec(const ContainerType& container, const std::vector<ObjectType*>& checkedObjects, bool* accepted) { // Create a check list widget for the given types and add it to the dialog auto checkListWidget = new CheckListWidget<ObjectType, ItemType>{}; setCheckListWidget(checkListWidget); checkListWidget->populateList(container); + checkListWidget->setCheckedObjects(checkedObjects); + + if (accepted) + *accepted = false; if (QDialog::exec() == QDialog::Accepted) { - std::vector<ObjectType*> checkedObjects; - - for (auto checkedItem : getCheckedItems<ItemType>()) - checkedObjects.push_back(checkedItem->object()); + if (accepted) + *accepted = true; - return checkedObjects; + return checkListWidget->getCheckedObjects(); } else return {}; } - -template<typename ItemType> -std::vector<ItemType*> CheckListDialog::getCheckedItems() const -{ - std::vector<ItemType*> checkedItems; - - for (int i = 0; i < _checkListWidget->count(); ++i) - { - if (auto item = dynamic_cast<ItemType*>(_checkListWidget->item(i))) - { - if (item->checkState() == Qt::Checked) - checkedItems.push_back(item); - } - } - - return checkedItems; -} diff --git a/Grinder/ui/image/ImageEditorPropertyWidget.cpp b/Grinder/ui/image/ImageEditorPropertyWidget.cpp index fe2ea35..d79f60d 100644 --- a/Grinder/ui/image/ImageEditorPropertyWidget.cpp +++ b/Grinder/ui/image/ImageEditorPropertyWidget.cpp @@ -47,7 +47,7 @@ void ImageEditorPropertyWidget::addProperties(const PropertyVector* properties) if (properties) { - for (auto& property : *properties) + for (auto& property : properties->sorted()) { if (!property->hasFlag(PropertyBase::Flag::ReadOnly) && !property->hasFlag(PropertyBase::Flag::Hidden)) { diff --git a/Grinder/ui/image/ImageEditorWidget.cpp b/Grinder/ui/image/ImageEditorWidget.cpp index 2cf73ae..f919e5b 100644 --- a/Grinder/ui/image/ImageEditorWidget.cpp +++ b/Grinder/ui/image/ImageEditorWidget.cpp @@ -10,7 +10,9 @@ #include "core/GrinderApplication.h" #include "image/ImageBuild.h" #include "image/ImageExceptions.h" +#include "image/tags/ImageTags.h" #include "ui/mainwnd/ImageReferencesListItem.h" +#include "ui/properties/editors/ImageTagsAllotmentPropertyEditor.h" #include "ui/dlg/CheckListDialog.h" #include "ui/StyleSheet.h" #include "util/UIUtils.h" @@ -33,6 +35,8 @@ ImageEditorWidget::ImageEditorWidget(ImageEditor* imageEditor, QWidget* parent) _duplicateImageBuild = UIUtils::createAction(this, "&Copy image build to next image", FILE_ICON_EDITOR_COPYFROMPREVIOUS, SLOT(duplicateImageBuild()), "Copy the current image build to the next image (Ctrl+D)", "Ctrl+D", Qt::WidgetWithChildrenShortcut); _duplicateImageBuildTo = UIUtils::createAction(this, "Copy image build to &multiple images...", "", SLOT(duplicateImageBuildTo()), "Copy the current image build to multiple images (Ctrl+Shift+D)", "Ctrl+Shift+D", Qt::WidgetWithChildrenShortcut); + _editImageBuildTags = UIUtils::createAction(this, "Edit the image build's tags...", FILE_ICON_TAGS, SLOT(editImageBuildTags()), "Edit the image tags assigned to the current image build (Ctrl+Shift+T)", "Ctrl+Shift+T", Qt::WidgetWithChildrenShortcut); + // Set up the duplicate image build menu _duplicateImageBuildMenu.addAction(_duplicateImageBuild); _duplicateImageBuildMenu.addSeparator(); @@ -73,6 +77,7 @@ void ImageEditorWidget::setImageBuild(const std::shared_ptr<ImageBuild>& imageBu // Listen for various events to update our actions connect(imageBuild.get(), SIGNAL(layerCreated(const std::shared_ptr<Layer>&)), this, SLOT(updateActions())); connect(imageBuild.get(), SIGNAL(layerRemoved(const std::shared_ptr<Layer>&)), this, SLOT(updateActions())); + connect(imageBuild.get(), SIGNAL(inputImageTagsChanged(const ImageTags*)), this, SLOT(updateActions())); updateActions(); } @@ -146,6 +151,8 @@ void ImageEditorWidget::setupImageControlBar() ui->imageSceneControlBar->addAction(_copyImageBuild, Qt::ToolButtonFollowStyle, Qt::AlignLeft); ui->imageSceneControlBar->addAction(_pasteImageBuild, Qt::ToolButtonFollowStyle, Qt::AlignLeft); ui->imageSceneControlBar->addAction(_duplicateImageBuild, Qt::ToolButtonFollowStyle, Qt::AlignLeft, &_duplicateImageBuildMenu); + ui->imageSceneControlBar->addSeparator(Qt::AlignLeft); + ui->imageSceneControlBar->addAction(_editImageBuildTags, Qt::ToolButtonFollowStyle, Qt::AlignLeft); ui->imageScene->setupUi(static_cast<QMenu*>(nullptr), ui->imageSceneControlBar); @@ -213,6 +220,8 @@ void ImageEditorWidget::updateActions() _copyImageBuild->setEnabled(imageBuild); _duplicateImageBuild->setEnabled(imageBuild && grinder()->project().imageReferences().size() > 1); _pasteImageBuild->setEnabled(imageBuild && grinder()->clipboardManager().hasData(ImageBuildVector::Serialization_Element)); + + _editImageBuildTags->setEnabled(_imageEditor->controller().activeImageBuild() && _imageEditor->controller().activeImageBuild()->inputImageTags()); } void ImageEditorWidget::updateSceneZoomLevel(qreal zoomLevel) @@ -282,7 +291,9 @@ void ImageEditorWidget::duplicateImageBuild() const void ImageEditorWidget::duplicateImageBuildTo() const { CheckListDialog dlg{"Select target images"}; - auto selectedImages = dlg.exec<ImageReference, ImageReferencesListItem>(grinder()->project().imageReferences().sorted()); + auto imageReferences = grinder()->project().imageReferences().sorted(); + imageReferences.erase(grinder()->projectController().activeImageReference()); // Do not include the current image reference in the available targets + auto selectedImages = dlg.exec<ImageReference, ImageReferencesListItem>(imageReferences); if (!selectedImages.empty()) { @@ -291,6 +302,16 @@ void ImageEditorWidget::duplicateImageBuildTo() const } } +void ImageEditorWidget::editImageBuildTags() +{ + if (auto imageBuild = _imageEditor->controller().activeImageBuild()) + { + // Use the image tags allotment property editor to edit the image build's tags + ImageTagsAllotmentPropertyEditor propertyEditor{imageBuild->imageTagsAllotment(), this}; + propertyEditor.invokeDialog(); + } +} + void ImageEditorWidget::primaryColorChanged(QColor color) { ui->primaryColorWidget->setColor(color); diff --git a/Grinder/ui/image/ImageEditorWidget.h b/Grinder/ui/image/ImageEditorWidget.h index dd1cf27..21f9273 100644 --- a/Grinder/ui/image/ImageEditorWidget.h +++ b/Grinder/ui/image/ImageEditorWidget.h @@ -67,6 +67,8 @@ namespace grndr void duplicateImageBuild() const; void duplicateImageBuildTo() const; + void editImageBuildTags(); + private: QAction* _newLayerAction{nullptr}; @@ -75,6 +77,8 @@ namespace grndr QAction* _duplicateImageBuild{nullptr}; QAction* _duplicateImageBuildTo{nullptr}; + QAction* _editImageBuildTags{nullptr}; + QMenu _duplicateImageBuildMenu; }; } diff --git a/Grinder/ui/image/tags/ImageTagsListWidget.cpp b/Grinder/ui/image/tags/ImageTagsListWidget.cpp index 948b0ab..ae1cd83 100644 --- a/Grinder/ui/image/tags/ImageTagsListWidget.cpp +++ b/Grinder/ui/image/tags/ImageTagsListWidget.cpp @@ -28,24 +28,15 @@ void ImageTagsListWidget::assignUiComponents(ImageEditor* imageEditor) void ImageTagsListWidget::imageBuildSwitched(ImageBuild* imageBuild) { - // Remove any previously connected signals from the block's image tags in-port + // Remove any previously connected signals from the image build's block if (_imageBuild) - { - if (auto imageTagsInPort = _imageBuild->block()->ports().selectByType(PortType::ImageTagsIn)) - disconnect(imageTagsInPort.get(), nullptr, this, nullptr); - } + disconnect(_imageBuild, nullptr, this, nullptr); _imageBuild = imageBuild; // Listen for connection signals from the block's image tags in-port if (_imageBuild) - { - if (auto imageTagsInPort = _imageBuild->block()->ports().selectByType(PortType::ImageTagsIn)) - { - connect(imageTagsInPort.get(), SIGNAL(portConnected(const Connection*)), this, SLOT(populateList()), Qt::QueuedConnection); - connect(imageTagsInPort.get(), SIGNAL(portDisconnected(const Connection*)), this, SLOT(populateList()), Qt::QueuedConnection); - } - } + connect(_imageBuild, SIGNAL(inputImageTagsChanged(const ImageTags*)), this, SLOT(populateList())); // A new image build is shown in the editor, so populate its associated image tags populateList(); @@ -53,12 +44,5 @@ void ImageTagsListWidget::imageBuildSwitched(ImageBuild* imageBuild) void ImageTagsListWidget::populateList() { - if (_imageBuild) - { - // See if the block has an image tags in-port and if an image tags property can be retrieved from it - auto imageTagsProperty = _imageBuild->block()->portProperty<ImageTagsProperty>(PortType::ImageTagsIn, PropertyID::ImageTags); - assignImageTags(imageTagsProperty ? &imageTagsProperty->object() : nullptr); - } - else - assignImageTags(nullptr); + assignImageTags(_imageBuild ? _imageBuild->inputImageTags() : nullptr); } diff --git a/Grinder/ui/image/tools/DraftItemTool.cpp b/Grinder/ui/image/tools/DraftItemTool.cpp index 43074b3..397908a 100644 --- a/Grinder/ui/image/tools/DraftItemTool.cpp +++ b/Grinder/ui/image/tools/DraftItemTool.cpp @@ -16,6 +16,9 @@ DraftItemTool::DraftItemTool(ImageEditor* imageEditor, DraftItemType itemType, Q { if (itemType == DraftItemType::Undefined) throw std::invalid_argument{_EXCPT("itemType may not be DraftItemType::Undefined")}; + + // We need to update the image tags allotment property whenever the image build has been switched + connect(&_imageEditor->controller(), &ImageEditorController::imageBuildSwitched, this, &DraftItemTool::imageBuildSwitched); } std::shared_ptr<DraftItem> DraftItemTool::createDraftItem(QPointF pos, bool selectItem, bool setDefaultProperties) @@ -75,6 +78,9 @@ void DraftItemTool::createProperties() _direction = createProperty<AngleProperty>(PropertyID::Direction, "Direction", 0.0); direction()->createConstraint<RangeConstraint>(0, 360); direction()->setDescription("The main direction/orientation of the box contents."); + + _imageTagsAllotment = createProperty<ImageTagsAllotmentProperty>(PropertyID::ImageTagsAllotment, "Image tags"); + imageTagsAllotment()->setDescription("The assigned image tags."); } ImageEditorTool::InputEventResult DraftItemTool::mousePressed(const QGraphicsSceneMouseEvent* event) @@ -153,3 +159,9 @@ void DraftItemTool::cancelDraftItemDrag() _dragInfo.draftItem = nullptr; } } + +void DraftItemTool::imageBuildSwitched(ImageBuild* imageBuild) +{ + // Assign the current image build to the image tags allotment property + imageTagsAllotment()->object().setImageBuild(imageBuild); +} diff --git a/Grinder/ui/image/tools/DraftItemTool.h b/Grinder/ui/image/tools/DraftItemTool.h index ee67d04..6c9b5dc 100644 --- a/Grinder/ui/image/tools/DraftItemTool.h +++ b/Grinder/ui/image/tools/DraftItemTool.h @@ -8,6 +8,7 @@ #include "ui/image/ImageEditorTool.h" #include "image/DraftItemType.h" +#include "image/tags/ImageTagsAllotmentProperty.h" namespace grndr { @@ -32,6 +33,8 @@ namespace grndr auto hasDirection() const { return dynamic_cast<BoolProperty*>(_hasDirection.get()); } auto direction() { return dynamic_cast<AngleProperty*>(_direction.get()); } auto direction() const { return dynamic_cast<AngleProperty*>(_direction.get()); } + auto imageTagsAllotment() { return dynamic_cast<ImageTagsAllotmentProperty*>(_imageTagsAllotment.get()); } + auto imageTagsAllotment() const { return dynamic_cast<ImageTagsAllotmentProperty*>(_imageTagsAllotment.get()); } protected: std::shared_ptr<DraftItem> createDraftItem(QPointF pos, bool selectItem, bool setDefaultProperties); @@ -57,6 +60,10 @@ namespace grndr std::shared_ptr<PropertyBase> _position; std::shared_ptr<PropertyBase> _hasDirection; std::shared_ptr<PropertyBase> _direction; + std::shared_ptr<PropertyBase> _imageTagsAllotment; + + private slots: + void imageBuildSwitched(ImageBuild* imageBuild); private: struct diff --git a/Grinder/ui/properties/PropertyTreeItemDelegate.h b/Grinder/ui/properties/PropertyTreeItemDelegate.h index b6dfc52..3283adc 100644 --- a/Grinder/ui/properties/PropertyTreeItemDelegate.h +++ b/Grinder/ui/properties/PropertyTreeItemDelegate.h @@ -28,7 +28,7 @@ namespace grndr virtual void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override; private: - PropertyTreeWidget* _widget{nullptr}; + PropertyTreeWidget* _widget{nullptr}; }; } diff --git a/Grinder/ui/properties/ValuePropertyTreeItem.cpp b/Grinder/ui/properties/ValuePropertyTreeItem.cpp index 82359b4..10e6a30 100644 --- a/Grinder/ui/properties/ValuePropertyTreeItem.cpp +++ b/Grinder/ui/properties/ValuePropertyTreeItem.cpp @@ -14,7 +14,6 @@ ValuePropertyTreeItem::ValuePropertyTreeItem(const std::shared_ptr<PropertyBase> throw std::invalid_argument{_EXCPT("property may not be null")}; setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsEditable); - setTextColor(1, QColor{20, 105, 140}); updateItem(); diff --git a/Grinder/ui/properties/editors/DialogPropertyEditor.h b/Grinder/ui/properties/editors/DialogPropertyEditor.h index 66f1f3f..7247ff7 100644 --- a/Grinder/ui/properties/editors/DialogPropertyEditor.h +++ b/Grinder/ui/properties/editors/DialogPropertyEditor.h @@ -16,6 +16,9 @@ namespace grndr public: DialogPropertyEditor(PropertyType* property, QWidget *parent = nullptr); + public: + virtual void invokeDialog() = 0; + protected: virtual void showEvent(QShowEvent* event) override; virtual void mouseDoubleClickEvent(QMouseEvent* event) override; @@ -24,7 +27,7 @@ namespace grndr virtual void applyPropertyValue() override; protected: - virtual void invokeDialog() = 0; + void setValueLabelText(QString text); private: void editPropertyClicked(bool checked); diff --git a/Grinder/ui/properties/editors/DialogPropertyEditor.impl.h b/Grinder/ui/properties/editors/DialogPropertyEditor.impl.h index 8cc7c05..1721dec 100644 --- a/Grinder/ui/properties/editors/DialogPropertyEditor.impl.h +++ b/Grinder/ui/properties/editors/DialogPropertyEditor.impl.h @@ -12,10 +12,11 @@ DialogPropertyEditor<PropertyType>::DialogPropertyEditor(PropertyType* property, { this->setFocusPolicy(Qt::NoFocus); - _valueLabel->setStyleSheet("margin-left: 1px;"); _valueLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + _valueLabel->setStyleSheet("QTreeWidget QLabel { margin-left: 1px; }"); _editButton->setText("..."); + _editButton->setToolTip("Edit items"); // Create a horizontal layout and add all controls to it auto layout = new QHBoxLayout{}; @@ -51,10 +52,18 @@ void DialogPropertyEditor<PropertyType>::mouseDoubleClickEvent(QMouseEvent* even invokeDialog(); } +template<typename PropertyType> +void DialogPropertyEditor<PropertyType>::setValueLabelText(QString text) +{ + QFontMetrics fontMetrics(_valueLabel->font()); + _valueLabel->setText(fontMetrics.elidedText(text, Qt::ElideRight, _valueLabel->width() - 2)); + _valueLabel->setToolTip(this->getPropertyValue()); +} + template<typename PropertyType> void DialogPropertyEditor<PropertyType>::applyPropertyValue() { - _valueLabel->setText(this->getPropertyValue()); + setValueLabelText(this->getPropertyValue()); } template<typename PropertyType> diff --git a/Grinder/ui/properties/editors/ImageTagsAllotmentPropertyEditor.cpp b/Grinder/ui/properties/editors/ImageTagsAllotmentPropertyEditor.cpp new file mode 100644 index 0000000..56f8a07 --- /dev/null +++ b/Grinder/ui/properties/editors/ImageTagsAllotmentPropertyEditor.cpp @@ -0,0 +1,41 @@ +/****************************************************************************** + * File: ImageTagsAllotmentPropertyEditor.cpp + * Date: 18.4.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "ImageTagsAllotmentPropertyEditor.h" +#include "image/tags/ImageTags.h" +#include "ui/dlg/CheckListDialog.h" +#include "ui/image/tags/ImageTagsListItem.h" + +ImageTagsAllotmentPropertyEditor::ImageTagsAllotmentPropertyEditor(ImageTagsAllotmentProperty* property, QWidget* parent) : DialogPropertyEditor(property, parent) +{ + +} + +void ImageTagsAllotmentPropertyEditor::invokeDialog() +{ + CheckListDialog dlg{"Assign image tags", true}; + + if (auto imageTags = _property->object().inputImageTags()) + { + bool accepted = false; + std::vector<ImageTag*> assignedTags{_property->object().assignedTags().cbegin(), _property->object().assignedTags().cend()}; + auto selectedTags = dlg.exec<ImageTag, ImageTagsListItem>(imageTags->tags(), assignedTags, &accepted); + + if (accepted) + { + // Assign all selected image tags + _property->object().unassignAllTags(); + + for (auto imageTag : selectedTags) + _property->object().assignTag(imageTag); + + // The associated image tags have been modified, so notify the property + _property->objectModified(); + } + } + else + QMessageBox::information(nullptr, "Assigning image tags", "There are currently no associated input image tags."); +} diff --git a/Grinder/ui/properties/editors/ImageTagsAllotmentPropertyEditor.h b/Grinder/ui/properties/editors/ImageTagsAllotmentPropertyEditor.h new file mode 100644 index 0000000..5a854b1 --- /dev/null +++ b/Grinder/ui/properties/editors/ImageTagsAllotmentPropertyEditor.h @@ -0,0 +1,26 @@ +/****************************************************************************** + * File: ImageTagsAllotmentPropertyEditor.h + * Date: 18.4.2018 + *****************************************************************************/ + +#ifndef IMAGETAGSALLOTMENTPROPERTYEDITOR_H +#define IMAGETAGSALLOTMENTPROPERTYEDITOR_H + +#include "DialogPropertyEditor.h" +#include "image/tags/ImageTagsAllotmentProperty.h" + +namespace grndr +{ + class ImageTagsAllotmentPropertyEditor : public DialogPropertyEditor<ImageTagsAllotmentProperty> + { + Q_OBJECT + + public: + ImageTagsAllotmentPropertyEditor(ImageTagsAllotmentProperty* property, QWidget *parent = nullptr); + + public: + virtual void invokeDialog() override; + }; +} + +#endif diff --git a/Grinder/ui/properties/editors/ImageTagsPropertyEditor.h b/Grinder/ui/properties/editors/ImageTagsPropertyEditor.h index 5a99143..028835c 100644 --- a/Grinder/ui/properties/editors/ImageTagsPropertyEditor.h +++ b/Grinder/ui/properties/editors/ImageTagsPropertyEditor.h @@ -18,7 +18,7 @@ namespace grndr public: ImageTagsPropertyEditor(ImageTagsProperty* property, QWidget *parent = nullptr); - protected: + public: virtual void invokeDialog() override; }; } diff --git a/Grinder/ui/widgets/CheckListWidget.h b/Grinder/ui/widgets/CheckListWidget.h index b8a899f..34af55e 100644 --- a/Grinder/ui/widgets/CheckListWidget.h +++ b/Grinder/ui/widgets/CheckListWidget.h @@ -23,6 +23,9 @@ namespace grndr template<typename ContainerType> void populateList(const ContainerType& container); + std::vector<ObjectType*> getCheckedObjects() const; + void setCheckedObjects(const std::vector<ObjectType*>& checkedObjects); + protected: virtual void contextMenuEvent(QContextMenuEvent* event) override; diff --git a/Grinder/ui/widgets/CheckListWidget.impl.h b/Grinder/ui/widgets/CheckListWidget.impl.h index 2b6e2f1..62e84c5 100644 --- a/Grinder/ui/widgets/CheckListWidget.impl.h +++ b/Grinder/ui/widgets/CheckListWidget.impl.h @@ -36,6 +36,38 @@ void CheckListWidget<ObjectType, ItemType>::populateList(const ContainerType& co } } +template<typename ObjectType, typename ItemType> +std::vector<ObjectType*> CheckListWidget<ObjectType, ItemType>::getCheckedObjects() const +{ + std::vector<ObjectType*> checkedItems; + + for (int i = 0; i < this->count(); ++i) + { + if (auto item = dynamic_cast<ItemType*>(this->item(i))) + { + if (item->checkState() == Qt::Checked) + checkedItems.push_back(item->object()); + } + } + + return checkedItems; +} + +template<typename ObjectType, typename ItemType> +void CheckListWidget<ObjectType, ItemType>::setCheckedObjects(const std::vector<ObjectType*>& checkedObjects) +{ + for (int i = 0; i < this->count(); ++i) + { + if (auto item = dynamic_cast<ItemType*>(this->item(i))) + { + if (std::find(checkedObjects.cbegin(), checkedObjects.cend(), item->object()) != checkedObjects.cend()) + item->setCheckState(Qt::Checked); + else + item->setCheckState(Qt::Unchecked); + } + } +} + template<typename ObjectType, typename ItemType> void CheckListWidget<ObjectType, ItemType>::contextMenuEvent(QContextMenuEvent* event) { diff --git a/Grinder/ui/widgets/ObjectListWidget.h b/Grinder/ui/widgets/ObjectListWidget.h index 0824193..516d39e 100644 --- a/Grinder/ui/widgets/ObjectListWidget.h +++ b/Grinder/ui/widgets/ObjectListWidget.h @@ -17,7 +17,7 @@ namespace grndr template<typename ObjectType, typename ItemType> class ObjectListWidget : public BaseListWidget { - static_assert(std::is_base_of<ObjectListItem<ObjectType>, ItemType>::value, "ItemType must be derived from ObjectListItem<ObjectType>"); + static_assert(std::is_base_of<ObjectListItem<std::remove_const_t<ObjectType>>, ItemType>::value, "ItemType must be derived from ObjectListItem<ObjectType>"); public: using object_type = ObjectType; -- GitLab