/*
* Copyright (C) 2014, 2016 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 .
*/
#include "organicgrid.h"
#include
#include
OrganicGrid::OrganicGrid()
: m_firstVisibleIndex(-1)
, m_numberOfModulesPerRow(-1)
{
}
QSizeF OrganicGrid::smallDelegateSize() const
{
return m_smallDelegateSize;
}
void OrganicGrid::setSmallDelegateSize(const QSizeF size)
{
if (m_smallDelegateSize != size) {
m_smallDelegateSize = size;
Q_EMIT smallDelegateSizeChanged();
if (isComponentComplete()) {
relayout();
}
}
}
QSizeF OrganicGrid::bigDelegateSize() const
{
return m_bigDelegateSize;
}
void OrganicGrid::setBigDelegateSize(const QSizeF size)
{
if (m_bigDelegateSize != size) {
m_bigDelegateSize = size;
Q_EMIT bigDelegateSizeChanged();
if (isComponentComplete()) {
relayout();
}
}
}
QPointF OrganicGrid::positionForIndex(int modelIndex) const
{
const qreal moduleHeight = m_smallDelegateSize.height() + rowSpacing() + m_bigDelegateSize.height();
const qreal moduleWidth = m_smallDelegateSize.width() * 2 + columnSpacing() * 2 + m_bigDelegateSize.width();
const int itemsPerRow = m_numberOfModulesPerRow * 6;
const int rowIndex = floor(modelIndex / itemsPerRow);
const int columnIndex = floor((modelIndex - rowIndex * itemsPerRow) / 6);
qreal yPos = (moduleHeight + rowSpacing()) * rowIndex;
const int moduleIndex = modelIndex % 6;
if (moduleIndex == 2) {
yPos += m_smallDelegateSize.height() + rowSpacing();
} else if (moduleIndex == 3 || moduleIndex == 5) {
yPos += m_bigDelegateSize.height() + rowSpacing();
}
qreal xPos = (moduleWidth + columnSpacing()) * columnIndex;
if (moduleIndex == 1) {
xPos += m_smallDelegateSize.width() + columnSpacing();
} else if (moduleIndex == 3) {
xPos += m_bigDelegateSize.width() + columnSpacing();
} else if (moduleIndex == 4) {
xPos += (m_smallDelegateSize.width() + columnSpacing()) * 2;
} else if (moduleIndex == 5) {
xPos += m_bigDelegateSize.width() + m_smallDelegateSize.width() + columnSpacing() * 2;
}
return QPointF(xPos, yPos);
}
QSizeF OrganicGrid::sizeForIndex(int modelIndex) const
{
const int moduleIndex = modelIndex % 6;
if (moduleIndex == 0 || moduleIndex == 1 || moduleIndex == 3 || moduleIndex == 5) {
return m_smallDelegateSize;
} else {
return m_bigDelegateSize;
}
}
void OrganicGrid::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
{
if (m_visibleItems.isEmpty()) {
*modelIndex = 0;
*yPos = 0;
} else {
*modelIndex = m_firstVisibleIndex + m_visibleItems.count();
// We create stuff in a 6-module basis, so always return back
// the y position of the first item
const int firstModuleIndex = ((*modelIndex) / 6) * 6;
*yPos = positionForIndex(firstModuleIndex).y();
}
}
void OrganicGrid::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
{
if (m_visibleItems.isEmpty()) {
*modelIndex = 0;
*yPos = 0;
} else {
*modelIndex = m_firstVisibleIndex - 1;
// We create stuff in a 6-module basis, so always return back
// the y position of the last item bottom
const int lastModuleIndex = ((*modelIndex) / 6) * 6 + 5;
*yPos = positionForIndex(lastModuleIndex).y();
*yPos += sizeForIndex(lastModuleIndex).height();
}
}
void OrganicGrid::addItemToView(int modelIndex, QQuickItem *item)
{
// modelIndex has to be either m_firstVisibleIndex - 1 or m_firstVisibleIndex + m_visibleItems.count() or the first
if (modelIndex == m_firstVisibleIndex + m_visibleItems.count()) {
m_visibleItems << item;
} else if (modelIndex == m_firstVisibleIndex - 1) {
m_firstVisibleIndex = modelIndex;
m_visibleItems.prepend(item);
} else if (modelIndex == 0) {
m_firstVisibleIndex = 0;
m_visibleItems << item;
} else {
qWarning() << "OrganicGrid::addItemToView - Got unexpected modelIndex"
<< modelIndex << m_firstVisibleIndex << m_visibleItems.count();
return;
}
const QPointF pos = positionForIndex(modelIndex);
item->setPosition(pos);
item->setSize(sizeForIndex(modelIndex));
}
bool OrganicGrid::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY)
{
bool changed = false;
// As adding, we also remove in a 6-module basis
int lastModuleIndex = (m_firstVisibleIndex / 6) * 6 + 5;
bool removeIndex = positionForIndex(lastModuleIndex).y() + sizeForIndex(lastModuleIndex).height() < bufferFromY;
while (removeIndex && !m_visibleItems.isEmpty()) {
releaseItem(m_visibleItems.takeFirst());
changed = true;
m_firstVisibleIndex++;
lastModuleIndex = (m_firstVisibleIndex / 6) * 6 + 5;
removeIndex = positionForIndex(lastModuleIndex).y() + sizeForIndex(lastModuleIndex).height() < bufferFromY;
}
int firstModuleIndex = ((m_firstVisibleIndex + m_visibleItems.count() - 1) / 6) * 6;
removeIndex = positionForIndex(firstModuleIndex).y() > bufferToY;
while (removeIndex && !m_visibleItems.isEmpty()) {
releaseItem(m_visibleItems.takeLast());
changed = true;
firstModuleIndex = ((m_firstVisibleIndex + m_visibleItems.count() - 1) / 6) * 6;
removeIndex = positionForIndex(firstModuleIndex).y() > bufferToY;
}
if (m_visibleItems.isEmpty()) {
m_firstVisibleIndex = -1;
}
return changed;
}
void OrganicGrid::cleanupExistingItems()
{
Q_FOREACH(QQuickItem *item, m_visibleItems)
releaseItem(item);
m_visibleItems.clear();
m_firstVisibleIndex = -1;
setImplicitHeightDirty();
}
void OrganicGrid::doRelayout()
{
const qreal moduleWidth = m_smallDelegateSize.width() * 2 + columnSpacing() * 2 + m_bigDelegateSize.width();
m_numberOfModulesPerRow = floor((width() + columnSpacing()) / (moduleWidth + columnSpacing()));
m_numberOfModulesPerRow = qMax(1, m_numberOfModulesPerRow);
int i = m_firstVisibleIndex;
const QList allItems = m_visibleItems;
m_visibleItems.clear();
Q_FOREACH(QQuickItem *item, allItems) {
addItemToView(i, item);
++i;
}
}
void OrganicGrid::updateItemCulling(qreal visibleFromY, qreal visibleToY)
{
Q_FOREACH(QQuickItem *item, m_visibleItems) {
QQuickItemPrivate::get(item)->setCulled(item->y() + item->height() <= visibleFromY || item->y() >= visibleToY);
}
}
void OrganicGrid::calculateImplicitHeight()
{
const qreal moduleHeight = m_smallDelegateSize.height() + rowSpacing() + m_bigDelegateSize.height();
const int itemCount = !model() ? 0 : model()->rowCount();
const int itemsPerRow = m_numberOfModulesPerRow * 6;
const int fullRows = floor(itemCount / itemsPerRow);
const qreal fullRowsHeight = fullRows == 0 ? 0 : fullRows * moduleHeight + rowSpacing() * (fullRows - 1);
const int remainingItems = itemCount - fullRows * itemsPerRow;
if (remainingItems == 0) {
setImplicitHeight(fullRowsHeight);
} else if (remainingItems <= 2) {
setImplicitHeight(fullRowsHeight + m_smallDelegateSize.height() + rowSpacing());
} else {
setImplicitHeight(fullRowsHeight + rowSpacing() + moduleHeight);
}
}
void OrganicGrid::processModelRemoves(const QVector &removes)
{
Q_FOREACH(const QQmlChangeSet::Change remove, removes) {
for (int i = remove.count - 1; i >= 0; --i) {
const int indexToRemove = remove.index + i;
// We only support removing from the end
const int lastIndex = m_firstVisibleIndex + m_visibleItems.count() - 1;
if (indexToRemove == lastIndex) {
releaseItem(m_visibleItems.takeLast());
} else {
if (indexToRemove < lastIndex) {
qDebug() << "OrganicGrid only supports removal from the end of the model, resetting instead";
cleanupExistingItems();
break;
}
}
}
}
if (m_visibleItems.isEmpty()) {
m_firstVisibleIndex = -1;
}
setImplicitHeightDirty();
}