/* * Copyright (C) 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * 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 General Public License * along with this program. If not, see . */ /* * The implementation is centered around m_visibleItems * that a list for each of the items in the view. * m_firstVisibleIndex is the index of the first item in m_visibleItems * m_lastInRowIndexPosition is a map that contains the x position * of items that are the last ones of a row so we can reconstruct the rows * when building back */ #include "horizontaljournal.h" #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-pedantic" #include #include #include #pragma GCC diagnostic pop static const qreal bufferRatio = 0.5; HorizontalJournal::HorizontalJournal() : m_firstVisibleIndex(-1) , m_rowHeight(0) { } qreal HorizontalJournal::rowHeight() const { return m_rowHeight; } void HorizontalJournal::setRowHeight(qreal rowHeight) { if (rowHeight != m_rowHeight) { m_rowHeight = rowHeight; Q_EMIT rowHeightChanged(); if (isComponentComplete()) { Q_FOREACH(QQuickItem *item, m_visibleItems) { item->setHeight(rowHeight); } relayout(); } } } void HorizontalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos) { if (m_visibleItems.isEmpty()) { *modelIndex = 0; *yPos = 0; } else { *modelIndex = m_firstVisibleIndex + m_visibleItems.count(); if (m_lastInRowIndexPosition.contains(*modelIndex - 1)) { *yPos = m_visibleItems.last()->y() + m_rowHeight + rowSpacing(); } else { *yPos = m_visibleItems.last()->y(); } } } void HorizontalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos) { if (m_visibleItems.isEmpty()) { *modelIndex = -1; *yPos = INT_MIN; } else { *modelIndex = m_firstVisibleIndex - 1; if (m_lastInRowIndexPosition.contains(*modelIndex)) { *yPos = m_visibleItems.first()->y() - rowSpacing() - m_rowHeight; } else { *yPos = m_visibleItems.first()->y(); } } } bool HorizontalJournal::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY) { bool changed = false; while (!m_visibleItems.isEmpty() && m_visibleItems.first()->y() + m_rowHeight < bufferFromY) { releaseItem(m_visibleItems.takeFirst()); changed = true; m_firstVisibleIndex++; } while (!m_visibleItems.isEmpty() && m_visibleItems.last()->y() > bufferToY) { releaseItem(m_visibleItems.takeLast()); changed = true; m_lastInRowIndexPosition.remove(m_firstVisibleIndex + m_visibleItems.count()); } if (m_visibleItems.isEmpty()) { m_firstVisibleIndex = -1; } return changed; } void HorizontalJournal::addItemToView(int modelIndex, QQuickItem *item) { if (item->height() != m_rowHeight) { qWarning() << "Item" << modelIndex << "height is not the one that the rowHeight mandates, resetting it"; item->setHeight(m_rowHeight); } if (m_visibleItems.isEmpty()) { Q_ASSERT(modelIndex == 0); item->setY(0); item->setX(0); m_visibleItems << item; m_firstVisibleIndex = 0; } else { // modelIndex has to be either m_firstVisibleIndex - 1 or m_firstVisibleIndex + m_visibleItems.count() if (modelIndex == m_firstVisibleIndex + m_visibleItems.count()) { QQuickItem *lastItem = m_visibleItems.last(); if (lastItem->x() + lastItem->width() + columnSpacing() + item->width() <= width()) { // Fits in the row item->setY(lastItem->y()); item->setX(lastItem->x() + lastItem->width() + columnSpacing()); } else { // Starts a new row item->setY(lastItem->y() + m_rowHeight + rowSpacing()); item->setX(0); m_lastInRowIndexPosition[modelIndex - 1] = lastItem->x(); } m_visibleItems << item; } else if (modelIndex == m_firstVisibleIndex - 1) { QQuickItem *firstItem = m_visibleItems.first(); if (m_lastInRowIndexPosition.contains(modelIndex)) { // It is the last item of its row, so start a new one since we're going back item->setY(firstItem->y() - rowSpacing() - m_rowHeight); item->setX(m_lastInRowIndexPosition[modelIndex]); } else { item->setY(firstItem->y()); item->setX(firstItem->x() - columnSpacing() - item->width()); } m_firstVisibleIndex = modelIndex; m_visibleItems.prepend(item); } else { qWarning() << "HorizontalJournal::addItemToView - Got unexpected modelIndex" << modelIndex << m_firstVisibleIndex << m_visibleItems.count(); } } } void HorizontalJournal::cleanupExistingItems() { // Cleanup the existing items Q_FOREACH(QQuickItem *item, m_visibleItems) releaseItem(item); m_visibleItems.clear(); m_lastInRowIndexPosition.clear(); m_firstVisibleIndex = -1; setImplicitHeightDirty(); } void HorizontalJournal::calculateImplicitHeight() { if (m_firstVisibleIndex >= 0) { const int nIndexes = m_firstVisibleIndex + m_visibleItems.count(); const double bottomMostY = m_visibleItems.last()->y() + m_rowHeight; const double averageHeight = bottomMostY / nIndexes; setImplicitHeight(bottomMostY + averageHeight * (model()->rowCount() - nIndexes)); } else { setImplicitHeight(0); } } void HorizontalJournal::processModelRemoves(const QVector &removes) { Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) { for (int i = remove.count - 1; i >= 0; --i) { const int indexToRemove = remove.index + i; // We only support removing from the end so // any of the last items of a column has to be indexToRemove const int lastIndex = m_firstVisibleIndex + m_visibleItems.count() - 1; if (indexToRemove == lastIndex) { releaseItem(m_visibleItems.takeLast()); m_lastInRowIndexPosition.remove(indexToRemove); } else { if (indexToRemove < lastIndex) { qFatal("HorizontalJournal only supports removal from the end of the model"); } else { setImplicitHeightDirty(); } } } } if (m_visibleItems.isEmpty()) { m_firstVisibleIndex = -1; } } void HorizontalJournal::doRelayout() { // If m_firstVisibleIndex is not 0 we need to drop all the delegates // since we can't consistently relayout without the first item being there if (m_firstVisibleIndex == 0) { int i = 0; const QList allItems = m_visibleItems; m_visibleItems.clear(); m_lastInRowIndexPosition.clear(); Q_FOREACH(QQuickItem *item, allItems) { addItemToView(i, item); ++i; } } else { Q_FOREACH(QQuickItem *item, m_visibleItems) { releaseItem(item); } m_visibleItems.clear(); m_lastInRowIndexPosition.clear(); m_firstVisibleIndex = -1; } } void HorizontalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY) { Q_FOREACH(QQuickItem *item, m_visibleItems) { QQuickItemPrivate::get(item)->setCulled(item->y() + m_rowHeight <= visibleFromY || item->y() >= visibleToY); } }