diff --git a/src/columnview.cpp b/src/columnview.cpp index d810a654..9ae6ba3b 100644 --- a/src/columnview.cpp +++ b/src/columnview.cpp @@ -1,1595 +1,1595 @@ /* * Copyright 2019 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 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 "columnview.h" #include "columnview_p.h" #include #include #include #include #include #include #include #include QHash ColumnView::m_attachedObjects = QHash(); class QmlComponentsPoolSingleton { public: QmlComponentsPoolSingleton() {} static QmlComponentsPool *instance(QQmlEngine *engine); private: QHash m_instances; }; Q_GLOBAL_STATIC(QmlComponentsPoolSingleton, privateQmlComponentsPoolSelf) QmlComponentsPool *QmlComponentsPoolSingleton::instance(QQmlEngine *engine) { Q_ASSERT(engine); auto componentPool = privateQmlComponentsPoolSelf->m_instances.value(engine); if (componentPool) { return componentPool; } componentPool = new QmlComponentsPool(engine); QObject::connect(componentPool, &QObject::destroyed, [engine]() { if (privateQmlComponentsPoolSelf) { privateQmlComponentsPoolSelf->m_instances.remove(engine); } }); privateQmlComponentsPoolSelf->m_instances[engine] = componentPool; return componentPool; } QmlComponentsPool::QmlComponentsPool(QQmlEngine *engine) : QObject(engine) { QQmlComponent *component = new QQmlComponent(engine, this); component->setData(QByteArrayLiteral("import QtQuick 2.7\n" "import org.kde.kirigami 2.7 as Kirigami\n" "QtObject {\n" "id: root\n" "readonly property Kirigami.Units units: Kirigami.Units\n" "readonly property Component separator: Kirigami.Separator {" "property Item column\n" "visible: column.Kirigami.ColumnView.view.contentX < column.x;" "anchors.top: column.top;" "anchors.bottom: column.bottom;" "}\n" "readonly property Component rightSeparator: Kirigami.Separator {" "property Item column\n" "anchors.top: column.top;" "anchors.right: column.right;" "anchors.bottom: column.bottom;" "}" - "}"), QUrl()); + "}"), QUrl(QStringLiteral("columnview.cpp"))); m_instance = component->create(); //qWarning()<errors(); Q_ASSERT(m_instance); m_separatorComponent = m_instance->property("separator").value(); Q_ASSERT(m_separatorComponent); m_rightSeparatorComponent = m_instance->property("rightSeparator").value(); Q_ASSERT(m_rightSeparatorComponent); m_units = m_instance->property("units").value(); Q_ASSERT(m_units); connect(m_units, SIGNAL(gridUnitChanged()), this, SIGNAL(gridUnitChanged())); connect(m_units, SIGNAL(longDurationChanged()), this, SIGNAL(longDurationChanged())); } QmlComponentsPool::~QmlComponentsPool() {} ///////// ColumnViewAttached::ColumnViewAttached(QObject *parent) : QObject(parent) {} ColumnViewAttached::~ColumnViewAttached() {} void ColumnViewAttached::setIndex(int index) { if (!m_customFillWidth && m_view) { const bool oldFillWidth = m_fillWidth; m_fillWidth = index == m_view->count() - 1; if (oldFillWidth != m_fillWidth) { emit fillWidthChanged(); } } if (index == m_index) { return; } m_index = index; emit indexChanged(); } int ColumnViewAttached::index() const { return m_index; } void ColumnViewAttached::setFillWidth(bool fill) { if (m_view) { disconnect(m_view.data(), &ColumnView::countChanged, this, nullptr); } m_customFillWidth = true; if (fill == m_fillWidth) { return; } m_fillWidth = fill; emit fillWidthChanged(); if (m_view) { m_view->polish(); } } bool ColumnViewAttached::fillWidth() const { return m_fillWidth; } qreal ColumnViewAttached::reservedSpace() const { return m_reservedSpace; } void ColumnViewAttached::setReservedSpace(qreal space) { if (m_view) { disconnect(m_view.data(), &ColumnView::columnWidthChanged, this, nullptr); } m_customReservedSpace = true; if (qFuzzyCompare(space, m_reservedSpace)) { return; } m_reservedSpace = space; emit reservedSpaceChanged(); if (m_view) { m_view->polish(); } } ColumnView *ColumnViewAttached::view() { return m_view; } void ColumnViewAttached::setView(ColumnView *view) { if (view == m_view) { return; } if (m_view) { disconnect(m_view.data(), nullptr, this, nullptr); } m_view = view; if (!m_customFillWidth && m_view) { m_fillWidth = m_index == m_view->count() - 1; connect(m_view.data(), &ColumnView::countChanged, this, [this]() { m_fillWidth = m_index == m_view->count() - 1; emit fillWidthChanged(); }); } if (!m_customReservedSpace && m_view) { m_reservedSpace = m_view->columnWidth(); connect(m_view.data(), &ColumnView::columnWidthChanged, this, [this]() { m_reservedSpace = m_view->columnWidth(); emit reservedSpaceChanged(); }); } emit viewChanged(); } QQuickItem *ColumnViewAttached::originalParent() const { return m_originalParent; } void ColumnViewAttached::setOriginalParent(QQuickItem *parent) { m_originalParent = parent; } bool ColumnViewAttached::shouldDeleteOnRemove() const { return m_shouldDeleteOnRemove; } void ColumnViewAttached::setShouldDeleteOnRemove(bool del) { m_shouldDeleteOnRemove = del; } bool ColumnViewAttached::preventStealing() const { return m_preventStealing; } void ColumnViewAttached::setPreventStealing(bool prevent) { if (prevent == m_preventStealing) { return; } m_preventStealing = prevent; emit preventStealingChanged(); } bool ColumnViewAttached::isPinned() const { return m_pinned; } void ColumnViewAttached::setPinned(bool pinned) { if (pinned == m_pinned) { return; } m_pinned = pinned; emit pinnedChanged(); if (m_view) { m_view->polish(); } } ///////// ContentItem::ContentItem(ColumnView *parent) : QQuickItem(parent), m_view(parent) { setFlags(flags() | ItemIsFocusScope); m_slideAnim = new QPropertyAnimation(this); m_slideAnim->setTargetObject(this); m_slideAnim->setPropertyName("x"); //NOTE: the duration will be taked from kirigami units upon classBegin m_slideAnim->setDuration(0); m_slideAnim->setEasingCurve(QEasingCurve(QEasingCurve::InOutQuad)); connect(m_slideAnim, &QPropertyAnimation::finished, this, [this] () { if (!m_view->currentItem()) { m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem)); } else { QRectF mapped = m_view->currentItem()->mapRectToItem(m_view, QRectF(QPointF(0, 0), m_view->currentItem()->size())); if (!QRectF(QPointF(0, 0), m_view->size()).intersects(mapped)) { m_view->setCurrentIndex(m_items.indexOf(m_viewAnchorItem)); } } }); connect(this, &QQuickItem::xChanged, this, &ContentItem::layoutPinnedItems); } ContentItem::~ContentItem() {} void ContentItem::setBoundedX(qreal x) { if (!parentItem()) { return; } m_slideAnim->stop(); setX(qRound(qBound(qMin(0.0, -width()+parentItem()->width()), x, 0.0))); } void ContentItem::animateX(qreal newX) { if (!parentItem()) { return; } const qreal to = qRound(qBound(qMin(0.0, -width()+parentItem()->width()), newX, 0.0)); m_slideAnim->stop(); m_slideAnim->setStartValue(x()); m_slideAnim->setEndValue(to); m_slideAnim->start(); } void ContentItem::snapToItem() { QQuickItem *firstItem = childAt(viewportLeft(), 0); if (!firstItem) { return; } QQuickItem *nextItem = childAt(firstItem->x() + firstItem->width() + 1, 0); //need to make the last item visible? if (nextItem && ((m_view->dragging() && m_lastDragDelta < 0) || (!m_view->dragging() && width() - (viewportRight()) < viewportLeft() - firstItem->x()))) { m_viewAnchorItem = nextItem; animateX(-nextItem->x() + m_leftPinnedSpace); //The first one found? } else if ((m_view->dragging() && m_lastDragDelta >= 0) || (!m_view->dragging() && viewportLeft() <= firstItem->x() + firstItem->width()/2) || !nextItem) { m_viewAnchorItem = firstItem; animateX(-firstItem->x() + m_leftPinnedSpace); //the second? } else { m_viewAnchorItem = nextItem; animateX(-nextItem->x() + m_leftPinnedSpace); } } qreal ContentItem::viewportLeft() const { return -x() + m_leftPinnedSpace; } qreal ContentItem::viewportRight() const { return -x() + m_view->width() - m_rightPinnedSpace; } qreal ContentItem::childWidth(QQuickItem *child) { if (!parentItem()) { return 0.0; } ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(child, true)); if (m_columnResizeMode == ColumnView::SingleColumn) { return qRound(parentItem()->width()); } else if (attached->fillWidth()) { return qRound(qBound(m_columnWidth, (parentItem()->width() - attached->reservedSpace()), parentItem()->width())); } else if (m_columnResizeMode == ColumnView::FixedColumns) { return qRound(qMin(parentItem()->width(), m_columnWidth)); // DynamicColumns } else { //TODO:look for Layout size hints qreal width = child->implicitWidth(); if (width < 1.0) { width = m_columnWidth; } return qRound(qMin(m_view->width(), width)); } } void ContentItem::layoutItems() { setY(m_view->topPadding()); setHeight(m_view->height() - m_view->topPadding() - m_view->bottomPadding()); qreal implicitWidth = 0; qreal implicitHeight = 0; qreal partialWidth = 0; int i = 0; m_leftPinnedSpace = 0; m_rightPinnedSpace = 0; for (QQuickItem *child : m_items) { ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(child, true)); if (child->isVisible()) { if (attached->isPinned() && m_view->columnResizeMode() != ColumnView::SingleColumn) { QQuickItem *sep = nullptr; int sepWidth = 0; if (m_view->separatorVisible()) { sep = ensureRightSeparator(child); sepWidth = (sep ? sep->width() : 0); } const qreal width = childWidth(child); child->setSize(QSizeF(width + sepWidth, height())); child->setPosition(QPointF(qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth), 0.0)); child->setZ(1); if (partialWidth <= -x()) { m_leftPinnedSpace = qMax(m_leftPinnedSpace, width); } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) { m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width()); } partialWidth += width; } else { child->setSize(QSizeF(childWidth(child), height())); auto it = m_rightSeparators.find(child); if (it != m_rightSeparators.end()) { it.value()->deleteLater(); m_rightSeparators.erase(it); } child->setPosition(QPointF(partialWidth, 0.0)); child->setZ(0); partialWidth += child->width(); } } attached->setIndex(i++); implicitWidth += child->implicitWidth(); implicitHeight = qMax(implicitHeight, child->implicitHeight()); } setWidth(partialWidth); setImplicitWidth(implicitWidth); setImplicitHeight(implicitHeight); m_view->setImplicitWidth(implicitWidth); m_view->setImplicitHeight(implicitHeight + m_view->topPadding() + m_view->bottomPadding()); const qreal newContentX = m_viewAnchorItem ? -m_viewAnchorItem->x() : 0.0; if (m_shouldAnimate) { animateX(newContentX); } else { setBoundedX(newContentX); } updateVisibleItems(); } void ContentItem::layoutPinnedItems() { if (m_view->columnResizeMode() == ColumnView::SingleColumn) { return; } qreal partialWidth = 0; m_leftPinnedSpace = 0; m_rightPinnedSpace = 0; for (QQuickItem *child : m_items) { ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(child, true)); if (child->isVisible()) { if (attached->isPinned()) { QQuickItem *sep = nullptr; int sepWidth = 0; if (m_view->separatorVisible()) { sep = ensureRightSeparator(child); sepWidth = (sep ? sep->width() : 0); } child->setPosition(QPointF(qMin(qMax(-x(), partialWidth), -x() + m_view->width() - child->width() + sepWidth), 0.0)); if (partialWidth <= -x()) { m_leftPinnedSpace = qMax(m_leftPinnedSpace, child->width() - sepWidth); } else if (partialWidth > -x() + m_view->width() - child->width() + sepWidth) { m_rightPinnedSpace = qMax(m_rightPinnedSpace, child->width()); } } partialWidth += child->width(); } } } void ContentItem::updateVisibleItems() { QList newItems; for (auto *item : m_items) { if (item->isVisible() && item->x() + x() < width() && item->x() + item->width() + x() > 0) { newItems << item; connect(item, &QObject::destroyed, this, [this, item] { m_visibleItems.removeAll(item); }); } } for (auto *item : m_visibleItems) { disconnect(item, &QObject::destroyed, this, nullptr); } const QQuickItem *oldFirstVisibleItem = m_visibleItems.isEmpty() ? nullptr : qobject_cast(m_visibleItems.first()); const QQuickItem *oldLastVisibleItem = m_visibleItems.isEmpty() ? nullptr : qobject_cast(m_visibleItems.last()); if (newItems != m_visibleItems) { m_visibleItems = newItems; emit m_view->visibleItemsChanged(); if (!newItems.isEmpty() && m_visibleItems.first() != oldFirstVisibleItem) { emit m_view->firstVisibleItemChanged(); } if (!newItems.isEmpty() && m_visibleItems.last() != oldLastVisibleItem) { emit m_view->lastVisibleItemChanged(); } } } void ContentItem::forgetItem(QQuickItem *item) { if (!m_items.contains(item)) { return; } ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, true)); attached->setView(nullptr); attached->setIndex(-1); disconnect(attached, nullptr, this, nullptr); disconnect(item, nullptr, this, nullptr); disconnect(item, nullptr, m_view, nullptr); QQuickItem *separatorItem = m_separators.take(item); if (separatorItem) { separatorItem->deleteLater(); } separatorItem = m_rightSeparators.take(item); if (separatorItem) { separatorItem->deleteLater(); } const int index = m_items.indexOf(item); m_items.removeAll(item); disconnect(item, &QObject::destroyed, this, nullptr); updateVisibleItems(); m_shouldAnimate = true; m_view->polish(); item->setVisible(false); if (index <= m_view->currentIndex()) { m_view->setCurrentIndex(qBound(0, index - 1, m_items.count() - 1)); } emit m_view->countChanged(); } QQuickItem *ContentItem::ensureSeparator(QQuickItem *item) { QQuickItem *separatorItem = m_separators.value(item); if (!separatorItem) { separatorItem = qobject_cast(privateQmlComponentsPoolSelf->instance(qmlEngine(item))->m_separatorComponent->beginCreate(QQmlEngine::contextForObject(item))); if (separatorItem) { separatorItem->setParentItem(item); separatorItem->setZ(9999); separatorItem->setProperty("column", QVariant::fromValue(item)); QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_separatorComponent->completeCreate(); m_separators[item] = separatorItem; } } return separatorItem; } QQuickItem *ContentItem::ensureRightSeparator(QQuickItem *item) { QQuickItem *separatorItem = m_rightSeparators.value(item); if (!separatorItem) { separatorItem = qobject_cast(QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_rightSeparatorComponent->beginCreate(QQmlEngine::contextForObject(item))); if (separatorItem) { separatorItem->setParentItem(item); separatorItem->setZ(9999); separatorItem->setProperty("column", QVariant::fromValue(item)); QmlComponentsPoolSingleton::instance(qmlEngine(item))->m_rightSeparatorComponent->completeCreate(); m_rightSeparators[item] = separatorItem; } } return separatorItem; } void ContentItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) { switch (change) { case QQuickItem::ItemChildAddedChange: { ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(value.item, true)); attached->setView(m_view); //connect(attached, &ColumnViewAttached::fillWidthChanged, m_view, &ColumnView::polish); connect(attached, &ColumnViewAttached::fillWidthChanged, this, [this, attached](){ m_view->polish(); }); connect(attached, &ColumnViewAttached::reservedSpaceChanged, m_view, &ColumnView::polish); value.item->setVisible(true); if (!m_items.contains(value.item)) { connect(value.item, &QQuickItem::widthChanged, m_view, &ColumnView::polish); QQuickItem *item = value.item; m_items << item; connect(item, &QObject::destroyed, this, [this, item]() { m_view->removeItem(item); }); } if (m_view->separatorVisible()) { ensureSeparator(value.item); } m_shouldAnimate = true; m_view->polish(); emit m_view->countChanged(); break; } case QQuickItem::ItemChildRemovedChange: { forgetItem(value.item); break; } case QQuickItem::ItemVisibleHasChanged: updateVisibleItems(); if (value.boolValue) { m_view->polish(); } break; default: break; } QQuickItem::itemChange(change, value); } void ContentItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { updateVisibleItems(); QQuickItem::geometryChanged(newGeometry, oldGeometry); } void ContentItem::syncItemsOrder() { if (m_items == childItems()) { return; } m_items = childItems(); //NOTE: polish() here sometimes gets indefinitely delayed and items chaging order isn't seen layoutItems(); } void ContentItem::updateRepeaterModel() { if (!sender()) { return; } QObject *modelObj = sender()->property("model").value(); if (!modelObj) { m_models.remove(sender()); return; } if (m_models[sender()]) { disconnect(m_models[sender()], nullptr, this, nullptr); } m_models[sender()] = modelObj; QAbstractItemModel *qaim = qobject_cast(modelObj); if (qaim) { connect(qaim, &QAbstractItemModel::rowsMoved, this, &ContentItem::syncItemsOrder); } else { connect(modelObj, SIGNAL(childrenChanged()), this, SLOT(syncItemsOrder())); } } ColumnView::ColumnView(QQuickItem *parent) : QQuickItem(parent), m_contentItem(nullptr) { //NOTE: this is to *not* trigger itemChange m_contentItem = new ContentItem(this); setAcceptedMouseButtons(Qt::LeftButton | Qt::BackButton | Qt::ForwardButton); setFiltersChildMouseEvents(true); connect(m_contentItem->m_slideAnim, &QPropertyAnimation::finished, this, [this] () { m_moving = false; emit movingChanged(); }); connect(m_contentItem, &ContentItem::widthChanged, this, &ColumnView::contentWidthChanged); connect(m_contentItem, &ContentItem::xChanged, this, &ColumnView::contentXChanged); connect(this, &ColumnView::activeFocusChanged, this, [this]() { if (hasActiveFocus() && m_currentItem) { m_currentItem->forceActiveFocus(); } }); ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(this, true)); attached->setView(this); attached = qobject_cast(qmlAttachedPropertiesObject(m_contentItem, true)); attached->setView(this); } ColumnView::~ColumnView() { } ColumnView::ColumnResizeMode ColumnView::columnResizeMode() const { return m_contentItem->m_columnResizeMode; } void ColumnView::setColumnResizeMode(ColumnResizeMode mode) { if (m_contentItem->m_columnResizeMode == mode) { return; } m_contentItem->m_columnResizeMode = mode; if (mode == SingleColumn && m_currentItem) { m_contentItem->m_viewAnchorItem = m_currentItem; } m_contentItem->m_shouldAnimate = false; polish(); emit columnResizeModeChanged(); } qreal ColumnView::columnWidth() const { return m_contentItem->m_columnWidth; } void ColumnView::setColumnWidth(qreal width) { // Always forget the internal binding when the user sets anything, even the same value disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, nullptr); if (m_contentItem->m_columnWidth == width) { return; } m_contentItem->m_columnWidth = width; m_contentItem->m_shouldAnimate = false; polish(); emit columnWidthChanged(); } int ColumnView::currentIndex() const { return m_currentIndex; } void ColumnView::setCurrentIndex(int index) { if (m_currentIndex == index || index < -1 || index >= m_contentItem->m_items.count()) { return; } m_currentIndex = index; if (index == -1) { m_currentItem.clear(); } else { m_currentItem = m_contentItem->m_items[index]; Q_ASSERT(m_currentItem); m_currentItem->forceActiveFocus(); // If the current item is not on view, scroll QRectF mappedCurrent = m_currentItem->mapRectToItem(this, QRectF(QPointF(0, 0), m_currentItem->size())); if (m_contentItem->m_slideAnim->state() == QAbstractAnimation::Running) { mappedCurrent.moveLeft(mappedCurrent.left() + m_contentItem->x() + m_contentItem->m_slideAnim->endValue().toInt()); } //m_contentItem->m_slideAnim->stop(); QRectF contentsRect(m_contentItem->m_leftPinnedSpace, 0, width() - m_contentItem->m_rightPinnedSpace - m_contentItem->m_leftPinnedSpace, height()); m_contentItem->m_shouldAnimate = true; if (!m_mouseDown) { if (!contentsRect.contains(mappedCurrent)) { m_contentItem->m_viewAnchorItem = m_currentItem; m_contentItem->animateX(-m_currentItem->x() + m_contentItem->m_leftPinnedSpace); } else { m_contentItem->snapToItem(); } } } emit currentIndexChanged(); emit currentItemChanged(); } QQuickItem *ColumnView::currentItem() { return m_currentItem; } QListColumnView::visibleItems() const { return m_contentItem->m_visibleItems; } QQuickItem *ColumnView::firstVisibleItem() const { if (m_contentItem->m_visibleItems.isEmpty()) { return nullptr; } return qobject_cast(m_contentItem->m_visibleItems.first()); } QQuickItem *ColumnView::lastVisibleItem() const { if (m_contentItem->m_visibleItems.isEmpty()) { return nullptr; } return qobject_cast(m_contentItem->m_visibleItems.last()); } int ColumnView::count() const { return m_contentItem->m_items.count(); } qreal ColumnView::topPadding() const { return m_topPadding; } void ColumnView::setTopPadding(qreal padding) { if (padding == m_topPadding) { return; } m_topPadding = padding; polish(); emit topPaddingChanged(); } qreal ColumnView::bottomPadding() const { return m_bottomPadding; } void ColumnView::setBottomPadding(qreal padding) { if (padding == m_bottomPadding) { return; } m_bottomPadding = padding; polish(); emit bottomPaddingChanged(); } QQuickItem *ColumnView::contentItem() const { return m_contentItem; } int ColumnView::scrollDuration() const { return m_contentItem->m_slideAnim->duration(); } void ColumnView::setScrollDuration(int duration) { disconnect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, nullptr); if (m_contentItem->m_slideAnim->duration() == duration) { return; } m_contentItem->m_slideAnim->setDuration(duration); emit scrollDurationChanged(); } bool ColumnView::separatorVisible() const { return m_separatorVisible; } void ColumnView::setSeparatorVisible(bool visible) { if (visible == m_separatorVisible) { return; } m_separatorVisible = visible; if (visible) { for (QQuickItem *item : m_contentItem->m_items) { QQuickItem *sep = m_contentItem->ensureSeparator(item); if (sep) { sep->setVisible(true); } ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, true)); if (attached->isPinned()) { QQuickItem *sep = m_contentItem->ensureRightSeparator(item); if (sep) { sep->setVisible(true); } } } } else { for (QQuickItem *sep : m_contentItem->m_separators.values()) { sep->setVisible(false); } for (QQuickItem *sep : m_contentItem->m_rightSeparators.values()) { sep->setVisible(false); } } emit separatorVisibleChanged(); } bool ColumnView::dragging() const { return m_dragging; } bool ColumnView::moving() const { return m_moving; } qreal ColumnView::contentWidth() const { return m_contentItem->width(); } qreal ColumnView::contentX() const { return -m_contentItem->x(); } void ColumnView::setContentX(qreal x) const { m_contentItem->setX(qRound(-x)); } bool ColumnView::interactive() const { return m_interactive; } void ColumnView::setInteractive(bool interactive) { if (m_interactive == interactive) { return; } m_interactive = interactive; if (!m_interactive) { if (m_dragging) { m_dragging = false; emit draggingChanged(); } m_contentItem->snapToItem(); setKeepMouseGrab(false); } emit interactiveChanged(); } void ColumnView::addItem(QQuickItem *item) { insertItem(m_contentItem->m_items.length(), item); } void ColumnView::insertItem(int pos, QQuickItem *item) { if (!item || m_contentItem->m_items.contains(item)) { return; } m_contentItem->m_items.insert(qBound(0, pos, m_contentItem->m_items.length()), item); connect(item, &QObject::destroyed, m_contentItem, [this, item]() { removeItem(item); }); ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, true)); attached->setOriginalParent(item->parentItem()); attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership); item->setParentItem(m_contentItem); item->forceActiveFocus(); // We layout immediately to be sure all geometries are final after the return of this call m_contentItem->m_shouldAnimate = false; m_contentItem->layoutItems(); emit contentChildrenChanged(); // In order to keep the same current item we need to increase the current index if displaced // NOTE: just updating m_currentIndex does *not* update currentItem (which is what we need atm) while setCurrentIndex will update also currentItem if (m_currentIndex >= pos) { ++m_currentIndex; emit currentIndexChanged(); } emit itemInserted(pos, item); } void ColumnView::moveItem(int from, int to) { if (m_contentItem->m_items.isEmpty() || from < 0 || from >= m_contentItem->m_items.length() || to < 0 || to >= m_contentItem->m_items.length()) { return; } m_contentItem->m_items.move(from, to); m_contentItem->m_shouldAnimate = true; if (from == m_currentIndex) { m_currentIndex = to; emit currentIndexChanged(); } else if (from < m_currentIndex && to > m_currentIndex) { --m_currentIndex; emit currentIndexChanged(); } else if (from > m_currentIndex && to <= m_currentIndex) { ++m_currentIndex; emit currentIndexChanged(); } polish(); } QQuickItem *ColumnView::removeItem(const QVariant &item) { if (item.canConvert()) { return removeItem(item.value()); } else if (item.canConvert()) { return removeItem(item.toInt()); } else { return nullptr; } } QQuickItem *ColumnView::removeItem(QQuickItem *item) { if (m_contentItem->m_items.isEmpty() || !m_contentItem->m_items.contains(item)) { return nullptr; } const int index = m_contentItem->m_items.indexOf(item); // In order to keep the same current item we need to increase the current index if displaced if (m_currentIndex >= index) { setCurrentIndex(m_currentIndex - 1); } m_contentItem->forgetItem(item); ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, false)); if (attached && attached->shouldDeleteOnRemove()) { item->deleteLater(); } else { item->setParentItem(attached ? attached->originalParent() : nullptr); } emit itemRemoved(item); return item; } QQuickItem *ColumnView::removeItem(int pos) { if (m_contentItem->m_items.isEmpty() || pos < 0 || pos >= m_contentItem->m_items.length()) { return nullptr; } return removeItem(m_contentItem->m_items[pos]); } QQuickItem *ColumnView::pop(QQuickItem *item) { QQuickItem *removed = nullptr; while (!m_contentItem->m_items.isEmpty() && m_contentItem->m_items.last() != item) { removed = removeItem(m_contentItem->m_items.last()); // if no item has been passed, just pop one if (!item) { break; } } return removed; } void ColumnView::clear() { for (QQuickItem *item : m_contentItem->m_items) { removeItem(item); } m_contentItem->m_items.clear(); emit contentChildrenChanged(); } bool ColumnView::containsItem(QQuickItem *item) { return m_contentItem->m_items.contains(item); } QQuickItem *ColumnView::itemAt(qreal x, qreal y) { return m_contentItem->childAt(x, y); } ColumnViewAttached *ColumnView::qmlAttachedProperties(QObject *object) { return new ColumnViewAttached(object); } void ColumnView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { m_contentItem->setY(m_topPadding); m_contentItem->setHeight(newGeometry.height() - m_topPadding - m_bottomPadding); m_contentItem->m_shouldAnimate = false; polish(); m_contentItem->updateVisibleItems(); QQuickItem::geometryChanged(newGeometry, oldGeometry); } bool ColumnView::childMouseEventFilter(QQuickItem *item, QEvent *event) { if (!m_interactive || item == m_contentItem) { return QQuickItem::childMouseEventFilter(item, event); } switch (event->type()) { case QEvent::MouseButtonPress: { QMouseEvent *me = static_cast(event); if (me->button() != Qt::LeftButton) { return false; } m_contentItem->m_slideAnim->stop(); if (item->property("preventStealing").toBool()) { m_contentItem->snapToItem(); return false; } m_oldMouseX = m_startMouseX = mapFromItem(item, me->localPos()).x(); m_mouseDown = true; me->setAccepted(false); setKeepMouseGrab(false); // On press, we set the current index of the view to the root item QQuickItem *candidateItem = item; while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) { candidateItem = candidateItem->parentItem(); } if (candidateItem->parentItem() == m_contentItem) { setCurrentIndex(m_contentItem->m_items.indexOf(candidateItem)); } break; } case QEvent::MouseMove: { QMouseEvent *me = static_cast(event); if (!(me->buttons() & Qt::LeftButton)) { return false; } if ((!keepMouseGrab() && item->keepMouseGrab()) || item->property("preventStealing").toBool()) { m_contentItem->snapToItem(); return false; } QQuickItem *candidateItem = item; while (candidateItem->parentItem() && candidateItem->parentItem() != m_contentItem) { candidateItem = candidateItem->parentItem(); } if (candidateItem->parentItem() == m_contentItem) { ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(candidateItem, true)); if (attached->preventStealing()) { return false; } } const QPointF pos = mapFromItem(item, me->localPos()); const bool wasDragging = m_dragging; // If a drag happened, start to steal all events, use startDragDistance * 2 to give time to widgets to take the mouse grab by themselves m_dragging = keepMouseGrab() || qAbs(mapFromItem(item, me->localPos()).x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 3; if (m_dragging != wasDragging) { m_moving = true; emit movingChanged(); emit draggingChanged(); } if (m_dragging) { m_contentItem->setBoundedX(m_contentItem->x() + pos.x() - m_oldMouseX); } m_contentItem->m_lastDragDelta = pos.x() - m_oldMouseX; m_oldMouseX = pos.x(); setKeepMouseGrab(m_dragging); me->setAccepted(m_dragging); return m_dragging; break; } case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); if (me->button() == Qt::BackButton && m_currentIndex > 0) { setCurrentIndex(m_currentIndex - 1); me->accept(); return true; } else if (me->button() == Qt::ForwardButton) { setCurrentIndex(m_currentIndex + 1); me->accept(); return true; } if (me->button() != Qt::LeftButton) { return false; } m_mouseDown = false; m_contentItem->snapToItem(); m_contentItem->m_lastDragDelta = 0; if (m_dragging) { m_dragging = false; emit draggingChanged(); } if (item->property("preventStealing").toBool()) { return false; } event->accept(); //if a drag happened, don't pass the event const bool block = keepMouseGrab(); setKeepMouseGrab(false); me->setAccepted(block); return block; break; } default: break; } return QQuickItem::childMouseEventFilter(item, event); } void ColumnView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::BackButton || event->button() == Qt::ForwardButton) { event->accept(); return; } if (!m_interactive) { return; } m_contentItem->snapToItem(); m_oldMouseX = event->localPos().x(); m_startMouseX = event->localPos().x(); m_mouseDown = true; setKeepMouseGrab(false); event->accept(); } void ColumnView::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::BackButton || event->buttons() & Qt::ForwardButton) { event->accept(); return; } if (!m_interactive) { return; } const bool wasDragging = m_dragging; // Same startDragDistance * 2 as the event filter m_dragging = keepMouseGrab() || qAbs(event->localPos().x() - m_startMouseX) > qApp->styleHints()->startDragDistance() * 2; if (m_dragging != wasDragging) { m_moving = true; emit movingChanged(); emit draggingChanged(); } setKeepMouseGrab(m_dragging); if (m_dragging) { m_contentItem->setBoundedX(m_contentItem->x() + event->pos().x() - m_oldMouseX); } m_contentItem->m_lastDragDelta = event->pos().x() - m_oldMouseX; m_oldMouseX = event->pos().x(); event->accept(); } void ColumnView::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::BackButton && m_currentIndex > 0) { setCurrentIndex(m_currentIndex - 1); event->accept(); return; } else if (event->button() == Qt::ForwardButton) { setCurrentIndex(m_currentIndex + 1); event->accept(); return; } m_mouseDown = false; if (!m_interactive) { return; } m_contentItem->snapToItem(); m_contentItem->m_lastDragDelta = 0; if (m_dragging) { m_dragging = false; emit draggingChanged(); } setKeepMouseGrab(false); event->accept(); } void ColumnView::mouseUngrabEvent() { m_mouseDown = false; if (m_contentItem->m_slideAnim->state() != QAbstractAnimation::Running) { m_contentItem->snapToItem(); } m_contentItem->m_lastDragDelta = 0; if (m_dragging) { m_dragging = false; emit draggingChanged(); } setKeepMouseGrab(false); } void ColumnView::classBegin() { auto syncColumnWidth = [this]() { m_contentItem->m_columnWidth = privateQmlComponentsPoolSelf->instance(qmlEngine(this))->m_units->property("gridUnit").toInt() * 20; emit columnWidthChanged(); }; connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::gridUnitChanged, this, syncColumnWidth); syncColumnWidth(); auto syncDuration = [this]() { m_contentItem->m_slideAnim->setDuration(QmlComponentsPoolSingleton::instance(qmlEngine(this))->m_units->property("longDuration").toInt()); emit scrollDurationChanged(); }; connect(QmlComponentsPoolSingleton::instance(qmlEngine(this)), &QmlComponentsPool::longDurationChanged, this, syncDuration); syncDuration(); QQuickItem::classBegin(); } void ColumnView::componentComplete() { m_complete = true; QQuickItem::componentComplete(); } void ColumnView::updatePolish() { m_contentItem->layoutItems(); } void ColumnView::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) { switch (change) { case QQuickItem::ItemChildAddedChange: if (m_contentItem && value.item != m_contentItem && !value.item->inherits("QQuickRepeater")) { addItem(value.item); } break; default: break; } QQuickItem::itemChange(change, value); } void ColumnView::contentChildren_append(QQmlListProperty *prop, QQuickItem *item) { // This can only be called from QML ColumnView *view = static_cast(prop->object); if (!view) { return; } view->m_contentItem->m_items.append(item); connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() { view->removeItem(item); }); ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, true)); attached->setOriginalParent(item->parentItem()); attached->setShouldDeleteOnRemove(item->parentItem() == nullptr && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership); item->setParentItem(view->m_contentItem); } int ColumnView::contentChildren_count(QQmlListProperty *prop) { ColumnView *view = static_cast(prop->object); if (!view) { return 0; } return view->m_contentItem->m_items.count(); } QQuickItem *ColumnView::contentChildren_at(QQmlListProperty *prop, int index) { ColumnView *view = static_cast(prop->object); if (!view) { return nullptr; } if (index < 0 || index >= view->m_contentItem->m_items.count()) { return nullptr; } return view->m_contentItem->m_items.value(index); } void ColumnView::contentChildren_clear(QQmlListProperty *prop) { ColumnView *view = static_cast(prop->object); if (!view) { return; } return view->m_contentItem->m_items.clear(); } QQmlListProperty ColumnView::contentChildren() { return QQmlListProperty(this, nullptr, contentChildren_append, contentChildren_count, contentChildren_at, contentChildren_clear); } void ColumnView::contentData_append(QQmlListProperty *prop, QObject *object) { ColumnView *view = static_cast(prop->object); if (!view) { return; } view->m_contentData.append(object); QQuickItem *item = qobject_cast(object); //exclude repeaters from layout if (item && item->inherits("QQuickRepeater")) { item->setParentItem(view); connect(item, SIGNAL(modelChanged()), view->m_contentItem, SLOT(updateRepeaterModel())); } else if (item) { view->m_contentItem->m_items.append(item); connect(item, &QObject::destroyed, view->m_contentItem, [view, item]() { view->removeItem(item); }); ColumnViewAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, true)); attached->setOriginalParent(item->parentItem()); attached->setShouldDeleteOnRemove(view->m_complete && !item->parentItem() && QQmlEngine::objectOwnership(item) == QQmlEngine::JavaScriptOwnership); item->setParentItem(view->m_contentItem); } else { object->setParent(view); } } int ColumnView::contentData_count(QQmlListProperty *prop) { ColumnView *view = static_cast(prop->object); if (!view) { return 0; } return view->m_contentData.count(); } QObject *ColumnView::contentData_at(QQmlListProperty *prop, int index) { ColumnView *view = static_cast(prop->object); if (!view) { return nullptr; } if (index < 0 || index >= view->m_contentData.count()) { return nullptr; } return view->m_contentData.value(index); } void ColumnView::contentData_clear(QQmlListProperty *prop) { ColumnView *view = static_cast(prop->object); if (!view) { return; } return view->m_contentData.clear(); } QQmlListProperty ColumnView::contentData() { return QQmlListProperty(this, nullptr, contentData_append, contentData_count, contentData_at, contentData_clear); } #include "moc_columnview.cpp" diff --git a/src/delegaterecycler.cpp b/src/delegaterecycler.cpp index 8db01478..79a43c1b 100644 --- a/src/delegaterecycler.cpp +++ b/src/delegaterecycler.cpp @@ -1,412 +1,412 @@ /* * Copyright 2011 Marco Martin * Copyright 2014 Aleix Pol Gonzalez * * 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 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 "delegaterecycler.h" #include #include #include #include DelegateRecyclerAttached::DelegateRecyclerAttached(QObject *parent) : QObject(parent) { } DelegateRecyclerAttached::~DelegateRecyclerAttached() {} /* void setRecycler(DelegateRecycler *recycler) { m_recycler = recycler; } DelegateRecycler *recycler() const { return m_recycler; } */ class DelegateCache { public: DelegateCache(); ~DelegateCache(); void ref(QQmlComponent *); void deref(QQmlComponent *); void insert(QQmlComponent *, QQuickItem *); QQuickItem *take(QQmlComponent *); private: static const int s_cacheSize = 40; QHash m_refs; QHash > m_unusedItems; }; Q_GLOBAL_STATIC(DelegateCache, s_delegateCache) DelegateCache::DelegateCache() { } DelegateCache::~DelegateCache() { for (auto& item : qAsConst(m_unusedItems)) { qDeleteAll(item); } } void DelegateCache::ref(QQmlComponent *component) { m_refs[component]++; } void DelegateCache::deref(QQmlComponent *component) { auto itRef = m_refs.find(component); if (itRef == m_refs.end()) { return; } (*itRef)--; if (*itRef <= 0) { m_refs.erase(itRef); qDeleteAll(m_unusedItems.take(component)); } } void DelegateCache::insert(QQmlComponent *component, QQuickItem *item) { auto& items = m_unusedItems[component]; if (items.length() >= s_cacheSize) { item->deleteLater(); return; } DelegateRecyclerAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, false)); if (attached) { emit attached->pooled(); } item->setParentItem(nullptr); items.append(item); } QQuickItem *DelegateCache::take(QQmlComponent *component) { auto it = m_unusedItems.find(component); if (it != m_unusedItems.end() && !it->isEmpty()) { return it->takeFirst(); } return nullptr; } DelegateRecycler::DelegateRecycler(QQuickItem *parent) : QQuickItem(parent) { setFlags(QQuickItem::ItemIsFocusScope); } DelegateRecycler::~DelegateRecycler() { if (m_sourceComponent) { s_delegateCache->insert(m_sourceComponent, m_item); s_delegateCache->deref(m_sourceComponent); } } void DelegateRecycler::syncIndex() { const QVariant newIndex = m_propertiesTracker->property("trackedIndex"); if (!newIndex.isValid()) { return; } QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); ctx->setContextProperty(QStringLiteral("index"), newIndex); } void DelegateRecycler::syncModel() { const QVariant newModel = m_propertiesTracker->property("trackedModel"); if (!newModel.isValid()) { return; } QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); ctx->setContextProperty(QStringLiteral("model"), newModel); //try to bind all properties QObject *modelObj = newModel.value(); if (modelObj) { const QMetaObject *metaObj = modelObj->metaObject(); for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { ctx->setContextProperty(QString::fromUtf8(metaObj->property(i).name()), metaObj->property(i).read(modelObj)); } } } void DelegateRecycler::syncModelProperties() { const QVariant model = m_propertiesTracker->property("trackedModel"); if (!model.isValid()) { return; } QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); //try to bind all properties QObject *modelObj = model.value(); if (modelObj) { const QMetaObject *metaObj = modelObj->metaObject(); for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { ctx->setContextProperty(QString::fromUtf8(metaObj->property(i).name()), metaObj->property(i).read(modelObj)); } } } void DelegateRecycler::syncModelData() { const QVariant newModelData = m_propertiesTracker->property("trackedModelData"); if (!newModelData.isValid()) { return; } QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); ctx->setContextProperty(QStringLiteral("modelData"), newModelData); } QQmlComponent *DelegateRecycler::sourceComponent() const { return m_sourceComponent; } void DelegateRecycler::setSourceComponent(QQmlComponent *component) { if (component && component->parent() == this) { qWarning() << "Error: source components cannot be declared inside DelegateRecycler"; return; } if (m_sourceComponent == component) { return; } if (!m_propertiesTracker) { static QMap propertiesTrackerComponent; auto engine = qmlEngine(this); auto it = propertiesTrackerComponent.find(engine); if (it == propertiesTrackerComponent.end()) { connect(engine, &QObject::destroyed, engine, [engine] { propertiesTrackerComponent.remove(engine); }); it = propertiesTrackerComponent.insert(engine, new QQmlComponent(engine, engine)); - (*it)->setData(QByteArrayLiteral("import QtQuick 2.3\nQtObject{property int trackedIndex: index; property var trackedModel: typeof model != 'undefined' ? model : null; property var trackedModelData: typeof modelData != 'undefined' ? modelData : null}"), QUrl()); + (*it)->setData(QByteArrayLiteral("import QtQuick 2.3\nQtObject{property int trackedIndex: index; property var trackedModel: typeof model != 'undefined' ? model : null; property var trackedModelData: typeof modelData != 'undefined' ? modelData : null}"), QUrl(QStringLiteral("delegaterecycler.cpp"))); } m_propertiesTracker = (*it)->create(QQmlEngine::contextForObject(this)); connect(m_propertiesTracker, SIGNAL(trackedIndexChanged()), this, SLOT(syncIndex())); connect(m_propertiesTracker, SIGNAL(trackedModelChanged()), this, SLOT(syncModel())); connect(m_propertiesTracker, SIGNAL(trackedModelDataChanged()), this, SLOT(syncModelData())); } if (m_sourceComponent) { if (m_item) { disconnect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints); disconnect(m_item.data(), &QQuickItem::implicitHeightChanged, this, &DelegateRecycler::updateHints); s_delegateCache->insert(component, m_item); } s_delegateCache->deref(component); } m_sourceComponent = component; s_delegateCache->ref(component); m_item = s_delegateCache->take(component); if (!m_item) { QQuickItem *candidate = parentItem(); QQmlContext *ctx = nullptr; while (candidate) { QQmlContext *parentCtx = QQmlEngine::contextForObject(candidate); if (parentCtx) { ctx = new QQmlContext(parentCtx, candidate); break; } else { candidate = candidate->parentItem(); } } Q_ASSERT(ctx); QObject *contextObjectToSet = nullptr; { // Find the first parent that has a context object with a valid translationDomain property, i.e. is a KLocalizedContext QQmlContext *auxCtx = ctx; while (auxCtx != nullptr) { QObject *auxCtxObj = auxCtx->contextObject(); if (auxCtxObj && auxCtxObj->property("translationDomain").isValid()) { contextObjectToSet = auxCtxObj; break; } auxCtx = auxCtx->parentContext(); } } if (contextObjectToSet) { ctx->setContextObject(contextObjectToSet); } QObject *modelObj = m_propertiesTracker->property("trackedModel").value(); if (modelObj) { const QMetaObject *metaObj = modelObj->metaObject(); for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { QMetaProperty prop = metaObj->property(i); ctx->setContextProperty(QString::fromUtf8(prop.name()), prop.read(modelObj)); if (prop.hasNotifySignal()) { QMetaMethod updateSlot = metaObject()->method( metaObject()->indexOfSlot("syncModelProperties()")); connect(modelObj, prop.notifySignal(), this, updateSlot); } } } ctx->setContextProperty(QStringLiteral("model"), m_propertiesTracker->property("trackedModel")); ctx->setContextProperty(QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData")); ctx->setContextProperty(QStringLiteral("index"), m_propertiesTracker->property("trackedIndex")); ctx->setContextProperty(QStringLiteral("delegateRecycler"), this); QObject * obj = component->create(ctx); m_item = qobject_cast(obj); if (!m_item) { obj->deleteLater(); } else { connect(m_item.data(), &QObject::destroyed, ctx, &QObject::deleteLater); //if the user binded an explicit width, consider it, otherwise base upon implicit m_widthFromItem = m_item->width() > 0 && m_item->width() != m_item->implicitWidth(); m_heightFromItem = m_item->height() > 0 && m_item->height() != m_item->implicitHeight(); if (m_widthFromItem && m_heightFromItem) { connect(m_item.data(), &QQuickItem::heightChanged, this, [this]() { updateSize(false); }); } } } else { syncModel(); QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext(); ctx->setContextProperties({ QQmlContext::PropertyPair{ QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData") }, QQmlContext::PropertyPair{ QStringLiteral("index"), m_propertiesTracker->property("trackedIndex")}, QQmlContext::PropertyPair{ QStringLiteral("delegateRecycler"), QVariant::fromValue(this) } }); DelegateRecyclerAttached *attached = qobject_cast(qmlAttachedPropertiesObject(m_item, false)); if (attached) { emit attached->reused(); } } if (m_item) { m_item->setParentItem(this); connect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints); connect(m_item.data(), &QQuickItem::implicitHeightChanged, this, &DelegateRecycler::updateHints); updateSize(true); } emit sourceComponentChanged(); } void DelegateRecycler::resetSourceComponent() { s_delegateCache->deref(m_sourceComponent); m_sourceComponent = nullptr; } DelegateRecyclerAttached *DelegateRecycler::qmlAttachedProperties(QObject *object) { return new DelegateRecyclerAttached(object); } void DelegateRecycler::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { if (m_item && newGeometry.size() != oldGeometry.size()) { updateSize(true); } QQuickItem::geometryChanged(newGeometry, oldGeometry); } void DelegateRecycler::focusInEvent(QFocusEvent *event) { QQuickItem::focusInEvent(event); if (!m_item) { return; } m_item->setFocus(event->reason()); } void DelegateRecycler::updateHints() { updateSize(false); } void DelegateRecycler::updateSize(bool parentResized) { if (!m_item) { return; } const bool needToUpdateWidth = !m_widthFromItem && parentResized && widthValid(); const bool needToUpdateHeight = !m_heightFromItem && parentResized && heightValid(); if (parentResized) { m_item->setPosition(QPoint(0,0)); } if (needToUpdateWidth && needToUpdateHeight) { m_item->setSize(QSizeF(width(), height())); } else if (needToUpdateWidth) { m_item->setWidth(width()); } else if (needToUpdateHeight) { m_item->setHeight(height()); } if (m_updatingSize) { return; } m_updatingSize = true; if (m_heightFromItem) { setHeight(m_item->height()); } if (m_widthFromItem) { setWidth(m_item->width()); } setImplicitSize(m_item->implicitWidth() >= 0 ? m_item->implicitWidth() : m_item->width(), m_item->implicitHeight() >= 0 ? m_item->implicitHeight() : m_item->height()); m_updatingSize = false; } diff --git a/src/libkirigami/basictheme.cpp b/src/libkirigami/basictheme.cpp index a32c3c45..e7ab164a 100644 --- a/src/libkirigami/basictheme.cpp +++ b/src/libkirigami/basictheme.cpp @@ -1,336 +1,336 @@ /* * Copyright (C) 2017 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 "basictheme_p.h" #include #include #include #include #include #include #include namespace Kirigami { class BasicThemeDeclarativeSingleton { public: BasicThemeDeclarativeSingleton() {} BasicThemeDeclarative self; }; Q_GLOBAL_STATIC(BasicThemeDeclarativeSingleton, privateBasicThemeDeclarativeSelf) BasicThemeDeclarative::BasicThemeDeclarative() { m_colorSyncTimer = new QTimer; m_colorSyncTimer->setInterval(0); m_colorSyncTimer->setSingleShot(true); } BasicThemeDeclarative::~BasicThemeDeclarative() { delete m_colorSyncTimer; } QObject *BasicThemeDeclarative::instance(const BasicTheme *theme) { if (m_declarativeBasicTheme) { return m_declarativeBasicTheme; } QQmlEngine *engine = qmlEngine(theme->parent()); Q_ASSERT(engine); QQmlComponent c(engine); //NOTE: for now is important this import stays at 2.0 c.setData("import QtQuick 2.6\n\ import org.kde.kirigami 2.0 as Kirigami\n\ QtObject {\n\ property QtObject theme: Kirigami.Theme\n\ - }", QUrl()); + }", QUrl(QStringLiteral("basictheme.cpp"))); QObject *obj = c.create(); m_declarativeBasicTheme = obj->property("theme").value(); return m_declarativeBasicTheme; } BasicTheme::BasicTheme(QObject *parent) : PlatformTheme(parent) { //TODO: correct? connect(qApp, &QGuiApplication::fontDatabaseChanged, this, [this]() {setDefaultFont(qApp->font());}); //connect all the declarative object signals to the timer start to compress, use the old syntax as they are all signals defined in QML connect(basicThemeDeclarative()->instance(this), SIGNAL(textColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(disabledTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(highlightColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(highlightedTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(backgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(alternateBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(linkColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(visitedLinkColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonAlternateBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonHoverColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(buttonFocusColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewAlternateBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewHoverColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(viewFocusColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryTextColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryAlternateBackgroundColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryHoverColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); connect(basicThemeDeclarative()->instance(this), SIGNAL(complementaryFocusColorChanged()), basicThemeDeclarative()->m_colorSyncTimer, SLOT(start())); //finally connect the timer to the sync connect(basicThemeDeclarative()->m_colorSyncTimer, &QTimer::timeout, this, &BasicTheme::syncColors); connect(this, &BasicTheme::colorSetChanged, this, &BasicTheme::syncColors); connect(this, &BasicTheme::colorGroupChanged, this, &BasicTheme::syncColors); connect(this, &PlatformTheme::colorSetChanged, this, &BasicTheme::syncCustomColorsToQML); connect(this, &PlatformTheme::colorsChanged, this, &BasicTheme::syncCustomColorsToQML); syncColors(); } BasicTheme::~BasicTheme() { } static inline QColor colorGroupTint(const QColor &color, PlatformTheme::ColorGroup group) { switch (group) { case PlatformTheme::Inactive: return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF()); case PlatformTheme::Disabled: return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF()*0.8); default: return color; } } //TODO: tint for which we need to chain to m_parentBasicTheme's color #define RESOLVECOLOR(colorName, upperCaseColor) \ QColor color;\ switch (colorSet()) {\ case BasicTheme::Button:\ color = basicThemeDeclarative()->instance(this)->property("button"#upperCaseColor).value();\ break;\ case BasicTheme::View:\ color = basicThemeDeclarative()->instance(this)->property("view"#upperCaseColor).value();\ break;\ case BasicTheme::Selection:\ color = basicThemeDeclarative()->instance(this)->property("selection"#upperCaseColor).value();\ break;\ case BasicTheme::Tooltip:\ color = basicThemeDeclarative()->instance(this)->property("tooltip"#upperCaseColor).value();\ break;\ case BasicTheme::Complementary:\ color = basicThemeDeclarative()->instance(this)->property("complementary"#upperCaseColor).value();\ break;\ case BasicTheme::Window:\ default:\ color = basicThemeDeclarative()->instance(this)->property(#colorName).value();\ }\ color = colorGroupTint(color, colorGroup()); #define PROXYCOLOR(colorName, upperCaseColor) \ colorGroupTint(basicThemeDeclarative()->instance(this)->property(#colorName).value(), colorGroup()) void BasicTheme::syncColors() { { RESOLVECOLOR(textColor, TextColor); setTextColor(color); }{ setDisabledTextColor(PROXYCOLOR(disabledTextColor, DisabledTextColor)); }{ RESOLVECOLOR(backgroundColor, BackgroundColor) setBackgroundColor(color); }{ RESOLVECOLOR(alternateBackgroundColor, AlternateBackgroundColor) setAlternateBackgroundColor(color); }{ setHighlightColor(PROXYCOLOR(highlightColor, HighlightColor)); }{ setHighlightedTextColor(PROXYCOLOR(highlightedTextColor, HighlightedTextColor)); }{ setActiveTextColor(PROXYCOLOR(activeTextColor, ActiveTextColor)); }{ setLinkColor(PROXYCOLOR(linkColor, LinkColor)); }{ setVisitedLinkColor(PROXYCOLOR(visitedLinkColor, VisitedLinkColor)); }{ setNegativeTextColor(PROXYCOLOR(negativeTextColor, NegativeTextColor)); }{ setNeutralTextColor(PROXYCOLOR(neutralTextColor, NeutralTextColor)); }{ setPositiveTextColor(PROXYCOLOR(positiveTextColor, PositiveTextColor)); }{ RESOLVECOLOR(hoverColor, HoverColor); setHoverColor(color); }{ RESOLVECOLOR(focusColor, FocusColor); setFocusColor(color); } //legacy { m_buttonTextColor = PROXYCOLOR(buttonTextColor, ButtonTextColor); m_buttonBackgroundColor = PROXYCOLOR(buttonBackgroundColor, ButtonBackgroundColor); m_buttonHoverColor = PROXYCOLOR(buttonHoverColor, ButtonHoverColor); m_buttonFocusColor = PROXYCOLOR(buttonFocusColor, ButtonFocusColor); m_viewTextColor = PROXYCOLOR(viewTextColor, ViewTextColor); m_viewBackgroundColor = PROXYCOLOR(viewBackgroundColor, ViewBackgroundColor); m_viewHoverColor = PROXYCOLOR(viewHoverColor, ViewHoverColor); m_viewFocusColor = PROXYCOLOR(viewFocusColor, ViewFocusColor); } QPalette pal = qApp->palette(); pal.setColor(QPalette::WindowText, textColor()); pal.setColor(QPalette::Button, m_buttonBackgroundColor); pal.setColor(QPalette::Light, m_buttonBackgroundColor.lighter(120)); pal.setColor(QPalette::Dark, m_buttonBackgroundColor.darker(120)); pal.setColor(QPalette::Mid, m_buttonBackgroundColor.darker(110)); pal.setColor(QPalette::Base, m_viewBackgroundColor); pal.setColor(QPalette::HighlightedText, highlightedTextColor()); pal.setColor(QPalette::Text, m_viewTextColor); pal.setColor(QPalette::Window, backgroundColor()); setPalette(pal); if (this->parent()) { //this will work on Qt 5.10+ but is a safe noop on older releases this->parent()->setProperty("palette", QVariant::fromValue(pal)); if (basicThemeDeclarative()->instance(this)) { QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagateColorSet", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, colorSet())); } } emit colorsChanged(); } void BasicTheme::syncCustomColorsToQML() { if (basicThemeDeclarative()->instance(this)) { QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagateTextColor", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, textColor())); QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagateBackgroundColor", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, backgroundColor())); QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagatePrimaryColor", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, highlightColor())); QMetaObject::invokeMethod(basicThemeDeclarative()->instance(this), "__propagateAccentColor", Q_ARG(QVariant, QVariant::fromValue(this->parent())), Q_ARG(QVariant, highlightColor())); } } QColor BasicTheme::buttonTextColor() const { qWarning()<<"WARNING: buttonTextColor is deprecated, use textColor with colorSet: Theme.Button instead"; return m_buttonTextColor; } QColor BasicTheme::buttonBackgroundColor() const { qWarning()<<"WARNING: buttonBackgroundColor is deprecated, use backgroundColor with colorSet: Theme.Button instead"; return m_buttonBackgroundColor; } QColor BasicTheme::buttonHoverColor() const { qWarning()<<"WARNING: buttonHoverColor is deprecated, use backgroundColor with colorSet: Theme.Button instead"; return m_buttonHoverColor; } QColor BasicTheme::buttonFocusColor() const { qWarning()<<"WARNING: buttonFocusColor is deprecated, use backgroundColor with colorSet: Theme.Button instead"; return m_buttonFocusColor; } QColor BasicTheme::viewTextColor() const { qWarning()<<"WARNING: viewTextColor is deprecated, use backgroundColor with colorSet: Theme.View instead"; return m_viewTextColor; } QColor BasicTheme::viewBackgroundColor() const { qWarning()<<"WARNING: viewBackgroundColor is deprecated, use backgroundColor with colorSet: Theme.View instead"; return m_viewBackgroundColor; } QColor BasicTheme::viewHoverColor() const { qWarning()<<"WARNING: viewHoverColor is deprecated, use backgroundColor with colorSet: Theme.View instead"; return m_viewHoverColor; } QColor BasicTheme::viewFocusColor() const { qWarning()<<"WARNING: viewFocusColor is deprecated, use backgroundColor with colorSet: Theme.View instead"; return m_viewFocusColor; } BasicThemeDeclarative *BasicTheme::basicThemeDeclarative() { return &privateBasicThemeDeclarativeSelf->self; } } #include "moc_basictheme_p.cpp"