forked from qt-creator/qt-creator
QmlDesigner: Make context menu grouping a single action
- Grouping actions has been merged into one checkable item. - range and view namespaces are available for Utils. Task-number: QDS-8662 Change-Id: I17b2190caf425d0d8271dd68af494dbc2b332f09 Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io> Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -132,6 +132,7 @@ add_qtc_library(Utils
|
|||||||
qtcolorbutton.cpp qtcolorbutton.h
|
qtcolorbutton.cpp qtcolorbutton.h
|
||||||
qtcprocess.cpp qtcprocess.h
|
qtcprocess.cpp qtcprocess.h
|
||||||
qtcsettings.cpp qtcsettings.h
|
qtcsettings.cpp qtcsettings.h
|
||||||
|
ranges.h
|
||||||
reloadpromptutils.cpp reloadpromptutils.h
|
reloadpromptutils.cpp reloadpromptutils.h
|
||||||
removefiledialog.cpp removefiledialog.h
|
removefiledialog.cpp removefiledialog.h
|
||||||
runextensions.cpp runextensions.h
|
runextensions.cpp runextensions.h
|
||||||
|
|||||||
95
src/libs/utils/ranges.h
Normal file
95
src/libs/utils/ranges.h
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if __cplusplus >= 202002L
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
namespace ranges {
|
||||||
|
using std::ranges::reverse_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace views {
|
||||||
|
using std::views::reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Utils
|
||||||
|
#else
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
namespace ranges {
|
||||||
|
|
||||||
|
template <typename Container>
|
||||||
|
class reverse_view
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using value_type = typename Container::value_type;
|
||||||
|
using size_type = typename Container::size_type;
|
||||||
|
using Type = value_type;
|
||||||
|
using pointer = Type *;
|
||||||
|
using const_pointer = const Type *;
|
||||||
|
using reference = Type &;
|
||||||
|
using const_reference = const Type &;
|
||||||
|
|
||||||
|
using reverse_iterator = typename Container::iterator;
|
||||||
|
using iterator = std::reverse_iterator<reverse_iterator>;
|
||||||
|
|
||||||
|
using const_reverse_iterator = typename Container::const_iterator;
|
||||||
|
using const_iterator = std::reverse_iterator<const_reverse_iterator>;
|
||||||
|
|
||||||
|
using Iterator = iterator;
|
||||||
|
using ConstIterator = const_iterator;
|
||||||
|
|
||||||
|
reverse_view(const Container &k) : d(&k) {}
|
||||||
|
|
||||||
|
const_reverse_iterator rbegin() const noexcept { return d->begin(); }
|
||||||
|
const_reverse_iterator rend() const noexcept { return d->end(); }
|
||||||
|
const_reverse_iterator crbegin() const noexcept { return d->begin(); }
|
||||||
|
const_reverse_iterator crend() const noexcept { return d->end(); }
|
||||||
|
|
||||||
|
const_iterator begin() const noexcept { return const_iterator(rend()); }
|
||||||
|
const_iterator end() const noexcept { return const_iterator(rbegin()); }
|
||||||
|
const_iterator cbegin() const noexcept { return const_iterator(rend()); }
|
||||||
|
const_iterator cend() const noexcept { return const_iterator(rbegin()); }
|
||||||
|
const_iterator constBegin() const noexcept { return const_iterator(rend()); }
|
||||||
|
const_iterator constEnd() const noexcept { return const_iterator(rbegin()); }
|
||||||
|
|
||||||
|
const_reference front() const noexcept { return *cbegin(); }
|
||||||
|
const_reference back() const noexcept { return *crbegin(); }
|
||||||
|
|
||||||
|
[[nodiscard]] size_type size() const noexcept { return d->size(); }
|
||||||
|
[[nodiscard]] bool empty() const noexcept { return d->size() == 0; }
|
||||||
|
explicit operator bool() const { return d->size(); }
|
||||||
|
|
||||||
|
const_reference operator[](size_type idx) const
|
||||||
|
{
|
||||||
|
if (idx < size())
|
||||||
|
return *(begin() + idx);
|
||||||
|
|
||||||
|
throw std::out_of_range("bad index in reverse side");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Container *d;
|
||||||
|
};
|
||||||
|
} // namespace ranges
|
||||||
|
|
||||||
|
namespace views {
|
||||||
|
|
||||||
|
constexpr struct {} reverse;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline ranges::reverse_view<T> operator|(const T &container, const decltype(reverse)&)
|
||||||
|
{
|
||||||
|
return ranges::reverse_view(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namsepace views
|
||||||
|
} // namespace Utils
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -614,6 +614,7 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
designeractionmanagerview.cpp designeractionmanagerview.h
|
designeractionmanagerview.cpp designeractionmanagerview.h
|
||||||
designericons.cpp designericons.h
|
designericons.cpp designericons.h
|
||||||
findimplementation.cpp findimplementation.h
|
findimplementation.cpp findimplementation.h
|
||||||
|
groupitemaction.cpp groupitemaction.h
|
||||||
layoutingridlayout.cpp layoutingridlayout.h
|
layoutingridlayout.cpp layoutingridlayout.h
|
||||||
modelnodecontextmenu.cpp modelnodecontextmenu.h
|
modelnodecontextmenu.cpp modelnodecontextmenu.h
|
||||||
modelnodecontextmenu_helper.cpp modelnodecontextmenu_helper.h
|
modelnodecontextmenu_helper.cpp modelnodecontextmenu_helper.h
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ const char decreaseIndexOfStackedContainerCommandId[] = "DecreaseIndexOfStackedC
|
|||||||
const char flowAssignEffectCommandId[] = "AssignFlowEffect";
|
const char flowAssignEffectCommandId[] = "AssignFlowEffect";
|
||||||
const char flowAssignCustomEffectCommandId[] = "AssignFlowCustomEffect";
|
const char flowAssignCustomEffectCommandId[] = "AssignFlowCustomEffect";
|
||||||
const char addToGroupItemCommandId[] = "AddToGroupItem";
|
const char addToGroupItemCommandId[] = "AddToGroupItem";
|
||||||
const char removeGroupItemCommandId[] = "RemoveToGroupItem";
|
|
||||||
const char fitRootToScreenCommandId[] = "FitRootToScreen";
|
const char fitRootToScreenCommandId[] = "FitRootToScreen";
|
||||||
const char fitSelectionToScreenCommandId[] = "FitSelectionToScreen";
|
const char fitSelectionToScreenCommandId[] = "FitSelectionToScreen";
|
||||||
const char editAnnotationsCommandId[] = "EditAnnotation";
|
const char editAnnotationsCommandId[] = "EditAnnotation";
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "designericons.h"
|
#include "designericons.h"
|
||||||
#include "designermcumanager.h"
|
#include "designermcumanager.h"
|
||||||
#include "formatoperation.h"
|
#include "formatoperation.h"
|
||||||
|
#include "groupitemaction.h"
|
||||||
#include "modelnodecontextmenu_helper.h"
|
#include "modelnodecontextmenu_helper.h"
|
||||||
#include "qmldesignerconstants.h"
|
#include "qmldesignerconstants.h"
|
||||||
#include "qmleditormenu.h"
|
#include "qmleditormenu.h"
|
||||||
@@ -1593,12 +1594,10 @@ void DesignerActionManager::createDefaultDesignerActions()
|
|||||||
&selectionEnabled,
|
&selectionEnabled,
|
||||||
&selectionEnabled));
|
&selectionEnabled));
|
||||||
|
|
||||||
addDesignerAction(new ActionGroup(
|
addDesignerAction(new GroupItemAction(
|
||||||
groupCategoryDisplayName,
|
|
||||||
groupCategory,
|
|
||||||
contextIcon(DesignerIcons::GroupSelectionIcon),
|
contextIcon(DesignerIcons::GroupSelectionIcon),
|
||||||
Priorities::Group,
|
{},
|
||||||
&studioComponentsAvailableAndSelectionCanBeLayouted));
|
Priorities::Group));
|
||||||
|
|
||||||
addDesignerAction(new ActionGroup(
|
addDesignerAction(new ActionGroup(
|
||||||
flowCategoryDisplayName,
|
flowCategoryDisplayName,
|
||||||
@@ -1745,24 +1744,6 @@ void DesignerActionManager::createDefaultDesignerActions()
|
|||||||
&isLayout,
|
&isLayout,
|
||||||
&isLayout));
|
&isLayout));
|
||||||
|
|
||||||
addDesignerAction(new ModelNodeContextMenuAction(addToGroupItemCommandId,
|
|
||||||
addToGroupItemDisplayName,
|
|
||||||
{},
|
|
||||||
groupCategory,
|
|
||||||
QKeySequence("Ctrl+Shift+g"),
|
|
||||||
1,
|
|
||||||
&addToGroupItem,
|
|
||||||
&selectionCanBeLayouted));
|
|
||||||
|
|
||||||
addDesignerAction(new ModelNodeContextMenuAction(removeGroupItemCommandId,
|
|
||||||
removeGroupItemDisplayName,
|
|
||||||
{},
|
|
||||||
groupCategory,
|
|
||||||
QKeySequence(),
|
|
||||||
2,
|
|
||||||
&removeGroup,
|
|
||||||
&isGroup));
|
|
||||||
|
|
||||||
addDesignerAction(new ModelNodeFormEditorAction(
|
addDesignerAction(new ModelNodeFormEditorAction(
|
||||||
addItemToStackedContainerCommandId,
|
addItemToStackedContainerCommandId,
|
||||||
addItemToStackedContainerDisplayName,
|
addItemToStackedContainerDisplayName,
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include "groupitemaction.h"
|
||||||
|
|
||||||
|
#include "nodeabstractproperty.h"
|
||||||
|
#include "nodelistproperty.h"
|
||||||
|
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
|
||||||
|
using namespace QmlDesigner;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool selectionsAreSiblings(const QList<ModelNode> &selectedItems)
|
||||||
|
{
|
||||||
|
const QList<ModelNode> prunedSelectedItems = ModelNode::pruneChildren(selectedItems);
|
||||||
|
if (prunedSelectedItems.size() < 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ModelNode modelItemNode(prunedSelectedItems.first());
|
||||||
|
if (!modelItemNode.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ModelNode parentNode = modelItemNode.parentProperty().parentModelNode();
|
||||||
|
if (!parentNode.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const ModelNode &node : Utils::span(prunedSelectedItems).subspan(1)) {
|
||||||
|
if (!node.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (node.parentProperty().parentModelNode() != parentNode)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelNode availableGroupNode(const SelectionContext &selection)
|
||||||
|
{
|
||||||
|
if (!selection.isValid())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (selection.singleNodeIsSelected()) {
|
||||||
|
const ModelNode node = selection.currentSingleSelectedNode();
|
||||||
|
if (node.metaInfo().isQtQuickStudioComponentsGroupItem())
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModelNode parentNode = selection
|
||||||
|
.firstSelectedModelNode()
|
||||||
|
.parentProperty().parentModelNode();
|
||||||
|
|
||||||
|
if (!parentNode.isValid())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
QList<ModelNode> allSiblingNodes = parentNode.directSubModelNodes();
|
||||||
|
|
||||||
|
QList<ModelNode> selectedNodes = ModelNode::pruneChildren(selection.selectedModelNodes());
|
||||||
|
if (selectedNodes.size() != allSiblingNodes.size())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
Utils::sort(allSiblingNodes);
|
||||||
|
Utils::sort(selectedNodes);
|
||||||
|
|
||||||
|
if (allSiblingNodes != selectedNodes)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (parentNode.metaInfo().isQtQuickStudioComponentsGroupItem())
|
||||||
|
return parentNode;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool itemsAreGrouped(const SelectionContext &selection)
|
||||||
|
{
|
||||||
|
return availableGroupNode(selection).isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool groupingEnabled(const SelectionContext &selection)
|
||||||
|
{
|
||||||
|
if (selection.singleNodeIsSelected())
|
||||||
|
return itemsAreGrouped(selection);
|
||||||
|
else
|
||||||
|
return selectionsAreSiblings(selection.selectedModelNodes());
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeGroup(const ModelNode &group)
|
||||||
|
{
|
||||||
|
QmlItemNode groupItem(group);
|
||||||
|
QmlItemNode parent = groupItem.instanceParentItem();
|
||||||
|
|
||||||
|
if (!groupItem || !parent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
group.view()->executeInTransaction(
|
||||||
|
"removeGroup", [group, &groupItem, parent]() {
|
||||||
|
for (const ModelNode &modelNode : group.directSubModelNodes()) {
|
||||||
|
if (QmlVisualNode qmlItem = modelNode) {
|
||||||
|
QPointF pos = qmlItem.position().toPointF();
|
||||||
|
pos = groupItem.instanceTransform().map(pos);
|
||||||
|
qmlItem.setPosition(pos);
|
||||||
|
|
||||||
|
parent.modelNode().defaultNodeListProperty().reparentHere(modelNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupItem.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleGrouping(const SelectionContext &selection)
|
||||||
|
{
|
||||||
|
if (!selection.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ModelNode groupNode = availableGroupNode(selection);
|
||||||
|
|
||||||
|
if (groupNode.isValid())
|
||||||
|
removeGroup(groupNode);
|
||||||
|
else
|
||||||
|
ModelNodeOperations::addToGroupItem(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // blank namespace
|
||||||
|
|
||||||
|
GroupItemAction::GroupItemAction(const QIcon &icon,
|
||||||
|
const QKeySequence &key,
|
||||||
|
int priority)
|
||||||
|
: ModelNodeAction(ComponentCoreConstants::addToGroupItemCommandId,
|
||||||
|
{},
|
||||||
|
icon,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
key,
|
||||||
|
priority,
|
||||||
|
&toggleGrouping,
|
||||||
|
&groupingEnabled)
|
||||||
|
{
|
||||||
|
setCheckable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupItemAction::updateContext()
|
||||||
|
{
|
||||||
|
using namespace ComponentCoreConstants;
|
||||||
|
ModelNodeAction::updateContext();
|
||||||
|
action()->setText(QString::fromLatin1(action()->isChecked()
|
||||||
|
? removeGroupItemDisplayName
|
||||||
|
: addToGroupItemDisplayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupItemAction::isChecked(const SelectionContext &selectionContext) const
|
||||||
|
{
|
||||||
|
return itemsAreGrouped(selectionContext);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <modelnodecontextmenu_helper.h>
|
||||||
|
|
||||||
|
namespace QmlDesigner {
|
||||||
|
|
||||||
|
class GroupItemAction: public ModelNodeAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroupItemAction(const QIcon &icon,
|
||||||
|
const QKeySequence &key,
|
||||||
|
int priority);
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void updateContext() override;
|
||||||
|
virtual bool isChecked(const SelectionContext &selectionContext) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -223,6 +223,7 @@ public:
|
|||||||
|
|
||||||
static bool isThisOrAncestorLocked(const ModelNode &node);
|
static bool isThisOrAncestorLocked(const ModelNode &node);
|
||||||
static ModelNode lowestCommonAncestor(const QList<ModelNode> &nodes);
|
static ModelNode lowestCommonAncestor(const QList<ModelNode> &nodes);
|
||||||
|
static QList<ModelNode> pruneChildren(const QList<ModelNode> &nodes);
|
||||||
|
|
||||||
qint32 internalId() const;
|
qint32 internalId() const;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <rewriterview.h>
|
#include <rewriterview.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/ranges.h>
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
@@ -1402,6 +1403,31 @@ ModelNode ModelNode::lowestCommonAncestor(const QList<ModelNode> &nodes)
|
|||||||
return accumulatedNode;
|
return accumulatedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<ModelNode> ModelNode::pruneChildren(const QList<ModelNode> &nodes)
|
||||||
|
{
|
||||||
|
QList<ModelNode> forwardNodes;
|
||||||
|
QList<ModelNode> backNodes;
|
||||||
|
|
||||||
|
auto pushIfIsNotChild = [] (QList<ModelNode> &container, const ModelNode &node) {
|
||||||
|
bool hasAncestor = Utils::anyOf(container,
|
||||||
|
[node] (const ModelNode &testNode) -> bool {
|
||||||
|
return testNode.isAncestorOf(node);
|
||||||
|
});
|
||||||
|
if (!hasAncestor)
|
||||||
|
container.append(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const ModelNode &node : nodes | Utils::views::reverse) {
|
||||||
|
if (node)
|
||||||
|
pushIfIsNotChild(forwardNodes, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ModelNode &node : forwardNodes | Utils::views::reverse)
|
||||||
|
pushIfIsNotChild(backNodes, node);
|
||||||
|
|
||||||
|
return backNodes;
|
||||||
|
}
|
||||||
|
|
||||||
void ModelNode::setScriptFunctions(const QStringList &scriptFunctionList)
|
void ModelNode::setScriptFunctions(const QStringList &scriptFunctionList)
|
||||||
{
|
{
|
||||||
model()->d->setScriptFunctions(m_internalNode, scriptFunctionList);
|
model()->d->setScriptFunctions(m_internalNode, scriptFunctionList);
|
||||||
|
|||||||
Reference in New Issue
Block a user