File indexing completed on 2024-05-12 17:08:54

0001 /*
0002     SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "appletslayout.h"
0008 #include "appletcontainer.h"
0009 #include "containmentlayoutmanager_debug.h"
0010 #include "gridlayoutmanager.h"
0011 
0012 #include <QQmlContext>
0013 #include <QQmlEngine>
0014 #include <QStyleHints>
0015 #include <QTimer>
0016 
0017 // Plasma
0018 #include <Containment>
0019 #include <Corona>
0020 #include <PlasmaQuick/AppletQuickItem>
0021 #include <chrono>
0022 #include <plasma/containment.h>
0023 
0024 using namespace std::chrono_literals;
0025 
0026 AppletsLayout::AppletsLayout(QQuickItem *parent)
0027     : QQuickItem(parent)
0028 {
0029     m_layoutManager = new GridLayoutManager(this);
0030 
0031     setFlags(QQuickItem::ItemIsFocusScope);
0032     setAcceptedMouseButtons(Qt::LeftButton);
0033 
0034     m_saveLayoutTimer = new QTimer(this);
0035     m_saveLayoutTimer->setSingleShot(true);
0036     m_saveLayoutTimer->setInterval(100ms);
0037     connect(m_layoutManager, &AbstractLayoutManager::layoutNeedsSaving, m_saveLayoutTimer, QOverload<>::of(&QTimer::start));
0038     connect(m_saveLayoutTimer, &QTimer::timeout, this, [this]() {
0039         // We can't assume m_containment to be valid: if we load in a plasmoid that can run also
0040         // in "applet" mode, m_containment will never be valid
0041         if (!m_containment) {
0042             return;
0043         }
0044         // We can't save the layout during bootup, for performance reasons and to avoid race consitions as much as possible, so if we needto save and still
0045         // starting up, don't actually savenow, but we will when Corona::startupCompleted is emitted
0046 
0047         if (!m_configKey.isEmpty() && m_containment && m_containment->corona()->isStartupCompleted()) {
0048             const QString serializedConfig = m_layoutManager->serializeLayout();
0049             m_containment->config().writeEntry(m_configKey, serializedConfig);
0050             m_containment->config().writeEntry(m_fallbackConfigKey, serializedConfig);
0051             // FIXME: something more efficient
0052             m_layoutManager->parseLayout(serializedConfig);
0053             m_savedSize = size();
0054             m_containment->corona()->requireConfigSync();
0055         }
0056     });
0057 
0058     m_layoutChangeTimer = new QTimer(this);
0059     m_layoutChangeTimer->setSingleShot(true);
0060     m_layoutChangeTimer->setInterval(100ms);
0061     connect(m_layoutChangeTimer, &QTimer::timeout, this, [this]() {
0062         // We can't assume m_containment to be valid: if we load in a plasmoid that can run also
0063         // in "applet" mode, m_containment will never be valid
0064         if (!m_containment || width() <= 0 || height() <= 0 || m_relayoutLock) {
0065             return;
0066         }
0067 
0068         const QString &serializedConfig = m_containment->config().readEntry(m_configKey, "");
0069         if ((m_layoutChanges & ConfigKeyChange)) {
0070             if (!m_configKey.isEmpty() && m_containment) {
0071                 m_layoutManager->parseLayout(serializedConfig);
0072                 if (width() > 0 && height() > 0) {
0073                     if (m_geometryBeforeResolutionChange.isEmpty()) {
0074                         m_layoutManager->resetLayoutFromConfig(QRectF(), QRectF());
0075                     } else {
0076                         m_layoutManager->resetLayoutFromConfig(QRectF(x(), y(), width(), height()), m_geometryBeforeResolutionChange);
0077                     }
0078                     m_savedSize = size();
0079                 }
0080             }
0081         } else if (m_layoutChanges & SizeChange) {
0082             const QRect newGeom(x(), y(), width(), height());
0083             // The size has been restored from the last one it has been saved: restore that exact same layout
0084             if (newGeom.size() == m_savedSize) {
0085                 m_layoutManager->resetLayoutFromConfig(QRectF(), QRectF());
0086 
0087                 // If the resize is consequence of a screen resolution change, queue a relayout maintaining the distance between screen edges
0088             } else if (!m_geometryBeforeResolutionChange.isEmpty()) {
0089                 m_layoutManager->layoutGeometryChanged(newGeom, m_geometryBeforeResolutionChange);
0090                 m_geometryBeforeResolutionChange = QRectF();
0091 
0092                 // If the user doesn't move a widget after this is done, the widget positions won't be saved and they will be in the wrong
0093                 // places on next login, so save them now.
0094 
0095                 save();
0096             }
0097         }
0098         m_layoutChanges = NoChange;
0099     });
0100     m_pressAndHoldTimer = new QTimer(this);
0101     m_pressAndHoldTimer->setSingleShot(true);
0102     connect(m_pressAndHoldTimer, &QTimer::timeout, this, [this]() {
0103         setEditMode(true);
0104     });
0105 }
0106 
0107 AppletsLayout::~AppletsLayout()
0108 {
0109 }
0110 
0111 PlasmaQuick::AppletQuickItem *AppletsLayout::containment() const
0112 {
0113     return m_containmentItem;
0114 }
0115 
0116 void AppletsLayout::setContainment(PlasmaQuick::AppletQuickItem *containmentItem)
0117 {
0118     // Forbid changing containmentItem at runtime
0119     if (m_containmentItem || containmentItem == m_containmentItem || !containmentItem->applet() || !containmentItem->applet()->isContainment()) {
0120         qCWarning(CONTAINMENTLAYOUTMANAGER_DEBUG) << "Error: cannot change the containment to AppletsLayout";
0121         return;
0122     }
0123 
0124     // Can't assign containments that aren't parents
0125     QQuickItem *candidate = parentItem();
0126     while (candidate) {
0127         if (candidate == m_containmentItem) {
0128             break;
0129         }
0130         candidate = candidate->parentItem();
0131     }
0132     if (candidate != m_containmentItem) {
0133         return;
0134     }
0135 
0136     m_containmentItem = containmentItem;
0137     m_containment = static_cast<Plasma::Containment *>(m_containmentItem->applet());
0138 
0139     connect(m_containmentItem, SIGNAL(appletAdded(QObject *, int, int)), this, SLOT(appletAdded(QObject *, int, int)));
0140 
0141     connect(m_containmentItem, SIGNAL(appletRemoved(QObject *)), this, SLOT(appletRemoved(QObject *)));
0142 
0143     Q_EMIT containmentChanged();
0144 }
0145 
0146 QString AppletsLayout::configKey() const
0147 {
0148     return m_configKey;
0149 }
0150 
0151 void AppletsLayout::setConfigKey(const QString &key)
0152 {
0153     if (m_configKey == key) {
0154         return;
0155     }
0156 
0157     m_configKey = key;
0158 
0159     // Reloading everything from the new config is expansive, event compress it
0160     m_layoutChanges |= ConfigKeyChange;
0161     m_layoutChangeTimer->start();
0162 
0163     Q_EMIT configKeyChanged();
0164 }
0165 
0166 QString AppletsLayout::fallbackConfigKey() const
0167 {
0168     return m_fallbackConfigKey;
0169 }
0170 
0171 void AppletsLayout::setFallbackConfigKey(const QString &key)
0172 {
0173     if (m_fallbackConfigKey == key) {
0174         return;
0175     }
0176 
0177     m_fallbackConfigKey = key;
0178 
0179     Q_EMIT fallbackConfigKeyChanged();
0180 }
0181 
0182 bool AppletsLayout::relayoutLock() const
0183 {
0184     return m_relayoutLock;
0185 }
0186 
0187 void AppletsLayout::setRelayoutLock(bool lock)
0188 {
0189     if (lock == m_relayoutLock) {
0190         return;
0191     }
0192 
0193     m_relayoutLock = lock;
0194 
0195     if (!lock && m_layoutChanges != NoChange) {
0196         m_layoutChangeTimer->start();
0197     }
0198 
0199     Q_EMIT relayoutLockChanged();
0200 }
0201 
0202 QJSValue AppletsLayout::acceptsAppletCallback() const
0203 {
0204     return m_acceptsAppletCallback;
0205 }
0206 
0207 qreal AppletsLayout::minimumItemWidth() const
0208 {
0209     return m_minimumItemSize.width();
0210 }
0211 
0212 void AppletsLayout::setMinimumItemWidth(qreal width)
0213 {
0214     if (qFuzzyCompare(width, m_minimumItemSize.width())) {
0215         return;
0216     }
0217 
0218     m_minimumItemSize.setWidth(width);
0219 
0220     Q_EMIT minimumItemWidthChanged();
0221 }
0222 
0223 qreal AppletsLayout::minimumItemHeight() const
0224 {
0225     return m_minimumItemSize.height();
0226 }
0227 
0228 void AppletsLayout::setMinimumItemHeight(qreal height)
0229 {
0230     if (qFuzzyCompare(height, m_minimumItemSize.height())) {
0231         return;
0232     }
0233 
0234     m_minimumItemSize.setHeight(height);
0235 
0236     Q_EMIT minimumItemHeightChanged();
0237 }
0238 
0239 qreal AppletsLayout::defaultItemWidth() const
0240 {
0241     return m_defaultItemSize.width();
0242 }
0243 
0244 void AppletsLayout::setDefaultItemWidth(qreal width)
0245 {
0246     if (qFuzzyCompare(width, m_defaultItemSize.width())) {
0247         return;
0248     }
0249 
0250     m_defaultItemSize.setWidth(width);
0251 
0252     Q_EMIT defaultItemWidthChanged();
0253 }
0254 
0255 qreal AppletsLayout::defaultItemHeight() const
0256 {
0257     return m_defaultItemSize.height();
0258 }
0259 
0260 void AppletsLayout::setDefaultItemHeight(qreal height)
0261 {
0262     if (qFuzzyCompare(height, m_defaultItemSize.height())) {
0263         return;
0264     }
0265 
0266     m_defaultItemSize.setHeight(height);
0267 
0268     Q_EMIT defaultItemHeightChanged();
0269 }
0270 
0271 qreal AppletsLayout::cellWidth() const
0272 {
0273     return m_layoutManager->cellSize().width();
0274 }
0275 
0276 void AppletsLayout::setCellWidth(qreal width)
0277 {
0278     if (qFuzzyCompare(width, m_layoutManager->cellSize().width())) {
0279         return;
0280     }
0281 
0282     m_layoutManager->setCellSize(QSizeF(width, m_layoutManager->cellSize().height()));
0283 
0284     Q_EMIT cellWidthChanged();
0285 }
0286 
0287 qreal AppletsLayout::cellHeight() const
0288 {
0289     return m_layoutManager->cellSize().height();
0290 }
0291 
0292 void AppletsLayout::setCellHeight(qreal height)
0293 {
0294     if (qFuzzyCompare(height, m_layoutManager->cellSize().height())) {
0295         return;
0296     }
0297 
0298     m_layoutManager->setCellSize(QSizeF(m_layoutManager->cellSize().width(), height));
0299 
0300     Q_EMIT cellHeightChanged();
0301 }
0302 
0303 void AppletsLayout::setAcceptsAppletCallback(const QJSValue &callback)
0304 {
0305     if (m_acceptsAppletCallback.strictlyEquals(callback)) {
0306         return;
0307     }
0308 
0309     if (!callback.isNull() && !callback.isCallable()) {
0310         return;
0311     }
0312 
0313     m_acceptsAppletCallback = callback;
0314 
0315     Q_EMIT acceptsAppletCallbackChanged();
0316 }
0317 
0318 QQmlComponent *AppletsLayout::appletContainerComponent() const
0319 {
0320     return m_appletContainerComponent;
0321 }
0322 
0323 void AppletsLayout::setAppletContainerComponent(QQmlComponent *component)
0324 {
0325     if (m_appletContainerComponent == component) {
0326         return;
0327     }
0328 
0329     m_appletContainerComponent = component;
0330 
0331     Q_EMIT appletContainerComponentChanged();
0332 }
0333 
0334 AppletsLayout::EditModeCondition AppletsLayout::editModeCondition() const
0335 {
0336     return m_editModeCondition;
0337 }
0338 
0339 void AppletsLayout::setEditModeCondition(AppletsLayout::EditModeCondition condition)
0340 {
0341     if (m_editModeCondition == condition) {
0342         return;
0343     }
0344 
0345     if (m_editModeCondition == Locked) {
0346         setEditMode(false);
0347     }
0348 
0349     m_editModeCondition = condition;
0350 
0351     Q_EMIT editModeConditionChanged();
0352 }
0353 
0354 bool AppletsLayout::editMode() const
0355 {
0356     return m_editMode;
0357 }
0358 
0359 void AppletsLayout::setEditMode(bool editMode)
0360 {
0361     if (m_editMode == editMode) {
0362         return;
0363     }
0364 
0365     m_editMode = editMode;
0366 
0367     Q_EMIT editModeChanged();
0368 }
0369 
0370 ItemContainer *AppletsLayout::placeHolder() const
0371 {
0372     return m_placeHolder;
0373 }
0374 
0375 void AppletsLayout::setPlaceHolder(ItemContainer *placeHolder)
0376 {
0377     if (m_placeHolder == placeHolder) {
0378         return;
0379     }
0380 
0381     m_placeHolder = placeHolder;
0382     m_placeHolder->setParentItem(this);
0383     m_placeHolder->setZ(9999);
0384     m_placeHolder->setOpacity(false);
0385 
0386     Q_EMIT placeHolderChanged();
0387 }
0388 
0389 QQuickItem *AppletsLayout::eventManagerToFilter() const
0390 {
0391     return m_eventManagerToFilter;
0392 }
0393 
0394 void AppletsLayout::setEventManagerToFilter(QQuickItem *item)
0395 {
0396     if (m_eventManagerToFilter == item) {
0397         return;
0398     }
0399 
0400     m_eventManagerToFilter = item;
0401     setFiltersChildMouseEvents(m_eventManagerToFilter);
0402     Q_EMIT eventManagerToFilterChanged();
0403 }
0404 
0405 void AppletsLayout::save()
0406 {
0407     m_saveLayoutTimer->start();
0408 }
0409 
0410 void AppletsLayout::showPlaceHolderAt(const QRectF &geom)
0411 {
0412     if (!m_placeHolder) {
0413         return;
0414     }
0415 
0416     m_placeHolder->setPosition(geom.topLeft());
0417     m_placeHolder->setSize(geom.size());
0418 
0419     m_layoutManager->positionItem(m_placeHolder);
0420 
0421     m_placeHolder->setProperty("opacity", 1);
0422 }
0423 
0424 void AppletsLayout::showPlaceHolderForItem(ItemContainer *item)
0425 {
0426     if (!m_placeHolder) {
0427         return;
0428     }
0429 
0430     m_placeHolder->setPreferredLayoutDirection(item->preferredLayoutDirection());
0431     m_placeHolder->setPosition(item->position());
0432     m_placeHolder->setSize(item->size());
0433 
0434     m_layoutManager->positionItem(m_placeHolder);
0435 
0436     m_placeHolder->setProperty("opacity", 1);
0437 }
0438 
0439 void AppletsLayout::hidePlaceHolder()
0440 {
0441     if (!m_placeHolder) {
0442         return;
0443     }
0444 
0445     m_placeHolder->setProperty("opacity", 0);
0446 }
0447 
0448 bool AppletsLayout::isRectAvailable(qreal x, qreal y, qreal width, qreal height)
0449 {
0450     return m_layoutManager->isRectAvailable(QRectF(x, y, width, height));
0451 }
0452 
0453 bool AppletsLayout::itemIsManaged(ItemContainer *item)
0454 {
0455     if (!item) {
0456         return false;
0457     }
0458 
0459     return m_layoutManager->itemIsManaged(item);
0460 }
0461 
0462 void AppletsLayout::positionItem(ItemContainer *item)
0463 {
0464     if (!item) {
0465         return;
0466     }
0467 
0468     item->setParent(this);
0469     m_layoutManager->positionItemAndAssign(item);
0470 }
0471 
0472 void AppletsLayout::restoreItem(ItemContainer *item)
0473 {
0474     m_layoutManager->restoreItem(item);
0475 }
0476 
0477 void AppletsLayout::releaseSpace(ItemContainer *item)
0478 {
0479     if (!item) {
0480         return;
0481     }
0482 
0483     m_layoutManager->releaseSpace(item);
0484 }
0485 
0486 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0487 void AppletsLayout::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
0488 #else
0489 void AppletsLayout::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
0490 #endif
0491 {
0492     // Ignore completely moves without resize
0493     if (newGeometry.size() == oldGeometry.size()) {
0494 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0495         QQuickItem::geometryChanged(newGeometry, oldGeometry);
0496 #else
0497         QQuickItem::geometryChange(newGeometry, oldGeometry);
0498 #endif
0499         return;
0500     }
0501 
0502     // Don't care for anything happening before startup completion
0503     if (!m_containment || !m_containment->corona() || !m_containment->corona()->isStartupCompleted()) {
0504 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0505         QQuickItem::geometryChanged(newGeometry, oldGeometry);
0506 #else
0507         QQuickItem::geometryChange(newGeometry, oldGeometry);
0508 #endif
0509         return;
0510     }
0511 
0512     // Only do a layouting procedure if we received a valid size
0513     if (!newGeometry.isEmpty() && newGeometry != oldGeometry) {
0514         m_layoutChanges |= SizeChange;
0515         m_layoutChangeTimer->start();
0516     }
0517 
0518 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0519     QQuickItem::geometryChanged(newGeometry, oldGeometry);
0520 #else
0521     QQuickItem::geometryChange(newGeometry, oldGeometry);
0522 #endif
0523 }
0524 
0525 void AppletsLayout::updatePolish()
0526 {
0527     m_layoutManager->resetLayout();
0528     m_savedSize = size();
0529 }
0530 
0531 void AppletsLayout::componentComplete()
0532 {
0533     if (!m_containment || !m_containmentItem) {
0534         QQuickItem::componentComplete();
0535         return;
0536     }
0537 
0538     if (!m_configKey.isEmpty()) {
0539         const QString &serializedConfig = m_containment->config().readEntry(m_configKey, "");
0540         if (!serializedConfig.isEmpty()) {
0541             m_layoutManager->parseLayout(serializedConfig);
0542         } else {
0543             m_layoutManager->parseLayout(m_containment->config().readEntry(m_fallbackConfigKey, ""));
0544         }
0545     }
0546 
0547     const QList<QObject *> appletObjects = m_containmentItem->property("applets").value<QList<QObject *>>();
0548 
0549     for (auto *obj : appletObjects) {
0550         PlasmaQuick::AppletQuickItem *appletItem = qobject_cast<PlasmaQuick::AppletQuickItem *>(obj);
0551 
0552         if (!obj) {
0553             continue;
0554         }
0555 
0556         AppletContainer *container = createContainerForApplet(appletItem);
0557         if (width() > 0 && height() > 0) {
0558             m_layoutManager->positionItemAndAssign(container);
0559         }
0560     }
0561 
0562     // layout all extra non applet items
0563     if (width() > 0 && height() > 0) {
0564         for (auto *child : childItems()) {
0565             ItemContainer *item = qobject_cast<ItemContainer *>(child);
0566             if (item && item != m_placeHolder && !m_layoutManager->itemIsManaged(item)) {
0567                 m_layoutManager->positionItemAndAssign(item);
0568             }
0569         }
0570     }
0571 
0572     if (m_containment && m_containment->corona()) {
0573         // We inhibit save during startup, so actually save now that startup is completed
0574         connect(m_containment->corona(), &Plasma::Corona::startupCompleted, this, [this]() {
0575             save();
0576         });
0577         // 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
0578         // constant
0579         connect(m_containment->corona(), &Plasma::Corona::screenGeometryChanged, this, [this](int id) {
0580             if (m_containment->screen() == id) {
0581                 m_geometryBeforeResolutionChange = QRectF(x(), y(), width(), height());
0582                 m_layoutChangeTimer->start();
0583             }
0584         });
0585         // If the containment is moved to a different screen, treat it like a resolution change
0586         connect(m_containment, &Plasma::Containment::screenChanged, this, [this]() {
0587             m_geometryBeforeResolutionChange = QRectF(x(), y(), width(), height());
0588             m_layoutChangeTimer->start();
0589         });
0590     }
0591     QQuickItem::componentComplete();
0592 }
0593 
0594 bool AppletsLayout::childMouseEventFilter(QQuickItem *item, QEvent *event)
0595 {
0596     if (item != m_eventManagerToFilter) {
0597         return QQuickItem::childMouseEventFilter(item, event);
0598     }
0599 
0600     switch (event->type()) {
0601     case QEvent::MouseButtonPress: {
0602         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0603         if (me->buttons() & Qt::LeftButton) {
0604             mousePressEvent(me);
0605         }
0606         break;
0607     }
0608     case QEvent::MouseMove: {
0609         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0610         mouseMoveEvent(me);
0611         break;
0612     }
0613     case QEvent::MouseButtonRelease: {
0614         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0615         mouseReleaseEvent(me);
0616         break;
0617     }
0618     case QEvent::UngrabMouse:
0619         mouseUngrabEvent();
0620         break;
0621     default:
0622         break;
0623     }
0624 
0625     return QQuickItem::childMouseEventFilter(item, event);
0626 }
0627 
0628 void AppletsLayout::mousePressEvent(QMouseEvent *event)
0629 {
0630     forceActiveFocus(Qt::MouseFocusReason);
0631 
0632     // Only accept synthesized events i.e. touch events, because we only want
0633     // to support press-and-hold. Click-and-hold is weird. See 457979.
0634     if (!(event->source() == Qt::MouseEventSynthesizedBySystem || event->source() == Qt::MouseEventSynthesizedByQt)) {
0635         const auto children = childItems();
0636         // If any container is in edit mode, accept the press event so we can
0637         // cancel the edit mode. If not, don't accept the event so it can be
0638         // passed on to other parts.
0639         if (std::none_of(children.begin(), children.end(), [](QQuickItem *child) {
0640                 auto container = qobject_cast<ItemContainer *>(child);
0641                 return container ? container->editMode() : false;
0642             })) {
0643             event->setAccepted(false);
0644         }
0645         return;
0646     }
0647 
0648     if (!m_editMode && m_editModeCondition == AppletsLayout::Manual) {
0649         return;
0650     }
0651 
0652     if (!m_editMode && m_editModeCondition == AppletsLayout::AfterPressAndHold) {
0653         m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
0654     }
0655 
0656     m_mouseDownWasEditMode = m_editMode;
0657     m_mouseDownPosition = event->windowPos();
0658 }
0659 
0660 void AppletsLayout::mouseMoveEvent(QMouseEvent *event)
0661 {
0662     if (!m_editMode && m_editModeCondition == AppletsLayout::Manual) {
0663         return;
0664     }
0665 
0666     if (!m_editMode && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) {
0667         m_pressAndHoldTimer->stop();
0668     }
0669 }
0670 
0671 void AppletsLayout::mouseReleaseEvent(QMouseEvent *event)
0672 {
0673     if (m_editMode
0674         && m_mouseDownWasEditMode
0675         // By only accepting synthetyzed events, this makes the
0676         // close by tapping in any empty area only work with real
0677         // touch events, as we want a different behavior between desktop
0678         // and tablet mode
0679         && (event->source() == Qt::MouseEventSynthesizedBySystem || event->source() == Qt::MouseEventSynthesizedByQt)
0680         && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() < QGuiApplication::styleHints()->startDragDistance()) {
0681         setEditMode(false);
0682     }
0683 
0684     m_pressAndHoldTimer->stop();
0685 
0686     if (!m_editMode) {
0687         for (auto *child : childItems()) {
0688             ItemContainer *item = qobject_cast<ItemContainer *>(child);
0689             if (item && item != m_placeHolder) {
0690                 item->setEditMode(false);
0691             }
0692         }
0693     }
0694 }
0695 
0696 void AppletsLayout::mouseUngrabEvent()
0697 {
0698     m_pressAndHoldTimer->stop();
0699 }
0700 
0701 void AppletsLayout::appletAdded(QObject *applet, int x, int y)
0702 {
0703     PlasmaQuick::AppletQuickItem *appletItem = qobject_cast<PlasmaQuick::AppletQuickItem *>(applet);
0704 
0705     // maybe even an assert?
0706     if (!appletItem) {
0707         return;
0708     }
0709 
0710     if (m_acceptsAppletCallback.isCallable()) {
0711         QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
0712         Q_ASSERT(engine);
0713         QJSValueList args;
0714         args << engine->newQObject(applet) << QJSValue(x) << QJSValue(y);
0715 
0716         if (!m_acceptsAppletCallback.call(args).toBool()) {
0717             Q_EMIT appletRefused(applet, x, y);
0718             return;
0719         }
0720     }
0721 
0722     AppletContainer *container = createContainerForApplet(appletItem);
0723     container->setPosition(QPointF(x, y));
0724     container->setVisible(true);
0725 
0726     m_layoutManager->positionItemAndAssign(container);
0727 }
0728 
0729 void AppletsLayout::appletRemoved(QObject *applet)
0730 {
0731     PlasmaQuick::AppletQuickItem *appletItem = qobject_cast<PlasmaQuick::AppletQuickItem *>(applet);
0732 
0733     // maybe even an assert?
0734     if (!appletItem) {
0735         return;
0736     }
0737 
0738     AppletContainer *container = m_containerForApplet.value(appletItem);
0739     if (!container) {
0740         return;
0741     }
0742 
0743     m_layoutManager->releaseSpace(container);
0744     m_containerForApplet.remove(appletItem);
0745     appletItem->setParentItem(this);
0746     container->deleteLater();
0747 }
0748 
0749 AppletContainer *AppletsLayout::createContainerForApplet(PlasmaQuick::AppletQuickItem *appletItem)
0750 {
0751     AppletContainer *container = m_containerForApplet.value(appletItem);
0752 
0753     if (container) {
0754         return container;
0755     }
0756 
0757     bool createdFromQml = true;
0758 
0759     if (m_appletContainerComponent) {
0760         QQmlContext *context = QQmlEngine::contextForObject(this);
0761         Q_ASSERT(context);
0762         QObject *instance = m_appletContainerComponent->beginCreate(context);
0763         container = qobject_cast<AppletContainer *>(instance);
0764         if (container) {
0765             container->setParentItem(this);
0766         } else {
0767             qCWarning(CONTAINMENTLAYOUTMANAGER_DEBUG) << "Error: provided component not an AppletContainer instance";
0768             if (instance) {
0769                 instance->deleteLater();
0770             }
0771             createdFromQml = false;
0772         }
0773     }
0774 
0775     if (!container) {
0776         container = new AppletContainer(this);
0777     }
0778 
0779     container->setVisible(false);
0780 
0781     const QSizeF appletSize = appletItem->size();
0782     container->setContentItem(appletItem);
0783 
0784     m_containerForApplet[appletItem] = container;
0785     container->setLayout(this);
0786     container->setKey(QLatin1String("Applet-") + QString::number(appletItem->applet()->id()));
0787 
0788     const bool geometryWasSaved = m_layoutManager->restoreItem(container);
0789 
0790     if (!geometryWasSaved) {
0791         container->setPosition(QPointF(appletItem->x() - container->leftPadding(), appletItem->y() - container->topPadding()));
0792 
0793         if (!appletSize.isEmpty()) {
0794             container->setSize(QSizeF(qMax(m_minimumItemSize.width(), appletSize.width() + container->leftPadding() + container->rightPadding()),
0795                                       qMax(m_minimumItemSize.height(), appletSize.height() + container->topPadding() + container->bottomPadding())));
0796         }
0797     }
0798 
0799     if (m_appletContainerComponent && createdFromQml) {
0800         m_appletContainerComponent->completeCreate();
0801     }
0802 
0803     // NOTE: This has to be done here as we need the component completed to have all the bindings evaluated
0804     if (!geometryWasSaved && appletSize.isEmpty()) {
0805         if (container->initialSize().width() > m_minimumItemSize.width() && container->initialSize().height() > m_minimumItemSize.height()) {
0806             const QSizeF size = m_layoutManager->cellAlignedContainingSize(container->initialSize());
0807             container->setSize(size);
0808         } else {
0809             container->setSize(
0810                 QSizeF(qMax(m_minimumItemSize.width(), m_defaultItemSize.width()), qMax(m_minimumItemSize.height(), m_defaultItemSize.height())));
0811         }
0812     }
0813 
0814     container->setVisible(true);
0815     appletItem->setVisible(true);
0816 
0817     return container;
0818 }
0819 
0820 #include "moc_appletslayout.cpp"