diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index c7d4cc67f0..d1f741ab69 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -24,6 +24,7 @@ #include "addpropertydialog.h" #include "changeimagelayerproperty.h" #include "changelayer.h" +#include "changemapobject.h" #include "changemapproperty.h" #include "changeobjectgroupproperties.h" #include "changeproperties.h" @@ -31,6 +32,7 @@ #include "compression.h" #include "mapdocument.h" #include "objectgroup.h" +#include "objecttemplate.h" #include "preferences.h" #include "propertybrowser.h" #include "tilesetchanges.h" @@ -307,21 +309,46 @@ class TileSizeProperty : public AbstractProperty MapDocument *mMapDocument; }; -class MapProperties : public QObject +class ObjectProperties : public QObject { Q_OBJECT public: - MapProperties(MapDocument *mapDocument, - ValueTypeEditorFactory *editorFactory, - QObject *parent = nullptr) + ObjectProperties(Document *document, Object *object, QObject *parent = nullptr) : QObject(parent) - , mMapDocument(mapDocument) - , mSizeProperty(new MapSizeProperty(mapDocument, editorFactory, this)) - , mTileSizeProperty(new TileSizeProperty(mapDocument, editorFactory, this)) + , mDocument(document) + , mObject(object) + { + mClassProperty = new ClassProperty(document, object, this); + } + + virtual void populateEditor(VariantEditor *) + { + // nothing added here due to property grouping + } + +protected: + void push(QUndoCommand *command) { - mClassProperty = new ClassProperty(mMapDocument, mMapDocument->map()); + mDocument->undoStack()->push(command); + } + + Document *mDocument; + Property *mClassProperty; + Object *mObject; +}; + + +class MapProperties : public ObjectProperties +{ + Q_OBJECT +public: + MapProperties(MapDocument *document, + ValueTypeEditorFactory *editorFactory, + QObject *parent = nullptr) + : ObjectProperties(document, document->map(), parent) + { mOrientationProperty = editorFactory->createProperty( tr("Orientation"), [this]() { @@ -329,16 +356,20 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto orientation = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, orientation)); + push(new ChangeMapProperty(mapDocument(), orientation)); }); + mSizeProperty = new MapSizeProperty(mapDocument(), editorFactory, this); + + mTileSizeProperty = new TileSizeProperty(mapDocument(), editorFactory, this); + mInfiniteProperty = editorFactory->createProperty( tr("Infinite"), [this]() { return map()->infinite(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, + push(new ChangeMapProperty(mapDocument(), Map::InfiniteProperty, value.toInt())); }); @@ -349,7 +380,7 @@ class MapProperties : public QObject return map()->hexSideLength(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, + push(new ChangeMapProperty(mapDocument(), Map::HexSideLengthProperty, value.toInt())); }); @@ -361,7 +392,7 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto staggerAxis = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, staggerAxis)); + push(new ChangeMapProperty(mapDocument(), staggerAxis)); }); mStaggerIndexProperty = editorFactory->createProperty( @@ -371,7 +402,7 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto staggerIndex = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, staggerIndex)); + push(new ChangeMapProperty(mapDocument(), staggerIndex)); }); mParallaxOriginProperty = editorFactory->createProperty( @@ -380,7 +411,7 @@ class MapProperties : public QObject return map()->parallaxOrigin(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, value.value())); + push(new ChangeMapProperty(mapDocument(), value.value())); }); mLayerDataFormatProperty = editorFactory->createProperty( @@ -390,7 +421,7 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto layerDataFormat = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, layerDataFormat)); + push(new ChangeMapProperty(mapDocument(), layerDataFormat)); }); mChunkSizeProperty = editorFactory->createProperty( @@ -399,7 +430,7 @@ class MapProperties : public QObject return map()->chunkSize(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, value.toSize())); + push(new ChangeMapProperty(mapDocument(), value.toSize())); }); mRenderOrderProperty = editorFactory->createProperty( @@ -409,7 +440,7 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto renderOrder = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, renderOrder)); + push(new ChangeMapProperty(mapDocument(), renderOrder)); }); mCompressionLevelProperty = editorFactory->createProperty( @@ -418,7 +449,7 @@ class MapProperties : public QObject return map()->compressionLevel(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, value.toInt())); + push(new ChangeMapProperty(mapDocument(), value.toInt())); }); mBackgroundColorProperty = editorFactory->createProperty( @@ -427,15 +458,15 @@ class MapProperties : public QObject return map()->backgroundColor(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, value.value())); + push(new ChangeMapProperty(mapDocument(), value.value())); }); updateEnabledState(); - connect(mMapDocument, &Document::changed, + connect(document, &Document::changed, this, &MapProperties::onChanged); } - void populateEditor(VariantEditor *editor) + void populateEditor(VariantEditor *editor) override { editor->addHeader(tr("Map")); editor->addProperty(mClassProperty); @@ -533,18 +564,16 @@ class MapProperties : public QObject } } - void push(QUndoCommand *command) + MapDocument *mapDocument() const { - mMapDocument->undoStack()->push(command); + return static_cast(mDocument); } Map *map() const { - return mMapDocument->map(); + return mapDocument()->map(); } - MapDocument *mMapDocument; - Property *mClassProperty; Property *mOrientationProperty; Property *mSizeProperty; Property *mTileSizeProperty; @@ -560,21 +589,19 @@ class MapProperties : public QObject Property *mBackgroundColorProperty; }; -class LayerProperties : public QObject +class LayerProperties : public ObjectProperties { Q_OBJECT public: - LayerProperties(MapDocument *mapDocument, Layer *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : QObject(parent) - , mMapDocument(mapDocument) - , mLayer(layer) + LayerProperties(MapDocument *document, Layer *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : ObjectProperties(document, object, parent) { // todo: would be nicer to avoid the SpinBox and use a custom widget // might also be nice to embed this in the header instead of using a property mIdProperty = editorFactory->createProperty( tr("ID"), - [this]() { return mLayer->id(); }, + [this]() { return layer()->id(); }, [](const QVariant &) {}); mIdProperty->setEnabled(false); @@ -582,63 +609,61 @@ class LayerProperties : public QObject mNameProperty = editorFactory->createProperty( tr("Name"), - [this]() { return mLayer->name(); }, + [this]() { return layer()->name(); }, [this](const QVariant &value) { - push(new SetLayerName(mMapDocument, { mLayer }, value.toString())); + push(new SetLayerName(mapDocument(), { layer() }, value.toString())); }); - mClassProperty = new ClassProperty(mMapDocument, mLayer); - mVisibleProperty = editorFactory->createProperty( tr("Visible"), - [this]() { return mLayer->isVisible(); }, + [this]() { return layer()->isVisible(); }, [this](const QVariant &value) { - push(new SetLayerVisible(mMapDocument, { mLayer }, value.toBool())); + push(new SetLayerVisible(mapDocument(), { layer() }, value.toBool())); }); mLockedProperty = editorFactory->createProperty( tr("Locked"), - [this]() { return mLayer->isLocked(); }, + [this]() { return layer()->isLocked(); }, [this](const QVariant &value) { - push(new SetLayerLocked(mMapDocument, { mLayer }, value.toBool())); + push(new SetLayerLocked(mapDocument(), { layer() }, value.toBool())); }); // todo: value should be between 0 and 1, and would be nice to use a slider (replacing the one in Layers view) // todo: singleStep should be 0.1 mOpacityProperty = editorFactory->createProperty( tr("Opacity"), - [this]() { return mLayer->opacity(); }, + [this]() { return layer()->opacity(); }, [this](const QVariant &value) { - push(new SetLayerOpacity(mMapDocument, { mLayer }, value.toReal())); + push(new SetLayerOpacity(mapDocument(), { layer() }, value.toReal())); }); mTintColorProperty = editorFactory->createProperty( tr("Tint Color"), - [this]() { return mLayer->tintColor(); }, + [this]() { return layer()->tintColor(); }, [this](const QVariant &value) { - push(new SetLayerTintColor(mMapDocument, { mLayer }, value.value())); + push(new SetLayerTintColor(mapDocument(), { layer() }, value.value())); }); mOffsetProperty = editorFactory->createProperty( tr("Offset"), - [this]() { return mLayer->offset(); }, + [this]() { return layer()->offset(); }, [this](const QVariant &value) { - push(new SetLayerOffset(mMapDocument, { mLayer }, value.value())); + push(new SetLayerOffset(mapDocument(), { layer() }, value.value())); }); // todo: singleStep should be 0.1 mParallaxFactorProperty = editorFactory->createProperty( tr("Parallax Factor"), - [this]() { return mLayer->parallaxFactor(); }, + [this]() { return layer()->parallaxFactor(); }, [this](const QVariant &value) { - push(new SetLayerParallaxFactor(mMapDocument, { mLayer }, value.toPointF())); + push(new SetLayerParallaxFactor(mapDocument(), { layer() }, value.toPointF())); }); - connect(mMapDocument, &Document::changed, + connect(document, &Document::changed, this, &LayerProperties::onChanged); } - virtual void populateEditor(VariantEditor *editor) + void populateEditor(VariantEditor *editor) override { editor->addHeader(tr("Layer")); editor->addProperty(mIdProperty); @@ -660,7 +685,7 @@ class LayerProperties : public QObject return; const auto &layerChange = static_cast(event); - if (layerChange.layer != mLayer) + if (layerChange.layer != layer()) return; if (layerChange.properties & LayerChangeEvent::VisibleProperty) @@ -677,17 +702,18 @@ class LayerProperties : public QObject emit mParallaxFactorProperty->valueChanged(); } - void push(QUndoCommand *command) + MapDocument *mapDocument() const { - mMapDocument->undoStack()->push(command); + return static_cast(mDocument); } - MapDocument *mMapDocument; - Layer *mLayer; + Layer *layer() const + { + return static_cast(mObject); + } Property *mIdProperty; Property *mNameProperty; - Property *mClassProperty; Property *mVisibleProperty; Property *mLockedProperty; Property *mOpacityProperty; @@ -701,22 +727,22 @@ class ImageLayerProperties : public LayerProperties Q_OBJECT public: - ImageLayerProperties(MapDocument *mapDocument, ImageLayer *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : LayerProperties(mapDocument, layer, editorFactory, parent) + ImageLayerProperties(MapDocument *document, ImageLayer *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : LayerProperties(document, object, editorFactory, parent) { // todo: set a file filter for selecting images (or map files?) mImageProperty = editorFactory->createProperty( tr("Image Source"), [this]() { return imageLayer()->imageSource(); }, [this](const QVariant &value) { - push(new ChangeImageLayerImageSource(mMapDocument, { imageLayer() }, value.toUrl())); + push(new ChangeImageLayerImageSource(mapDocument(), { imageLayer() }, value.toUrl())); }); mTransparentColorProperty = editorFactory->createProperty( tr("Transparent Color"), [this]() { return imageLayer()->transparentColor(); }, [this](const QVariant &value) { - push(new ChangeImageLayerTransparentColor(mMapDocument, { imageLayer() }, value.value())); + push(new ChangeImageLayerTransparentColor(mapDocument(), { imageLayer() }, value.value())); }); // todo: consider merging Repeat X and Y into a single property @@ -724,14 +750,14 @@ class ImageLayerProperties : public LayerProperties tr("Repeat X"), [this]() { return imageLayer()->repeatX(); }, [this](const QVariant &value) { - push(new ChangeImageLayerRepeatX(mMapDocument, { imageLayer() }, value.toBool())); + push(new ChangeImageLayerRepeatX(mapDocument(), { imageLayer() }, value.toBool())); }); mRepeatYProperty = editorFactory->createProperty( tr("Repeat Y"), [this]() { return imageLayer()->repeatY(); }, [this](const QVariant &value) { - push(new ChangeImageLayerRepeatY(mMapDocument, { imageLayer() }, value.toBool())); + push(new ChangeImageLayerRepeatY(mapDocument(), { imageLayer() }, value.toBool())); }); } @@ -755,7 +781,7 @@ class ImageLayerProperties : public LayerProperties return; const auto &layerChange = static_cast(event); - if (layerChange.layer != mLayer) + if (layerChange.layer != layer()) return; if (layerChange.properties & ImageLayerChangeEvent::ImageSourceProperty) @@ -770,7 +796,7 @@ class ImageLayerProperties : public LayerProperties ImageLayer *imageLayer() const { - return static_cast(mLayer); + return static_cast(mObject); } Property *mImageProperty; @@ -784,14 +810,14 @@ class ObjectGroupProperties : public LayerProperties Q_OBJECT public: - ObjectGroupProperties(MapDocument *mapDocument, ObjectGroup *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : LayerProperties(mapDocument, layer, editorFactory, parent) + ObjectGroupProperties(MapDocument *document, ObjectGroup *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : LayerProperties(document, object, editorFactory, parent) { mColorProperty = editorFactory->createProperty( tr("Color"), [this]() { return objectGroup()->color(); }, [this](const QVariant &value) { - push(new ChangeObjectGroupColor(mMapDocument, { objectGroup() }, value.value())); + push(new ChangeObjectGroupColor(mapDocument(), { objectGroup() }, value.value())); }); mDrawOrderProperty = editorFactory->createProperty( @@ -799,7 +825,7 @@ class ObjectGroupProperties : public LayerProperties [this]() { return QVariant::fromValue(objectGroup()->drawOrder()); }, [this](const QVariant &value) { ObjectGroup::DrawOrder drawOrder = static_cast(value.toInt()); - push(new ChangeObjectGroupDrawOrder(mMapDocument, { objectGroup() }, drawOrder)); + push(new ChangeObjectGroupDrawOrder(mapDocument(), { objectGroup() }, drawOrder)); }); } @@ -831,35 +857,32 @@ class ObjectGroupProperties : public LayerProperties ObjectGroup *objectGroup() const { - return static_cast(mLayer); + return static_cast(mObject); } Property *mColorProperty; Property *mDrawOrderProperty; }; -class TilesetProperties : public QObject +class TilesetProperties : public ObjectProperties { Q_OBJECT public: - TilesetProperties(TilesetDocument *tilesetDocument, + TilesetProperties(TilesetDocument *document, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : QObject(parent) - , mTilesetDocument(tilesetDocument) + : ObjectProperties(document, document->tileset().data(), parent) { mNameProperty = editorFactory->createProperty( tr("Name"), [this]() { - return mTilesetDocument->tileset()->name(); + return tilesetDocument()->tileset()->name(); }, [this](const QVariant &value) { - push(new RenameTileset(mTilesetDocument, value.toString())); + push(new RenameTileset(tilesetDocument(), value.toString())); }); - mClassProperty = new ClassProperty(tilesetDocument, tilesetDocument->tileset().data()); - mObjectAlignmentProperty = editorFactory->createProperty( tr("Object Alignment"), [this]() { @@ -867,7 +890,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto objectAlignment = static_cast(value.toInt()); - push(new ChangeTilesetObjectAlignment(mTilesetDocument, objectAlignment)); + push(new ChangeTilesetObjectAlignment(tilesetDocument(), objectAlignment)); }); mTileOffsetProperty = editorFactory->createProperty( @@ -876,7 +899,7 @@ class TilesetProperties : public QObject return tileset()->tileOffset(); }, [this](const QVariant &value) { - push(new ChangeTilesetTileOffset(mTilesetDocument, value.value())); + push(new ChangeTilesetTileOffset(tilesetDocument(), value.value())); }); mTileRenderSizeProperty = editorFactory->createProperty( @@ -886,7 +909,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto tileRenderSize = static_cast(value.toInt()); - push(new ChangeTilesetTileRenderSize(mTilesetDocument, tileRenderSize)); + push(new ChangeTilesetTileRenderSize(tilesetDocument(), tileRenderSize)); }); mFillModeProperty = editorFactory->createProperty( @@ -896,7 +919,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto fillMode = static_cast(value.toInt()); - push(new ChangeTilesetFillMode(mTilesetDocument, fillMode)); + push(new ChangeTilesetFillMode(tilesetDocument(), fillMode)); }); mBackgroundColorProperty = editorFactory->createProperty( @@ -905,7 +928,7 @@ class TilesetProperties : public QObject return tileset()->backgroundColor(); }, [this](const QVariant &value) { - push(new ChangeTilesetBackgroundColor(mTilesetDocument, value.value())); + push(new ChangeTilesetBackgroundColor(tilesetDocument(), value.value())); }); mOrientationProperty = editorFactory->createProperty( @@ -915,7 +938,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto orientation = static_cast(value.toInt()); - push(new ChangeTilesetOrientation(mTilesetDocument, orientation)); + push(new ChangeTilesetOrientation(tilesetDocument(), orientation)); }); mGridSizeProperty = editorFactory->createProperty( @@ -924,7 +947,7 @@ class TilesetProperties : public QObject return tileset()->gridSize(); }, [this](const QVariant &value) { - push(new ChangeTilesetGridSize(mTilesetDocument, value.toSize())); + push(new ChangeTilesetGridSize(tilesetDocument(), value.toSize())); }); // todo: needs 1 as minimum value @@ -934,7 +957,7 @@ class TilesetProperties : public QObject return tileset()->columnCount(); }, [this](const QVariant &value) { - push(new ChangeTilesetColumnCount(mTilesetDocument, value.toInt())); + push(new ChangeTilesetColumnCount(tilesetDocument(), value.toInt())); }); // todo: this needs a custom widget @@ -945,7 +968,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto flags = static_cast(value.toInt()); - push(new ChangeTilesetTransformationFlags(mTilesetDocument, flags)); + push(new ChangeTilesetTransformationFlags(tilesetDocument(), flags)); }); // todo: this needs a custom widget @@ -955,20 +978,20 @@ class TilesetProperties : public QObject return tileset()->imageSource().toString(); }, [](const QVariant &) { - // push(new ChangeTilesetImage(mTilesetDocument, value.toString())); + // push(new ChangeTilesetImage(tilesetDocument(), value.toString())); }); updateEnabledState(); - connect(mTilesetDocument, &Document::changed, + connect(tilesetDocument(), &Document::changed, this, &TilesetProperties::onChanged); - connect(mTilesetDocument, &TilesetDocument::tilesetNameChanged, + connect(tilesetDocument(), &TilesetDocument::tilesetNameChanged, mNameProperty, &Property::valueChanged); - connect(mTilesetDocument, &TilesetDocument::tilesetTileOffsetChanged, + connect(tilesetDocument(), &TilesetDocument::tilesetTileOffsetChanged, mTileOffsetProperty, &Property::valueChanged); - connect(mTilesetDocument, &TilesetDocument::tilesetObjectAlignmentChanged, + connect(tilesetDocument(), &TilesetDocument::tilesetObjectAlignmentChanged, mObjectAlignmentProperty, &Property::valueChanged); - connect(mTilesetDocument, &TilesetDocument::tilesetChanged, + connect(tilesetDocument(), &TilesetDocument::tilesetChanged, this, &TilesetProperties::onTilesetChanged); } @@ -1025,19 +1048,17 @@ class TilesetProperties : public QObject mColumnCountProperty->setEnabled(collection); } - void push(QUndoCommand *command) + TilesetDocument *tilesetDocument() const { - mTilesetDocument->undoStack()->push(command); + return static_cast(mDocument); } Tileset *tileset() const { - return mTilesetDocument->tileset().data(); + return tilesetDocument()->tileset().data(); } - TilesetDocument *mTilesetDocument; Property *mNameProperty; - Property *mClassProperty; Property *mObjectAlignmentProperty; Property *mTileOffsetProperty; Property *mTileRenderSizeProperty; @@ -1050,10 +1071,279 @@ class TilesetProperties : public QObject Property *mImageProperty; }; +class MapObjectProperties : public ObjectProperties +{ + Q_OBJECT + +public: + MapObjectProperties(MapDocument *document, MapObject *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : ObjectProperties(document, object, parent) + , mDegreesEditorFactory(std::make_unique()) + { + mDegreesEditorFactory->setSuffix(QStringLiteral("°")); + + mIdProperty = editorFactory->createProperty( + tr("ID"), + [this]() { return mapObject()->id(); }, + [](const QVariant &) {}); + mIdProperty->setEnabled(false); + + mTemplateProperty = editorFactory->createProperty( + tr("Template"), + [this]() { + if (auto objectTemplate = mapObject()->objectTemplate()) + return QUrl::fromLocalFile(objectTemplate->fileName()); + return QUrl(); + }, + [](const QVariant &) {}); + mTemplateProperty->setEnabled(false); + + mNameProperty = editorFactory->createProperty( + tr("Name"), + [this]() { + return mapObject()->name(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::NameProperty, value); + }); + + mVisibleProperty = editorFactory->createProperty( + tr("Visible"), + [this]() { + return mapObject()->isVisible(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::VisibleProperty, value); + }); + + mPositionProperty = editorFactory->createProperty( + tr("Position"), + [this]() { + return mapObject()->position(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::PositionProperty, value); + }); + + mSizeProperty = editorFactory->createProperty( + tr("Size"), + [this]() { + return mapObject()->size(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::SizeProperty, value); + }); + + mRotationProperty = new GetSetProperty( + tr("Rotation"), + [this]() { + return mapObject()->rotation(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::RotationProperty, value); + }, + mDegreesEditorFactory.get(), this); + + // todo: make this a custom widget with "Horizontal" and "Vertical" checkboxes + mFlippingProperty = editorFactory->createProperty( + tr("Flipping"), + [this]() { + return mapObject()->cell().flags(); + }, + [this](const QVariant &value) { + const int flippingFlags = value.toInt(); + + MapObjectCell mapObjectCell; + mapObjectCell.object = mapObject(); + mapObjectCell.cell = mapObject()->cell(); + mapObjectCell.cell.setFlippedHorizontally(flippingFlags & 1); + mapObjectCell.cell.setFlippedVertically(flippingFlags & 2); + + auto command = new ChangeMapObjectCells(mDocument, { mapObjectCell }); + + command->setText(QCoreApplication::translate("Undo Commands", + "Flip %n Object(s)", + nullptr, + mapDocument()->selectedObjects().size())); + push(command); + }); + + mTextProperty = editorFactory->createProperty( + tr("Text"), + [this]() { + return mapObject()->textData().text; + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextProperty, value); + }); + + mTextAlignmentProperty = editorFactory->createProperty( + tr("Alignment"), + [this]() { + return QVariant::fromValue(mapObject()->textData().alignment); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextAlignmentProperty, value); + }); + + mTextFontProperty = editorFactory->createProperty( + tr("Font"), + [this]() { + return mapObject()->textData().font; + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextFontProperty, value); + }); + + mTextWordWrapProperty = editorFactory->createProperty( + tr("Word Wrap"), + [this]() { + return mapObject()->textData().wordWrap; + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextWordWrapProperty, value); + }); + + mTextColorProperty = editorFactory->createProperty( + tr("Text Color"), + [this]() { + return mapObject()->textData().color; + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextColorProperty, value); + }); + + connect(document, &Document::changed, + this, &MapObjectProperties::onChanged); + + updateEnabledState(); + } + + void populateEditor(VariantEditor *editor) override + { + editor->addHeader(tr("Object")); + editor->addProperty(mIdProperty); + editor->addProperty(mTemplateProperty); + editor->addProperty(mNameProperty); + editor->addProperty(mClassProperty); + editor->addSeparator(); + + if (mapDocument()->allowHidingObjects()) + editor->addProperty(mVisibleProperty); + + editor->addProperty(mPositionProperty); + + if (mapObject()->hasDimensions()) + editor->addProperty(mSizeProperty); + + if (mapObject()->canRotate()) + editor->addProperty(mRotationProperty); + + if (mapObject()->isTileObject()) { + editor->addSeparator(); + editor->addProperty(mFlippingProperty); + } + + if (mapObject()->shape() == MapObject::Text) { + editor->addSeparator(); + editor->addProperty(mTextProperty); + editor->addProperty(mTextAlignmentProperty); + editor->addProperty(mTextFontProperty); + editor->addProperty(mTextWordWrapProperty); + editor->addProperty(mTextColorProperty); + } + } + +private: + void onChanged(const ChangeEvent &event) + { + if (event.type != ChangeEvent::MapObjectsChanged) + return; + + const auto &change = static_cast(event); + if (!change.mapObjects.contains(mapObject())) + return; + + if (change.properties & MapObject::NameProperty) + emit mNameProperty->valueChanged(); + if (change.properties & MapObject::VisibleProperty) + emit mVisibleProperty->valueChanged(); + if (change.properties & MapObject::PositionProperty) + emit mPositionProperty->valueChanged(); + if (change.properties & MapObject::SizeProperty) + emit mSizeProperty->valueChanged(); + if (change.properties & MapObject::RotationProperty) + emit mRotationProperty->valueChanged(); + if (change.properties & MapObject::CellProperty) + emit mFlippingProperty->valueChanged(); + if (change.properties & MapObject::TextProperty) + emit mTextProperty->valueChanged(); + if (change.properties & MapObject::TextFontProperty) + emit mTextFontProperty->valueChanged(); + if (change.properties & MapObject::TextAlignmentProperty) + emit mTextAlignmentProperty->valueChanged(); + if (change.properties & MapObject::TextWordWrapProperty) + emit mTextWordWrapProperty->valueChanged(); + if (change.properties & MapObject::TextColorProperty) + emit mTextColorProperty->valueChanged(); + } + + void updateEnabledState() + { + mVisibleProperty->setEnabled(mapDocument()->allowHidingObjects()); + mSizeProperty->setEnabled(mapObject()->hasDimensions()); + mRotationProperty->setEnabled(mapObject()->canRotate()); + mFlippingProperty->setEnabled(mapObject()->isTileObject()); + + const bool isText = mapObject()->shape() == MapObject::Text; + mTextProperty->setEnabled(isText); + mTextAlignmentProperty->setEnabled(isText); + mTextFontProperty->setEnabled(isText); + mTextWordWrapProperty->setEnabled(isText); + mTextColorProperty->setEnabled(isText); + } + + MapDocument *mapDocument() const + { + return static_cast(mDocument); + } + + MapObject *mapObject() const + { + return static_cast(mObject); + } + + void changeMapObject(MapObject::Property property, const QVariant &value) + { + push(new ChangeMapObject(mapDocument(), mapObject(), property, value)); + } + + Property *mIdProperty; + Property *mTemplateProperty; + Property *mNameProperty; + Property *mVisibleProperty; + Property *mPositionProperty; + Property *mSizeProperty; + Property *mRotationProperty; + + // for tile objects + Property *mFlippingProperty; + + // for text objects + Property *mTextProperty; + Property *mTextAlignmentProperty; + Property *mTextFontProperty; + Property *mTextWordWrapProperty; + Property *mTextColorProperty; + + std::unique_ptr mDegreesEditorFactory; +}; + void PropertiesWidget::currentObjectChanged(Object *object) { mPropertyBrowser->clear(); + delete mPropertiesObject; mPropertiesObject = nullptr; @@ -1061,56 +1351,54 @@ void PropertiesWidget::currentObjectChanged(Object *object) switch (object->typeId()) { case Object::LayerType: { auto mapDocument = static_cast(mDocument); - auto layer = static_cast(object); - LayerProperties *layerProperties = nullptr; - switch (layer->layerType()) { + switch (static_cast(object)->layerType()) { case Layer::ImageLayerType: - layerProperties = new ImageLayerProperties(mapDocument, - static_cast(layer), - mDefaultEditorFactory.get(), this); + mPropertiesObject = new ImageLayerProperties(mapDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); break; case Layer::ObjectGroupType: - layerProperties = new ObjectGroupProperties(mapDocument, - static_cast(layer), - mDefaultEditorFactory.get(), this); + mPropertiesObject = new ObjectGroupProperties(mapDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); break; case Layer::TileLayerType: case Layer::GroupLayerType: - layerProperties = new LayerProperties(mapDocument, - layer, - mDefaultEditorFactory.get(), this); + mPropertiesObject = new LayerProperties(mapDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); break; } - - layerProperties->populateEditor(mPropertyBrowser); - mPropertiesObject = layerProperties; + break; } case Object::MapObjectType: + mPropertiesObject = new MapObjectProperties(static_cast(mDocument), + static_cast(object), mDefaultEditorFactory.get(), this); break; - case Object::MapType: { - auto mapDocument = static_cast(mDocument); - auto properties = new MapProperties(mapDocument, mDefaultEditorFactory.get(), this); - properties->populateEditor(mPropertyBrowser); - mPropertiesObject = properties; + case Object::MapType: + mPropertiesObject = new MapProperties(static_cast(mDocument), + mDefaultEditorFactory.get(), this); + break; + case Object::TilesetType: + mPropertiesObject = new TilesetProperties(static_cast(mDocument), + mDefaultEditorFactory.get(), this); break; - } - case Object::TilesetType: { - auto tilesetDocument = static_cast(mDocument); - auto properties = new TilesetProperties(tilesetDocument, - mDefaultEditorFactory.get(), this); - properties->populateEditor(mPropertyBrowser); - mPropertiesObject = properties; - } case Object::TileType: + // todo case Object::WangSetType: + // todo case Object::WangColorType: + // todo case Object::ProjectType: case Object::WorldType: break; } } + if (mPropertiesObject) + mPropertiesObject->populateEditor(mPropertyBrowser); + bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType; bool isTileset = object && object->isPartOfTileset(); bool enabled = object && (!isTileset || editingTileset); diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index 37db77ee6d..d6ad390af1 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -30,6 +30,7 @@ class Object; class Document; class EditorFactory; +class ObjectProperties; class ValueTypeEditorFactory; class VariantEditor; @@ -81,7 +82,7 @@ public slots: void retranslateUi(); Document *mDocument = nullptr; - QObject *mPropertiesObject = nullptr; + ObjectProperties *mPropertiesObject = nullptr; VariantEditor *mPropertyBrowser; std::unique_ptr mDefaultEditorFactory; QAction *mActionAddProperty; diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index 36e899a3b0..3bb86cb855 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -41,6 +41,9 @@ SpinBox::SpinBox(QWidget *parent) setRange(std::numeric_limits::lowest(), std::numeric_limits::max()); + // Don't respond to keyboard input immediately. + setKeyboardTracking(false); + // Allow the widget to shrink horizontally. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } @@ -64,6 +67,9 @@ DoubleSpinBox::DoubleSpinBox(QWidget *parent) // Increase possible precision. setDecimals(9); + // Don't respond to keyboard input immediately. + setKeyboardTracking(false); + // Allow the widget to shrink horizontally. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } @@ -88,36 +94,114 @@ QString DoubleSpinBox::textFromValue(double val) const } -SizeEdit::SizeEdit(QWidget *parent) +ResponsivePairswiseWidget::ResponsivePairswiseWidget(QWidget *parent) : QWidget(parent) - , m_widthLabel(new QLabel(QStringLiteral("W"), this)) - , m_heightLabel(new QLabel(QStringLiteral("H"), this)) - , m_widthSpinBox(new SpinBox(this)) - , m_heightSpinBox(new SpinBox(this)) { - m_widthLabel->setAlignment(Qt::AlignCenter); - m_heightLabel->setAlignment(Qt::AlignCenter); - auto layout = new QGridLayout(this); layout->setContentsMargins(QMargins()); layout->setColumnStretch(1, 1); - layout->setColumnStretch(3, 1); layout->setSpacing(Utils::dpiScaled(3)); +} +void ResponsivePairswiseWidget::setWidgetPairs(const QVector &widgetPairs) +{ const int horizontalMargin = Utils::dpiScaled(3); - m_widthLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); - m_heightLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 0, 2); - layout->addWidget(m_heightSpinBox, 0, 3); + for (auto &pair : widgetPairs) { + pair.label->setAlignment(Qt::AlignCenter); + pair.label->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); + } + + m_widgetPairs = widgetPairs; + + addWidgetsToLayout(); +} + +void ResponsivePairswiseWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + const auto orientation = event->size().width() < minimumHorizontalWidth() + ? Qt::Vertical : Qt::Horizontal; + + if (m_orientation != orientation) { + m_orientation = orientation; + + auto layout = this->layout(); + + // Remove all widgets from layout, without deleting them + for (auto &pair : m_widgetPairs) { + layout->removeWidget(pair.label); + layout->removeWidget(pair.widget); + } + + addWidgetsToLayout(); + + // This avoids flickering when the layout changes + layout->activate(); + } +} + +void ResponsivePairswiseWidget::addWidgetsToLayout() +{ + auto layout = qobject_cast(this->layout()); + + const int maxColumns = m_orientation == Qt::Horizontal ? 4 : 2; + int row = 0; + int column = 0; + + for (auto &pair : m_widgetPairs) { + layout->addWidget(pair.label, row, column); + layout->addWidget(pair.widget, row, column + 1); + column += 2; + + if (column == maxColumns) { + column = 0; + ++row; + } + } + + layout->setColumnStretch(3, m_orientation == Qt::Horizontal ? 1 : 0); +} + +int ResponsivePairswiseWidget::minimumHorizontalWidth() const +{ + const int spacing = layout()->spacing(); + int sum = 0; + int minimum = 0; + int index = 0; + + for (auto &pair : m_widgetPairs) { + sum += (pair.label->minimumSizeHint().width() + + pair.widget->minimumSizeHint().width() + + spacing * 2); + + if (++index % 2 == 0) { + minimum = std::max(sum - spacing, minimum); + sum = 0; + } + } + + return minimum; +} + + +SizeEdit::SizeEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_widthSpinBox(new SpinBox(this)) + , m_heightSpinBox(new SpinBox(this)) +{ + setWidgetPairs({ + { m_widthLabel, m_widthSpinBox }, + { m_heightLabel, m_heightSpinBox }, + }); connect(m_widthSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); connect(m_heightSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); } - void SizeEdit::setValue(const QSize &size) { m_widthSpinBox->setValue(size.width()); @@ -126,53 +210,136 @@ void SizeEdit::setValue(const QSize &size) QSize SizeEdit::value() const { - return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); + return QSize(m_widthSpinBox->value(), + m_heightSpinBox->value()); } -void SizeEdit::resizeEvent(QResizeEvent *event) + +SizeFEdit::SizeFEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_widthSpinBox(new DoubleSpinBox(this)) + , m_heightSpinBox(new DoubleSpinBox(this)) { - QWidget::resizeEvent(event); + setWidgetPairs({ + { m_widthLabel, m_widthSpinBox }, + { m_heightLabel, m_heightSpinBox }, + }); - const auto orientation = event->size().width() < minimumHorizontalWidth() - ? Qt::Vertical : Qt::Horizontal; + connect(m_widthSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &SizeFEdit::valueChanged); + connect(m_heightSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &SizeFEdit::valueChanged); +} - if (m_orientation != orientation) { - m_orientation = orientation; +void SizeFEdit::setValue(const QSizeF &size) +{ + m_widthSpinBox->setValue(size.width()); + m_heightSpinBox->setValue(size.height()); +} - auto layout = qobject_cast(this->layout()); +QSizeF SizeFEdit::value() const +{ + return QSizeF(m_widthSpinBox->value(), + m_heightSpinBox->value()); +} - // Remove all widgets from layout, without deleting them - layout->removeWidget(m_widthLabel); - layout->removeWidget(m_widthSpinBox); - layout->removeWidget(m_heightLabel); - layout->removeWidget(m_heightSpinBox); - - if (orientation == Qt::Horizontal) { - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 0, 2); - layout->addWidget(m_heightSpinBox, 0, 3); - layout->setColumnStretch(3, 1); - } else { - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 1, 0); - layout->addWidget(m_heightSpinBox, 1, 1); - layout->setColumnStretch(3, 0); - } - // this avoids flickering when the layout changes - layout->activate(); - } +PointEdit::PointEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_xLabel(new QLabel(QStringLiteral("X"), this)) + , m_yLabel(new QLabel(QStringLiteral("Y"), this)) + , m_xSpinBox(new SpinBox(this)) + , m_ySpinBox(new SpinBox(this)) +{ + setWidgetPairs({ + { m_xLabel, m_xSpinBox }, + { m_yLabel, m_ySpinBox }, + }); + + connect(m_xSpinBox, qOverload(&QSpinBox::valueChanged), this, &PointEdit::valueChanged); + connect(m_ySpinBox, qOverload(&QSpinBox::valueChanged), this, &PointEdit::valueChanged); +} + +void PointEdit::setValue(const QPoint &point) +{ + m_xSpinBox->setValue(point.x()); + m_ySpinBox->setValue(point.y()); +} + +QPoint PointEdit::value() const +{ + return QPoint(m_xSpinBox->value(), + m_ySpinBox->value()); +} + + +PointFEdit::PointFEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_xLabel(new QLabel(QStringLiteral("X"), this)) + , m_yLabel(new QLabel(QStringLiteral("Y"), this)) + , m_xSpinBox(new DoubleSpinBox(this)) + , m_ySpinBox(new DoubleSpinBox(this)) +{ + setWidgetPairs({ + { m_xLabel, m_xSpinBox }, + { m_yLabel, m_ySpinBox }, + }); + + connect(m_xSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &PointFEdit::valueChanged); + connect(m_ySpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &PointFEdit::valueChanged); +} + +void PointFEdit::setValue(const QPointF &point) +{ + m_xSpinBox->setValue(point.x()); + m_ySpinBox->setValue(point.y()); +} + +QPointF PointFEdit::value() const +{ + return QPointF(m_xSpinBox->value(), + m_ySpinBox->value()); +} + + +RectFEdit::RectFEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_xLabel(new QLabel(QStringLiteral("X"), this)) + , m_yLabel(new QLabel(QStringLiteral("Y"), this)) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_xSpinBox(new DoubleSpinBox(this)) + , m_ySpinBox(new DoubleSpinBox(this)) + , m_widthSpinBox(new DoubleSpinBox(this)) + , m_heightSpinBox(new DoubleSpinBox(this)) +{ + setWidgetPairs({ + { m_xLabel, m_xSpinBox }, + { m_yLabel, m_ySpinBox }, + { m_widthLabel, m_widthSpinBox }, + { m_heightLabel, m_heightSpinBox }, + }); + + connect(m_xSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &RectFEdit::valueChanged); + connect(m_ySpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &RectFEdit::valueChanged); + connect(m_widthSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &RectFEdit::valueChanged); + connect(m_heightSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &RectFEdit::valueChanged); +} + +void RectFEdit::setValue(const QRectF &rect) +{ + m_xSpinBox->setValue(rect.x()); + m_ySpinBox->setValue(rect.y()); + m_widthSpinBox->setValue(rect.width()); + m_heightSpinBox->setValue(rect.height()); } -int SizeEdit::minimumHorizontalWidth() const +QRectF RectFEdit::value() const { - return m_widthLabel->minimumSizeHint().width() + - m_widthSpinBox->minimumSizeHint().width() + - m_heightLabel->minimumSizeHint().width() + - m_heightSpinBox->minimumSizeHint().width() + - layout()->spacing() * 3; + return QRectF(m_xSpinBox->value(), + m_ySpinBox->value(), + m_widthSpinBox->value(), + m_heightSpinBox->value()); } diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index f33eaf7fbd..342b0b505a 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -41,10 +41,39 @@ class DoubleSpinBox : public QDoubleSpinBox QString textFromValue(double val) const override; }; +/** + * A widget that shows label/widget pairs, wrapping them either two per row + * or each on their own row, depending on the available space. + */ +class ResponsivePairswiseWidget : public QWidget +{ + Q_OBJECT + +public: + struct WidgetPair { + QLabel *label; + QWidget *widget; + }; + + ResponsivePairswiseWidget(QWidget *parent = nullptr); + + void setWidgetPairs(const QVector &widgetPairs); + +protected: + void resizeEvent(QResizeEvent *event) override; + +private: + void addWidgetsToLayout(); + int minimumHorizontalWidth() const; + + Qt::Orientation m_orientation = Qt::Horizontal; + QVector m_widgetPairs; +}; + /** * A widget for editing a QSize value. */ -class SizeEdit : public QWidget +class SizeEdit : public ResponsivePairswiseWidget { Q_OBJECT Q_PROPERTY(QSize value READ value WRITE setValue NOTIFY valueChanged FINAL) @@ -59,17 +88,112 @@ class SizeEdit : public QWidget void valueChanged(); private: - void resizeEvent(QResizeEvent *event) override; - - int minimumHorizontalWidth() const; - - Qt::Orientation m_orientation = Qt::Horizontal; QLabel *m_widthLabel; QLabel *m_heightLabel; SpinBox *m_widthSpinBox; SpinBox *m_heightSpinBox; }; +/** + * A widget for editing a QSizeF value. + */ +class SizeFEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QSizeF value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + SizeFEdit(QWidget *parent = nullptr); + + void setValue(const QSizeF &size); + QSizeF value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_widthLabel; + QLabel *m_heightLabel; + DoubleSpinBox *m_widthSpinBox; + DoubleSpinBox *m_heightSpinBox; +}; + +/** + * A widget for editing a QPoint value. + */ +class PointEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QPoint value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + PointEdit(QWidget *parent = nullptr); + + void setValue(const QPoint &size); + QPoint value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_xLabel; + QLabel *m_yLabel; + SpinBox *m_xSpinBox; + SpinBox *m_ySpinBox; +}; + +/** + * A widget for editing a QPointF value. + */ +class PointFEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QPointF value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + PointFEdit(QWidget *parent = nullptr); + + void setValue(const QPointF &size); + QPointF value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_xLabel; + QLabel *m_yLabel; + DoubleSpinBox *m_xSpinBox; + DoubleSpinBox *m_ySpinBox; +}; + +/** + * A widget for editing a QRectF value. + */ +class RectFEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QRectF value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + RectFEdit(QWidget *parent = nullptr); + + void setValue(const QRectF &size); + QRectF value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_xLabel; + QLabel *m_yLabel; + QLabel *m_widthLabel; + QLabel *m_heightLabel; + DoubleSpinBox *m_xSpinBox; + DoubleSpinBox *m_ySpinBox; + DoubleSpinBox *m_widthSpinBox; + DoubleSpinBox *m_heightSpinBox; +}; + /** * A label that elides its text if there is not enough space. * diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 854b52d5c7..66ef71b144 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -111,45 +112,41 @@ class UrlEditorFactory : public EditorFactory QString m_filter; }; -class IntEditorFactory : public EditorFactory +QWidget *IntEditorFactory::createEditor(Property *property, QWidget *parent) { -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new SpinBox(parent); - auto syncEditor = [=] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toInt()); - }; - syncEditor(); + auto editor = new SpinBox(parent); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toInt()); + }; + syncEditor(); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, qOverload(&SpinBox::valueChanged), - property, &Property::setValue); + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, qOverload(&SpinBox::valueChanged), + property, &Property::setValue); + + return editor; +} - return editor; - } -}; -class FloatEditorFactory : public EditorFactory +QWidget *FloatEditorFactory::createEditor(Property *property, QWidget *parent) { -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new DoubleSpinBox(parent); - auto syncEditor = [=] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toDouble()); - }; - syncEditor(); + auto editor = new DoubleSpinBox(parent); + editor->setSuffix(m_suffix); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, qOverload(&DoubleSpinBox::valueChanged), - property, &Property::setValue); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toDouble()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, qOverload(&DoubleSpinBox::valueChanged), + property, &Property::setValue); + + return editor; +} - return editor; - } -}; class BoolEditorFactory : public EditorFactory { @@ -175,99 +172,50 @@ class BoolEditorFactory : public EditorFactory } }; -// todo: implement responsive layout (see SizeEdit) class PointEditorFactory : public EditorFactory { public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto editor = new QWidget(parent); - auto horizontalLayout = new QHBoxLayout(editor); - horizontalLayout->setContentsMargins(QMargins()); - - auto xLabel = new QLabel(QStringLiteral("X"), editor); - horizontalLayout->addWidget(xLabel, 0, Qt::AlignRight); - - auto xSpinBox = new SpinBox(editor); - xLabel->setBuddy(xSpinBox); - horizontalLayout->addWidget(xSpinBox, 1); - - auto yLabel = new QLabel(QStringLiteral("Y"), editor); - horizontalLayout->addWidget(yLabel, 0, Qt::AlignRight); - - auto ySpinBox = new SpinBox(editor); - yLabel->setBuddy(ySpinBox); - horizontalLayout->addWidget(ySpinBox, 1); - - auto syncEditor = [=] { - const QSignalBlocker xBlocker(xSpinBox); - const QSignalBlocker yBlocker(ySpinBox); - const auto point = property->value().toPoint(); - xSpinBox->setValue(point.x()); - ySpinBox->setValue(point.y()); - }; - auto syncProperty = [=] { - property->setValue(QPoint(xSpinBox->value(), ySpinBox->value())); + auto editor = new PointEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toPoint()); }; syncEditor(); QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(xSpinBox, qOverload(&SpinBox::valueChanged), - property, syncProperty); - QObject::connect(ySpinBox, qOverload(&SpinBox::valueChanged), - property, syncProperty); + QObject::connect(editor, &PointEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); return editor; } }; -// todo: implement responsive layout (see SizeEdit) class PointFEditorFactory : public EditorFactory { public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto editor = new QWidget(parent); - auto horizontalLayout = new QHBoxLayout(editor); - horizontalLayout->setContentsMargins(QMargins()); - - auto xLabel = new QLabel(QStringLiteral("X"), editor); - horizontalLayout->addWidget(xLabel, 0, Qt::AlignRight); - - auto xSpinBox = new DoubleSpinBox(editor); - xLabel->setBuddy(xSpinBox); - horizontalLayout->addWidget(xSpinBox, 1); - - auto yLabel = new QLabel(QStringLiteral("Y"), editor); - horizontalLayout->addWidget(yLabel, 0, Qt::AlignRight); - - auto ySpinBox = new DoubleSpinBox(editor); - yLabel->setBuddy(ySpinBox); - horizontalLayout->addWidget(ySpinBox, 1); - - auto syncEditor = [=] { - const QSignalBlocker xBlocker(xSpinBox); - const QSignalBlocker yBlocker(ySpinBox); - const auto point = property->value().toPointF(); - xSpinBox->setValue(point.x()); - ySpinBox->setValue(point.y()); - }; - auto syncProperty = [=] { - property->setValue(QPointF(xSpinBox->value(), ySpinBox->value())); + auto editor = new PointFEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toPointF()); }; syncEditor(); QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(xSpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); - QObject::connect(ySpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); + QObject::connect(editor, &PointFEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); return editor; } }; - class SizeEditorFactory : public EditorFactory { public: @@ -290,75 +238,45 @@ class SizeEditorFactory : public EditorFactory } }; -// todo: implement responsive layout (see SizeEdit) -class RectFEditorFactory : public EditorFactory +class SizeFEditorFactory : public EditorFactory { public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto editor = new QWidget(parent); - auto gridLayout = new QGridLayout(editor); - gridLayout->setContentsMargins(QMargins()); - gridLayout->setColumnStretch(4, 1); - - auto xLabel = new QLabel(QStringLiteral("X"), editor); - gridLayout->addWidget(xLabel, 0, 0, Qt::AlignRight); - - auto xSpinBox = new DoubleSpinBox(editor); - xLabel->setBuddy(xSpinBox); - gridLayout->addWidget(xSpinBox, 0, 1); - - auto yLabel = new QLabel(QStringLiteral("Y"), editor); - gridLayout->addWidget(yLabel, 0, 2, Qt::AlignRight); - - auto ySpinBox = new DoubleSpinBox(editor); - yLabel->setBuddy(ySpinBox); - gridLayout->addWidget(ySpinBox, 0, 3); - - auto widthLabel = new QLabel(QStringLiteral("W"), editor); - widthLabel->setToolTip(tr("Width")); - gridLayout->addWidget(widthLabel, 1, 0, Qt::AlignRight); - - auto widthSpinBox = new DoubleSpinBox(editor); - widthLabel->setBuddy(widthSpinBox); - gridLayout->addWidget(widthSpinBox, 1, 1); + auto editor = new SizeFEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toSizeF()); + }; + syncEditor(); - auto heightLabel = new QLabel(QStringLiteral("H"), editor); - heightLabel->setToolTip(tr("Height")); - gridLayout->addWidget(heightLabel, 1, 2, Qt::AlignRight); + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &SizeFEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); - auto heightSpinBox = new DoubleSpinBox(editor); - heightLabel->setBuddy(heightSpinBox); - gridLayout->addWidget(heightSpinBox, 1, 3); + return editor; + } +}; - auto syncEditor = [=] { - const QSignalBlocker xBlocker(xSpinBox); - const QSignalBlocker yBlocker(ySpinBox); - const QSignalBlocker widthBlocker(widthSpinBox); - const QSignalBlocker heightBlocker(heightSpinBox); - const auto rect = property->value().toRectF(); - xSpinBox->setValue(rect.x()); - ySpinBox->setValue(rect.y()); - widthSpinBox->setValue(rect.width()); - heightSpinBox->setValue(rect.height()); - }; - auto syncProperty = [=] { - property->setValue(QRectF(xSpinBox->value(), - ySpinBox->value(), - widthSpinBox->value(), - heightSpinBox->value())); +class RectFEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(Property *property, QWidget *parent) override + { + auto editor = new RectFEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toRectF()); }; syncEditor(); QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(xSpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); - QObject::connect(ySpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); - QObject::connect(widthSpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); - QObject::connect(heightSpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); + QObject::connect(editor, &RectFEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); return editor; } @@ -387,6 +305,132 @@ class ColorEditorFactory : public EditorFactory } }; +class FontEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(Property *property, QWidget *parent) override + { + auto editor = new QWidget(parent); + auto layout = new QVBoxLayout(editor); + auto fontComboBox = new QFontComboBox(editor); + auto sizeSpinBox = new QSpinBox(editor); + auto boldCheckBox = new QCheckBox(tr("Bold"), editor); + auto italicCheckBox = new QCheckBox(tr("Italic"), editor); + auto underlineCheckBox = new QCheckBox(tr("Underline"), editor); + auto strikeoutCheckBox = new QCheckBox(tr("Strikeout"), editor); + auto kerningCheckBox = new QCheckBox(tr("Kerning"), editor); + sizeSpinBox->setRange(1, 999); + sizeSpinBox->setSuffix(tr(" px")); + sizeSpinBox->setKeyboardTracking(false); + layout->setContentsMargins(QMargins()); + layout->setSpacing(Utils::dpiScaled(3)); + layout->addWidget(fontComboBox); + layout->addWidget(sizeSpinBox); + layout->addWidget(boldCheckBox); + layout->addWidget(italicCheckBox); + layout->addWidget(underlineCheckBox); + layout->addWidget(strikeoutCheckBox); + layout->addWidget(kerningCheckBox); + + auto syncEditor = [=] { + const auto font = property->value().value(); + const QSignalBlocker fontBlocker(fontComboBox); + const QSignalBlocker sizeBlocker(sizeSpinBox); + const QSignalBlocker boldBlocker(boldCheckBox); + const QSignalBlocker italicBlocker(italicCheckBox); + const QSignalBlocker underlineBlocker(underlineCheckBox); + const QSignalBlocker strikeoutBlocker(strikeoutCheckBox); + const QSignalBlocker kerningBlocker(kerningCheckBox); + fontComboBox->setCurrentFont(font); + sizeSpinBox->setValue(font.pixelSize()); + boldCheckBox->setChecked(font.bold()); + italicCheckBox->setChecked(font.italic()); + underlineCheckBox->setChecked(font.underline()); + strikeoutCheckBox->setChecked(font.strikeOut()); + kerningCheckBox->setChecked(font.kerning()); + }; + + auto syncProperty = [=] { + auto font = fontComboBox->currentFont(); + font.setPixelSize(sizeSpinBox->value()); + font.setBold(boldCheckBox->isChecked()); + font.setItalic(italicCheckBox->isChecked()); + font.setUnderline(underlineCheckBox->isChecked()); + font.setStrikeOut(strikeoutCheckBox->isChecked()); + font.setKerning(kerningCheckBox->isChecked()); + property->setValue(font); + }; + + syncEditor(); + + QObject::connect(property, &Property::valueChanged, fontComboBox, syncEditor); + QObject::connect(fontComboBox, &QFontComboBox::currentFontChanged, property, syncProperty); + QObject::connect(sizeSpinBox, qOverload(&QSpinBox::valueChanged), property, syncProperty); + QObject::connect(boldCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(italicCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(underlineCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(strikeoutCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(kerningCheckBox, &QCheckBox::toggled, property, syncProperty); + + return editor; + } +}; + +class AlignmentEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(Property *property, QWidget *parent) override + { + auto editor = new QWidget(parent); + auto layout = new QGridLayout(editor); + layout->setContentsMargins(QMargins()); + layout->setSpacing(Utils::dpiScaled(3)); + + auto horizontalLabel = new ElidingLabel(tr("Horizontal"), editor); + layout->addWidget(horizontalLabel, 0, 0); + + auto verticalLabel = new ElidingLabel(tr("Vertical"), editor); + layout->addWidget(verticalLabel, 1, 0); + + auto horizontalComboBox = new QComboBox(editor); + horizontalComboBox->addItem(tr("Left"), Qt::AlignLeft); + horizontalComboBox->addItem(tr("Center"), Qt::AlignHCenter); + horizontalComboBox->addItem(tr("Right"), Qt::AlignRight); + horizontalComboBox->addItem(tr("Justify"), Qt::AlignJustify); + layout->addWidget(horizontalComboBox, 0, 1); + + auto verticalComboBox = new QComboBox(editor); + verticalComboBox->addItem(tr("Top"), Qt::AlignTop); + verticalComboBox->addItem(tr("Center"), Qt::AlignVCenter); + verticalComboBox->addItem(tr("Bottom"), Qt::AlignBottom); + layout->addWidget(verticalComboBox, 1, 1); + + layout->setColumnStretch(1, 1); + + auto syncEditor = [=] { + const QSignalBlocker horizontalBlocker(horizontalComboBox); + const QSignalBlocker verticalBlocker(verticalComboBox); + const auto alignment = property->value().value(); + horizontalComboBox->setCurrentIndex(horizontalComboBox->findData(static_cast(alignment & Qt::AlignHorizontal_Mask))); + verticalComboBox->setCurrentIndex(verticalComboBox->findData(static_cast(alignment & Qt::AlignVertical_Mask))); + }; + + auto syncProperty = [=] { + const Qt::Alignment alignment(horizontalComboBox->currentData().toInt() | + verticalComboBox->currentData().toInt()); + property->setValue(QVariant::fromValue(alignment)); + }; + + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(horizontalComboBox, qOverload(&QComboBox::currentIndexChanged), property, syncProperty); + QObject::connect(verticalComboBox, qOverload(&QComboBox::currentIndexChanged), property, syncProperty); + + return editor; + } +}; + ValueProperty::ValueProperty(const QString &name, const QVariant &value, @@ -425,7 +469,7 @@ VariantEditor::VariantEditor(QWidget *parent) : QScrollArea(parent) { m_widget = new QWidget; - m_widget->setBackgroundRole(QPalette::Base); + m_widget->setBackgroundRole(QPalette::AlternateBase); auto verticalLayout = new QVBoxLayout(m_widget); m_gridLayout = new QGridLayout; verticalLayout->addLayout(m_gridLayout); @@ -555,7 +599,6 @@ QWidget *VariantEditor::createEditor(Property *property) } - EnumEditorFactory::EnumEditorFactory(const QStringList &enumNames, const QList &enumValues) : m_enumNamesModel(enumNames) @@ -605,12 +648,15 @@ ValueTypeEditorFactory::ValueTypeEditorFactory() registerEditorFactory(QMetaType::Double, std::make_unique()); registerEditorFactory(QMetaType::Int, std::make_unique()); registerEditorFactory(QMetaType::QColor, std::make_unique()); + registerEditorFactory(QMetaType::QFont, std::make_unique()); registerEditorFactory(QMetaType::QPoint, std::make_unique()); registerEditorFactory(QMetaType::QPointF, std::make_unique()); registerEditorFactory(QMetaType::QRectF, std::make_unique()); registerEditorFactory(QMetaType::QSize, std::make_unique()); + registerEditorFactory(QMetaType::QSizeF, std::make_unique()); registerEditorFactory(QMetaType::QString, std::make_unique()); registerEditorFactory(QMetaType::QUrl, std::make_unique()); + registerEditorFactory(qMetaTypeId(), std::make_unique()); } void ValueTypeEditorFactory::registerEditorFactory(int type, std::unique_ptr factory) diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 8099181347..3b282cb3a1 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -89,6 +89,21 @@ class EditorFactory virtual QWidget *createEditor(Property *property, QWidget *parent) = 0; }; +struct IntEditorFactory : EditorFactory +{ + QWidget *createEditor(Property *property, QWidget *parent) override; +}; + +struct FloatEditorFactory : EditorFactory +{ + QWidget *createEditor(Property *property, QWidget *parent) override; + + void setSuffix(const QString &suffix) { m_suffix = suffix; } + +private: + QString m_suffix; +}; + /** * An editor factory that creates a combo box for enum properties. */