diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs index fcba0f79ce..8b4ca0cf35 100644 --- a/src/tiled/libtilededitor.qbs +++ b/src/tiled/libtilededitor.qbs @@ -407,6 +407,8 @@ DynamicLibrary { "propertieswidget.h", "propertybrowser.cpp", "propertybrowser.h", + "propertyeditorwidgets.cpp", + "propertyeditorwidgets.h", "propertytypeseditor.cpp", "propertytypeseditor.h", "propertytypeseditor.ui", diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 0e7a882c01..9afabf0231 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -46,7 +46,6 @@ namespace Tiled { PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} - , mDocument(nullptr) , mPropertyBrowser(new VariantEditor(this)) , mDefaultEditorFactory(std::make_unique()) { @@ -150,8 +149,8 @@ class MapOrientationProperty : public EnumProperty Q_OBJECT public: - MapOrientationProperty(MapDocument *mapDocument) - : EnumProperty(tr("Orientation")) + MapOrientationProperty(MapDocument *mapDocument, QObject *parent = nullptr) + : EnumProperty(tr("Orientation"), parent) , mMapDocument(mapDocument) { setEnumNames({ @@ -166,9 +165,6 @@ class MapOrientationProperty : public EnumProperty Map::Staggered, Map::Hexagonal, }); - - connect(mMapDocument, &MapDocument::mapChanged, - this, &Property::valueChanged); } QVariant value() const override @@ -192,8 +188,9 @@ class MapSizeProperty : public AbstractProperty Q_OBJECT public: - MapSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory) - : AbstractProperty(tr("Map Size"), editorFactory) + MapSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory, + QObject *parent = nullptr) + : AbstractProperty(tr("Map Size"), editorFactory, parent) , mMapDocument(mapDocument) { connect(mMapDocument, &MapDocument::mapChanged, @@ -231,12 +228,12 @@ class TileSizeProperty : public AbstractProperty Q_OBJECT public: - TileSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory) - : AbstractProperty(tr("Tile Size"), editorFactory) + TileSizeProperty(MapDocument *mapDocument, + EditorFactory *editorFactory, + QObject *parent = nullptr) + : AbstractProperty(tr("Tile Size"), editorFactory, parent) , mMapDocument(mapDocument) { - connect(mMapDocument, &Document::changed, - this, &TileSizeProperty::onChanged); } QVariant value() const override @@ -265,17 +262,114 @@ class TileSizeProperty : public AbstractProperty }; private: - void onChanged(const ChangeEvent &event) + MapDocument *mMapDocument; +}; + +class MapProperties : public QObject +{ + Q_OBJECT + +public: + MapProperties(MapDocument *mapDocument, + ValueTypeEditorFactory *editorFactory, + QObject *parent = nullptr) + : QObject(parent) + , mMapDocument(mapDocument) + , mOrientationProperty(new MapOrientationProperty(mapDocument, this)) + , mSizeProperty(new MapSizeProperty(mapDocument, editorFactory, this)) + , mTileSizeProperty(new TileSizeProperty(mapDocument, editorFactory, this)) + { + mInfiniteProperty = editorFactory->createProperty( + tr("Infinite"), + [this]() { + return mMapDocument->map()->infinite(); + }, + [this](const QVariant &value) { + auto command = new ChangeMapProperty(mMapDocument, + Map::InfiniteProperty, + value.toBool()); + mMapDocument->undoStack()->push(command); + }); + + mHexSideLengthProperty = editorFactory->createProperty( + tr("Hex Side Length"), + [this]() { + return mMapDocument->map()->hexSideLength(); + }, + [this](const QVariant &value) { + auto command = new ChangeMapProperty(mMapDocument, + Map::HexSideLengthProperty, + value.toInt()); + mMapDocument->undoStack()->push(command); + }); + + connect(mMapDocument, &MapDocument::changed, + this, &MapProperties::onMapChanged); + } + + void populateEditor(VariantEditor *editor) + { + editor->addHeader(tr("Map")); + editor->addProperty(mOrientationProperty); + editor->addProperty(mSizeProperty); + editor->addProperty(mTileSizeProperty); + editor->addProperty(mInfiniteProperty); + editor->addProperty(mHexSideLengthProperty); + // editor->addProperty(mStaggerAxisProperty); + // editor->addProperty(mStaggerIndexProperty); + // editor->addProperty(mParallaxOriginProperty); + // editor->addProperty(mLayerDataFormatProperty); + // editor->addProperty(mChunkSizeProperty); + // editor->addProperty(mTileRenderOrderProperty); + // editor->addProperty(mCompressionLevelProperty); + // editor->addProperty(mBackgroundColorProperty); + } + +private: + void onMapChanged(const ChangeEvent &event) { if (event.type != ChangeEvent::MapChanged) return; const auto property = static_cast(event).property; - if (property == Map::TileWidthProperty || property == Map::TileHeightProperty) - emit valueChanged(); + switch (property) { + case Map::TileWidthProperty: + case Map::TileHeightProperty: + emit mTileSizeProperty->valueChanged(); + break; + case Map::InfiniteProperty: + emit mInfiniteProperty->valueChanged(); + break; + case Map::HexSideLengthProperty: + case Map::StaggerAxisProperty: + case Map::StaggerIndexProperty: + case Map::ParallaxOriginProperty: + case Map::OrientationProperty: + emit mOrientationProperty->valueChanged(); + break; + case Map::RenderOrderProperty: + case Map::BackgroundColorProperty: + case Map::LayerDataFormatProperty: + case Map::CompressionLevelProperty: + case Map::ChunkSizeProperty: + break; + } } MapDocument *mMapDocument; + Property *mOrientationProperty; + Property *mSizeProperty; + Property *mTileSizeProperty; + Property *mInfiniteProperty; + Property *mHexSideLengthProperty; + Property *mStaggerAxisProperty; + Property *mStaggerIndexProperty; + Property *mParallaxOriginProperty; + Property *mLayerDataFormatProperty; + Property *mChunkSizeProperty; + Property *mTileRenderOrderProperty; + Property *mCompressionLevelProperty; + Property *mBackgroundColorProperty; }; @@ -283,6 +377,8 @@ void PropertiesWidget::currentObjectChanged(Object *object) { // mPropertyBrowser->setObject(object); mPropertyBrowser->clear(); + delete mPropertiesObject; + mPropertiesObject = nullptr; if (object) { switch (object->typeId()) { @@ -290,24 +386,10 @@ void PropertiesWidget::currentObjectChanged(Object *object) case Object::MapObjectType: break; case Object::MapType: { - Map *map = static_cast(object); auto mapDocument = static_cast(mDocument); - - mPropertyBrowser->addHeader(tr("Map")); - mPropertyBrowser->addProperty(new MapOrientationProperty(mapDocument)); - mPropertyBrowser->addProperty(new MapSizeProperty(mapDocument, mDefaultEditorFactory.get())); - mPropertyBrowser->addProperty(new TileSizeProperty(mapDocument, mDefaultEditorFactory.get())); - // todo: infinite - // todo: hex side length - // todo: stagger axis - // todo: stagger index - // todo: parallax origin - // todo: layer data format - // todo: chunk size - // todo: tile render order - // todo: compression level - // todo: background color - + auto properties = new MapProperties(mapDocument, mDefaultEditorFactory.get(), this); + properties->populateEditor(mPropertyBrowser); + mPropertiesObject = properties; break; } case Object::TilesetType: diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index f9f5db095e..55447f7247 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -76,7 +76,8 @@ public slots: void retranslateUi(); - Document *mDocument; + Document *mDocument = nullptr; + QObject *mPropertiesObject = nullptr; VariantEditor *mPropertyBrowser; std::unique_ptr mDefaultEditorFactory; QAction *mActionAddProperty; diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp new file mode 100644 index 0000000000..36e899a3b0 --- /dev/null +++ b/src/tiled/propertyeditorwidgets.cpp @@ -0,0 +1,230 @@ +#include "propertyeditorwidgets.h" + +#include "utils.h" + +#include +#include +#include +#include +#include + +namespace Tiled { + +/** + * Strips a floating point number representation of redundant trailing zeros. + * Examples: + * + * 0.01000 -> 0.01 + * 3.000 -> 3.0 + */ +static QString removeRedundantTrialingZeros(const QString &text) +{ + const QString decimalPoint = QLocale::system().decimalPoint(); + const auto decimalPointIndex = text.lastIndexOf(decimalPoint); + if (decimalPointIndex < 0) // return if there is no decimal point + return text; + + const auto afterDecimalPoint = decimalPointIndex + decimalPoint.length(); + int redundantZeros = 0; + + for (int i = text.length() - 1; i > afterDecimalPoint && text.at(i) == QLatin1Char('0'); --i) + ++redundantZeros; + + return text.left(text.length() - redundantZeros); +} + + +SpinBox::SpinBox(QWidget *parent) + : QSpinBox(parent) +{ + // Allow the full range by default. + setRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + + // Allow the widget to shrink horizontally. + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +QSize SpinBox::minimumSizeHint() const +{ + // Don't adjust the horizontal size hint based on the maximum value. + auto hint = QSpinBox::minimumSizeHint(); + hint.setWidth(Utils::dpiScaled(50)); + return hint; +} + + +DoubleSpinBox::DoubleSpinBox(QWidget *parent) + : QDoubleSpinBox(parent) +{ + // Allow the full range by default. + setRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + + // Increase possible precision. + setDecimals(9); + + // Allow the widget to shrink horizontally. + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +QSize DoubleSpinBox::minimumSizeHint() const +{ + // Don't adjust the horizontal size hint based on the maximum value. + auto hint = QDoubleSpinBox::minimumSizeHint(); + hint.setWidth(Utils::dpiScaled(50)); + return hint; +} + +QString DoubleSpinBox::textFromValue(double val) const +{ + auto text = QDoubleSpinBox::textFromValue(val); + + // remove redundant trailing 0's in case of high precision + if (decimals() > 3) + return removeRedundantTrialingZeros(text); + + return text; +} + + +SizeEdit::SizeEdit(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)); + + 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); + + 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()); + m_heightSpinBox->setValue(size.height()); +} + +QSize SizeEdit::value() const +{ + return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); +} + +void SizeEdit::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 = qobject_cast(this->layout()); + + // 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(); + } +} + +int SizeEdit::minimumHorizontalWidth() const +{ + return m_widthLabel->minimumSizeHint().width() + + m_widthSpinBox->minimumSizeHint().width() + + m_heightLabel->minimumSizeHint().width() + + m_heightSpinBox->minimumSizeHint().width() + + layout()->spacing() * 3; +} + + +ElidingLabel::ElidingLabel(QWidget *parent) + : ElidingLabel(QString(), parent) +{} + +ElidingLabel::ElidingLabel(const QString &text, QWidget *parent) + : QLabel(text, parent) +{ + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); +} + +QSize ElidingLabel::minimumSizeHint() const +{ + auto hint = QLabel::minimumSizeHint(); + hint.setWidth(std::min(hint.width(), Utils::dpiScaled(30))); + return hint; +} + +void ElidingLabel::paintEvent(QPaintEvent *) +{ + const int m = margin(); + const QRect cr = contentsRect().adjusted(m, m, -m, -m); + const Qt::LayoutDirection dir = text().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight; + const int align = QStyle::visualAlignment(dir, alignment()); + const int flags = align | (dir == Qt::LeftToRight ? Qt::TextForceLeftToRight + : Qt::TextForceRightToLeft); + + QStyleOption opt; + opt.initFrom(this); + + const auto elidedText = opt.fontMetrics.elidedText(text(), Qt::ElideRight, cr.width()); + const bool isElided = elidedText != text(); + + if (isElided != m_isElided) { + m_isElided = isElided; + setToolTip(isElided ? text() : QString()); + } + + QPainter painter(this); + QWidget::style()->drawItemText(&painter, cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); +} + + +QSize LineEditLabel::sizeHint() const +{ + auto hint = ElidingLabel::sizeHint(); + hint.setHeight(m_lineEdit.sizeHint().height()); + return hint; +} + +} // namespace Tiled + +#include "moc_propertyeditorwidgets.cpp" diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h new file mode 100644 index 0000000000..f33eaf7fbd --- /dev/null +++ b/src/tiled/propertyeditorwidgets.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include + +class QLabel; + +namespace Tiled { + +/** + * A spin box that allows the full range by default and shrinks horizontally. + * It also doesn't adjust the horizontal size hint based on the maximum value. + */ +class SpinBox : public QSpinBox +{ + Q_OBJECT + +public: + SpinBox(QWidget *parent = nullptr); + + QSize minimumSizeHint() const override; +}; + +/** + * A double spin box that allows the full range by default and shrinks + * horizontally. It also doesn't adjust the horizontal size hint based on the + * maximum value. + * + * The precision is increased to 9 decimal places. Redundant trailing 0's are + * removed. + */ +class DoubleSpinBox : public QDoubleSpinBox +{ + Q_OBJECT + +public: + DoubleSpinBox(QWidget *parent = nullptr); + + QSize minimumSizeHint() const override; + QString textFromValue(double val) const override; +}; + +/** + * A widget for editing a QSize value. + */ +class SizeEdit : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QSize value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + SizeEdit(QWidget *parent = nullptr); + + void setValue(const QSize &size); + QSize value() const; + +signals: + 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 label that elides its text if there is not enough space. + * + * The elided text is shown as a tooltip. + */ +class ElidingLabel : public QLabel +{ + Q_OBJECT + +public: + explicit ElidingLabel(QWidget *parent = nullptr); + ElidingLabel(const QString &text, QWidget *parent = nullptr); + + QSize minimumSizeHint() const override; + void paintEvent(QPaintEvent *) override; + +private: + bool m_isElided = false; +}; + +/** + * A label that matches its preferred height with that of a line edit. + */ +class LineEditLabel : public ElidingLabel +{ + Q_OBJECT + +public: + using ElidingLabel::ElidingLabel; + + QSize sizeHint() const override; + +private: + QLineEdit m_lineEdit; +}; + +} // namespace Tiled diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 60d2290417..523ad504c4 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -19,10 +19,12 @@ */ #include "varianteditor.h" + #include "colorbutton.h" #include "compression.h" #include "map.h" #include "utils.h" +#include "propertyeditorwidgets.h" #include #include @@ -51,164 +53,17 @@ QWidget *AbstractProperty::createEditor(QWidget *parent) } -class SpinBox : public QSpinBox -{ - Q_OBJECT - -public: - SpinBox(QWidget *parent = nullptr) - : QSpinBox(parent) - { - // Allow the full range by default. - setRange(std::numeric_limits::lowest(), - std::numeric_limits::max()); - - // Allow the widget to shrink horizontally. - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - } - - QSize minimumSizeHint() const override - { - // Don't adjust the horizontal size hint based on the maximum value. - auto hint = QSpinBox::minimumSizeHint(); - hint.setWidth(Utils::dpiScaled(50)); - return hint; - } -}; - -/** - * Strips a floating point number representation of redundant trailing zeros. - * Examples: - * - * 0.01000 -> 0.01 - * 3.000 -> 3.0 - */ -QString removeRedundantTrialingZeros(const QString &text) -{ - const QString decimalPoint = QLocale::system().decimalPoint(); - const auto decimalPointIndex = text.lastIndexOf(decimalPoint); - if (decimalPointIndex < 0) // return if there is no decimal point - return text; - - const auto afterDecimalPoint = decimalPointIndex + decimalPoint.length(); - int redundantZeros = 0; - - for (int i = text.length() - 1; i > afterDecimalPoint && text.at(i) == QLatin1Char('0'); --i) - ++redundantZeros; - - return text.left(text.length() - redundantZeros); -} - -class DoubleSpinBox : public QDoubleSpinBox -{ - Q_OBJECT - -public: - DoubleSpinBox(QWidget *parent = nullptr) - : QDoubleSpinBox(parent) - { - // Allow the full range by default. - setRange(std::numeric_limits::lowest(), - std::numeric_limits::max()); - - // Increase possible precision. - setDecimals(9); - - // Allow the widget to shrink horizontally. - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - } - - QSize minimumSizeHint() const override - { - // Don't adjust the horizontal size hint based on the maximum value. - auto hint = QDoubleSpinBox::minimumSizeHint(); - hint.setWidth(Utils::dpiScaled(50)); - return hint; - } - - // QDoubleSpinBox interface - QString textFromValue(double val) const override - { - auto text = QDoubleSpinBox::textFromValue(val); - - // remove redundant trailing 0's in case of high precision - if (decimals() > 3) - return removeRedundantTrialingZeros(text); - - return text; - } -}; - - -// A label that elides its text if there is not enough space -class ElidingLabel : public QLabel -{ - Q_OBJECT - -public: - explicit ElidingLabel(QWidget *parent = nullptr) - : ElidingLabel(QString(), parent) - {} - - ElidingLabel(const QString &text, QWidget *parent = nullptr) - : QLabel(text, parent) - { - setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); - } - - QSize minimumSizeHint() const override - { - auto hint = QLabel::minimumSizeHint(); - hint.setWidth(std::min(hint.width(), Utils::dpiScaled(30))); - return hint; - } - - void paintEvent(QPaintEvent *) override - { - const int m = margin(); - const QRect cr = contentsRect().adjusted(m, m, -m, -m); - const Qt::LayoutDirection dir = text().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight; - const int align = QStyle::visualAlignment(dir, alignment()); - const int flags = align | (dir == Qt::LeftToRight ? Qt::TextForceLeftToRight - : Qt::TextForceRightToLeft); - - QStyleOption opt; - opt.initFrom(this); - - const auto elidedText = opt.fontMetrics.elidedText(text(), Qt::ElideRight, cr.width()); - - const bool isElided = elidedText != text(); - if (isElided != m_isElided) { - m_isElided = isElided; - setToolTip(isElided ? text() : QString()); - } - - QPainter painter(this); - QWidget::style()->drawItemText(&painter, cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); - } - -private: - bool m_isElided = false; -}; - -// A label that matches its preferred height with that of a line edit -class LineEditLabel : public ElidingLabel -{ - Q_OBJECT +GetSetProperty::GetSetProperty(const QString &name, + std::function get, + std::function set, + EditorFactory *editorFactory, + QObject *parent) + : AbstractProperty(name, editorFactory, parent) + , m_get(std::move(get)) + , m_set(std::move(set)) +{} -public: - using ElidingLabel::ElidingLabel; - QSize sizeHint() const override - { - auto hint = ElidingLabel::sizeHint(); - hint.setHeight(lineEdit.sizeHint().height()); - return hint; - } - -private: - QLineEdit lineEdit; -}; class StringEditorFactory : public EditorFactory { @@ -251,14 +106,19 @@ class BoolEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new QCheckBox(parent); - bool checked = value.toBool(); - editor->setChecked(checked); - editor->setText(checked ? tr("On") : tr("Off")); + auto syncEditor = [=]() { + const QSignalBlocker blocker(editor); + bool checked = property->value().toBool(); + editor->setChecked(checked); + editor->setText(checked ? tr("On") : tr("Off")); + }; + syncEditor(); - QObject::connect(editor, &QCheckBox::toggled, [editor](bool checked) { + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &QCheckBox::toggled, property, [=](bool checked) { editor->setText(checked ? QObject::tr("On") : QObject::tr("Off")); + property->setValue(checked); }); return editor; @@ -327,34 +187,6 @@ class PointFEditorFactory : public EditorFactory } }; -/** - * A widget for editing a QSize value. - */ -class SizeEdit : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QSize value READ value WRITE setValue NOTIFY valueChanged FINAL) - -public: - SizeEdit(QWidget *parent = nullptr); - - void setValue(const QSize &size); - QSize value() const; - -signals: - 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; -}; class SizeEditorFactory : public EditorFactory { @@ -460,11 +292,8 @@ void ValueProperty::setValue(const QVariant &value) EnumProperty::EnumProperty(const QString &name, - const QStringList &enumNames, - const QList &enumValues, QObject *parent) : AbstractProperty(name, &m_editorFactory, parent) - , m_editorFactory(enumNames, enumValues) {} void EnumProperty::setEnumNames(const QStringList &enumNames) @@ -699,91 +528,6 @@ QWidget *VariantEditor::createEditor(Property *property) return nullptr; } -SizeEdit::SizeEdit(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)); - - 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); - - 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()); - m_heightSpinBox->setValue(size.height()); -} - -QSize SizeEdit::value() const -{ - return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); -} - -void SizeEdit::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 = qobject_cast(this->layout()); - - // 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(); - } -} - -int SizeEdit::minimumHorizontalWidth() const -{ - return m_widthLabel->minimumSizeHint().width() + - m_widthSpinBox->minimumSizeHint().width() + - m_heightLabel->minimumSizeHint().width() + - m_heightSpinBox->minimumSizeHint().width() + - layout()->spacing() * 3; -} EnumEditorFactory::EnumEditorFactory(const QStringList &enumNames, @@ -872,6 +616,17 @@ ValueProperty *ValueTypeEditorFactory::createProperty(const QString &name, const return nullptr; } +AbstractProperty *ValueTypeEditorFactory::createProperty(const QString &name, + std::function get, + std::function set) +{ + const int type = get().userType(); + auto factory = m_factories.find(type); + if (factory != m_factories.end()) + return new GetSetProperty(name, get, set, factory->second.get()); + return nullptr; +} + QWidget *ValueTypeEditorFactory::createEditor(Property *property, QWidget *parent) { const auto value = property->value(); @@ -918,4 +673,3 @@ void QObjectProperty::setValue(const QVariant &value) } // namespace Tiled #include "moc_varianteditor.cpp" -#include "varianteditor.moc" diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 2057629da6..8099181347 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -129,6 +129,31 @@ class AbstractProperty : public Property EditorFactory *m_editorFactory; }; +/** + * A property that uses the given functions to get and set the value and uses + * an editor factory to create its editor. + * + * The property does not take ownership of the editor factory. + */ +class GetSetProperty : public AbstractProperty +{ + Q_OBJECT + +public: + GetSetProperty(const QString &name, + std::function get, + std::function set, + EditorFactory *editorFactory, + QObject *parent = nullptr); + + QVariant value() const override { return m_get(); } + void setValue(const QVariant &value) override { m_set(value); } + +private: + std::function m_get; + std::function m_set; +}; + /** * A property that stores a value of a given type and uses an editor factory to * create its editor. @@ -210,6 +235,15 @@ class ValueTypeEditorFactory : public EditorFactory */ ValueProperty *createProperty(const QString &name, const QVariant &value); + /** + * Creates a property with the given name and get/set functions. The + * property will use the editor factory registered for the type of the + * value. + */ + AbstractProperty *createProperty(const QString &name, + std::function get, + std::function set); + QWidget *createEditor(Property *property, QWidget *parent) override; private: @@ -226,8 +260,6 @@ class EnumProperty : public AbstractProperty public: EnumProperty(const QString &name, - const QStringList &enumNames = {}, - const QList &enumValues = {}, QObject *parent = nullptr); void setEnumNames(const QStringList &enumNames);