#include "kitemlistcontroller.h"
#include "kitemlistheader.h"
-#include "kitemlistheaderwidget_p.h"
-#include "kitemlistrubberband_p.h"
#include "kitemlistselectionmanager.h"
-#include "kitemlistsizehintresolver_p.h"
-#include "kitemlistviewlayouter_p.h"
-#include "kitemlistviewanimation_p.h"
#include "kitemlistwidget.h"
+#include "private/kitemlistheaderwidget.h"
+#include "private/kitemlistrubberband.h"
+#include "private/kitemlistsizehintresolver.h"
+#include "private/kitemlistviewlayouter.h"
+#include "private/kitemlistviewanimation.h"
+
#include <KDebug>
#include <QCursor>
m_styleOption(),
m_visibleItems(),
m_visibleGroups(),
- m_columnWidthsCache(),
m_visibleCells(),
m_sizeHintResolver(0),
m_layouter(0),
if (itemSize.isEmpty()) {
if (m_headerWidget->automaticColumnResizing()) {
- updateColumnWidthsCache();
+ updatePreferredColumnWidths();
} else {
// Only apply the changed height and respect the header widths
// set by the user
m_layouter->setItemOffset(offset);
if (m_headerWidget->isVisible()) {
- m_headerWidget->setPos(-offset, 0);
+ m_headerWidget->setOffset(offset);
}
// Don't check whether the m_layoutTimer is active: Changing the
{
const QList<QByteArray> previousRoles = m_visibleRoles;
m_visibleRoles = roles;
+ onVisibleRolesChanged(roles, previousRoles);
+
+ m_sizeHintResolver->clearCache();
+ m_layouter->markAsDirty();
+
+ if (m_itemSize.isEmpty()) {
+ m_headerWidget->setColumns(roles);
+ updatePreferredColumnWidths();
+ if (!m_headerWidget->automaticColumnResizing()) {
+ // The column-width of new roles are still 0. Apply the preferred
+ // column-width as default with.
+ foreach (const QByteArray& role, m_visibleRoles) {
+ if (m_headerWidget->columnWidth(role) == 0) {
+ const qreal width = m_headerWidget->preferredColumnWidth(role);
+ m_headerWidget->setColumnWidth(role, width);
+ }
+ }
+
+ applyColumnWidthsFromHeader();
+ }
+ }
const bool alternateBackgroundsChanged = m_itemSize.isEmpty() &&
((roles.count() > 1 && previousRoles.count() <= 1) ||
it.next();
KItemListWidget* widget = it.value();
widget->setVisibleRoles(roles);
- updateWidgetColumnWidths(widget);
if (alternateBackgroundsChanged) {
updateAlternateBackgroundForWidget(widget);
}
}
- m_sizeHintResolver->clearCache();
- m_layouter->markAsDirty();
-
- if (m_headerWidget->isVisible()) {
- m_headerWidget->setColumns(roles);
- m_headerWidget->setAutomaticColumnResizing(true);
- }
-
- updateColumnWidthsCache();
doLayout(NoAnimation);
-
- onVisibleRolesChanged(roles, previousRoles);
}
QList<QByteArray> KItemListView::visibleRoles() const
const QSizeF newSize = rect.size();
if (m_itemSize.isEmpty()) {
- // The item size is dynamic:
- // Changing the geometry does not require to do an expensive
- // update of the visible-roles sizes, only the stretched sizes
- // need to be adjusted to the new size.
- updateColumnWidthsForHeader();
-
- if (!m_headerWidget->automaticColumnResizing()) {
- QSizeF dynamicItemSize = m_layouter->itemSize();
-
- if (m_itemSize.width() < 0) {
- const qreal requiredWidth = columnWidthsSum();
- if (newSize.width() > requiredWidth) {
- dynamicItemSize.setWidth(newSize.width());
- }
- const qreal headerWidth = qMax(newSize.width(), requiredWidth);
- m_headerWidget->resize(headerWidth, m_headerWidget->size().height());
- }
-
+ m_headerWidget->resize(rect.width(), m_headerWidget->size().height());
+ if (m_headerWidget->automaticColumnResizing()) {
+ applyAutomaticColumnWidths();
+ } else {
+ const qreal requiredWidth = columnWidthsSum();
+ const QSizeF dynamicItemSize(qMax(newSize.width(), requiredWidth),
+ m_itemSize.height());
m_layouter->setItemSize(dynamicItemSize);
}
// Triggering a synchronous layout is fine from a performance point of view,
// as with dynamic item sizes no moving animation must be done.
m_layouter->setSize(newSize);
- doLayout(Animation);
+ doLayout(NoAnimation);
} else {
const bool animate = !changesItemGridLayout(newSize,
m_layouter->itemSize(),
QSizeF KItemListView::itemSizeHint(int index) const
{
- Q_UNUSED(index);
- return itemSize();
-}
-
-QHash<QByteArray, qreal> KItemListView::columnWidths(const KItemRangeList& itemRanges) const
-{
- Q_UNUSED(itemRanges);
- return QHash<QByteArray, qreal>();
+ return m_widgetCreator->itemSizeHint(index, this);
}
void KItemListView::setSupportsItemExpanding(bool supportsExpanding)
void KItemListView::setHeaderVisible(bool visible)
{
-
if (visible && !m_headerWidget->isVisible()) {
+ QStyleOptionHeader option;
+ const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection,
+ &option, QSize());
+
m_headerWidget->setPos(0, 0);
+ m_headerWidget->resize(size().width(), headerSize.height());
m_headerWidget->setModel(m_model);
m_headerWidget->setColumns(m_visibleRoles);
m_headerWidget->setZValue(1);
connect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
this, SIGNAL(sortRoleChanged(QByteArray,QByteArray)));
- m_headerWidget->setAutomaticColumnResizing(true);
-
- m_layouter->setHeaderHeight(m_headerWidget->size().height());
+ m_layouter->setHeaderHeight(headerSize.height());
m_headerWidget->setVisible(true);
} else if (!visible && m_headerWidget->isVisible()) {
disconnect(m_headerWidget, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)),
this, SIGNAL(sortRoleChanged(QByteArray,QByteArray)));
m_layouter->setHeaderHeight(0);
- m_headerWidget->setAutomaticColumnResizing(true);
m_headerWidget->setVisible(false);
}
}
void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
{
- updateColumnWidthsCache(itemRanges);
+ if (m_itemSize.isEmpty()) {
+ updatePreferredColumnWidths(itemRanges);
+ }
const bool hasMultipleRanges = (itemRanges.count() > 1);
if (hasMultipleRanges) {
void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
{
- updateColumnWidthsCache();
+ if (m_itemSize.isEmpty()) {
+ // Don't pass the item-range: The preferred column-widths of
+ // all items must be adjusted when removing items.
+ updatePreferredColumnWidths();
+ }
const bool hasMultipleRanges = (itemRanges.count() > 1);
if (hasMultipleRanges) {
m_layouter->markAsDirty();
+ int removedItemsCount = 0;
+ for (int i = 0; i < itemRanges.count(); ++i) {
+ removedItemsCount += itemRanges[i].count;
+ }
+
for (int i = itemRanges.count() - 1; i >= 0; --i) {
- const KItemRange& range = itemRanges.at(i);
+ const KItemRange& range = itemRanges[i];
const int index = range.index;
const int count = range.count;
if (index < 0 || count <= 0) {
const int firstRemovedIndex = index;
const int lastRemovedIndex = index + count - 1;
- const int lastIndex = m_model->count() + count - 1;
+ const int lastIndex = m_model->count() - 1 + removedItemsCount;
+ removedItemsCount -= count;
// Remove all KItemListWidget instances that got deleted
for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
const QSet<QByteArray>& roles)
{
const bool updateSizeHints = itemSizeHintUpdateRequired(roles);
- if (updateSizeHints) {
- updateColumnWidthsCache(itemRanges);
+ if (updateSizeHints && m_itemSize.isEmpty()) {
+ updatePreferredColumnWidths(itemRanges);
}
foreach (const KItemRange& itemRange, itemRanges) {
qreal currentWidth,
qreal previousWidth)
{
+ Q_UNUSED(role);
+ Q_UNUSED(currentWidth);
Q_UNUSED(previousWidth);
m_headerWidget->setAutomaticColumnResizing(false);
-
- if (m_columnWidthsCache.contains(role)) {
- m_columnWidthsCache.insert(role, currentWidth);
-
- // Apply the new size to the layouter
- const qreal requiredWidth = columnWidthsSum();
- const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth),
- m_itemSize.height());
- m_layouter->setItemSize(dynamicItemSize);
-
- // Update the role sizes for all visible widgets
- QHashIterator<int, KItemListWidget*> it(m_visibleItems);
- while (it.hasNext()) {
- it.next();
- updateWidgetColumnWidths(it.value());
- }
- doLayout(NoAnimation);
- }
+ applyColumnWidthsFromHeader();
+ doLayout(NoAnimation);
}
void KItemListView::slotHeaderColumnMoved(const QByteArray& role,
return m_itemSize.isEmpty() && m_visibleRoles.count() > 1;
}
-void KItemListView::applyColumnWidthsFromHeader()
+QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const
{
- qreal roleWidthSum = 0;
- foreach (const QByteArray& role, m_visibleRoles) {
- const qreal width = m_headerWidget->columnWidth(role);
- m_columnWidthsCache.insert(role, width);
- roleWidthSum += width;
+ QElapsedTimer timer;
+ timer.start();
+
+ QHash<QByteArray, qreal> widths;
+
+ // Calculate the minimum width for each column that is required
+ // to show the headline unclipped.
+ const QFontMetricsF fontMetrics(m_headerWidget->font());
+ const int gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin);
+ const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin);
+ foreach (const QByteArray& visibleRole, visibleRoles()) {
+ const QString headerText = m_model->roleDescription(visibleRole);
+ const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2;
+ widths.insert(visibleRole, headerWidth);
}
+ // Calculate the preferred column withs for each item and ignore values
+ // smaller than the width for showing the headline unclipped.
+ int calculatedItemCount = 0;
+ bool maxTimeExceeded = false;
+ foreach (const KItemRange& itemRange, itemRanges) {
+ const int startIndex = itemRange.index;
+ const int endIndex = startIndex + itemRange.count - 1;
+
+ for (int i = startIndex; i <= endIndex; ++i) {
+ foreach (const QByteArray& visibleRole, visibleRoles()) {
+ qreal maxWidth = widths.value(visibleRole, 0);
+ const qreal width = m_widgetCreator->preferredRoleColumnWidth(visibleRole, i, this);
+ maxWidth = qMax(width, maxWidth);
+ widths.insert(visibleRole, maxWidth);
+ }
+
+ if (calculatedItemCount > 100 && timer.elapsed() > 200) {
+ // When having several thousands of items calculating the sizes can get
+ // very expensive. We accept a possibly too small role-size in favour
+ // of having no blocking user interface.
+ maxTimeExceeded = true;
+ break;
+ }
+ ++calculatedItemCount;
+ }
+ if (maxTimeExceeded) {
+ break;
+ }
+ }
+
+ return widths;
+}
+
+void KItemListView::applyColumnWidthsFromHeader()
+{
// Apply the new size to the layouter
- const QSizeF dynamicItemSize(qMax(size().width(), roleWidthSum),
+ const qreal requiredWidth = columnWidthsSum();
+ const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth),
m_itemSize.height());
m_layouter->setItemSize(dynamicItemSize);
it.next();
updateWidgetColumnWidths(it.value());
}
- doLayout(NoAnimation);
}
void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget)
}
}
-void KItemListView::updateColumnWidthsCache(const KItemRangeList& itemRanges)
+void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges)
{
- if (!m_itemSize.isEmpty() || !m_headerWidget->automaticColumnResizing()) {
- return;
- }
-
+ Q_ASSERT(m_itemSize.isEmpty());
const int itemCount = m_model->count();
int rangesItemCount = 0;
foreach (const KItemRange& range, itemRanges) {
}
if (itemCount == rangesItemCount) {
- m_columnWidthsCache = columnWidths(itemRanges);
- if (m_headerWidget->isVisible()) {
- // Assure the the sizes are not smaller than the minimum defined by the header
- const qreal minHeaderRoleWidth = m_headerWidget->minimumColumnWidth();
- QMutableHashIterator<QByteArray, qreal> it (m_columnWidthsCache);
- while (it.hasNext()) {
- it.next();
- const qreal width = it.value();
- if (width < minHeaderRoleWidth) {
- m_columnWidthsCache.insert(it.key(), minHeaderRoleWidth);
- }
- }
+ const QHash<QByteArray, qreal> preferredWidths = preferredColumnWidths(itemRanges);
+ foreach (const QByteArray& role, m_visibleRoles) {
+ m_headerWidget->setPreferredColumnWidth(role, preferredWidths.value(role));
}
} else {
// Only a sub range of the roles need to be determined.
// The chances are good that the widths of the sub ranges
// already fit into the available widths and hence no
// expensive update might be required.
- bool updateRequired = false;
+ bool changed = false;
- const QHash<QByteArray, qreal> updatedWidths = columnWidths(itemRanges);
+ const QHash<QByteArray, qreal> updatedWidths = preferredColumnWidths(itemRanges);
QHashIterator<QByteArray, qreal> it(updatedWidths);
while (it.hasNext()) {
it.next();
const QByteArray& role = it.key();
const qreal updatedWidth = it.value();
- const qreal currentWidth = m_columnWidthsCache.value(role);
+ const qreal currentWidth = m_headerWidget->preferredColumnWidth(role);
if (updatedWidth > currentWidth) {
- m_columnWidthsCache.insert(role, updatedWidth);
- updateRequired = true;
+ m_headerWidget->setPreferredColumnWidth(role, updatedWidth);
+ changed = true;
}
}
- if (!updateRequired) {
+ if (!changed) {
// All the updated sizes are smaller than the current sizes and no change
// of the stretched roles-widths is required
return;
}
}
- updateColumnWidthsForHeader();
+ if (m_headerWidget->automaticColumnResizing()) {
+ applyAutomaticColumnWidths();
+ }
}
-void KItemListView::updateColumnWidthsCache()
+void KItemListView::updatePreferredColumnWidths()
{
- if (!m_model) {
- return;
- }
-
- const int itemCount = m_model->count();
- if (itemCount > 0) {
- updateColumnWidthsCache(KItemRangeList() << KItemRange(0, itemCount));
+ if (m_model) {
+ updatePreferredColumnWidths(KItemRangeList() << KItemRange(0, m_model->count()));
}
}
-void KItemListView::updateColumnWidthsForHeader()
+void KItemListView::applyAutomaticColumnWidths()
{
- if (!m_itemSize.isEmpty() || !m_headerWidget->automaticColumnResizing() || m_visibleRoles.isEmpty()) {
- return;
- }
+ Q_ASSERT(m_itemSize.isEmpty());
+ Q_ASSERT(m_headerWidget->automaticColumnResizing());
// Calculate the maximum size of an item by considering the
// visible role sizes and apply them to the layouter. If the
// first role will get stretched.
foreach (const QByteArray& role, m_visibleRoles) {
- m_headerWidget->setColumnWidth(role, m_columnWidthsCache.value(role));
+ const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role);
+ m_headerWidget->setColumnWidth(role, preferredWidth);
}
- const QByteArray role = m_visibleRoles.first();
- qreal firstColumnWidth = m_columnWidthsCache.value(role);
-
+ const QByteArray firstRole = m_visibleRoles.first();
+ qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole);
QSizeF dynamicItemSize = m_itemSize;
- if (dynamicItemSize.width() <= 0) {
- const qreal requiredWidth = columnWidthsSum();
- const qreal availableWidth = size().width();
- if (requiredWidth != availableWidth) {
- // Stretch the first role to use the whole remaining width
- firstColumnWidth += availableWidth - requiredWidth;
-
- // TODO: A proper calculation of the minimum width depends on the implementation
- // of KItemListWidget. Probably a kind of minimum size-hint should be introduced
- // later.
- const qreal minWidth = m_styleOption.iconSize * 2 + 200;
- if (firstColumnWidth < minWidth) {
- firstColumnWidth = minWidth;
- }
-
- m_headerWidget->setColumnWidth(role, firstColumnWidth);
+ qreal requiredWidth = columnWidthsSum();
+ const qreal availableWidth = size().width();
+ if (requiredWidth < availableWidth) {
+ // Stretch the first column to use the whole remaining width
+ firstColumnWidth += availableWidth - requiredWidth;
+ m_headerWidget->setColumnWidth(firstRole, firstColumnWidth);
+ } else if (requiredWidth > availableWidth) {
+ // Shrink the first column to be able to show as much other
+ // columns as possible
+ qreal shrinkedFirstColumnWidth = firstColumnWidth - requiredWidth + availableWidth;
+
+ // TODO: A proper calculation of the minimum width depends on the implementation
+ // of KItemListWidget. Probably a kind of minimum size-hint should be introduced
+ // later.
+ const qreal minWidth = qMin(firstColumnWidth, qreal(m_styleOption.iconSize * 2 + 200));
+ if (shrinkedFirstColumnWidth < minWidth) {
+ shrinkedFirstColumnWidth = minWidth;
}
- dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth);
+
+ m_headerWidget->setColumnWidth(firstRole, shrinkedFirstColumnWidth);
+ requiredWidth -= firstColumnWidth - shrinkedFirstColumnWidth;
}
- m_layouter->setItemSize(dynamicItemSize);
+ dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth);
- if (m_headerWidget->isVisible()) {
- m_headerWidget->resize(dynamicItemSize.width(), m_headerWidget->size().height());
- }
+ m_layouter->setItemSize(dynamicItemSize);
// Update the role sizes for all visible widgets
QHashIterator<int, KItemListWidget*> it(m_visibleItems);
qreal KItemListView::columnWidthsSum() const
{
- qreal widthSum = 0;
- QHashIterator<QByteArray, qreal> it(m_columnWidthsCache);
- while (it.hasNext()) {
- it.next();
- widthSum += it.value();
+ qreal widthsSum = 0;
+ foreach (const QByteArray& role, m_visibleRoles) {
+ widthsSum += m_headerWidget->columnWidth(role);
}
- return widthSum;
+ return widthsSum;
}
QRectF KItemListView::headerBoundaries() const