diff --git a/components/containmentlayoutmanager/appletslayout.cpp b/components/containmentlayoutmanager/appletslayout.cpp index e0cbb9db0..35c010447 100644 --- a/components/containmentlayoutmanager/appletslayout.cpp +++ b/components/containmentlayoutmanager/appletslayout.cpp @@ -1,742 +1,741 @@ /* * Copyright 2019 by Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "appletslayout.h" #include "appletcontainer.h" #include "gridlayoutmanager.h" #include #include #include #include // Plasma #include #include #include AppletsLayout::AppletsLayout(QQuickItem *parent) : QQuickItem(parent) { m_layoutManager = new GridLayoutManager(this); setFlags(QQuickItem::ItemIsFocusScope); setAcceptedMouseButtons(Qt::LeftButton); m_saveLayoutTimer = new QTimer(this); m_saveLayoutTimer->setSingleShot(true); m_saveLayoutTimer->setInterval(100); connect(m_layoutManager, &AbstractLayoutManager::layoutNeedsSaving, m_saveLayoutTimer, QOverload<>::of(&QTimer::start)); connect(m_saveLayoutTimer, &QTimer::timeout, this, [this] () { if (!m_configKey.isEmpty() && m_containment && m_containment->corona()->isStartupCompleted()) { const QString serializedConfig = m_layoutManager->serializeLayout(); m_containment->config().writeEntry(m_configKey, serializedConfig); //FIXME: something more efficient m_layoutManager->parseLayout(serializedConfig); m_savedSize = size(); m_containment->corona()->requireConfigSync(); } }); m_configKeyChangeTimer = new QTimer(this); m_configKeyChangeTimer->setSingleShot(true); m_configKeyChangeTimer->setInterval(100); connect(m_configKeyChangeTimer, &QTimer::timeout, this, [this] () { if (!m_configKey.isEmpty() && m_containment) { m_layoutManager->parseLayout(m_containment->config().readEntry(m_configKey, "")); if (width() > 0 && height() > 0) { m_layoutManager->resetLayoutFromConfig(); m_savedSize = size(); } } }); m_pressAndHoldTimer = new QTimer(this); m_pressAndHoldTimer->setSingleShot(true); connect(m_pressAndHoldTimer, &QTimer::timeout, this, [this]() { setEditMode(true); }); m_sizeSyncTimer = new QTimer(this); m_sizeSyncTimer->setSingleShot(true); m_sizeSyncTimer->setInterval(150); connect(m_sizeSyncTimer, &QTimer::timeout, this, [this]() { const QRect newGeom(x(), y(), width(), height()); // The size has been restored from the last one it has been saved: restore that exact same layout if (newGeom.size() == m_savedSize) { m_layoutManager->resetLayoutFromConfig(); // If the resize is consequence of a screen resolution change, queue a relayout maintaining the distance between screen edges } else if (!m_geometryBeforeResolutionChange.isEmpty()) { m_layoutManager->layoutGeometryChanged(newGeom, m_geometryBeforeResolutionChange); m_geometryBeforeResolutionChange = QRectF(); // Heuristically relayout items only when the plasma startup is fully completed } else { polish(); } }); } AppletsLayout::~AppletsLayout() { } PlasmaQuick::AppletQuickItem *AppletsLayout::containment() const { return m_containmentItem; } void AppletsLayout::setContainment(PlasmaQuick::AppletQuickItem *containmentItem) { // Forbid changing containmentItem at runtime if (m_containmentItem || containmentItem == m_containmentItem || !containmentItem->applet() || !containmentItem->applet()->isContainment()) { qWarning() << "Error: cannot change the containment to AppletsLayout"; return; } // Can't assign containments that aren't parents QQuickItem *candidate = parentItem(); while (candidate) { if (candidate == m_containmentItem) { break; } candidate = candidate->parentItem(); } if (candidate != m_containmentItem) { return; } m_containmentItem = containmentItem; m_containment = static_cast(m_containmentItem->applet()); connect(m_containmentItem, SIGNAL(appletAdded(QObject *, int, int)), this, SLOT(appletAdded(QObject *, int, int))); connect(m_containmentItem, SIGNAL(appletRemoved(QObject *)), this, SLOT(appletRemoved(QObject *))); emit containmentChanged(); } QString AppletsLayout::configKey() const { return m_configKey; } void AppletsLayout::setConfigKey(const QString &key) { if (m_configKey == key) { return; } m_configKey = key; // Reloading everything from the new config is expansive, event compress it m_configKeyChangeTimer->start(); emit configKeyChanged(); } QJSValue AppletsLayout::acceptsAppletCallback() const { return m_acceptsAppletCallback; } qreal AppletsLayout::minimumItemWidth() const { return m_minimumItemSize.width(); } void AppletsLayout::setMinimumItemWidth(qreal width) { if (qFuzzyCompare(width, m_minimumItemSize.width())) { return; } m_minimumItemSize.setWidth(width); emit minimumItemWidthChanged(); } qreal AppletsLayout::minimumItemHeight() const { return m_minimumItemSize.height(); } void AppletsLayout::setMinimumItemHeight(qreal height) { if (qFuzzyCompare(height, m_minimumItemSize.height())) { return; } m_minimumItemSize.setHeight(height); emit minimumItemHeightChanged(); } qreal AppletsLayout::defaultItemWidth() const { return m_defaultItemSize.width(); } void AppletsLayout::setDefaultItemWidth(qreal width) { if (qFuzzyCompare(width, m_defaultItemSize.width())) { return; } m_defaultItemSize.setWidth(width); emit defaultItemWidthChanged(); } qreal AppletsLayout::defaultItemHeight() const { return m_defaultItemSize.height(); } void AppletsLayout::setDefaultItemHeight(qreal height) { if (qFuzzyCompare(height, m_defaultItemSize.height())) { return; } m_defaultItemSize.setHeight(height); emit defaultItemHeightChanged(); } qreal AppletsLayout::cellWidth() const { return m_layoutManager->cellSize().width(); } void AppletsLayout::setCellWidth(qreal width) { if (qFuzzyCompare(width, m_layoutManager->cellSize().width())) { return; } m_layoutManager->setCellSize(QSizeF(width, m_layoutManager->cellSize().height())); emit cellWidthChanged(); } qreal AppletsLayout::cellHeight() const { return m_layoutManager->cellSize().height(); } void AppletsLayout::setCellHeight(qreal height) { if (qFuzzyCompare(height, m_layoutManager->cellSize().height())) { return; } m_layoutManager->setCellSize(QSizeF(m_layoutManager->cellSize().width(), height)); emit cellHeightChanged(); } void AppletsLayout::setAcceptsAppletCallback(const QJSValue& callback) { if (m_acceptsAppletCallback.strictlyEquals(callback)) { return; } if (!callback.isNull() && !callback.isCallable()) { return; } m_acceptsAppletCallback = callback; Q_EMIT acceptsAppletCallbackChanged(); } QQmlComponent *AppletsLayout::appletContainerComponent() const { return m_appletContainerComponent; } void AppletsLayout::setAppletContainerComponent(QQmlComponent *component) { if (m_appletContainerComponent == component) { return; } m_appletContainerComponent = component; emit appletContainerComponentChanged(); } AppletsLayout::EditModeCondition AppletsLayout::editModeCondition() const { return m_editModeCondition; } void AppletsLayout::setEditModeCondition(AppletsLayout::EditModeCondition condition) { if (m_editModeCondition == condition) { return; } if (m_editModeCondition == Locked) { setEditMode(false); } m_editModeCondition = condition; emit editModeConditionChanged(); } bool AppletsLayout::editMode() const { return m_editMode; } void AppletsLayout::setEditMode(bool editMode) { if (m_editMode == editMode) { return; } m_editMode = editMode; emit editModeChanged(); } ItemContainer *AppletsLayout::placeHolder() const { return m_placeHolder; } void AppletsLayout::setPlaceHolder(ItemContainer *placeHolder) { if (m_placeHolder == placeHolder) { return; } m_placeHolder = placeHolder; m_placeHolder->setParentItem(this); m_placeHolder->setZ(9999); m_placeHolder->setOpacity(false); emit placeHolderChanged(); } QQuickItem *AppletsLayout::eventManagerToFilter() const { return m_eventManagerToFilter; } void AppletsLayout::setEventManagerToFilter(QQuickItem *item) { if (m_eventManagerToFilter == item) { return; } m_eventManagerToFilter = item; setFiltersChildMouseEvents(m_eventManagerToFilter); emit eventManagerToFilterChanged(); } - void AppletsLayout::save() { m_saveLayoutTimer->start(); } void AppletsLayout::showPlaceHolderAt(const QRectF &geom) { if (!m_placeHolder) { return; } m_placeHolder->setPosition(geom.topLeft()); m_placeHolder->setSize(geom.size()); m_layoutManager->positionItem(m_placeHolder); m_placeHolder->setProperty("opacity", 1); } void AppletsLayout::showPlaceHolderForItem(ItemContainer *item) { if (!m_placeHolder) { return; } m_placeHolder->setPreferredLayoutDirection(item->preferredLayoutDirection()); m_placeHolder->setPosition(item->position()); m_placeHolder->setSize(item->size()); m_layoutManager->positionItem(m_placeHolder); m_placeHolder->setProperty("opacity", 1); } void AppletsLayout::hidePlaceHolder() { if (!m_placeHolder) { return; } m_placeHolder->setProperty("opacity", 0); } bool AppletsLayout::isRectAvailable(qreal x, qreal y, qreal width, qreal height) { return m_layoutManager->isRectAvailable(QRectF(x, y, width, height)); } bool AppletsLayout::itemIsManaged(ItemContainer *item) { if (!item) { return false; } return m_layoutManager->itemIsManaged(item); } void AppletsLayout::positionItem(ItemContainer *item) { if (!item) { return; } item->setParent(this); m_layoutManager->positionItemAndAssign(item); } void AppletsLayout::restoreItem(ItemContainer *item) { m_layoutManager->restoreItem(item); } void AppletsLayout::releaseSpace(ItemContainer *item) { if (!item) { return; } m_layoutManager->releaseSpace(item); } void AppletsLayout::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { // Ignore completely moves without resize if (newGeometry.size() == oldGeometry.size()) { QQuickItem::geometryChanged(newGeometry, oldGeometry); return; } // Don't care for anything happening before startup completion if (!m_containment || !m_containment->corona() || !m_containment->corona()->isStartupCompleted()) { QQuickItem::geometryChanged(newGeometry, oldGeometry); return; } // Only do a layouting procedure if we received a valid size if (!newGeometry.isEmpty()) { m_sizeSyncTimer->start(); } QQuickItem::geometryChanged(newGeometry, oldGeometry); } void AppletsLayout::updatePolish() { m_layoutManager->resetLayout(); m_savedSize = size(); } void AppletsLayout::componentComplete() { if (!m_containment || !m_containmentItem) { QQuickItem::componentComplete(); return; } if (!m_configKey.isEmpty()) { m_layoutManager->parseLayout(m_containment->config().readEntry(m_configKey, "")); } QList appletObjects = m_containmentItem->property("applets").value >(); for (auto *obj : appletObjects) { PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(obj); if (!obj) { continue; } AppletContainer *container = createContainerForApplet(appletItem); if (width() > 0 && height() > 0) { m_layoutManager->positionItemAndAssign(container); } } //layout all extra non applet items if (width() > 0 && height() > 0) { for (auto *child : childItems()) { ItemContainer *item = qobject_cast(child); if (item && item != m_placeHolder && !m_layoutManager->itemIsManaged(item)) { m_layoutManager->positionItemAndAssign(item); } } } if (m_containment && m_containment->corona()) { connect(m_containment->corona(), &Plasma::Corona::startupCompleted, this, [](){ // m_savedSize = size(); }); // When the screen geometry changes, we need to know the geometry just before it did, so we can apply out heuristic of keeping the distance with borders constant connect(m_containment->corona(), &Plasma::Corona::screenGeometryChanged, this, [this](int id){ if (m_containment->screen() == id) { m_geometryBeforeResolutionChange = QRectF(x(), y(), width(), height()); } }); } QQuickItem::componentComplete(); } bool AppletsLayout::childMouseEventFilter(QQuickItem *item, QEvent *event) { if (item != m_eventManagerToFilter) { return QQuickItem::childMouseEventFilter(item, event); } switch (event->type()) { case QEvent::MouseButtonPress: { QMouseEvent *me = static_cast(event); if (me->buttons() & Qt::LeftButton) { mousePressEvent(me); } break; } case QEvent::MouseMove: { QMouseEvent *me = static_cast(event); mouseMoveEvent(me); break; } case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); mouseReleaseEvent(me); break; } case QEvent::UngrabMouse: mouseUngrabEvent(); break; default: break; } return QQuickItem::childMouseEventFilter(item, event); } void AppletsLayout::mousePressEvent(QMouseEvent *event) { forceActiveFocus(Qt::MouseFocusReason); if (!m_editMode && m_editModeCondition == AppletsLayout::Manual) { return; } if (!m_editMode && m_editModeCondition == AppletsLayout::AfterPressAndHold) { m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval()); } m_mouseDownWasEditMode = m_editMode; m_mouseDownPosition = event->windowPos(); //event->setAccepted(false); } void AppletsLayout::mouseMoveEvent(QMouseEvent *event) { if (!m_editMode && m_editModeCondition == AppletsLayout::Manual) { return; } if (!m_editMode && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) { m_pressAndHoldTimer->stop(); } } void AppletsLayout::mouseReleaseEvent(QMouseEvent *event) { if (m_editMode && m_mouseDownWasEditMode // By only accepting synthetyzed events, this makes the // close by tapping in any empty area only work with real // touch events, as we want a different behavior between desktop // and tablet mode && (event->source() == Qt::MouseEventSynthesizedBySystem || event->source() == Qt::MouseEventSynthesizedByQt) && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() < QGuiApplication::styleHints()->startDragDistance()) { setEditMode(false); } m_pressAndHoldTimer->stop(); if (!m_editMode) { for (auto *child : childItems()) { ItemContainer *item = qobject_cast(child); if (item && item != m_placeHolder) { item->setEditMode(false); } } } } void AppletsLayout::mouseUngrabEvent() { m_pressAndHoldTimer->stop(); } void AppletsLayout::appletAdded(QObject *applet, int x, int y) { PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(applet); //maybe even an assert? if (!appletItem) { return; } if (m_acceptsAppletCallback.isCallable()) { QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine(); Q_ASSERT(engine); QJSValueList args; args << engine->newQObject(applet) << QJSValue(x) << QJSValue(y); if (!m_acceptsAppletCallback.call(args).toBool()) { emit appletRefused(applet, x, y); return; } } AppletContainer *container = createContainerForApplet(appletItem); container->setPosition(QPointF(x, y)); container->setVisible(true); m_layoutManager->positionItemAndAssign(container); } void AppletsLayout::appletRemoved(QObject *applet) { PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(applet); //maybe even an assert? if (!appletItem) { return; } AppletContainer *container = m_containerForApplet.value(appletItem); if (!container) { return; } m_layoutManager->releaseSpace(container); m_containerForApplet.remove(appletItem); appletItem->setParentItem(this); container->deleteLater(); } AppletContainer *AppletsLayout::createContainerForApplet(PlasmaQuick::AppletQuickItem *appletItem) { AppletContainer *container = m_containerForApplet.value(appletItem); if (container) { return container; } bool createdFromQml = true; if (m_appletContainerComponent) { QQmlContext *context = QQmlEngine::contextForObject(this); Q_ASSERT(context); QObject *instance = m_appletContainerComponent->beginCreate(context); container = qobject_cast(instance); if (container) { container->setParentItem(this); } else { qWarning() << "Error: provided component not an AppletContainer instance"; if (instance) { instance->deleteLater(); } createdFromQml = false; } } if (!container) { container = new AppletContainer(this); } container->setVisible(false); const QSizeF appletSize = appletItem->size(); container->setContentItem(appletItem); m_containerForApplet[appletItem] = container; container->setLayout(this); container->setKey(QLatin1String("Applet-") + QString::number(appletItem->applet()->id())); const bool geometryWasSaved = m_layoutManager->restoreItem(container); if (!geometryWasSaved) { container->setPosition(QPointF(appletItem->x() - container->leftPadding(), appletItem->y() - container->topPadding())); if (!appletSize.isEmpty()) { container->setSize(QSizeF(qMax(m_minimumItemSize.width(), appletSize.width() + container->leftPadding() + container->rightPadding()), qMax(m_minimumItemSize.height(), appletSize.height() + container->topPadding() + container->bottomPadding()))); } } if (m_appletContainerComponent && createdFromQml) { m_appletContainerComponent->completeCreate(); } //NOTE: This has to be done here as we need the component completed to have all the bindings evaluated if (!geometryWasSaved && appletSize.isEmpty()) { if (container->initialSize().width() > m_minimumItemSize.width() && container->initialSize().height() > m_minimumItemSize.height()) { const QSizeF size = m_layoutManager->cellAlignedContainingSize( container->initialSize()); container->setSize(size); } else { container->setSize(QSizeF(qMax(m_minimumItemSize.width(), m_defaultItemSize.width()), qMax(m_minimumItemSize.height(), m_defaultItemSize.height()))); } } container->setVisible(true); appletItem->setVisible(true); return container; } #include "moc_appletslayout.cpp" diff --git a/components/containmentlayoutmanager/itemcontainer.cpp b/components/containmentlayoutmanager/itemcontainer.cpp index 488f91526..58b33bcdf 100644 --- a/components/containmentlayoutmanager/itemcontainer.cpp +++ b/components/containmentlayoutmanager/itemcontainer.cpp @@ -1,790 +1,797 @@ /* * Copyright 2019 by Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "itemcontainer.h" #include "configoverlay.h" #include #include #include #include #include #include #include #include ItemContainer::ItemContainer(QQuickItem *parent) : QQuickItem(parent) { setFiltersChildMouseEvents(true); setFlags(QQuickItem::ItemIsFocusScope); setActiveFocusOnTab(true); setAcceptedMouseButtons(Qt::LeftButton); setLayout(qobject_cast(parent)); m_editModeTimer = new QTimer(this); m_editModeTimer->setSingleShot(true); connect(this, &QQuickItem::parentChanged, this, [this]() { setLayout(qobject_cast(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() { 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; 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; emit dragActiveChanged(); } setConfigOverlayVisible(editMode); 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())); emit editModeConditionChanged(); } AppletsLayout::PreferredLayoutDirection ItemContainer::preferredLayoutDirection() const { return m_preferredLayoutDirection; } void ItemContainer::setPreferredLayoutDirection(AppletsLayout::PreferredLayoutDirection direction) { if (direction == m_preferredLayoutDirection) { return; } m_preferredLayoutDirection = direction; 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) { 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)) { emit editModeConditionChanged(); } }); connect(m_layout, &AppletsLayout::editModeChanged, this, [this]() { setAcceptHoverEvents(m_editModeCondition == AfterMouseOver || m_layout->editMode()); }); 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; } 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; 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(instance); if (!m_configOverlay) { qWarning() << "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]() { emit configOverlayVisibleChanged(m_configOverlay->open()); }); emit configOverlayItemChanged(); } if (m_configOverlay) { m_configOverlay->setOpen(visible); } } void ItemContainer::contentData_append(QQmlListProperty *prop, QObject *object) { ItemContainer *container = static_cast(prop->object); if (!container) { return; } // QQuickItem *item = qobject_cast(object); container->m_contentData.append(object); } int ItemContainer::contentData_count(QQmlListProperty *prop) { ItemContainer *container = static_cast(prop->object); if (!container) { return 0; } return container->m_contentData.count(); } QObject *ItemContainer::contentData_at(QQmlListProperty *prop, int index) { ItemContainer *container = static_cast(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 *prop) { ItemContainer *container = static_cast(prop->object); if (!container) { return; } return container->m_contentData.clear(); } QQmlListProperty ItemContainer::contentData() { return QQmlListProperty(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); emit contentWidthChanged(); emit contentHeightChanged(); } void ItemContainer::componentComplete() { if (!m_contentItem) { //qWarning()<<"Creating default contentItem"; m_contentItem = new QQuickItem(this); syncChildItemsGeometry(size()); } for (auto *o : m_contentData) { QQuickItem *item = qobject_cast(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(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(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) { 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; 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) { 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; 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); } 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) { 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)); 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()); emit backgroundChanged(); } int ItemContainer::leftPadding() const { return m_leftPadding; } void ItemContainer::setLeftPadding(int padding) { if (m_leftPadding == padding) { return; } m_leftPadding = padding; syncChildItemsGeometry(size()); emit leftPaddingChanged(); emit contentWidthChanged(); } int ItemContainer::topPadding() const { return m_topPadding; } void ItemContainer::setTopPadding(int padding) { if (m_topPadding == padding) { return; } m_topPadding = padding; syncChildItemsGeometry(size()); emit topPaddingChanged(); emit contentHeightChanged(); } int ItemContainer::rightPadding() const { return m_rightPadding; } void ItemContainer::setRightPadding(int padding) { if (m_rightPadding == padding) { return; } m_rightPadding = padding; syncChildItemsGeometry(size()); emit rightPaddingChanged(); emit contentWidthChanged(); } int ItemContainer::bottomPadding() const { return m_bottomPadding; } void ItemContainer::setBottomPadding(int padding) { if (m_bottomPadding == padding) { return; } m_bottomPadding = padding; syncChildItemsGeometry(size()); emit bottomPaddingChanged(); 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" diff --git a/components/containmentlayoutmanager/itemcontainer.h b/components/containmentlayoutmanager/itemcontainer.h index 542a548ba..199088fc8 100644 --- a/components/containmentlayoutmanager/itemcontainer.h +++ b/components/containmentlayoutmanager/itemcontainer.h @@ -1,245 +1,247 @@ /* * Copyright 2019 by Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #pragma once #include #include #include #include "appletslayout.h" class QTimer; class ConfigOverlay; class ItemContainer: public QQuickItem { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(AppletsLayout *layout READ layout NOTIFY layoutChanged) //TODO: make it unchangeable? probably not Q_PROPERTY(QString key READ key WRITE setKey NOTIFY keyChanged) Q_PROPERTY(ItemContainer::EditModeCondition editModeCondition READ editModeCondition WRITE setEditModeCondition NOTIFY editModeConditionChanged) Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged) Q_PROPERTY(bool dragActive READ dragActive NOTIFY dragActiveChanged) Q_PROPERTY(AppletsLayout::PreferredLayoutDirection preferredLayoutDirection READ preferredLayoutDirection WRITE setPreferredLayoutDirection NOTIFY preferredLayoutDirectionChanged) Q_PROPERTY(QQmlComponent *configOverlayComponent READ configOverlayComponent WRITE setConfigOverlayComponent NOTIFY configOverlayComponentChanged) Q_PROPERTY(bool configOverlayVisible READ configOverlayVisible WRITE setConfigOverlayVisible NOTIFY configOverlayVisibleChanged) Q_PROPERTY(QQuickItem *configOverlayItem READ configOverlayItem NOTIFY configOverlayItemChanged) /** * Initial size this container asks to have upon creation. only positive values are considered */ Q_PROPERTY(QSizeF initialSize READ initialSize WRITE setInitialSize NOTIFY initialSizeChanged) // From there mostly a clone of QQC2 Control Q_PROPERTY(QQuickItem *contentItem READ contentItem WRITE setContentItem NOTIFY contentItemChanged) Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged) /** * Padding adds a space between each edge of the content item and the background item, effectively controlling the size of the content item. */ Q_PROPERTY(int leftPadding READ leftPadding WRITE setLeftPadding NOTIFY leftPaddingChanged) Q_PROPERTY(int rightPadding READ rightPadding WRITE setRightPadding NOTIFY rightPaddingChanged) Q_PROPERTY(int topPadding READ topPadding WRITE setTopPadding NOTIFY topPaddingChanged) Q_PROPERTY(int bottomPadding READ bottomPadding WRITE setBottomPadding NOTIFY bottomPaddingChanged) /** * The size of the contents: the size of this item minus the padding */ Q_PROPERTY(int contentWidth READ contentWidth NOTIFY contentWidthChanged) Q_PROPERTY(int contentHeight READ contentHeight NOTIFY contentHeightChanged) Q_PROPERTY(QQmlListProperty contentData READ contentData FINAL) // Q_CLASSINFO("DeferredPropertyNames", "background,contentItem") Q_CLASSINFO("DefaultProperty", "contentData") public: enum EditModeCondition { Locked = AppletsLayout::EditModeCondition::Locked, Manual = AppletsLayout::EditModeCondition::Manual, AfterPressAndHold = AppletsLayout::EditModeCondition::AfterPressAndHold, AfterPress, AfterMouseOver }; Q_ENUMS(EditModeCondition) ItemContainer(QQuickItem *parent = nullptr); ~ItemContainer(); QQmlListProperty contentData(); QString key() const; void setKey(const QString &id); bool editMode() const; void setEditMode(bool edit); bool dragActive() const; + Q_INVOKABLE void cancelEdit(); + EditModeCondition editModeCondition() const; void setEditModeCondition(EditModeCondition condition); AppletsLayout::PreferredLayoutDirection preferredLayoutDirection() const; void setPreferredLayoutDirection(AppletsLayout::PreferredLayoutDirection direction); QQmlComponent *configOverlayComponent() const; void setConfigOverlayComponent(QQmlComponent *component); bool configOverlayVisible() const; void setConfigOverlayVisible(bool visible); //TODO: keep this accessible? ConfigOverlay *configOverlayItem() const; QSizeF initialSize() const; void setInitialSize(const QSizeF &size); // Control-like api QQuickItem *contentItem() const; void setContentItem(QQuickItem *item); QQuickItem *background() const; void setBackground(QQuickItem *item); // Setters and getters for the padding int leftPadding() const; void setLeftPadding(int padding); int topPadding() const; void setTopPadding(int padding); int rightPadding() const; void setRightPadding(int padding); int bottomPadding() const; void setBottomPadding(int padding); int contentWidth() const; int contentHeight() const; AppletsLayout *layout() const; // Not for QML void setLayout(AppletsLayout *layout); QObject *layoutAttached() const {return m_layoutAttached;} protected: void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; //void classBegin() override; void componentComplete() override; bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseUngrabEvent() override; void hoverEnterEvent(QHoverEvent *event) override; void hoverLeaveEvent(QHoverEvent *event) override; Q_SIGNALS: /** * The user manually dragged the ItemContainer around * @param newPosition new position of the ItemContainer in parent coordinates * @param dragCenter position in ItemContainer coordinates of the drag hotspot, i.e. where the user pressed the mouse or the * finger over the ItemContainer */ void userDrag(const QPointF &newPosition, const QPointF &dragCenter); void dragActiveChanged(); /** * The attached layout object changed some of its size hints */ void sizeHintsChanged(); //QML property notifiers void layoutChanged(); void keyChanged(); void editModeConditionChanged(); void editModeChanged(bool editMode); void preferredLayoutDirectionChanged(); void configOverlayComponentChanged(); void configOverlayItemChanged(); void initialSizeChanged(); void configOverlayVisibleChanged(bool configOverlayVisile); void backgroundChanged(); void contentItemChanged(); void leftPaddingChanged(); void rightPaddingChanged(); void topPaddingChanged(); void bottomPaddingChanged(); void contentWidthChanged(); void contentHeightChanged(); private: void syncChildItemsGeometry(const QSizeF &size); void sendUngrabRecursive(QQuickItem *item); //internal accessorts for the contentData QProperty static void contentData_append(QQmlListProperty *prop, QObject *object); static int contentData_count(QQmlListProperty *prop); static QObject *contentData_at(QQmlListProperty *prop, int index); static void contentData_clear(QQmlListProperty *prop); QPointer m_contentItem; QPointer m_backgroundItem; //Internal implementation detail: this is used to reparent all items to contentItem QList m_contentData; /** * Padding adds a space between each edge of the content item and the background item, effectively controlling the size of the content item. */ int m_leftPadding = 0; int m_rightPadding = 0; int m_topPadding = 0; int m_bottomPadding = 0; QString m_key; QPointer m_layout; QTimer *m_editModeTimer = nullptr; QTimer *m_closeEditModeTimer = nullptr; QTimer *m_sizeHintAdjustTimer = nullptr; QObject *m_layoutAttached = nullptr; EditModeCondition m_editModeCondition = Manual; QSizeF m_initialSize; QPointer m_configOverlayComponent; ConfigOverlay *m_configOverlay = nullptr; QPointF m_lastMousePosition = QPoint(-1, -1); QPointF m_mouseDownPosition = QPoint(-1, -1); AppletsLayout::PreferredLayoutDirection m_preferredLayoutDirection = AppletsLayout::Closest; bool m_editMode = false; bool m_mouseDown = false; bool m_mouseSynthetizedFromTouch = false; bool m_dragActive = false; };