From 4eefe97e81f9ea3db9bb6b555d6000a2bfbfb3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= <d_muel20@uni-muenster.de> Date: Fri, 29 Jun 2018 17:31:14 +0200 Subject: [PATCH] * Properties are now grouped * Properties are more dynamic now --- Grinder/Grinder.pro | 6 +- Grinder/Version.h | 6 +- Grinder/common/properties/PropertyBase.h | 8 +- Grinder/common/properties/PropertyObject.cpp | 56 ++++++++++++- Grinder/common/properties/PropertyObject.h | 28 ++++++- .../common/properties/PropertyObject.impl.h | 10 +-- Grinder/common/properties/PropertyVector.cpp | 5 ++ Grinder/common/properties/PropertyVector.h | 1 + .../processors/AdaptiveThresholdProcessor.cpp | 2 +- Grinder/image/DraftItem.cpp | 15 ++++ Grinder/image/DraftItem.h | 1 + Grinder/image/ImageBuild.cpp | 1 + Grinder/image/draftitems/BoxDraftItem.cpp | 6 +- Grinder/image/draftitems/LineDraftItem.cpp | 4 + Grinder/image/draftitems/PixelsDraftItem.cpp | 13 +++ Grinder/image/draftitems/PixelsDraftItem.h | 1 + Grinder/pipeline/PipelineItem.cpp | 1 + .../blocks/AdaptiveThresholdBlock.cpp | 5 +- .../pipeline/blocks/AdaptiveThresholdBlock.h | 3 - .../pipeline/blocks/AlphaBlendingBlock.cpp | 2 + .../pipeline/blocks/BinaryThresholdBlock.cpp | 13 +++ .../pipeline/blocks/BinaryThresholdBlock.h | 1 + Grinder/pipeline/blocks/BlurBlock.cpp | 27 +++++-- Grinder/pipeline/blocks/BlurBlock.h | 1 + Grinder/pipeline/blocks/ContoursBlock.cpp | 16 +++- Grinder/pipeline/blocks/ContoursBlock.h | 3 +- .../pipeline/blocks/ConvertToColorBlock.cpp | 2 + Grinder/pipeline/blocks/DilateBlock.cpp | 2 + .../blocks/DistanceTransformBlock.cpp | 12 +++ .../pipeline/blocks/DistanceTransformBlock.h | 1 + Grinder/pipeline/blocks/ErodeBlock.cpp | 2 + Grinder/pipeline/blocks/ImageTagsBlock.cpp | 2 + Grinder/pipeline/blocks/InputBlock.cpp | 2 + Grinder/pipeline/blocks/NormalizeBlock.cpp | 2 + Grinder/pipeline/blocks/OutputBlock.cpp | 2 + Grinder/pipeline/blocks/ReplaceColorBlock.cpp | 2 + Grinder/pipeline/blocks/SharpenBlock.cpp | 2 + .../DistanceTransformMaskProperty.cpp | 1 - .../ui/image/ImageEditorPropertyWidget.cpp | 68 +++++++++++++--- Grinder/ui/image/ImageEditorPropertyWidget.h | 6 +- Grinder/ui/image/ImageEditorTool.cpp | 1 + Grinder/ui/image/tools/BoxDraftItemTool.cpp | 4 + Grinder/ui/image/tools/DraftItemTool.cpp | 14 ++++ Grinder/ui/image/tools/DraftItemTool.h | 1 + Grinder/ui/image/tools/EraserTool.cpp | 2 + Grinder/ui/image/tools/LineDraftItemTool.cpp | 2 + Grinder/ui/image/tools/PaintbrushTool.cpp | 2 + Grinder/ui/mainwnd/GrinderWindow.ui | 3 - Grinder/ui/properties/BlockPropertyTreeItem.h | 2 +- .../ui/properties/GroupPropertyTreeItem.cpp | 29 +++++++ Grinder/ui/properties/GroupPropertyTreeItem.h | 28 +++++++ Grinder/ui/properties/PropertyTreeWidget.cpp | 81 ++++++++++++++++--- Grinder/ui/properties/PropertyTreeWidget.h | 4 +- .../ui/properties/ValuePropertyTreeItem.cpp | 16 +++- .../editors/AnglePropertyEditor.cpp | 1 + 55 files changed, 463 insertions(+), 68 deletions(-) create mode 100644 Grinder/ui/properties/GroupPropertyTreeItem.cpp create mode 100644 Grinder/ui/properties/GroupPropertyTreeItem.h diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro index 428f934..d50b0f1 100644 --- a/Grinder/Grinder.pro +++ b/Grinder/Grinder.pro @@ -276,7 +276,8 @@ SOURCES += \ pipeline/blocks/ContoursBlock.cpp \ engine/processors/ContoursProcessor.cpp \ pipeline/blocks/WatershedBlock.cpp \ - engine/processors/WatershedProcessor.cpp + engine/processors/WatershedProcessor.cpp \ + ui/properties/GroupPropertyTreeItem.cpp HEADERS += \ ui/mainwnd/GrinderWindow.h \ @@ -601,7 +602,8 @@ HEADERS += \ pipeline/blocks/ContoursBlock.h \ engine/processors/ContoursProcessor.h \ pipeline/blocks/WatershedBlock.h \ - engine/processors/WatershedProcessor.h + engine/processors/WatershedProcessor.h \ + ui/properties/GroupPropertyTreeItem.h FORMS += \ ui/mainwnd/GrinderWindow.ui \ diff --git a/Grinder/Version.h b/Grinder/Version.h index 1fa6dd4..c69bad4 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 "26.06.2018" +#define GRNDR_INFO_DATE "29.06.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 5 +#define GRNDR_VERSION_MINOR 6 #define GRNDR_VERSION_REVISION 0 -#define GRNDR_VERSION_BUILD 200 +#define GRNDR_VERSION_BUILD 203 namespace grndr { diff --git a/Grinder/common/properties/PropertyBase.h b/Grinder/common/properties/PropertyBase.h index 2ef5a4b..ee796f5 100644 --- a/Grinder/common/properties/PropertyBase.h +++ b/Grinder/common/properties/PropertyBase.h @@ -26,9 +26,10 @@ namespace grndr None = 0x0000, ReadOnly = 0x0001, Hidden = 0x0002, + Disabled = 0x0004, }; - Q_DECLARE_FLAGS(Flags, Flag) + Q_DECLARE_FLAGS(Flags, Flag) public: PropertyBase(PropertyID id, QString name, Flags flags = Flag::None); @@ -51,9 +52,11 @@ namespace grndr void setName(QString name) { _name = name; } QString getDescription() const { return _description; } void setDescription(QString desc) { _description = desc; } + QString getGroup() const { return _group; } + void setGroup(QString group) { _group = group; } bool hasFlag(Flag flag) const { return _flags.testFlag(flag); } - void setFlag(Flag flag) { _flags |= flag; } + void setFlag(Flag flag, bool set = true) { _flags.setFlag(flag, set); } Flags getFlags() const { return _flags; } void setFlags(Flags flags) { _flags = flags; } @@ -76,6 +79,7 @@ namespace grndr PropertyID _id{PropertyID::None}; QString _name{""}; QString _description{""}; + QString _group; Flags _flags{Flag::None}; }; diff --git a/Grinder/common/properties/PropertyObject.cpp b/Grinder/common/properties/PropertyObject.cpp index 107e5af..797cfbe 100644 --- a/Grinder/common/properties/PropertyObject.cpp +++ b/Grinder/common/properties/PropertyObject.cpp @@ -56,13 +56,51 @@ void PropertyObject::removeProperty(const PropertyBase* prop) auto it = _properties.find(prop); if (it != _properties.cend()) + { + disconnect(prop, nullptr, this, nullptr); _properties.erase(it); + } else throw PropertyItemException{this, _EXCPT("Tried to remove a property not belonging to this item")}; } } -std::shared_ptr<PropertyBase> PropertyObject::_createProperty(PropertyID id, std::function<std::shared_ptr<PropertyBase>()> creator) +void PropertyObject::setPropertyGroup(QString group, QString insertBefore) +{ + // Add the group if it doesn't exist yet + if (!_groups.contains(group, Qt::CaseInsensitive)) + { + auto it = std::find(_groups.begin(), _groups.end(), insertBefore); + _groups.insert(it, group); + } + + _currentGroup = group; +} + +bool PropertyObject::enableDependantProperty(PropertyBase* updatedProp, std::shared_ptr<PropertyBase> superordinateProp, std::shared_ptr<PropertyBase> dependantProp, std::function<bool()> condition) const +{ + if (!updatedProp || updatedProp == superordinateProp.get()) + { + dependantProp->setFlag(PropertyBase::Flag::Disabled, !condition()); + return true; + } + else + return false; +} + +bool PropertyObject::updateDependantProperty(PropertyBase* updatedProp, std::shared_ptr<PropertyBase> superordinateProp, std::shared_ptr<PropertyBase> dependantProp, QString name, QString desc, std::function<bool()> condition) const +{ + if ((!updatedProp || updatedProp == superordinateProp.get()) && condition()) + { + dependantProp->setName(name); + dependantProp->setDescription(desc); + return true; + } + else + return false; +} + +std::shared_ptr<PropertyBase> PropertyObject::_createProperty(PropertyID id, std::function<std::shared_ptr<PropertyBase>()> creator, PropertyID insertBefore) { if (id == PropertyID::None) throw std::invalid_argument{_EXCPT("id may not be empty")}; @@ -74,11 +112,25 @@ std::shared_ptr<PropertyBase> PropertyObject::_createProperty(PropertyID id, std try { // Propagate initialization errors to the caller prop->initProperty(); + + if (!_currentGroup.isEmpty()) + prop->setGroup(_currentGroup); + + // Get notified about all property value changes + connect(prop.get(), &PropertyBase::valueChanged, this, &PropertyObject::propertyValueChanged); } catch (...) { throw; } - _properties.push_back(prop); + if (insertBefore != PropertyID::None) + { + auto propBefore = _properties.selectByID(insertBefore); + auto it = _properties.find(propBefore); + _properties.insert(it, prop); + } + else + _properties.push_back(prop); + return prop; } diff --git a/Grinder/common/properties/PropertyObject.h b/Grinder/common/properties/PropertyObject.h index e9d4f0c..bfd620f 100644 --- a/Grinder/common/properties/PropertyObject.h +++ b/Grinder/common/properties/PropertyObject.h @@ -20,26 +20,46 @@ namespace grndr public: PropertyVector& properties() { return _properties; } const PropertyVector& properties() const { return _properties; } + auto properties(QString group) const { return _properties.selectByGroup(group); } + + QStringList getPropertyGroups() const { return _groups; } public: virtual void serialize(SerializationContext& ctx) const; virtual void deserialize(DeserializationContext& ctx); protected: - virtual void createProperties() { } + virtual void createProperties() { setPropertyGroup("General"); } + virtual bool updateProperties(PropertyBase* updatedProp = nullptr) { Q_UNUSED(updatedProp); return false; } template<typename PropType> - std::shared_ptr<PropertyBase> createProperty(PropertyID id, QString name, PropertyBase::Flags flags = PropertyBase::Flag::None); + std::shared_ptr<PropertyBase> createProperty(PropertyID id, QString name, PropertyBase::Flags flags = PropertyBase::Flag::None, PropertyID insertBefore = PropertyID::None); template<typename PropType> - std::shared_ptr<PropertyBase> createProperty(PropertyID id, QString name, typename PropType::value_type defValue, PropertyBase::Flags flags = PropertyBase::Flag::None); + std::shared_ptr<PropertyBase> createProperty(PropertyID id, QString name, typename PropType::value_type defValue, PropertyBase::Flags flags = PropertyBase::Flag::None, PropertyID insertBefore = PropertyID::None); void removeProperty(PropertyID id); void removeProperty(const PropertyBase* prop); + void setPropertyGroup(QString group, QString insertBefore = ""); + + protected: + bool enableDependantProperty(PropertyBase* updatedProp, std::shared_ptr<PropertyBase> superordinateProp, std::shared_ptr<PropertyBase> dependantProp, std::function<bool()> condition) const; + bool updateDependantProperty(PropertyBase* updatedProp, std::shared_ptr<PropertyBase> superordinateProp, std::shared_ptr<PropertyBase> dependantProp, QString name, QString desc, std::function<bool()> condition) const; + + signals: + void propertiesUpdated(); + private: - std::shared_ptr<PropertyBase> _createProperty(PropertyID id, std::function<std::shared_ptr<PropertyBase>()> creator); + std::shared_ptr<PropertyBase> _createProperty(PropertyID id, std::function<std::shared_ptr<PropertyBase>()> creator, PropertyID insertBefore); + + protected slots: + void propertyValueChanged() { if (updateProperties(dynamic_cast<PropertyBase*>(sender()))) emit propertiesUpdated(); } protected: PropertyVector _properties; + + private: + QStringList _groups; + QString _currentGroup{""}; }; } diff --git a/Grinder/common/properties/PropertyObject.impl.h b/Grinder/common/properties/PropertyObject.impl.h index dad8bf1..c931251 100644 --- a/Grinder/common/properties/PropertyObject.impl.h +++ b/Grinder/common/properties/PropertyObject.impl.h @@ -8,13 +8,13 @@ #include "PropertyExceptions.h" template<typename PropType> -std::shared_ptr<PropertyBase> PropertyObject::createProperty(PropertyID id, QString name, PropertyBase::Flags flags) -{ - return _createProperty(id, [id, name, flags](){ return std::make_shared<PropType>(id, name, flags); }); +std::shared_ptr<PropertyBase> PropertyObject::createProperty(PropertyID id, QString name, PropertyBase::Flags flags, PropertyID insertBefore) +{ + return _createProperty(id, [id, name, flags](){ return std::make_shared<PropType>(id, name, flags); }, insertBefore); } template<typename PropType> -std::shared_ptr<PropertyBase> PropertyObject::createProperty(PropertyID id, QString name, typename PropType::value_type defValue, PropertyBase::Flags flags) +std::shared_ptr<PropertyBase> PropertyObject::createProperty(PropertyID id, QString name, typename PropType::value_type defValue, PropertyBase::Flags flags, PropertyID insertBefore) { - return _createProperty(id, [id, name, defValue, flags](){ return std::make_shared<PropType>(id, name, defValue, flags); }); + return _createProperty(id, [id, name, defValue, flags](){ return std::make_shared<PropType>(id, name, defValue, flags); }, insertBefore); } diff --git a/Grinder/common/properties/PropertyVector.cpp b/Grinder/common/properties/PropertyVector.cpp index 6a61ceb..d78d6b6 100644 --- a/Grinder/common/properties/PropertyVector.cpp +++ b/Grinder/common/properties/PropertyVector.cpp @@ -23,3 +23,8 @@ PropertyVector::pointer_type PropertyVector::selectByID(PropertyID id) const { return selectFirst([id](auto prop) { return prop->getID() == id; }); } + +PropertyVector::pointer_vec_type PropertyVector::selectByGroup(QString group) const +{ + return select([group](auto prop) { return prop->getGroup().compare(group, Qt::CaseInsensitive) == 0; }); +} diff --git a/Grinder/common/properties/PropertyVector.h b/Grinder/common/properties/PropertyVector.h index efd0e0c..91f5392 100644 --- a/Grinder/common/properties/PropertyVector.h +++ b/Grinder/common/properties/PropertyVector.h @@ -26,6 +26,7 @@ namespace grndr PropType* property(PropertyID id) const; pointer_type selectByID(PropertyID id) const; + pointer_vec_type selectByGroup(QString group) const; }; } diff --git a/Grinder/engine/processors/AdaptiveThresholdProcessor.cpp b/Grinder/engine/processors/AdaptiveThresholdProcessor.cpp index 9bd2c53..08c5d33 100644 --- a/Grinder/engine/processors/AdaptiveThresholdProcessor.cpp +++ b/Grinder/engine/processors/AdaptiveThresholdProcessor.cpp @@ -22,7 +22,7 @@ void AdaptiveThresholdProcessor::execute(EngineExecutionContext& ctx) if (auto dataBlob = portData(ctx, _block->inPort())) { cv::Mat processedImage; - cv::adaptiveThreshold(dataBlob->getMatrix(), processedImage, *_block->targetValue(), *_block->thresholdMethod(), *_block->thresholdType(), *_block->blockSize(), *_block->constant()); + cv::adaptiveThreshold(dataBlob->getMatrix(), processedImage, *_block->targetValue(), *_block->thresholdMethod(), *_block->thresholdType(), *_block->blockSize(), 0.0); ctx.setContextEntry(_block->outPort(), DataBlob{getPortDataDescriptor(_block->outPort()), std::move(processedImage), dataBlob->getColorSpace()}); } diff --git a/Grinder/image/DraftItem.cpp b/Grinder/image/DraftItem.cpp index f8dab2d..08fc694 100644 --- a/Grinder/image/DraftItem.cpp +++ b/Grinder/image/DraftItem.cpp @@ -19,6 +19,7 @@ DraftItem::DraftItem(Layer* layer, DraftItemType type) : LayerItem(layer), void DraftItem::initDraftItem() { createProperties(); + updateProperties(); } int DraftItem::getZOrder() const @@ -44,12 +45,16 @@ void DraftItem::createProperties() PropertyObject::createProperties(); // Create standard properties + setPropertyGroup("General"); + _primaryColor = createProperty<ColorProperty>(PropertyID::PrimaryColor, "Primary color", QColor{255, 255, 255}, PropertyBase::Flag::Hidden); primaryColor()->setDescription("The primary color to use."); _position = createProperty<PointProperty>(PropertyID::Position, "Position", QPoint{0, 0}); position()->setDescription("The item position."); + setPropertyGroup("Attributes"); + _hasDirection = createProperty<BoolProperty>(PropertyID::HasDirection, "Has direction", false); hasDirection()->setDescription("Specifies whether the box has a direction/orientation."); @@ -61,3 +66,13 @@ void DraftItem::createProperties() imageTagsAllotment()->setDescription("The assigned tags."); imageTagsAllotment()->object().setImageBuild(_layer->imageBuild()); } + +bool DraftItem::updateProperties(PropertyBase* updatedProp) +{ + bool updated = PropertyObject::updateProperties(updatedProp); + + // Update properties to reflect property dependencies + updated |= enableDependantProperty(updatedProp, _hasDirection, _direction, [this]()->bool { return *hasDirection(); }); + + return updated; +} diff --git a/Grinder/image/DraftItem.h b/Grinder/image/DraftItem.h index 012b3b9..89df8ec 100644 --- a/Grinder/image/DraftItem.h +++ b/Grinder/image/DraftItem.h @@ -55,6 +55,7 @@ namespace grndr protected: virtual void createProperties() override; + virtual bool updateProperties(PropertyBase* updatedProp = nullptr) override; protected: DraftItemType _type{DraftItemType::Undefined}; diff --git a/Grinder/image/ImageBuild.cpp b/Grinder/image/ImageBuild.cpp index 8ac4d6c..f3feb68 100644 --- a/Grinder/image/ImageBuild.cpp +++ b/Grinder/image/ImageBuild.cpp @@ -32,6 +32,7 @@ ImageBuild::ImageBuild(const Block* block, const std::vector<const ImageReferenc void ImageBuild::initImageBuild() { createProperties(); + updateProperties(); // Create background image layers for all assigned image references for (const auto imageRef : _imageReferences) diff --git a/Grinder/image/draftitems/BoxDraftItem.cpp b/Grinder/image/draftitems/BoxDraftItem.cpp index 24e7fbd..3e5e6ca 100644 --- a/Grinder/image/draftitems/BoxDraftItem.cpp +++ b/Grinder/image/draftitems/BoxDraftItem.cpp @@ -23,10 +23,14 @@ void BoxDraftItem::createProperties() { DraftItem::createProperties(); - // Create specific properties + // Create specific properties + setPropertyGroup("General"); + _boxSize = createProperty<SizeProperty>(PropertyID::Size, "Size", QSize{50, 50}); boxSize()->setDescription("The size of the box."); + setPropertyGroup("Bounding box", "Attributes"); + _lineWidth = createProperty<UIntProperty>(PropertyID::Width, "Line width", 5); lineWidth()->createConstraint<RangeConstraint>(1, 100); lineWidth()->setDescription("The width of the box lines."); diff --git a/Grinder/image/draftitems/LineDraftItem.cpp b/Grinder/image/draftitems/LineDraftItem.cpp index 919db96..4eacd2d 100644 --- a/Grinder/image/draftitems/LineDraftItem.cpp +++ b/Grinder/image/draftitems/LineDraftItem.cpp @@ -39,9 +39,13 @@ void LineDraftItem::createProperties() DraftItem::createProperties(); // Create specific properties + setPropertyGroup("General"); + _endPosition = createProperty<PointProperty>(PropertyID::EndPosition, "End position", QPoint{0, 0}); endPosition()->setDescription("The end position of the line."); + setPropertyGroup("Line", "Attributes"); + _lineWidth = createProperty<UIntProperty>(PropertyID::Width, "Line width", 3); lineWidth()->createConstraint<RangeConstraint>(1, 100); lineWidth()->setDescription("The width of the line."); diff --git a/Grinder/image/draftitems/PixelsDraftItem.cpp b/Grinder/image/draftitems/PixelsDraftItem.cpp index 423d3c9..e23db2a 100644 --- a/Grinder/image/draftitems/PixelsDraftItem.cpp +++ b/Grinder/image/draftitems/PixelsDraftItem.cpp @@ -46,6 +46,8 @@ void PixelsDraftItem::createProperties() DraftItem::createProperties(); // Create specific properties + setPropertyGroup("Bounding box", "Attributes"); + _showBox = createProperty<BoolProperty>(PropertyID::ShowBox, "Show box", false); showBox()->setDescription("Show the bounding box enclosing the pixels."); @@ -57,3 +59,14 @@ void PixelsDraftItem::createProperties() boxLineWidth()->createConstraint<RangeConstraint>(1, 100); boxLineWidth()->setDescription("The line width of the bounding box."); } + +bool PixelsDraftItem::updateProperties(PropertyBase* updatedProp) +{ + bool updated = DraftItem::updateProperties(updatedProp); + + // Update properties to reflect property dependencies + updated |= enableDependantProperty(updatedProp, _showBox, _boxMargin, [this]()->bool { return *showBox(); }); + updated |= enableDependantProperty(updatedProp, _showBox, _boxLineWidth, [this]()->bool { return *showBox(); }); + + return updated; +} diff --git a/Grinder/image/draftitems/PixelsDraftItem.h b/Grinder/image/draftitems/PixelsDraftItem.h index 2086068..5d9cdeb 100644 --- a/Grinder/image/draftitems/PixelsDraftItem.h +++ b/Grinder/image/draftitems/PixelsDraftItem.h @@ -41,6 +41,7 @@ namespace grndr protected: virtual void createProperties() override; + virtual bool updateProperties(PropertyBase* updatedProp = nullptr) override; private: LayerPixelsData _pixelsData; diff --git a/Grinder/pipeline/PipelineItem.cpp b/Grinder/pipeline/PipelineItem.cpp index 8f39c5e..550e50c 100644 --- a/Grinder/pipeline/PipelineItem.cpp +++ b/Grinder/pipeline/PipelineItem.cpp @@ -19,6 +19,7 @@ PipelineItem::PipelineItem(Pipeline* pipeline, QString name) : void PipelineItem::initPipelineItem() { createProperties(); + updateProperties(); } void PipelineItem::serialize(SerializationContext& ctx) const diff --git a/Grinder/pipeline/blocks/AdaptiveThresholdBlock.cpp b/Grinder/pipeline/blocks/AdaptiveThresholdBlock.cpp index 8cb8d10..0f3dd6b 100644 --- a/Grinder/pipeline/blocks/AdaptiveThresholdBlock.cpp +++ b/Grinder/pipeline/blocks/AdaptiveThresholdBlock.cpp @@ -25,6 +25,8 @@ void AdaptiveThresholdBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _thresholdMethod = createProperty<AdaptiveThresholdMethodProperty>(PropertyID::Method, "Method", static_cast<int>(AdaptiveThresholdMethod::Gaussian)); thresholdMethod()->setDescription("The adaptive method."); @@ -39,9 +41,6 @@ void AdaptiveThresholdBlock::createProperties() blockSize()->createConstraint<RangeConstraint>(3, std::numeric_limits<unsigned int>::max()); blockSize()->createConstraint<EvenOddConstraint>(EvenOddConstraint<unsigned int>::Type::Odd); blockSize()->setDescription("The size of the pixel neighborhood used to calculate the threshold value for a pixel. Must be an odd value (3, 5, 7, and so on)."); - - _constant = createProperty<RealProperty>(PropertyID::Constant, "Constant", 0.0); - constant()->setDescription("Constant subtracted from the (weighted) mean."); } void AdaptiveThresholdBlock::createPorts() diff --git a/Grinder/pipeline/blocks/AdaptiveThresholdBlock.h b/Grinder/pipeline/blocks/AdaptiveThresholdBlock.h index 934bf6e..5b39f32 100644 --- a/Grinder/pipeline/blocks/AdaptiveThresholdBlock.h +++ b/Grinder/pipeline/blocks/AdaptiveThresholdBlock.h @@ -35,8 +35,6 @@ namespace grndr auto targetValue() const { return dynamic_cast<const UIntProperty*>(_targetValue.get()); } auto blockSize() { return dynamic_cast<UIntProperty*>(_blockSize.get()); } auto blockSize() const { return dynamic_cast<const UIntProperty*>(_blockSize.get()); } - auto constant() { return dynamic_cast<RealProperty*>(_constant.get()); } - auto constant() const { return dynamic_cast<const RealProperty*>(_constant.get()); } Port* inPort() { return _inPort.get(); } const Port* inPort() const { return _inPort.get(); } @@ -52,7 +50,6 @@ namespace grndr std::shared_ptr<PropertyBase> _thresholdType; std::shared_ptr<PropertyBase> _targetValue; std::shared_ptr<PropertyBase> _blockSize; - std::shared_ptr<PropertyBase> _constant; std::shared_ptr<Port> _inPort; std::shared_ptr<Port> _outPort; diff --git a/Grinder/pipeline/blocks/AlphaBlendingBlock.cpp b/Grinder/pipeline/blocks/AlphaBlendingBlock.cpp index de8de1f..a894cd6 100644 --- a/Grinder/pipeline/blocks/AlphaBlendingBlock.cpp +++ b/Grinder/pipeline/blocks/AlphaBlendingBlock.cpp @@ -24,6 +24,8 @@ void AlphaBlendingBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _alpha = createProperty<RealProperty>(PropertyID::Alpha, "Alpha", 0.5); alpha()->createConstraint<RangeConstraint>(0.0, 1.0); alpha()->setDescription("The alpha blending factor (Out = In1*Alpha + In2*[1-Alpha])."); diff --git a/Grinder/pipeline/blocks/BinaryThresholdBlock.cpp b/Grinder/pipeline/blocks/BinaryThresholdBlock.cpp index 8b8287c..5f1dfe6 100644 --- a/Grinder/pipeline/blocks/BinaryThresholdBlock.cpp +++ b/Grinder/pipeline/blocks/BinaryThresholdBlock.cpp @@ -25,6 +25,8 @@ void BinaryThresholdBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _thresholdType = createProperty<BinaryThresholdTypeProperty>(PropertyID::Type, "Type", static_cast<int>(BinaryThresholdType::Binary)); thresholdType()->setDescription("The threshold type."); @@ -40,6 +42,17 @@ void BinaryThresholdBlock::createProperties() targetValue()->setDescription("The value to use for binary thresholding if a pixel is above (below if inverted) the threshold value; ranges from 0 to 255."); } +bool BinaryThresholdBlock::updateProperties(PropertyBase* updatedProp) +{ + bool updated = Block::updateProperties(updatedProp); + + // Update properties to reflect property dependencies + updated |= enableDependantProperty(updatedProp, _thresholdType, _targetValue, [this]() { return *thresholdType() == BinaryThresholdType::Binary || *thresholdType() == BinaryThresholdType::BinaryInv; }); + updated |= enableDependantProperty(updatedProp, _thresholdMode, _threshold, [this]() { return *thresholdMode() == BinaryThresholdMode::Manual; }); + + return updated; +} + void BinaryThresholdBlock::createPorts() { DataDescriptors inPortDataDescs = {DataDescriptor::imageDescriptor(true, DataDescriptor::ValueType::Any), DataDescriptor::imageDescriptor(false, DataDescriptor::ValueType::Any)}; diff --git a/Grinder/pipeline/blocks/BinaryThresholdBlock.h b/Grinder/pipeline/blocks/BinaryThresholdBlock.h index 66b2e63..0c83dfe 100644 --- a/Grinder/pipeline/blocks/BinaryThresholdBlock.h +++ b/Grinder/pipeline/blocks/BinaryThresholdBlock.h @@ -43,6 +43,7 @@ namespace grndr protected: virtual void createProperties() override; + virtual bool updateProperties(PropertyBase* updatedProp = nullptr) override; virtual void createPorts() override; private: diff --git a/Grinder/pipeline/blocks/BlurBlock.cpp b/Grinder/pipeline/blocks/BlurBlock.cpp index 1753211..3564085 100644 --- a/Grinder/pipeline/blocks/BlurBlock.cpp +++ b/Grinder/pipeline/blocks/BlurBlock.cpp @@ -24,6 +24,8 @@ void BlurBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _blurType = createProperty<BlurTypeProperty>(PropertyID::Type, "Type", static_cast<int>(BlurType::Gauss)); blurType()->setDescription("The blur type."); @@ -32,16 +34,31 @@ void BlurBlock::createProperties() kernelSize()->createConstraint<EvenOddConstraint>(EvenOddConstraint<unsigned int>::Type::Odd); kernelSize()->setDescription("The size of the kernel used for blurring. Must be an odd value (3, 5, 7, and so on)."); - _sigmaX = createProperty<RealProperty>(PropertyID::SigmaX, "Sigma X/Color", 0.0); - sigmaX()->setDescription("For Gaussian blur, this is the deviation in X direction (if zero, it is computed from the Kernel size). For bilateral filtering, this is the filter sigma in color space."); - - _sigmaY = createProperty<RealProperty>(PropertyID::SigmaY, "Sigma Y/Coords", 0.0); - sigmaY()->setDescription("For Gaussian blur, this is the deviation in Y direction (if zero, it is computed from the Kernel size). For bilateral filtering, this is the filter sigma in coordinate space."); + _sigmaX = createProperty<RealProperty>(PropertyID::SigmaX, "Sigma X", 0.0); + _sigmaY = createProperty<RealProperty>(PropertyID::SigmaY, "Sigma Y", 0.0); _diameter = createProperty<UIntProperty>(PropertyID::Diameter, "Diameter", 5); diameter()->setDescription("The pixel neighborhood diameter used for bilateral filtering."); } +bool BlurBlock::updateProperties(PropertyBase* updatedProp) +{ + bool updated = Block::updateProperties(updatedProp); + + // Update properties to reflect property dependencies + updated |= enableDependantProperty(updatedProp, _blurType, _kernelSize, [this]() { return *blurType() != BlurType::Bilateral; }); + updated |= enableDependantProperty(updatedProp, _blurType, _sigmaX, [this]() { return *blurType() == BlurType::Gauss || *blurType() == BlurType::Bilateral; }); + updated |= enableDependantProperty(updatedProp, _blurType, _sigmaY, [this]() { return *blurType() == BlurType::Gauss || *blurType() == BlurType::Bilateral; }); + updated |= enableDependantProperty(updatedProp, _blurType, _diameter, [this]() { return *blurType() == BlurType::Bilateral; }); + + updated |= updateDependantProperty(updatedProp, _blurType, _sigmaX, "Sigma X", "Standard deviation in X direction; if zero, it is computed from the Kernel size.", [this]() { return *blurType() == BlurType::Gauss; }); + updated |= updateDependantProperty(updatedProp, _blurType, _sigmaY, "Sigma Y", "Standard deviation in Y direction; if zero, it is computed from the Kernel size.", [this]() { return *blurType() == BlurType::Gauss; }); + updated |= updateDependantProperty(updatedProp, _blurType, _sigmaX, "Sigma color", "Filter sigma in color space.", [this]() { return *blurType() == BlurType::Bilateral; }); + updated |= updateDependantProperty(updatedProp, _blurType, _sigmaY, "Sigma coords", "Filter sigma in coordinate space.", [this]() { return *blurType() == BlurType::Bilateral; }); + + return updated; +} + void BlurBlock::createPorts() { DataDescriptors inPortDataDescs = {DataDescriptor::imageDescriptor(true, DataDescriptor::ValueType::Any), DataDescriptor::imageDescriptor(false, DataDescriptor::ValueType::Any)}; diff --git a/Grinder/pipeline/blocks/BlurBlock.h b/Grinder/pipeline/blocks/BlurBlock.h index 11d417d..2338c7f 100644 --- a/Grinder/pipeline/blocks/BlurBlock.h +++ b/Grinder/pipeline/blocks/BlurBlock.h @@ -44,6 +44,7 @@ namespace grndr protected: virtual void createProperties() override; + virtual bool updateProperties(PropertyBase* updatedProp = nullptr) override; virtual void createPorts() override; private: diff --git a/Grinder/pipeline/blocks/ContoursBlock.cpp b/Grinder/pipeline/blocks/ContoursBlock.cpp index 2765f23..f74fc6b 100644 --- a/Grinder/pipeline/blocks/ContoursBlock.cpp +++ b/Grinder/pipeline/blocks/ContoursBlock.cpp @@ -24,12 +24,24 @@ void ContoursBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + + _fill = createProperty<BoolProperty>(PropertyID::Fill, "Fill contours", false); + fill()->setDescription("Fill the contour interiors."); + _width = createProperty<UIntProperty>(PropertyID::Width, "Width", 1); width()->createConstraint<RangeConstraint>(1, std::numeric_limits<unsigned int>::max()); width()->setDescription("The width (thickness) of the lines the contours are drawn with."); +} - _fill = createProperty<BoolProperty>(PropertyID::Fill, "Fill contours", false); - fill()->setDescription("Fill the contour interiors."); +bool ContoursBlock::updateProperties(PropertyBase* updatedProp) +{ + bool updated = Block::updateProperties(updatedProp); + + // Update properties to reflect property dependencies + updated |= enableDependantProperty(updatedProp, _fill, _width, [this]()->bool { return !*fill(); }); + + return updated; } void ContoursBlock::createPorts() diff --git a/Grinder/pipeline/blocks/ContoursBlock.h b/Grinder/pipeline/blocks/ContoursBlock.h index 365209a..8e991a5 100644 --- a/Grinder/pipeline/blocks/ContoursBlock.h +++ b/Grinder/pipeline/blocks/ContoursBlock.h @@ -39,6 +39,7 @@ namespace grndr protected: virtual void createProperties() override; + virtual bool updateProperties(PropertyBase* updatedProp) override; virtual void createPorts() override; private: @@ -47,7 +48,7 @@ namespace grndr std::shared_ptr<Port> _maskPort; std::shared_ptr<Port> _targetPort; - std::shared_ptr<Port> _outPort; + std::shared_ptr<Port> _outPort; }; } diff --git a/Grinder/pipeline/blocks/ConvertToColorBlock.cpp b/Grinder/pipeline/blocks/ConvertToColorBlock.cpp index 087995a..8f0d9d7 100644 --- a/Grinder/pipeline/blocks/ConvertToColorBlock.cpp +++ b/Grinder/pipeline/blocks/ConvertToColorBlock.cpp @@ -25,6 +25,8 @@ void ConvertToColorBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _colorSpace = createProperty<ColorSpaceProperty>(PropertyID::ColorSpace, "Color space", static_cast<int>(ColorSpace::RGB)); colorSpace()->setDescription("The target color space."); } diff --git a/Grinder/pipeline/blocks/DilateBlock.cpp b/Grinder/pipeline/blocks/DilateBlock.cpp index 8422d8a..eb7bda7 100644 --- a/Grinder/pipeline/blocks/DilateBlock.cpp +++ b/Grinder/pipeline/blocks/DilateBlock.cpp @@ -24,6 +24,8 @@ void DilateBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _morphShape = createProperty<MorphShapeProperty>(PropertyID::Type, "Shape", static_cast<int>(MorphShape::Rectangle)); morphShape()->setDescription("The shape of the structuring element used for morphing."); diff --git a/Grinder/pipeline/blocks/DistanceTransformBlock.cpp b/Grinder/pipeline/blocks/DistanceTransformBlock.cpp index ab2e002..71b102e 100644 --- a/Grinder/pipeline/blocks/DistanceTransformBlock.cpp +++ b/Grinder/pipeline/blocks/DistanceTransformBlock.cpp @@ -24,6 +24,8 @@ void DistanceTransformBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _distanceType = createProperty<DistanceTypeProperty>(PropertyID::Type, "Distance type", static_cast<int>(DistanceType::L2)); distanceType()->setDescription("The distance type."); @@ -31,6 +33,16 @@ void DistanceTransformBlock::createProperties() maskSize()->setDescription("The mask size."); } +bool DistanceTransformBlock::updateProperties(PropertyBase* updatedProp) +{ + bool updated = Block::updateProperties(updatedProp); + + // Update properties to reflect property dependencies + updated |= enableDependantProperty(updatedProp, _distanceType, _maskSize, [this]() { return *distanceType() == DistanceType::L2; }); + + return updated; +} + void DistanceTransformBlock::createPorts() { DataDescriptors inPortDataDescs = {DataDescriptor::imageDescriptor(false)}; diff --git a/Grinder/pipeline/blocks/DistanceTransformBlock.h b/Grinder/pipeline/blocks/DistanceTransformBlock.h index e65d446..9efa309 100644 --- a/Grinder/pipeline/blocks/DistanceTransformBlock.h +++ b/Grinder/pipeline/blocks/DistanceTransformBlock.h @@ -39,6 +39,7 @@ namespace grndr protected: virtual void createProperties() override; + virtual bool updateProperties(PropertyBase* updatedProp = nullptr) override; virtual void createPorts() override; private: diff --git a/Grinder/pipeline/blocks/ErodeBlock.cpp b/Grinder/pipeline/blocks/ErodeBlock.cpp index 8579a76..61e6ca3 100644 --- a/Grinder/pipeline/blocks/ErodeBlock.cpp +++ b/Grinder/pipeline/blocks/ErodeBlock.cpp @@ -24,6 +24,8 @@ void ErodeBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _morphShape = createProperty<MorphShapeProperty>(PropertyID::Type, "Shape", static_cast<int>(MorphShape::Rectangle)); morphShape()->setDescription("The shape of the structuring element used for morphing."); diff --git a/Grinder/pipeline/blocks/ImageTagsBlock.cpp b/Grinder/pipeline/blocks/ImageTagsBlock.cpp index eab2f58..8836dcb 100644 --- a/Grinder/pipeline/blocks/ImageTagsBlock.cpp +++ b/Grinder/pipeline/blocks/ImageTagsBlock.cpp @@ -36,6 +36,8 @@ void ImageTagsBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _imageTags = createProperty<ImageTagsProperty>(PropertyID::ImageTags, "Tags"); imageTags()->setDescription("A list of user-definable tags."); } diff --git a/Grinder/pipeline/blocks/InputBlock.cpp b/Grinder/pipeline/blocks/InputBlock.cpp index bd5b00c..45d2a45 100644 --- a/Grinder/pipeline/blocks/InputBlock.cpp +++ b/Grinder/pipeline/blocks/InputBlock.cpp @@ -25,6 +25,8 @@ void InputBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _imageReference = createProperty<ImageReferenceProperty>(PropertyID::ImageReference, "Image", nullptr); imageReference()->setDescription("The image to use as the input; can either be the currently active image or a fixed one."); } diff --git a/Grinder/pipeline/blocks/NormalizeBlock.cpp b/Grinder/pipeline/blocks/NormalizeBlock.cpp index c9f18df..f38df35 100644 --- a/Grinder/pipeline/blocks/NormalizeBlock.cpp +++ b/Grinder/pipeline/blocks/NormalizeBlock.cpp @@ -24,6 +24,8 @@ void NormalizeBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _lowerRange = createProperty<UIntProperty>(PropertyID::Alpha, "Lower range", 0); lowerRange()->setDescription("The lower range value used for range normalization."); diff --git a/Grinder/pipeline/blocks/OutputBlock.cpp b/Grinder/pipeline/blocks/OutputBlock.cpp index fa1b57b..b43bce4 100644 --- a/Grinder/pipeline/blocks/OutputBlock.cpp +++ b/Grinder/pipeline/blocks/OutputBlock.cpp @@ -24,6 +24,8 @@ void OutputBlock::createProperties() { Block::createProperties(); + setPropertyGroup("Miscellaneous"); + _scaleFactor = createProperty<RealProperty>(PropertyID::Factor, "Scale factor", 1.0); scaleFactor()->setDescription("Scale factor for the image data."); diff --git a/Grinder/pipeline/blocks/ReplaceColorBlock.cpp b/Grinder/pipeline/blocks/ReplaceColorBlock.cpp index 92d2792..e997a74 100644 --- a/Grinder/pipeline/blocks/ReplaceColorBlock.cpp +++ b/Grinder/pipeline/blocks/ReplaceColorBlock.cpp @@ -24,6 +24,8 @@ void ReplaceColorBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _fromColor = createProperty<ColorProperty>(PropertyID::From, "From color", QColor{255, 255, 255}); fromColor()->setDescription("The color to replace."); diff --git a/Grinder/pipeline/blocks/SharpenBlock.cpp b/Grinder/pipeline/blocks/SharpenBlock.cpp index 22b0bf5..744c3ae 100644 --- a/Grinder/pipeline/blocks/SharpenBlock.cpp +++ b/Grinder/pipeline/blocks/SharpenBlock.cpp @@ -24,6 +24,8 @@ void SharpenBlock::createProperties() { Block::createProperties(); + setPropertyGroup("General"); + _sharpenType = createProperty<SharpenTypeProperty>(PropertyID::Type, "Type", static_cast<int>(SharpenType::Laplacian)); sharpenType()->setDescription("The sharpening type."); diff --git a/Grinder/pipeline/properties/DistanceTransformMaskProperty.cpp b/Grinder/pipeline/properties/DistanceTransformMaskProperty.cpp index a80b40e..14af4bb 100644 --- a/Grinder/pipeline/properties/DistanceTransformMaskProperty.cpp +++ b/Grinder/pipeline/properties/DistanceTransformMaskProperty.cpp @@ -12,5 +12,4 @@ void DistanceTransformMaskProperty::initProperty() _enumMap[DistanceTransformMask::Mask3] = "3"; _enumMap[DistanceTransformMask::Mask5] = "5"; - _enumMap[DistanceTransformMask::Precise] = "Precise"; } diff --git a/Grinder/ui/image/ImageEditorPropertyWidget.cpp b/Grinder/ui/image/ImageEditorPropertyWidget.cpp index f768046..e0b5720 100644 --- a/Grinder/ui/image/ImageEditorPropertyWidget.cpp +++ b/Grinder/ui/image/ImageEditorPropertyWidget.cpp @@ -14,7 +14,7 @@ ImageEditorPropertyWidget::ImageEditorPropertyWidget(QWidget* parent) : QWidget(parent), _layout{new QGridLayout{this}} { - _layout->setContentsMargins(6, 6, 6, 6); + _layout->setContentsMargins(4, 4, 10, 4); _layout->setHorizontalSpacing(6); _layout->setVerticalSpacing(4); setLayout(_layout); @@ -35,26 +35,51 @@ void ImageEditorPropertyWidget::assignUiComponents(ImageEditor* imageEditor) void ImageEditorPropertyWidget::clear() { + if (_currentPropertyObject) + disconnect(_currentPropertyObject, nullptr, this, nullptr); + + _currentPropertyObject = nullptr; + // Remove all properties from the layout UIUtils::removeChildrenFromLayout(_layout); } -void ImageEditorPropertyWidget::addProperties(const PropertyVector* properties) +void ImageEditorPropertyWidget::addProperties(const PropertyObject* propertyObject) { clear(); + _currentPropertyObject = propertyObject; + bool propertiesEmpty = true; - if (properties) + if (propertyObject) { - for (auto& property : properties->sorted()) + for (auto group : propertyObject->getPropertyGroups()) { - if (!property->hasFlag(PropertyBase::Flag::ReadOnly) && !property->hasFlag(PropertyBase::Flag::Hidden)) + std::vector<std::shared_ptr<PropertyBase>> properties; + + for (auto& property : propertyObject->properties(group)) + { + // Ignore read-only properties + if (!property->hasFlag(PropertyBase::Flag::ReadOnly) && !property->hasFlag(PropertyBase::Flag::Hidden)) + properties.push_back(property); + } + + if (!properties.empty()) { - addProperty(property); propertiesEmpty = false; + + // Add an item for the property group + addPropertyGroup(group); + + // Add items for all properties belonging to the current group + for (auto& property : properties) + addProperty(property); } } + + // Reflect property updates + connect(propertyObject, &PropertyObject::propertiesUpdated, this, &ImageEditorPropertyWidget::updateProperties, Qt::QueuedConnection); // Delay the update to prevent race conditions } if (propertiesEmpty) @@ -68,10 +93,22 @@ void ImageEditorPropertyWidget::addProperties(const PropertyVector* properties) _layout->addItem(new QSpacerItem{0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding}, _layout->rowCount(), 1); } +void ImageEditorPropertyWidget::addPropertyGroup(QString group) +{ + auto label = new QLabel{"<em>" + group + "</em>"}; + label->setFont(GrinderApplication::boldFont(label)); + label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + label->setStyleSheet(QString{"background: qlineargradient(spread:pad, x1:0 y1:0, x2:1 y2:0, stop:0 %1, stop:1 %2); padding: 2px;"} + .arg(QPalette{}.color(QPalette::Dark).lighter(120).name()).arg(QPalette{}.color(QPalette::Button).lighter(104).name())); + + int row = _layout->rowCount(); + _layout->addWidget(label, row, 0, 1, 2); +} + void ImageEditorPropertyWidget::addProperty(const std::shared_ptr<PropertyBase>& property) { auto editor = property->createEditor(nullptr); - auto label = new QLabel{property->getName() + ":"}; + auto label = new QLabel{" " + property->getName() + ":"}; label->setFont(GrinderApplication::boldFont(label)); if (!editor) @@ -82,11 +119,14 @@ void ImageEditorPropertyWidget::addProperty(const std::shared_ptr<PropertyBase>& int row = _layout->rowCount(); _layout->addWidget(label, row, 0); _layout->addWidget(editor, row, 1); + + if (property->hasFlag(PropertyBase::Flag::Disabled)) + editor->setEnabled(false); } void ImageEditorPropertyWidget::populateProperties() -{ - const PropertyVector* properties = &_imageEditor->editorTools().activeTool()->properties(); // Use the active tool's properties by default +{ + const PropertyObject* propertyObject = _imageEditor->editorTools().activeTool(); // Use the active tool's properties by default // See if there are any items selected in the editor if (auto scene = _imageEditor->controller().activeScene()) @@ -99,12 +139,18 @@ void ImageEditorPropertyWidget::populateProperties() if (selectedItems.size() == 1) { if (auto draftItem = selectedItems.front()->draftItem().lock()) // Make sure that the underlying draft item still exists - properties = &draftItem->properties(); + propertyObject = draftItem.get(); } } } - addProperties(properties); + addProperties(propertyObject); +} + +void ImageEditorPropertyWidget::updateProperties() +{ + // Just re-create all property editors to reflect the updated properties + populateProperties(); } void ImageEditorPropertyWidget::imageEditorToolChanged(const ImageEditorTool* tool) diff --git a/Grinder/ui/image/ImageEditorPropertyWidget.h b/Grinder/ui/image/ImageEditorPropertyWidget.h index 24c64f9..2ddd69c 100644 --- a/Grinder/ui/image/ImageEditorPropertyWidget.h +++ b/Grinder/ui/image/ImageEditorPropertyWidget.h @@ -32,11 +32,13 @@ namespace grndr void clear(); private: - void addProperties(const PropertyVector* properties); + void addProperties(const PropertyObject* propertyObject); + void addPropertyGroup(QString group); void addProperty(const std::shared_ptr<PropertyBase>& property); private slots: void populateProperties(); + void updateProperties(); void imageEditorToolChanged(const ImageEditorTool* tool); @@ -45,6 +47,8 @@ namespace grndr private: QGridLayout* _layout{nullptr}; + + const PropertyObject* _currentPropertyObject{nullptr}; }; } diff --git a/Grinder/ui/image/ImageEditorTool.cpp b/Grinder/ui/image/ImageEditorTool.cpp index 74f8efe..934dc29 100644 --- a/Grinder/ui/image/ImageEditorTool.cpp +++ b/Grinder/ui/image/ImageEditorTool.cpp @@ -19,6 +19,7 @@ ImageEditorTool::ImageEditorTool(ImageEditor* imageEditor, QString name, QString void ImageEditorTool::initImageEditorTool() { createProperties(); + updateProperties(); } void ImageEditorTool::toolActivated(ImageEditorTool* prevTool) diff --git a/Grinder/ui/image/tools/BoxDraftItemTool.cpp b/Grinder/ui/image/tools/BoxDraftItemTool.cpp index a251400..07b15f1 100644 --- a/Grinder/ui/image/tools/BoxDraftItemTool.cpp +++ b/Grinder/ui/image/tools/BoxDraftItemTool.cpp @@ -19,9 +19,13 @@ void BoxDraftItemTool::createProperties() DraftItemTool::createProperties(); // Create specific properties + setPropertyGroup("General"); + _boxSize = createProperty<SizeProperty>(PropertyID::Size, "Size", QSize{50, 50}); boxSize()->setDescription("The size of the box."); + setPropertyGroup("Bounding box", "Attributes"); + _lineWidth = createProperty<UIntProperty>(PropertyID::Width, "Line width", 5); lineWidth()->createConstraint<RangeConstraint>(1, 100); lineWidth()->setDescription("The width of the box lines."); diff --git a/Grinder/ui/image/tools/DraftItemTool.cpp b/Grinder/ui/image/tools/DraftItemTool.cpp index 9b93e38..70733b3 100644 --- a/Grinder/ui/image/tools/DraftItemTool.cpp +++ b/Grinder/ui/image/tools/DraftItemTool.cpp @@ -66,12 +66,16 @@ void DraftItemTool::createProperties() ImageEditorTool::createProperties(); // Create standard properties + setPropertyGroup("General"); + _primaryColor = createProperty<ColorProperty>(PropertyID::PrimaryColor, "Primary color", QColor{255, 255, 255}, PropertyBase::Flag::Hidden); primaryColor()->setDescription("The primary color to use."); _position = createProperty<PointProperty>(PropertyID::Position, "Position", QPoint{0, 0}, PropertyBase::Flag::Hidden); position()->setDescription("The item position."); + setPropertyGroup("Attributes"); + _hasDirection = createProperty<BoolProperty>(PropertyID::HasDirection, "Has direction", false); hasDirection()->setDescription("Specifies whether the box has a direction/orientation."); @@ -83,6 +87,16 @@ void DraftItemTool::createProperties() imageTagsAllotment()->setDescription("The assigned tags."); } +bool DraftItemTool::updateProperties(PropertyBase* updatedProp) +{ + bool updated = PropertyObject::updateProperties(updatedProp); + + // Update properties to reflect property dependencies + updated |= enableDependantProperty(updatedProp, _hasDirection, _direction, [this]()->bool { return *hasDirection(); }); + + return updated; +} + ImageEditorTool::InputEventResult DraftItemTool::mousePressed(const QGraphicsSceneMouseEvent* event) { // Remember the initial position for later use diff --git a/Grinder/ui/image/tools/DraftItemTool.h b/Grinder/ui/image/tools/DraftItemTool.h index 97f1def..47a36c0 100644 --- a/Grinder/ui/image/tools/DraftItemTool.h +++ b/Grinder/ui/image/tools/DraftItemTool.h @@ -42,6 +42,7 @@ namespace grndr protected: virtual void createProperties() override; + virtual bool updateProperties(PropertyBase* updatedProp = nullptr) override; virtual InputEventResult mousePressed(const QGraphicsSceneMouseEvent* event) override; virtual InputEventResult mouseMoved(const QGraphicsSceneMouseEvent* event) override; diff --git a/Grinder/ui/image/tools/EraserTool.cpp b/Grinder/ui/image/tools/EraserTool.cpp index a077b53..8084c73 100644 --- a/Grinder/ui/image/tools/EraserTool.cpp +++ b/Grinder/ui/image/tools/EraserTool.cpp @@ -21,6 +21,8 @@ void EraserTool::createProperties() PaintbrushTool::createProperties(); // The eraser should have a default width equal to the cursor size + setPropertyGroup("General"); + penWidth()->setValue(_cursor.pixmap().width()); penWidth()->setName("Eraser width"); penWidth()->setDescription("The width of the eraser in (screen) pixels."); diff --git a/Grinder/ui/image/tools/LineDraftItemTool.cpp b/Grinder/ui/image/tools/LineDraftItemTool.cpp index bbb1321..a018bcf 100644 --- a/Grinder/ui/image/tools/LineDraftItemTool.cpp +++ b/Grinder/ui/image/tools/LineDraftItemTool.cpp @@ -19,6 +19,8 @@ void LineDraftItemTool::createProperties() DraftItemTool::createProperties(); // Create specific properties + setPropertyGroup("Line", "Attributes"); + _lineWidth = createProperty<UIntProperty>(PropertyID::Width, "Line width", 3); lineWidth()->createConstraint<RangeConstraint>(1, 100); lineWidth()->setDescription("The width of the line."); diff --git a/Grinder/ui/image/tools/PaintbrushTool.cpp b/Grinder/ui/image/tools/PaintbrushTool.cpp index 993ffb2..a50d898 100644 --- a/Grinder/ui/image/tools/PaintbrushTool.cpp +++ b/Grinder/ui/image/tools/PaintbrushTool.cpp @@ -26,6 +26,8 @@ void PaintbrushTool::createProperties() ImageEditorTool::createProperties(); // Create specific properties + setPropertyGroup("General"); + _penWidth = createProperty<UIntProperty>(PropertyID::Width, "Brush width", 1); penWidth()->createConstraint<RangeConstraint>(1, 100); penWidth()->setDescription("The width of the pixels to paint."); diff --git a/Grinder/ui/mainwnd/GrinderWindow.ui b/Grinder/ui/mainwnd/GrinderWindow.ui index b1a0e45..42bb2a0 100644 --- a/Grinder/ui/mainwnd/GrinderWindow.ui +++ b/Grinder/ui/mainwnd/GrinderWindow.ui @@ -358,9 +358,6 @@ <property name="uniformRowHeights"> <bool>true</bool> </property> - <property name="sortingEnabled"> - <bool>true</bool> - </property> <property name="columnCount"> <number>2</number> </property> diff --git a/Grinder/ui/properties/BlockPropertyTreeItem.h b/Grinder/ui/properties/BlockPropertyTreeItem.h index 567bb6f..6107ca9 100644 --- a/Grinder/ui/properties/BlockPropertyTreeItem.h +++ b/Grinder/ui/properties/BlockPropertyTreeItem.h @@ -21,7 +21,7 @@ namespace grndr virtual void updateItem() override; public: - const std::weak_ptr<Block>& pipelineItem() const { return _block; } + const std::weak_ptr<Block>& block() const { return _block; } private: std::weak_ptr<Block> _block; diff --git a/Grinder/ui/properties/GroupPropertyTreeItem.cpp b/Grinder/ui/properties/GroupPropertyTreeItem.cpp new file mode 100644 index 0000000..3a56b25 --- /dev/null +++ b/Grinder/ui/properties/GroupPropertyTreeItem.cpp @@ -0,0 +1,29 @@ +/****************************************************************************** + * File: GroupPropertyTreeItem.cpp + * Date: 29.6.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "GroupPropertyTreeItem.h" + +GroupPropertyTreeItem::GroupPropertyTreeItem(QString group) : + _group{group} +{ + if (group.isEmpty()) + throw std::invalid_argument{_EXCPT("group may not be empty")}; + + setFlags(Qt::ItemIsEnabled); + + QFont fontGroup = font(0); + fontGroup.setBold(true); + fontGroup.setItalic(true); + setFont(0, fontGroup); + + updateItem(); +} + +void GroupPropertyTreeItem::updateItem() +{ + setText(0, _group); + setToolTip(0, text(0)); +} diff --git a/Grinder/ui/properties/GroupPropertyTreeItem.h b/Grinder/ui/properties/GroupPropertyTreeItem.h new file mode 100644 index 0000000..b37cfe9 --- /dev/null +++ b/Grinder/ui/properties/GroupPropertyTreeItem.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * File: GroupPropertyTreeItem.h + * Date: 29.6.2018 + *****************************************************************************/ + +#ifndef GROUPPROPERTYTREEITEM_H +#define GROUPPROPERTYTREEITEM_H + +#include "PropertyTreeItem.h" + +namespace grndr +{ + class Block; + + class GroupPropertyTreeItem : public PropertyTreeItem + { + public: + GroupPropertyTreeItem(QString group); + + public: + virtual void updateItem() override; + + private: + QString _group; + }; +} + +#endif diff --git a/Grinder/ui/properties/PropertyTreeWidget.cpp b/Grinder/ui/properties/PropertyTreeWidget.cpp index c47e7b8..881fe20 100644 --- a/Grinder/ui/properties/PropertyTreeWidget.cpp +++ b/Grinder/ui/properties/PropertyTreeWidget.cpp @@ -7,6 +7,7 @@ #include "PropertyTreeWidget.h" #include "BlockPropertyTreeItem.h" #include "ValuePropertyTreeItem.h" +#include "GroupPropertyTreeItem.h" #include "PropertyTreeItemDelegate.h" #include "core/GrinderApplication.h" #include "ui/graph/GraphBlockNode.h" @@ -44,6 +45,23 @@ void PropertyTreeWidget::setupUi(QLabel* propertyDescLabel) updatePropertyDescription(); } +void PropertyTreeWidget::clear() +{ + for (int i = 0; i < topLevelItemCount(); ++i) + { + if (auto blockItem = dynamic_cast<BlockPropertyTreeItem*>(topLevelItem(i))) + { + if (auto block = blockItem->block().lock()) // Make sure that the underlying block still exists + disconnect(block.get(), nullptr, this, nullptr); + } + } + + QTreeWidget::clear(); + + updateActions(); + updatePropertyDescription(); +} + void PropertyTreeWidget::showEvent(QShowEvent* event) { QTreeWidget::showEvent(event); @@ -80,7 +98,7 @@ void PropertyTreeWidget::keyPressEvent(QKeyEvent* event) QTreeWidget::keyPressEvent(event); } -void PropertyTreeWidget::addBlocks(const std::vector<std::shared_ptr<grndr::Block> >& blocks) +void PropertyTreeWidget::addBlocks(const std::vector<std::shared_ptr<Block>>& blocks) { // Create a top-level item for every block for (auto block : blocks) @@ -88,10 +106,13 @@ void PropertyTreeWidget::addBlocks(const std::vector<std::shared_ptr<grndr::Bloc BlockPropertyTreeItem* treeItem = new BlockPropertyTreeItem{block}; addTopLevelItem(treeItem); + // Add all properties of the block addProperties(block.get(), treeItem); treeItem->setExpanded(true); treeItem->setFirstColumnSpanned(true); + + connect(block.get(), &PropertyObject::propertiesUpdated, this, &PropertyTreeWidget::updateProperties); } updateColumnWidths(); @@ -99,24 +120,39 @@ void PropertyTreeWidget::addBlocks(const std::vector<std::shared_ptr<grndr::Bloc void PropertyTreeWidget::addProperties(const PipelineItem* pipelineItem, QTreeWidgetItem* parentItem) { - std::vector<std::shared_ptr<PropertyBase>> properties; + bool propertiesEmpty = true; - // Ignore read-only properties - for (auto& property : pipelineItem->properties()) + for (auto group : pipelineItem->getPropertyGroups()) { - if (!property->hasFlag(PropertyBase::Flag::ReadOnly) && !property->hasFlag(PropertyBase::Flag::Hidden)) - properties.push_back(property); - } + std::vector<std::shared_ptr<PropertyBase>> properties; - if (!properties.empty()) - { - for (auto& property : properties) + for (auto& property : pipelineItem->properties(group)) { - ValuePropertyTreeItem* treeItem = new ValuePropertyTreeItem{property}; - parentItem->addChild(treeItem); + // Ignore read-only properties + if (!property->hasFlag(PropertyBase::Flag::ReadOnly) && !property->hasFlag(PropertyBase::Flag::Hidden)) + properties.push_back(property); + } + + if (!properties.empty()) + { + propertiesEmpty = false; + + // Add an item for the property group + GroupPropertyTreeItem* groupItem = new GroupPropertyTreeItem{group}; + parentItem->addChild(groupItem); + + // Add items for all properties belonging to the current group + for (auto& property : properties) + { + ValuePropertyTreeItem* treeItem = new ValuePropertyTreeItem{property}; + parentItem->addChild(treeItem); + } + + groupItem->setFirstColumnSpanned(true); } } - else + + if (propertiesEmpty) { // Add an item showing that the pipeline item has no properties QTreeWidgetItem* emptyItem = new QTreeWidgetItem({"No properties to display"}); @@ -194,6 +230,25 @@ void PropertyTreeWidget::sceneSelectionChanged() updateActions(); } +void PropertyTreeWidget::updateProperties() +{ + // Update all property items + for (int i = 0; i < topLevelItemCount(); ++i) + { + auto rootItem = topLevelItem(i); + + for (int j = 0; j < rootItem->childCount(); ++j) + { + if (auto childItem = dynamic_cast<ValuePropertyTreeItem*>(rootItem->child(j))) + childItem->updateItem(); + } + } + + updateActions(); + updatePropertyDescription(); + updateColumnWidths(); +} + void PropertyTreeWidget::updateActions() { auto item = currentValuePropertyItem(); diff --git a/Grinder/ui/properties/PropertyTreeWidget.h b/Grinder/ui/properties/PropertyTreeWidget.h index 6658e76..73bc9db 100644 --- a/Grinder/ui/properties/PropertyTreeWidget.h +++ b/Grinder/ui/properties/PropertyTreeWidget.h @@ -31,7 +31,7 @@ namespace grndr public: void setupUi(QLabel* propertyDescLabel); - void clear() { QTreeWidget::clear(); updateActions(); updatePropertyDescription(); } + void clear(); protected: virtual void showEvent(QShowEvent* event) override; @@ -54,6 +54,8 @@ namespace grndr void sceneSelectionChanged(); + void updateProperties(); + void expandAllItems() { expandAllItems(true); } void collapseAllItems() { expandAllItems(false); } diff --git a/Grinder/ui/properties/ValuePropertyTreeItem.cpp b/Grinder/ui/properties/ValuePropertyTreeItem.cpp index 10e6a30..ae1a884 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(); @@ -35,10 +34,21 @@ void ValuePropertyTreeItem::updateItem() { if (auto property = _property.lock()) // Make sure that the underlying property still exists { - setText(0, property->getName()); - setToolTip(0, text(0)); + setText(0, " " + property->getName()); + setToolTip(0, property->getName()); setText(1, property->toString()); setToolTip(1, text(1)); + + if (property->hasFlag(PropertyBase::Flag::Disabled)) + { + setFlags(flags()&~Qt::ItemIsEnabled); + setTextColor(1, QPalette{}.color(QPalette::Disabled, QPalette::WindowText)); + } + else + { + setFlags(flags()|Qt::ItemIsEnabled); + setTextColor(1, QColor{20, 105, 140}); + } } } diff --git a/Grinder/ui/properties/editors/AnglePropertyEditor.cpp b/Grinder/ui/properties/editors/AnglePropertyEditor.cpp index b0751e2..0349c29 100644 --- a/Grinder/ui/properties/editors/AnglePropertyEditor.cpp +++ b/Grinder/ui/properties/editors/AnglePropertyEditor.cpp @@ -19,6 +19,7 @@ AnglePropertyEditor::AnglePropertyEditor(AngleProperty* property, QWidget* paren _angleDial->setNotchTarget(45); _angleDial->setInvertedAppearance(true); _angleDial->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + _angleDial->setValue(90); _angleEdit->setRange(0, 360); _angleEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); -- GitLab