2022-04-02 18:24:21 +05:00

769 lines
20 KiB
C++

/*
SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "itemcontainer.h"
#include "configoverlay.h"
#include "containmentlayoutmanager_debug.h"
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickWindow>
#include <QStyleHints>
#include <QTimer>
#include <cmath>
#include <PlasmaQuick/AppletQuickItem>
ItemContainer::ItemContainer(QQuickItem *parent)
: QQuickItem(parent)
{
setFiltersChildMouseEvents(true);
setFlags(QQuickItem::ItemIsFocusScope);
setActiveFocusOnTab(true);
setAcceptedMouseButtons(Qt::LeftButton);
setLayout(qobject_cast<AppletsLayout *>(parent));
m_editModeTimer = new QTimer(this);
m_editModeTimer->setSingleShot(true);
connect(this, &QQuickItem::parentChanged, this, [this]() {
setLayout(qobject_cast<AppletsLayout *>(parentItem()));
});
connect(m_editModeTimer, &QTimer::timeout, this, [this]() {
setEditMode(true);
});
setKeepMouseGrab(true);
m_sizeHintAdjustTimer = new QTimer(this);
m_sizeHintAdjustTimer->setSingleShot(true);
m_sizeHintAdjustTimer->setInterval(0);
connect(m_sizeHintAdjustTimer, &QTimer::timeout, this, &ItemContainer::sizeHintsChanged);
}
ItemContainer::~ItemContainer()
{
disconnect(this, &QQuickItem::parentChanged, this, nullptr);
if (m_contentItem) {
m_contentItem->setEnabled(true);
}
}
QString ItemContainer::key() const
{
return m_key;
}
void ItemContainer::setKey(const QString &key)
{
if (m_key == key) {
return;
}
m_key = key;
Q_EMIT keyChanged();
}
bool ItemContainer::editMode() const
{
return m_editMode;
}
bool ItemContainer::dragActive() const
{
return m_dragActive;
}
void ItemContainer::cancelEdit()
{
m_editModeTimer->stop();
m_mouseDown = false;
setEditMode(false);
}
void ItemContainer::setEditMode(bool editMode)
{
if (m_editMode == editMode) {
return;
}
if (editMode && editModeCondition() == Locked) {
return;
}
m_editMode = editMode;
if (m_editModeCondition != AfterMouseOver || (m_layout && m_layout->editMode())) {
m_contentItem->setEnabled(!editMode);
}
if (editMode) {
setZ(1);
} else {
setZ(0);
}
if (m_mouseDown) {
sendUngrabRecursive(m_contentItem);
grabMouse();
}
if (m_dragActive != editMode && m_mouseDown) {
m_dragActive = editMode && m_mouseDown;
Q_EMIT dragActiveChanged();
}
setConfigOverlayVisible(editMode);
Q_EMIT editModeChanged(editMode);
}
ItemContainer::EditModeCondition ItemContainer::editModeCondition() const
{
if (m_layout && m_layout->editModeCondition() == AppletsLayout::Locked) {
return Locked;
}
return m_editModeCondition;
}
void ItemContainer::setEditModeCondition(EditModeCondition condition)
{
if (condition == m_editModeCondition) {
return;
}
if (condition == Locked) {
setEditMode(false);
}
m_editModeCondition = condition;
setAcceptHoverEvents(condition == AfterMouseOver || (m_layout && m_layout->editMode()));
Q_EMIT editModeConditionChanged();
}
AppletsLayout::PreferredLayoutDirection ItemContainer::preferredLayoutDirection() const
{
return m_preferredLayoutDirection;
}
void ItemContainer::setPreferredLayoutDirection(AppletsLayout::PreferredLayoutDirection direction)
{
if (direction == m_preferredLayoutDirection) {
return;
}
m_preferredLayoutDirection = direction;
Q_EMIT preferredLayoutDirectionChanged();
}
void ItemContainer::setLayout(AppletsLayout *layout)
{
if (m_layout == layout) {
return;
}
if (m_layout) {
disconnect(m_layout, &AppletsLayout::editModeConditionChanged, this, nullptr);
disconnect(m_layout, &AppletsLayout::editModeChanged, this, nullptr);
if (m_editMode) {
m_layout->hidePlaceHolder();
}
}
m_layout = layout;
if (!layout) {
Q_EMIT layoutChanged();
return;
}
if (parentItem() != layout) {
setParentItem(layout);
}
connect(m_layout, &AppletsLayout::editModeConditionChanged, this, [this]() {
if (m_layout->editModeCondition() == AppletsLayout::Locked) {
setEditMode(false);
}
if ((m_layout->editModeCondition() == AppletsLayout::Locked) != (m_editModeCondition == ItemContainer::Locked)) {
Q_EMIT editModeConditionChanged();
}
});
connect(m_layout, &AppletsLayout::editModeChanged, this, [this]() {
setAcceptHoverEvents(m_editModeCondition == AfterMouseOver || m_layout->editMode());
});
Q_EMIT layoutChanged();
}
AppletsLayout *ItemContainer::layout() const
{
return m_layout;
}
void ItemContainer::syncChildItemsGeometry(const QSizeF &size)
{
if (m_contentItem) {
m_contentItem->setPosition(QPointF(m_leftPadding, m_topPadding));
m_contentItem->setSize(QSizeF(size.width() - m_leftPadding - m_rightPadding, size.height() - m_topPadding - m_bottomPadding));
}
if (m_backgroundItem) {
m_backgroundItem->setPosition(QPointF(0, 0));
m_backgroundItem->setSize(size);
}
if (m_configOverlay) {
m_configOverlay->setPosition(QPointF(0, 0));
m_configOverlay->setSize(size);
}
}
QQmlComponent *ItemContainer::configOverlayComponent() const
{
return m_configOverlayComponent;
}
void ItemContainer::setConfigOverlayComponent(QQmlComponent *component)
{
if (component == m_configOverlayComponent) {
return;
}
m_configOverlayComponent = component;
if (m_configOverlay) {
m_configOverlay->deleteLater();
m_configOverlay = nullptr;
}
Q_EMIT configOverlayComponentChanged();
}
ConfigOverlay *ItemContainer::configOverlayItem() const
{
return m_configOverlay;
}
QSizeF ItemContainer::initialSize() const
{
return m_initialSize;
}
void ItemContainer::setInitialSize(const QSizeF &size)
{
if (m_initialSize == size) {
return;
}
m_initialSize = size;
Q_EMIT initialSizeChanged();
}
bool ItemContainer::configOverlayVisible() const
{
return m_configOverlay && m_configOverlay->open();
}
void ItemContainer::setConfigOverlayVisible(bool visible)
{
if (!m_configOverlayComponent) {
return;
}
if (visible == configOverlayVisible()) {
return;
}
if (visible && !m_configOverlay) {
QQmlContext *context = QQmlEngine::contextForObject(this);
Q_ASSERT(context);
QObject *instance = m_configOverlayComponent->beginCreate(context);
m_configOverlay = qobject_cast<ConfigOverlay *>(instance);
if (!m_configOverlay) {
qCWarning(CONTAINMENTLAYOUTMANAGER_DEBUG) << "Error: Applet configOverlay not of ConfigOverlay type";
if (instance) {
instance->deleteLater();
}
return;
}
m_configOverlay->setVisible(false);
m_configOverlay->setItemContainer(this);
m_configOverlay->setParentItem(this);
m_configOverlay->setTouchInteraction(m_mouseSynthetizedFromTouch);
m_configOverlay->setZ(999);
m_configOverlay->setPosition(QPointF(0, 0));
m_configOverlay->setSize(size());
m_configOverlayComponent->completeCreate();
connect(m_configOverlay, &ConfigOverlay::openChanged, this, [this]() {
Q_EMIT configOverlayVisibleChanged(m_configOverlay->open());
});
Q_EMIT configOverlayItemChanged();
}
if (m_configOverlay) {
m_configOverlay->setOpen(visible);
}
}
void ItemContainer::contentData_append(QQmlListProperty<QObject> *prop, QObject *object)
{
ItemContainer *container = static_cast<ItemContainer *>(prop->object);
if (!container) {
return;
}
// QQuickItem *item = qobject_cast<QQuickItem *>(object);
container->m_contentData.append(object);
}
int ItemContainer::contentData_count(QQmlListProperty<QObject> *prop)
{
ItemContainer *container = static_cast<ItemContainer *>(prop->object);
if (!container) {
return 0;
}
return container->m_contentData.count();
}
QObject *ItemContainer::contentData_at(QQmlListProperty<QObject> *prop, int index)
{
ItemContainer *container = static_cast<ItemContainer *>(prop->object);
if (!container) {
return nullptr;
}
if (index < 0 || index >= container->m_contentData.count()) {
return nullptr;
}
return container->m_contentData.value(index);
}
void ItemContainer::contentData_clear(QQmlListProperty<QObject> *prop)
{
ItemContainer *container = static_cast<ItemContainer *>(prop->object);
if (!container) {
return;
}
return container->m_contentData.clear();
}
QQmlListProperty<QObject> ItemContainer::contentData()
{
return QQmlListProperty<QObject>(this, nullptr, contentData_append, contentData_count, contentData_at, contentData_clear);
}
void ItemContainer::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
syncChildItemsGeometry(newGeometry.size());
QQuickItem::geometryChanged(newGeometry, oldGeometry);
Q_EMIT contentWidthChanged();
Q_EMIT contentHeightChanged();
}
void ItemContainer::componentComplete()
{
if (!m_contentItem) {
// qWarning()<<"Creating default contentItem";
m_contentItem = new QQuickItem(this);
syncChildItemsGeometry(size());
}
for (auto *o : qAsConst(m_contentData)) {
QQuickItem *item = qobject_cast<QQuickItem *>(o);
if (item) {
item->setParentItem(m_contentItem);
}
}
// Search for the Layout attached property
// Qt6: this should become public api
// https://bugreports.qt.io/browse/QTBUG-77103
for (auto *o : children()) {
if (o->inherits("QQuickLayoutAttached")) {
m_layoutAttached = o;
}
}
if (m_layoutAttached) {
// NOTE: new syntax cannot be used because we don't have access to the QQuickLayoutAttached class
connect(m_layoutAttached, SIGNAL(minimumHeightChanged()), m_sizeHintAdjustTimer, SLOT(start()));
connect(m_layoutAttached, SIGNAL(minimumWidthChanged()), m_sizeHintAdjustTimer, SLOT(start()));
connect(m_layoutAttached, SIGNAL(preferredHeightChanged()), m_sizeHintAdjustTimer, SLOT(start()));
connect(m_layoutAttached, SIGNAL(preferredWidthChanged()), m_sizeHintAdjustTimer, SLOT(start()));
connect(m_layoutAttached, SIGNAL(maximumHeightChanged()), m_sizeHintAdjustTimer, SLOT(start()));
connect(m_layoutAttached, SIGNAL(maximumWidthChanged()), m_sizeHintAdjustTimer, SLOT(start()));
}
QQuickItem::componentComplete();
}
void ItemContainer::sendUngrabRecursive(QQuickItem *item)
{
if (!item || !item->window()) {
return;
}
for (auto *child : item->childItems()) {
sendUngrabRecursive(child);
}
QEvent ev(QEvent::UngrabMouse);
QCoreApplication::sendEvent(item, &ev);
}
bool ItemContainer::childMouseEventFilter(QQuickItem *item, QEvent *event)
{
// Don't filter the configoverlay
if (item == m_configOverlay || (m_configOverlay && m_configOverlay->isAncestorOf(item)) || (!m_editMode && m_editModeCondition == Manual)) {
return QQuickItem::childMouseEventFilter(item, event);
}
// give more time before closing
if (m_closeEditModeTimer && m_closeEditModeTimer->isActive()) {
m_closeEditModeTimer->start();
}
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
if (me->button() != Qt::LeftButton && !(me->buttons() & Qt::LeftButton)) {
return QQuickItem::childMouseEventFilter(item, event);
}
forceActiveFocus(Qt::MouseFocusReason);
m_mouseDown = true;
m_mouseSynthetizedFromTouch = me->source() == Qt::MouseEventSynthesizedBySystem || me->source() == Qt::MouseEventSynthesizedByQt;
if (m_configOverlay) {
m_configOverlay->setTouchInteraction(m_mouseSynthetizedFromTouch);
}
const bool wasEditMode = m_editMode;
if (m_layout && m_layout->editMode()) {
setEditMode(true);
} else if (m_editModeCondition == AfterPressAndHold) {
m_editModeTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
}
m_lastMousePosition = me->windowPos();
m_mouseDownPosition = me->windowPos();
if (m_editMode && !wasEditMode) {
event->accept();
return true;
}
} else if (event->type() == QEvent::MouseMove) {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
if (!m_editMode && QPointF(me->windowPos() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) {
m_editModeTimer->stop();
} else if (m_editMode) {
event->accept();
}
} else if (event->type() == QEvent::MouseButtonRelease) {
m_editModeTimer->stop();
m_mouseDown = false;
m_mouseSynthetizedFromTouch = false;
ungrabMouse();
event->accept();
m_dragActive = false;
if (m_editMode) {
Q_EMIT dragActiveChanged();
}
}
return QQuickItem::childMouseEventFilter(item, event);
}
void ItemContainer::mousePressEvent(QMouseEvent *event)
{
forceActiveFocus(Qt::MouseFocusReason);
if (!m_editMode && m_editModeCondition == Manual) {
return;
}
m_mouseDown = true;
m_mouseSynthetizedFromTouch = event->source() == Qt::MouseEventSynthesizedBySystem || event->source() == Qt::MouseEventSynthesizedByQt;
if (m_configOverlay) {
m_configOverlay->setTouchInteraction(m_mouseSynthetizedFromTouch);
}
if (m_layout && m_layout->editMode()) {
setEditMode(true);
}
if (m_editMode) {
grabMouse();
setCursor(Qt::ClosedHandCursor);
m_dragActive = true;
Q_EMIT dragActiveChanged();
} else if (m_editModeCondition == AfterPressAndHold) {
m_editModeTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
}
m_lastMousePosition = event->windowPos();
m_mouseDownPosition = event->windowPos();
event->accept();
}
void ItemContainer::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event);
if (!m_layout || (!m_editMode && m_editModeCondition == Manual)) {
return;
}
m_mouseDown = false;
m_mouseSynthetizedFromTouch = false;
m_editModeTimer->stop();
ungrabMouse();
if (m_editMode && !m_layout->itemIsManaged(this)) {
m_layout->hidePlaceHolder();
m_layout->positionItem(this);
}
m_dragActive = false;
if (m_editMode) {
Q_EMIT dragActiveChanged();
setCursor(Qt::OpenHandCursor);
}
event->accept();
}
void ItemContainer::mouseMoveEvent(QMouseEvent *event)
{
if ((event->button() == Qt::NoButton && event->buttons() == Qt::NoButton) || (!m_editMode && m_editModeCondition == Manual)) {
return;
}
if (!m_editMode && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) {
if (m_editModeCondition == AfterPress) {
setEditMode(true);
} else {
m_editModeTimer->stop();
}
}
if (!m_editMode) {
return;
}
if (m_layout && m_layout->itemIsManaged(this)) {
m_layout->releaseSpace(this);
grabMouse();
m_dragActive = true;
Q_EMIT dragActiveChanged();
} else {
setPosition(QPointF(x() + event->windowPos().x() - m_lastMousePosition.x(), y() + event->windowPos().y() - m_lastMousePosition.y()));
if (m_layout) {
m_layout->showPlaceHolderForItem(this);
}
Q_EMIT userDrag(QPointF(x(), y()), event->pos());
}
m_lastMousePosition = event->windowPos();
event->accept();
}
void ItemContainer::mouseUngrabEvent()
{
m_mouseDown = false;
m_mouseSynthetizedFromTouch = false;
m_editModeTimer->stop();
ungrabMouse();
if (m_layout && m_editMode && !m_layout->itemIsManaged(this)) {
m_layout->hidePlaceHolder();
m_layout->positionItem(this);
}
m_dragActive = false;
if (m_editMode) {
Q_EMIT dragActiveChanged();
}
}
void ItemContainer::hoverEnterEvent(QHoverEvent *event)
{
Q_UNUSED(event);
if (m_editModeCondition != AfterMouseOver && !m_layout->editMode()) {
return;
}
if (m_closeEditModeTimer) {
m_closeEditModeTimer->stop();
}
if (m_layout->editMode()) {
setCursor(Qt::OpenHandCursor);
setEditMode(true);
} else {
m_editModeTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
}
}
void ItemContainer::hoverLeaveEvent(QHoverEvent *event)
{
Q_UNUSED(event);
if (m_editModeCondition != AfterMouseOver && !m_layout->editMode()) {
return;
}
m_editModeTimer->stop();
if (!m_closeEditModeTimer) {
m_closeEditModeTimer = new QTimer(this);
m_closeEditModeTimer->setSingleShot(true);
m_closeEditModeTimer->setInterval(500);
connect(m_closeEditModeTimer, &QTimer::timeout, this, [this]() {
setEditMode(false);
});
}
m_closeEditModeTimer->start();
}
QQuickItem *ItemContainer::contentItem() const
{
return m_contentItem;
}
void ItemContainer::setContentItem(QQuickItem *item)
{
if (m_contentItem == item) {
return;
}
m_contentItem = item;
item->setParentItem(this);
m_contentItem->setPosition(QPointF(m_leftPadding, m_topPadding));
m_contentItem->setSize(QSizeF(width() - m_leftPadding - m_rightPadding, height() - m_topPadding - m_bottomPadding));
Q_EMIT contentItemChanged();
}
QQuickItem *ItemContainer::background() const
{
return m_backgroundItem;
}
void ItemContainer::setBackground(QQuickItem *item)
{
if (m_backgroundItem == item) {
return;
}
m_backgroundItem = item;
m_backgroundItem->setParentItem(this);
m_backgroundItem->setPosition(QPointF(0, 0));
m_backgroundItem->setSize(size());
Q_EMIT backgroundChanged();
}
int ItemContainer::leftPadding() const
{
return m_leftPadding;
}
void ItemContainer::setLeftPadding(int padding)
{
if (m_leftPadding == padding) {
return;
}
m_leftPadding = padding;
syncChildItemsGeometry(size());
Q_EMIT leftPaddingChanged();
Q_EMIT contentWidthChanged();
}
int ItemContainer::topPadding() const
{
return m_topPadding;
}
void ItemContainer::setTopPadding(int padding)
{
if (m_topPadding == padding) {
return;
}
m_topPadding = padding;
syncChildItemsGeometry(size());
Q_EMIT topPaddingChanged();
Q_EMIT contentHeightChanged();
}
int ItemContainer::rightPadding() const
{
return m_rightPadding;
}
void ItemContainer::setRightPadding(int padding)
{
if (m_rightPadding == padding) {
return;
}
m_rightPadding = padding;
syncChildItemsGeometry(size());
Q_EMIT rightPaddingChanged();
Q_EMIT contentWidthChanged();
}
int ItemContainer::bottomPadding() const
{
return m_bottomPadding;
}
void ItemContainer::setBottomPadding(int padding)
{
if (m_bottomPadding == padding) {
return;
}
m_bottomPadding = padding;
syncChildItemsGeometry(size());
Q_EMIT bottomPaddingChanged();
Q_EMIT contentHeightChanged();
}
int ItemContainer::contentWidth() const
{
return width() - m_leftPadding - m_rightPadding;
}
int ItemContainer::contentHeight() const
{
return height() - m_topPadding - m_bottomPadding;
}
#include "moc_itemcontainer.cpp"