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:
Ali Kianian
2022-12-29 22:42:30 +02:00
parent 7bccb8d714
commit 5368330534
9 changed files with 306 additions and 24 deletions

View File

@@ -132,6 +132,7 @@ add_qtc_library(Utils
qtcolorbutton.cpp qtcolorbutton.h
qtcprocess.cpp qtcprocess.h
qtcsettings.cpp qtcsettings.h
ranges.h
reloadpromptutils.cpp reloadpromptutils.h
removefiledialog.cpp removefiledialog.h
runextensions.cpp runextensions.h

95
src/libs/utils/ranges.h Normal file
View 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

View File

@@ -614,6 +614,7 @@ extend_qtc_plugin(QmlDesigner
designeractionmanagerview.cpp designeractionmanagerview.h
designericons.cpp designericons.h
findimplementation.cpp findimplementation.h
groupitemaction.cpp groupitemaction.h
layoutingridlayout.cpp layoutingridlayout.h
modelnodecontextmenu.cpp modelnodecontextmenu.h
modelnodecontextmenu_helper.cpp modelnodecontextmenu_helper.h

View File

@@ -75,7 +75,6 @@ const char decreaseIndexOfStackedContainerCommandId[] = "DecreaseIndexOfStackedC
const char flowAssignEffectCommandId[] = "AssignFlowEffect";
const char flowAssignCustomEffectCommandId[] = "AssignFlowCustomEffect";
const char addToGroupItemCommandId[] = "AddToGroupItem";
const char removeGroupItemCommandId[] = "RemoveToGroupItem";
const char fitRootToScreenCommandId[] = "FitRootToScreen";
const char fitSelectionToScreenCommandId[] = "FitSelectionToScreen";
const char editAnnotationsCommandId[] = "EditAnnotation";

View File

@@ -9,6 +9,7 @@
#include "designericons.h"
#include "designermcumanager.h"
#include "formatoperation.h"
#include "groupitemaction.h"
#include "modelnodecontextmenu_helper.h"
#include "qmldesignerconstants.h"
#include "qmleditormenu.h"
@@ -1593,12 +1594,10 @@ void DesignerActionManager::createDefaultDesignerActions()
&selectionEnabled,
&selectionEnabled));
addDesignerAction(new ActionGroup(
groupCategoryDisplayName,
groupCategory,
addDesignerAction(new GroupItemAction(
contextIcon(DesignerIcons::GroupSelectionIcon),
Priorities::Group,
&studioComponentsAvailableAndSelectionCanBeLayouted));
{},
Priorities::Group));
addDesignerAction(new ActionGroup(
flowCategoryDisplayName,
@@ -1745,24 +1744,6 @@ void DesignerActionManager::createDefaultDesignerActions()
&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(
addItemToStackedContainerCommandId,
addItemToStackedContainerDisplayName,

View File

@@ -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);
}

View File

@@ -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;
};
}

View File

@@ -223,6 +223,7 @@ public:
static bool isThisOrAncestorLocked(const ModelNode &node);
static ModelNode lowestCommonAncestor(const QList<ModelNode> &nodes);
static QList<ModelNode> pruneChildren(const QList<ModelNode> &nodes);
qint32 internalId() const;

View File

@@ -19,6 +19,7 @@
#include <rewriterview.h>
#include <utils/algorithm.h>
#include <utils/ranges.h>
#include <QHash>
#include <QRegularExpression>
@@ -1402,6 +1403,31 @@ ModelNode ModelNode::lowestCommonAncestor(const QList<ModelNode> &nodes)
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)
{
model()->d->setScriptFunctions(m_internalNode, scriptFunctionList);