diff --git a/src/tiled/propertiesview.cpp b/src/tiled/propertiesview.cpp index 782fbb1c24..0c92c7df86 100644 --- a/src/tiled/propertiesview.cpp +++ b/src/tiled/propertiesview.cpp @@ -28,11 +28,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -136,6 +138,12 @@ QWidget *GroupProperty::createLabel(int level, QWidget *parent) return label; } +void GroupProperty::addContextMenuActions(QMenu *menu) +{ + menu->addAction(tr("Expand All"), this, &GroupProperty::expandAll); + menu->addAction(tr("Collapse All"), this, &GroupProperty::collapseAll); +} + void GroupProperty::setExpanded(bool expanded) { if (m_expanded != expanded) { @@ -224,6 +232,18 @@ QWidget *UrlProperty::createEditor(QWidget *parent) return editor; } +void UrlProperty::addContextMenuActions(QMenu *menu) +{ + const QString localFile = value().toLocalFile(); + + if (!localFile.isEmpty()) { + Utils::addOpenContainingFolderAction(*menu, localFile); + + if (QFileInfo { localFile }.isFile()) + Utils::addOpenWithSystemEditorAction(*menu, localFile); + } +} + QWidget *IntProperty::createEditor(QWidget *parent) { auto widget = new QWidget(parent); @@ -969,6 +989,12 @@ bool PropertiesView::focusProperty(Property *property, FocusTarget target) return true; } +Property *PropertiesView::focusedProperty() const +{ + auto propertyWidget = qobject_cast(focusWidget()); + return propertyWidget ? propertyWidget->property() : nullptr; +} + bool PropertiesView::eventFilter(QObject *watched, QEvent *event) { if (event->type() == QEvent::Show) { @@ -986,10 +1012,18 @@ bool PropertiesView::eventFilter(QObject *watched, QEvent *event) return QScrollArea::eventFilter(watched, event); } +void PropertiesView::mousePressEvent(QMouseEvent *event) +{ + // If the view gets mouse press then no PropertyWidget was clicked + if (event->modifiers() == Qt::NoModifier) + setSelectedProperties({}); + + QScrollArea::mousePressEvent(event); +} + void PropertiesView::keyPressEvent(QKeyEvent *event) { - auto propertyWidget = qobject_cast(focusWidget()); - auto property = propertyWidget ? propertyWidget->property() : nullptr; + auto property = focusedProperty(); const auto key = event->key(); const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; @@ -1092,10 +1126,7 @@ bool PropertiesView::focusNextPrevProperty(Property *property, bool next, bool s return false; } -/** - * Removes the given property from the editor. The property is not deleted. - */ -void PropertiesView::removeProperty(Property *property) +void PropertiesView::deletePropertyWidgets(Property *property) { auto it = m_propertyWidgets.constFind(property); Q_ASSERT(it != m_propertyWidgets.constEnd()); @@ -1113,7 +1144,7 @@ void PropertiesView::removeProperty(Property *property) rowWidget->deleteLater(); - m_propertyWidgets.erase(it); + forgetProperty(property); // This appears to be necessary to avoid flickering due to relayouting // not being done before the next paint. @@ -1121,8 +1152,21 @@ void PropertiesView::removeProperty(Property *property) widget->layout()->activate(); widget = widget->parentWidget(); } +} + +void PropertiesView::forgetProperty(Property *property) +{ + m_propertyWidgets.remove(property); + + if (property == m_selectionStart) + m_selectionStart = nullptr; property->disconnect(this); + + if (GroupProperty *groupProperty = qobject_cast(property)) { + for (auto subProperty : groupProperty->subProperties()) + forgetProperty(subProperty); + } } /** @@ -1197,10 +1241,6 @@ PropertiesView::PropertyWidgets PropertiesView::createPropertyWidgets(Property * const auto displayMode = property->displayMode(); - connect(property, &Property::destroyed, this, [this](QObject *object) { - removeProperty(static_cast(object)); - }); - if (displayMode == Property::DisplayMode::ChildrenOnly) { QWidget *children; if (auto groupProperty = qobject_cast(property)) @@ -1240,7 +1280,7 @@ PropertiesView::PropertyWidgets PropertiesView::createPropertyWidgets(Property * rowWidget->setSelectable(property->actions().testFlag(Property::Action::Select)); - connect(rowWidget, &PropertyWidget::clicked, this, [=](Qt::KeyboardModifiers modifiers) { + connect(rowWidget, &PropertyWidget::mousePressed, this, [=](Qt::MouseButton button, Qt::KeyboardModifiers modifiers) { if (modifiers & Qt::ShiftModifier) { // Select range between m_selectionStart and clicked property if (assignSelectedPropertiesRange(m_root, m_selectionStart, property)) @@ -1254,10 +1294,12 @@ PropertiesView::PropertyWidgets PropertiesView::createPropertyWidgets(Property * } } else { // Select only the clicked property - if (property->actions().testFlag(Property::Action::Select)) - setSelectedProperties({property}); - else + if (property->actions().testFlag(Property::Action::Select)) { + if (button == Qt::LeftButton || !property->isSelected()) + setSelectedProperties({property}); + } else { setSelectedProperties({}); + } } m_selectionStart = property; @@ -1371,6 +1413,9 @@ QWidget *PropertiesView::createChildrenWidget(GroupProperty *groupProperty, createPropertyWidgets(property, children, level, layout, index); }); + connect(groupProperty, &GroupProperty::propertyRemoved, + this, &PropertiesView::deletePropertyWidgets); + return children; } diff --git a/src/tiled/propertiesview.h b/src/tiled/propertiesview.h index 2fc1d0aef1..7cbeca311b 100644 --- a/src/tiled/propertiesview.h +++ b/src/tiled/propertiesview.h @@ -97,6 +97,7 @@ class Property : public QObject virtual QWidget *createLabel(int level, QWidget *parent); virtual QWidget *createEditor(QWidget *parent) = 0; + virtual void addContextMenuActions(QMenu *) {} signals: void nameChanged(const QString &name); @@ -165,6 +166,7 @@ class GroupProperty : public Property QWidget *createLabel(int level, QWidget *parent) override; QWidget *createEditor(QWidget */* parent */) override { return nullptr; } + void addContextMenuActions(QMenu *) override; void setHeader(bool header) { m_header = header; } @@ -194,10 +196,23 @@ class GroupProperty : public Property void deleteProperty(Property *property) { - m_subProperties.removeOne(property); + removeProperty(property); delete property; } + /** + * Removes the given property from this group. Ownership of the property + * is transferred to the caller. + */ + void removeProperty(Property *property) + { + if (!m_subProperties.removeOne(property)) + return; + + property->m_parent = nullptr; + emit propertyRemoved(property); + } + int indexOfProperty(Property *property) const { return m_subProperties.indexOf(property); @@ -210,6 +225,7 @@ class GroupProperty : public Property signals: void expandedChanged(bool expanded); void propertyAdded(int index, Property *property); + void propertyRemoved(Property *property); private: bool m_header = true; @@ -270,9 +286,13 @@ struct MultilineStringProperty : StringProperty struct UrlProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; + void addContextMenuActions(QMenu *) override; + void setFilter(const QString &filter) { m_filter = filter; } void setIsDirectory(bool isDirectory) { m_isDirectory = isDirectory; } + private: QString m_filter; bool m_isDirectory = false; @@ -528,6 +548,7 @@ class PropertiesView : public QScrollArea }; bool focusProperty(Property *property, FocusTarget target = FocusEditor); + Property *focusedProperty() const; signals: void selectedPropertiesChanged(); @@ -535,12 +556,14 @@ class PropertiesView : public QScrollArea protected: bool eventFilter(QObject *watched, QEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; void keyPressEvent(QKeyEvent *event) override; private: bool focusNextPrevProperty(Property *property, bool next, bool shiftPressed); - void removeProperty(Property *property); + void deletePropertyWidgets(Property *property); + void forgetProperty(Property *property); QWidget *focusPropertyImpl(GroupProperty *group, Property *property, FocusTarget target); diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 621b2d41ff..032ece0615 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -35,6 +35,7 @@ #include "compression.h" #include "mapdocument.h" #include "objectgroup.h" +#include "objectrefdialog.h" #include "objecttemplate.h" #include "preferences.h" #include "propertiesview.h" @@ -2160,21 +2161,29 @@ class WangColorProperties : public ObjectProperties PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} + , mResetIcon(QIcon(QStringLiteral(":/images/16/edit-clear.png"))) + , mRemoveIcon(QIcon(QStringLiteral(":/images/16/remove.png"))) + , mAddIcon(QIcon(QStringLiteral(":/images/16/add.png"))) + , mRenameIcon(QIcon(QLatin1String(":/images/16/rename.png"))) , mRootProperty(new GroupProperty()) , mCustomProperties(new CustomProperties(mRootProperty)) , mPropertiesView(new PropertiesView(this)) { + mResetIcon.addFile(QStringLiteral(":/images/24/edit-clear.png")); + mRemoveIcon.addFile(QStringLiteral(":/images/22/remove.png")); + mAddIcon.addFile(QStringLiteral(":/images/22/add.png")); + mRootProperty->addProperty(mCustomProperties); mActionAddProperty = new QAction(this); mActionAddProperty->setEnabled(false); - mActionAddProperty->setIcon(QIcon(QLatin1String(":/images/16/add.png"))); + mActionAddProperty->setIcon(mAddIcon); connect(mActionAddProperty, &QAction::triggered, this, &PropertiesWidget::showAddValueProperty); mActionRemoveProperty = new QAction(this); mActionRemoveProperty->setEnabled(false); - mActionRemoveProperty->setIcon(QIcon(QLatin1String(":/images/16/remove.png"))); + mActionRemoveProperty->setIcon(mRemoveIcon); mActionRemoveProperty->setShortcuts(QKeySequence::Delete); mActionRemoveProperty->setPriority(QAction::LowPriority); connect(mActionRemoveProperty, &QAction::triggered, @@ -2182,7 +2191,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) mActionRenameProperty = new QAction(this); mActionRenameProperty->setEnabled(false); - mActionRenameProperty->setIcon(QIcon(QLatin1String(":/images/16/rename.png"))); + mActionRenameProperty->setIcon(mRenameIcon); mActionRenameProperty->setPriority(QAction::LowPriority); connect(mActionRenameProperty, &QAction::triggered, this, &PropertiesWidget::renameSelectedProperty); @@ -2213,9 +2222,6 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) connect(mPropertiesView, &PropertiesView::selectedPropertiesChanged, this, &PropertiesWidget::updateActions); - connect(mCustomProperties, &VariantMapProperty::renameRequested, - this, &PropertiesWidget::renameProperty); - retranslateUi(); } @@ -2400,7 +2406,8 @@ void CustomProperties::refresh() // Suggest properties from selected objects. Properties suggestedProperties; - for (auto object : mDocument->currentObjects()) + const auto currentObjects = mDocument->currentObjects(); + for (auto object : currentObjects) if (object != mDocument->currentObject()) mergeProperties(suggestedProperties, object->properties()); @@ -2630,48 +2637,47 @@ void PropertiesWidget::renameProperty(const QString &name) void PropertiesWidget::showContextMenu(const QPoint &pos) { -#if 0 const Object *object = mDocument->currentObject(); if (!object) return; - const QList items = mPropertyBrowser->selectedItems(); - const bool customPropertiesSelected = !items.isEmpty() && mPropertyBrowser->allCustomPropertyItems(items); + const auto properties = mPropertiesView->selectedProperties(); + const bool customPropertiesSelected = !properties.isEmpty(); bool currentObjectHasAllProperties = true; QStringList propertyNames; - for (QtBrowserItem *item : items) { - const QString propertyName = item->property()->propertyName(); - propertyNames.append(propertyName); + for (auto property : properties) { + propertyNames.append(property->name()); - if (!object->hasProperty(propertyName)) + if (!object->hasProperty(property->name())) currentObjectHasAllProperties = false; } - QMenu contextMenu(mPropertyBrowser); + QMenu contextMenu(mPropertiesView); - if (customPropertiesSelected && propertyNames.size() == 1) { - const auto value = object->resolvedProperty(propertyNames.first()); - if (value.userType() == filePathTypeId()) { - const FilePath filePath = value.value(); - const QString localFile = filePath.url.toLocalFile(); + // Add properties specific to the just clicked property + if (auto focusedProperty = mPropertiesView->focusedProperty()) { + focusedProperty->addContextMenuActions(&contextMenu); - if (!localFile.isEmpty()) { - Utils::addOpenContainingFolderAction(contextMenu, localFile); + // Provide the Add, Remove and Reset actions also here + if (const auto actions = focusedProperty->actions()) { + if (!contextMenu.isEmpty()) + contextMenu.addSeparator(); - if (QFileInfo { localFile }.isFile()) - Utils::addOpenWithSystemEditorAction(contextMenu, localFile); + // Note: No "Remove" added here since it's covered below + + if (actions.testFlag(Property::Action::Add)) { + QAction *add = contextMenu.addAction(mAddIcon, tr("Add"), + focusedProperty, &Property::addRequested); + add->setEnabled(!actions.testFlag(Property::Action::AddDisabled)); + Utils::setThemeIcon(add, "add"); } - } else if (value.userType() == objectRefTypeId()) { - if (auto mapDocument = qobject_cast(mDocument)) { - const DisplayObjectRef objectRef(value.value(), mapDocument); - - contextMenu.addAction(QCoreApplication::translate("Tiled::PropertiesDock", "Go to Object"), [=] { - if (auto object = objectRef.object()) { - objectRef.mapDocument->setSelectedObjects({object}); - emit objectRef.mapDocument->focusMapObjectRequested(object); - } - })->setEnabled(objectRef.object()); + + if (actions.testFlag(Property::Action::Reset)) { + QAction *reset = contextMenu.addAction(mResetIcon, tr("Reset"), + focusedProperty, &Property::resetRequested); + reset->setEnabled(focusedProperty->isModified()); + Utils::setThemeIcon(reset, "edit-clear"); } } } @@ -2747,12 +2753,12 @@ void PropertiesWidget::showContextMenu(const QPoint &pos) ActionManager::applyMenuExtensions(&contextMenu, MenuIds::propertiesViewProperties); - const QPoint globalPos = mPropertyBrowser->mapToGlobal(pos); + const QPoint globalPos = mPropertiesView->mapToGlobal(pos); const QAction *selectedItem = contextMenu.exec(globalPos); if (selectedItem && convertMenu && selectedItem->parentWidget() == convertMenu) { QUndoStack *undoStack = mDocument->undoStack(); - undoStack->beginMacro(QCoreApplication::translate("Tiled::PropertiesDock", "Convert Property/Properties", nullptr, items.size())); + undoStack->beginMacro(QCoreApplication::translate("Tiled::PropertiesDock", "Convert Property/Properties", nullptr, properties.size())); for (const QString &propertyName : propertyNames) { QVariant propertyValue = object->property(propertyName); @@ -2766,8 +2772,16 @@ void PropertiesWidget::showContextMenu(const QPoint &pos) } undoStack->endMacro(); + + // Restore selected properties + QList selectedProperties; + for (const QString &name : propertyNames) { + if (auto property = mCustomProperties->property(name)) + selectedProperties.append(property); + } + if (!selectedProperties.isEmpty()) + mPropertiesView->setSelectedProperties(selectedProperties); } -#endif } bool PropertiesWidget::event(QEvent *event) diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index ce0dd22e95..ccb8f67302 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -84,6 +84,10 @@ public slots: void retranslateUi(); + QIcon mResetIcon; + QIcon mRemoveIcon; + QIcon mAddIcon; + QIcon mRenameIcon; Document *mDocument = nullptr; GroupProperty *mRootProperty = nullptr; ObjectProperties *mPropertiesObject = nullptr; diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index 625bb33a1f..c7f794a562 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -930,22 +930,8 @@ void PropertyWidget::paintEvent(QPaintEvent *event) void PropertyWidget::mousePressEvent(QMouseEvent *event) { - // Not 100% correct hack to make sure only one property is selected when - // the context menu opens. Handling of the context menu should probably be - // moved elsewhere. - if (event->button() == Qt::RightButton) - emit clicked(Qt::NoModifier); - else - emit clicked(event->modifiers()); - setFocus(Qt::MouseFocusReason); - - QWidget::mousePressEvent(event); -} - -void PropertyWidget::contextMenuEvent(QContextMenuEvent *event) -{ - emit m_property->contextMenuRequested(event->globalPos()); + emit mousePressed(event->button(), event->modifiers()); } } // namespace Tiled diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index bfe3c1cc69..d28e6a4e29 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -23,6 +23,7 @@ #include #include #include +#include #include class QLabel; @@ -373,15 +374,16 @@ class PropertyWidget : public QWidget bool isSelected() const { return m_selected; } signals: - void clicked(Qt::KeyboardModifiers modifiers); + void mousePressed(Qt::MouseButton button, Qt::KeyboardModifiers modifiers); protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override; - void contextMenuEvent(QContextMenuEvent *event) override; private: - Property *m_property; + // Protected by QPointer because PropertyWidget might outlive the Property + // for a short moment, due to delayed widget deletion. + QPointer m_property; bool m_selectable = false; bool m_selected = false; }; diff --git a/src/tiled/propertytypeseditor.cpp b/src/tiled/propertytypeseditor.cpp index 4b3063bfa8..54ab4e7dc6 100644 --- a/src/tiled/propertytypeseditor.cpp +++ b/src/tiled/propertytypeseditor.cpp @@ -953,8 +953,6 @@ void PropertyTypesEditor::addClassProperties() connect(mMembersProperty, &VariantMapProperty::valueChanged, this, &PropertyTypesEditor::classMembersChanged); - connect(mMembersProperty, &VariantMapProperty::renameRequested, - this, &PropertyTypesEditor::renameMember); connect(mMembersView, &PropertiesView::selectedPropertiesChanged, this, &PropertyTypesEditor::selectedMembersChanged); diff --git a/src/tiled/variantmapproperty.cpp b/src/tiled/variantmapproperty.cpp index b7b1a41263..99b1acf215 100644 --- a/src/tiled/variantmapproperty.cpp +++ b/src/tiled/variantmapproperty.cpp @@ -63,20 +63,23 @@ class ObjectRefProperty : public PropertyTemplate }); return editor; } + + void addContextMenuActions(QMenu *menu) override + { + auto objectRef = value(); + menu->addAction(QCoreApplication::translate("Tiled::PropertiesDock", "Go to Object"), [=] { + if (auto object = objectRef.object()) { + objectRef.mapDocument->setSelectedObjects({object}); + emit objectRef.mapDocument->focusMapObjectRequested(object); + } + })->setEnabled(objectRef.object()); + } }; VariantMapProperty::VariantMapProperty(const QString &name, QObject *parent) : GroupProperty(name, parent) - , m_resetIcon(QIcon(QStringLiteral(":/images/16/edit-clear.png"))) - , m_removeIcon(QIcon(QStringLiteral(":/images/16/remove.png"))) - , m_addIcon(QIcon(QStringLiteral(":/images/16/add.png"))) - , m_renameIcon(QIcon(QLatin1String(":/images/16/rename.png"))) { - m_resetIcon.addFile(QStringLiteral(":/images/24/edit-clear.png")); - m_removeIcon.addFile(QStringLiteral(":/images/22/remove.png")); - m_addIcon.addFile(QStringLiteral(":/images/22/add.png")); - connect(Preferences::instance(), &Preferences::propertyTypesChanged, this, &VariantMapProperty::propertyTypesChanged); } @@ -287,10 +290,6 @@ Property *VariantMapProperty::createProperty(const QStringList &path, property->setToolTip(QStringLiteral("%1 : %2") .arg(property->name(), typeName)); - - connect(property, &Property::contextMenuRequested, this, [=](const QPoint &globalPos) { - memberContextMenuRequested(property, path, globalPos); - }); } return property; @@ -429,54 +428,6 @@ void VariantMapProperty::emitMemberValueChanged(const QStringList &path, const Q emit valueChanged(); } -void VariantMapProperty::memberContextMenuRequested(Property *property, const QStringList &path, const QPoint &globalPos) -{ - QMenu menu; - - // Add Expand All and Collapse All actions to group properties - if (auto groupProperty = qobject_cast(property)) { - menu.addAction(tr("Expand All"), groupProperty, &GroupProperty::expandAll); - menu.addAction(tr("Collapse All"), groupProperty, &GroupProperty::collapseAll); - } - - // Provide the Add, Remove and Reset actions also here - if (isEnabled() && property->actions()) { - menu.addSeparator(); - - if (property->actions() & Property::Action::Add) { - QAction *add = menu.addAction(m_addIcon, tr("Add Property"), this, [this, name = path.first()] { - addMember(name, mSuggestions.value(name)); - }); - Utils::setThemeIcon(add, "add"); - } - if (property->actions() & Property::Action::Remove) { - QAction *remove = menu.addAction(m_removeIcon, tr("Remove Property"), this, [this, name = path.first()] { - removeMember(name); - }); - Utils::setThemeIcon(remove, "remove"); - - // If a property can be removed, it can also be renamed - menu.addAction(m_renameIcon, tr("Rename Property..."), this, [this, name = path.first()] { - emit renameRequested(name); - }); - } - if (property->actions() & Property::Action::Reset) { - QAction *reset = menu.addAction(m_resetIcon, tr("Reset Member"), this, [=] { - setClassMember(path, QVariant()); - emitValueChangedRecursively(property); - }); - reset->setEnabled(property->isModified()); - Utils::setThemeIcon(reset, "edit-clear"); - } - } - - // todo: Add "Convert" sub-menu - // todo: Add "Copy" and "Paste" actions - - if (!menu.isEmpty()) - menu.exec(globalPos); -} - AddValueProperty::AddValueProperty(QObject *parent) : Property(QString(), parent) @@ -533,7 +484,7 @@ QWidget *AddValueProperty::createLabel(int level, QWidget *parent) nameEdit->installEventFilter(this); - connect(qApp, &QApplication::focusChanged, nameEdit, [=] (QWidget *, QWidget *focusWidget) { + connect(qApp, &QApplication::focusChanged, this, [=](QWidget *, QWidget *focusWidget) { // Ignore focus in different windows (popups, dialogs, etc.) if (!focusWidget || focusWidget->window() != parent->window()) return; diff --git a/src/tiled/variantmapproperty.h b/src/tiled/variantmapproperty.h index 6c8304f44e..5e66c33181 100644 --- a/src/tiled/variantmapproperty.h +++ b/src/tiled/variantmapproperty.h @@ -52,7 +52,6 @@ class VariantMapProperty : public GroupProperty signals: void memberValueChanged(const QStringList &path, const QVariant &value); - void renameRequested(const QString &name); protected: virtual void propertyTypesChanged(); @@ -79,12 +78,6 @@ class VariantMapProperty : public GroupProperty void emitMemberValueChanged(const QStringList &path, const QVariant &value); - void memberContextMenuRequested(Property *property, const QStringList &path, const QPoint &globalPos); - - QIcon m_resetIcon; - QIcon m_removeIcon; - QIcon m_addIcon; - QIcon m_renameIcon; bool mEmittingValueChanged = false; QVariantMap mValue; QVariantMap mSuggestions;