X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/613758b5ec7c57a5c115ae7a40ccaff71b923db2..6a3f8086a372ca1c21ab474c7934e8f8e4b238f5:/src/kitemviews/kitemlistview.cpp diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index b7d4c2470..6a68ae798 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -22,25 +22,30 @@ #include "kitemlistview.h" +#include +#include "kitemlistcontainer.h" #include "kitemlistcontroller.h" -#include "kitemlistheader_p.h" -#include "kitemlistrubberband_p.h" +#include "kitemlistheader.h" #include "kitemlistselectionmanager.h" -#include "kitemlistsizehintresolver_p.h" -#include "kitemlistviewlayouter_p.h" -#include "kitemlistviewanimation_p.h" #include "kitemlistwidget.h" -#include +#include "private/kitemlistheaderwidget.h" +#include "private/kitemlistrubberband.h" +#include "private/kitemlistsizehintresolver.h" +#include "private/kitemlistviewlayouter.h" +#include "private/kitemlistviewanimation.h" #include #include +#include #include #include #include #include #include +#include "kitemlistviewaccessible.h" + namespace { // Time in ms until reaching the autoscroll margin triggers // an initial autoscrolling @@ -50,23 +55,39 @@ namespace { const int RepeatingAutoScrollDelay = 1000 / 60; } +#ifndef QT_NO_ACCESSIBILITY +QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object) +{ + Q_UNUSED(key) + + if (KItemListContainer* container = qobject_cast(object)) { + return new KItemListContainerAccessible(container); + } else if (KItemListView* view = qobject_cast(object)) { + return new KItemListViewAccessible(view); + } + + return 0; +} +#endif + KItemListView::KItemListView(QGraphicsWidget* parent) : QGraphicsWidget(parent), m_enabledSelectionToggles(false), m_grouped(false), + m_supportsItemExpanding(false), + m_editingRole(false), m_activeTransactions(0), m_endTransactionAnimationHint(Animation), m_itemSize(), m_controller(0), m_model(0), m_visibleRoles(), - m_visibleRolesSizes(), - m_stretchedVisibleRolesSizes(), m_widgetCreator(0), m_groupHeaderCreator(0), m_styleOption(), m_visibleItems(), m_visibleGroups(), + m_visibleCells(), m_sizeHintResolver(0), m_layouter(0), m_animation(0), @@ -81,7 +102,8 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_autoScrollIncrement(0), m_autoScrollTimer(0), m_header(0), - m_useHeaderWidths(false) + m_headerWidget(0), + m_dropIndicator() { setAcceptHoverEvents(true); @@ -101,76 +123,31 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_rubberBand = new KItemListRubberBand(this); connect(m_rubberBand, SIGNAL(activationChanged(bool)), this, SLOT(slotRubberBandActivationChanged(bool))); -} - -KItemListView::~KItemListView() -{ - delete m_sizeHintResolver; - m_sizeHintResolver = 0; -} - -void KItemListView::setScrollOrientation(Qt::Orientation orientation) -{ - const Qt::Orientation previousOrientation = m_layouter->scrollOrientation(); - if (orientation == previousOrientation) { - return; - } - - m_layouter->setScrollOrientation(orientation); - m_animation->setScrollOrientation(orientation); - m_sizeHintResolver->clearCache(); - - if (m_grouped) { - QMutableHashIterator it (m_visibleGroups); - while (it.hasNext()) { - it.next(); - it.value()->setScrollOrientation(orientation); - } - updateGroupHeaderHeight(); - } + m_headerWidget = new KItemListHeaderWidget(this); + m_headerWidget->setVisible(false); - doLayout(NoAnimation); + m_header = new KItemListHeader(this); - onScrollOrientationChanged(orientation, previousOrientation); - emit scrollOrientationChanged(orientation, previousOrientation); -} +#ifndef QT_NO_ACCESSIBILITY + QAccessible::installFactory(accessibleInterfaceFactory); +#endif -Qt::Orientation KItemListView::scrollOrientation() const -{ - return m_layouter->scrollOrientation(); } -void KItemListView::setItemSize(const QSizeF& itemSize) +KItemListView::~KItemListView() { - const QSizeF previousSize = m_itemSize; - if (itemSize == previousSize) { - return; - } + // The group headers are children of the widgets created by + // widgetCreator(). So it is mandatory to delete the group headers + // first. + delete m_groupHeaderCreator; + m_groupHeaderCreator = 0; - // Skip animations when the number of rows or columns - // are changed in the grid layout. Although the animation - // engine can handle this usecase, it looks obtrusive. - const bool animate = !changesItemGridLayout(m_layouter->size(), - itemSize, - m_layouter->itemMargin()); - - m_itemSize = itemSize; + delete m_widgetCreator; + m_widgetCreator = 0; - if (itemSize.isEmpty()) { - updateVisibleRolesSizes(); - } else { - m_layouter->setItemSize(itemSize); - } - - m_sizeHintResolver->clearCache(); - doLayout(animate ? Animation : NoAnimation); - onItemSizeChanged(itemSize, previousSize); -} - -QSizeF KItemListView::itemSize() const -{ - return m_itemSize; + delete m_sizeHintResolver; + m_sizeHintResolver = 0; } void KItemListView::setScrollOffset(qreal offset) @@ -211,8 +188,8 @@ void KItemListView::setItemOffset(qreal offset) } m_layouter->setItemOffset(offset); - if (m_header) { - m_header->setPos(-offset, 0); + if (m_headerWidget->isVisible()) { + m_headerWidget->setOffset(offset); } // Don't check whether the m_layoutTimer is active: Changing the @@ -235,28 +212,43 @@ void KItemListView::setVisibleRoles(const QList& roles) { const QList 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) || + (roles.count() <= 1 && previousRoles.count() > 1)); QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); KItemListWidget* widget = it.value(); widget->setVisibleRoles(roles); - widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); - } - - m_sizeHintResolver->clearCache(); - m_layouter->markAsDirty(); - - if (m_header) { - m_header->setVisibleRoles(roles); - m_header->setVisibleRolesWidths(headerRolesWidths()); - m_useHeaderWidths = false; + if (alternateBackgroundsChanged) { + updateAlternateBackgroundForWidget(widget); + } } - updateVisibleRolesSizes(); doLayout(NoAnimation); - - onVisibleRolesChanged(roles, previousRoles); } QList KItemListView::visibleRoles() const @@ -275,7 +267,6 @@ void KItemListView::setAutoScroll(bool enabled) delete m_autoScrollTimer; m_autoScrollTimer = 0; } - } bool KItemListView::autoScroll() const @@ -313,55 +304,39 @@ KItemModelBase* KItemListView::model() const void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator) { + if (m_widgetCreator) { + delete m_widgetCreator; + } m_widgetCreator = widgetCreator; } KItemListWidgetCreatorBase* KItemListView::widgetCreator() const { + if (!m_widgetCreator) { + m_widgetCreator = defaultWidgetCreator(); + } return m_widgetCreator; } void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator) { + if (m_groupHeaderCreator) { + delete m_groupHeaderCreator; + } m_groupHeaderCreator = groupHeaderCreator; } KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const { + if (!m_groupHeaderCreator) { + m_groupHeaderCreator = defaultGroupHeaderCreator(); + } return m_groupHeaderCreator; } -void KItemListView::setStyleOption(const KItemListStyleOption& option) +QSizeF KItemListView::itemSize() const { - const KItemListStyleOption previousOption = m_styleOption; - m_styleOption = option; - - bool animate = true; - const QSizeF margin(option.horizontalMargin, option.verticalMargin); - if (margin != m_layouter->itemMargin()) { - // Skip animations when the number of rows or columns - // are changed in the grid layout. Although the animation - // engine can handle this usecase, it looks obtrusive. - animate = !changesItemGridLayout(m_layouter->size(), - m_layouter->itemSize(), - margin); - m_layouter->setItemMargin(margin); - } - - if (m_grouped) { - updateGroupHeaderHeight(); - } - - QHashIterator it(m_visibleItems); - while (it.hasNext()) { - it.next(); - it.value()->setStyleOption(option); - } - - m_sizeHintResolver->clearCache(); - doLayout(animate ? Animation : NoAnimation); - - onStyleOptionChanged(option, previousOption); + return m_itemSize; } const KItemListStyleOption& KItemListView::styleOption() const @@ -379,45 +354,26 @@ void KItemListView::setGeometry(const QRectF& rect) 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. - updateStretchedVisibleRolesSizes(); - - if (m_useHeaderWidths) { - QSizeF dynamicItemSize = m_layouter->itemSize(); - - if (m_itemSize.width() < 0) { - const qreal requiredWidth = visibleRolesSizesWidthSum(); - if (newSize.width() > requiredWidth) { - dynamicItemSize.setWidth(newSize.width()); - } - const qreal headerWidth = qMax(newSize.width(), requiredWidth); - m_header->resize(headerWidth, m_header->size().height()); - } - - if (m_itemSize.height() < 0) { - const qreal requiredHeight = visibleRolesSizesHeightSum(); - if (newSize.height() > requiredHeight) { - dynamicItemSize.setHeight(newSize.height()); - } - // TODO: KItemListHeader is not prepared for vertical alignment - } - + 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(), - m_layouter->itemMargin()); + m_layouter->itemMargin()); m_layouter->setSize(newSize); - + if (animate) { // Trigger an asynchronous relayout with m_layoutTimer to prevent // performance bottlenecks. If the timer is exceeded, an animated layout @@ -490,19 +446,21 @@ int KItemListView::lastVisibleIndex() const QSizeF KItemListView::itemSizeHint(int index) const { - Q_UNUSED(index); - return itemSize(); + return widgetCreator()->itemSizeHint(index, this); } -QHash KItemListView::visibleRolesSizes(const KItemRangeList& itemRanges) const +void KItemListView::setSupportsItemExpanding(bool supportsExpanding) { - Q_UNUSED(itemRanges); - return QHash(); + if (m_supportsItemExpanding != supportsExpanding) { + m_supportsItemExpanding = supportsExpanding; + updateSiblingsInformation(); + onSupportsItemExpandingChanged(supportsExpanding); + } } bool KItemListView::supportsItemExpanding() const { - return false; + return m_supportsItemExpanding; } QRectF KItemListView::itemRect(int index) const @@ -526,8 +484,8 @@ QRectF KItemListView::itemContextRect(int index) const void KItemListView::scrollToItem(int index) { QRectF viewGeometry = geometry(); - if (m_header) { - const qreal headerHeight = m_header->size().height(); + if (m_headerWidget->isVisible()) { + const qreal headerHeight = m_headerWidget->size().height(); viewGeometry.adjust(0, headerHeight, 0, 0); } const QRectF currentRect = itemRect(index); @@ -582,44 +540,88 @@ bool KItemListView::isTransactionActive() const return m_activeTransactions > 0; } -void KItemListView::setHeaderShown(bool show) +void KItemListView::setHeaderVisible(bool visible) { + if (visible && !m_headerWidget->isVisible()) { + QStyleOptionHeader option; + const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, + &option, QSize()); - if (show && !m_header) { - m_header = new KItemListHeader(this); - m_header->setPos(0, 0); - m_header->setModel(m_model); - m_header->setVisibleRoles(m_visibleRoles); - m_header->setVisibleRolesWidths(headerRolesWidths()); - m_header->setZValue(1); + 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_header, SIGNAL(visibleRoleWidthChanged(QByteArray,qreal,qreal)), - this, SLOT(slotVisibleRoleWidthChanged(QByteArray,qreal,qreal))); - connect(m_header, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + connect(m_headerWidget, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), + this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal))); + connect(m_headerWidget, SIGNAL(columnMoved(QByteArray,int,int)), + this, SLOT(slotHeaderColumnMoved(QByteArray,int,int))); + connect(m_headerWidget, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder))); - connect(m_header, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + connect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), this, SIGNAL(sortRoleChanged(QByteArray,QByteArray))); - m_useHeaderWidths = false; + m_layouter->setHeaderHeight(headerSize.height()); + m_headerWidget->setVisible(true); + } else if (!visible && m_headerWidget->isVisible()) { + disconnect(m_headerWidget, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), + this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal))); + disconnect(m_headerWidget, SIGNAL(columnMoved(QByteArray,int,int)), + this, SLOT(slotHeaderColumnMoved(QByteArray,int,int))); + disconnect(m_headerWidget, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + disconnect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SIGNAL(sortRoleChanged(QByteArray,QByteArray))); - m_layouter->setHeaderHeight(m_header->size().height()); - } else if (!show && m_header) { - delete m_header; - m_header = 0; - m_useHeaderWidths = false; m_layouter->setHeaderHeight(0); + m_headerWidget->setVisible(false); } } -bool KItemListView::isHeaderShown() const +bool KItemListView::isHeaderVisible() const { - return m_header != 0; + return m_headerWidget->isVisible(); +} + +KItemListHeader* KItemListView::header() const +{ + return m_header; } QPixmap KItemListView::createDragPixmap(const QSet& indexes) const { - Q_UNUSED(indexes); - return QPixmap(); + QPixmap pixmap; + + if (indexes.count() == 1) { + KItemListWidget* item = m_visibleItems.value(indexes.toList().first()); + QGraphicsView* graphicsView = scene()->views()[0]; + if (item && graphicsView) { + pixmap = item->createDragPixmap(0, graphicsView); + } + } else { + // TODO: Not implemented yet. Probably extend the interface + // from KItemListWidget::createDragPixmap() to return a pixmap + // that can be used for multiple indexes. + } + + return pixmap; +} + +void KItemListView::editRole(int index, const QByteArray& role) +{ + KItemListWidget* widget = m_visibleItems.value(index); + if (!widget || m_editingRole) { + return; + } + + m_editingRole = true; + widget->setEditedRole(role); + + connect(widget, SIGNAL(roleEditingCanceled(int,QByteArray,QVariant)), + this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant))); + connect(widget, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)), + this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant))); } void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) @@ -644,6 +646,154 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt opt.rect = rubberBandRect.toRect(); style()->drawControl(QStyle::CE_RubberBand, &opt, painter); } + + if (!m_dropIndicator.isEmpty()) { + const QRectF r = m_dropIndicator.toRect(); + + QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color(); + painter->setPen(color); + + // TODO: The following implementation works only for a vertical scroll-orientation + // and assumes a height of the m_draggingInsertIndicator of 1. + Q_ASSERT(r.height() == 1); + painter->drawLine(r.left() + 1, r.top(), r.right() - 1, r.top()); + + color.setAlpha(128); + painter->setPen(color); + painter->drawRect(r.left(), r.top() - 1, r.width() - 1, 2); + } +} + +void KItemListView::setItemSize(const QSizeF& size) +{ + const QSizeF previousSize = m_itemSize; + if (size == previousSize) { + return; + } + + // Skip animations when the number of rows or columns + // are changed in the grid layout. Although the animation + // engine can handle this usecase, it looks obtrusive. + const bool animate = !changesItemGridLayout(m_layouter->size(), + size, + m_layouter->itemMargin()); + + const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) && + (( m_itemSize.isEmpty() && !size.isEmpty()) || + (!m_itemSize.isEmpty() && size.isEmpty())); + + m_itemSize = size; + + if (alternateBackgroundsChanged) { + // For an empty item size alternate backgrounds are drawn if more than + // one role is shown. Assure that the backgrounds for visible items are + // updated when changing the size in this context. + updateAlternateBackgrounds(); + } + + if (size.isEmpty()) { + if (m_headerWidget->automaticColumnResizing()) { + updatePreferredColumnWidths(); + } else { + // Only apply the changed height and respect the header widths + // set by the user + const qreal currentWidth = m_layouter->itemSize().width(); + const QSizeF newSize(currentWidth, size.height()); + m_layouter->setItemSize(newSize); + } + } else { + m_layouter->setItemSize(size); + } + + m_sizeHintResolver->clearCache(); + doLayout(animate ? Animation : NoAnimation); + onItemSizeChanged(size, previousSize); +} + +void KItemListView::setStyleOption(const KItemListStyleOption& option) +{ + const KItemListStyleOption previousOption = m_styleOption; + m_styleOption = option; + + bool animate = true; + const QSizeF margin(option.horizontalMargin, option.verticalMargin); + if (margin != m_layouter->itemMargin()) { + // Skip animations when the number of rows or columns + // are changed in the grid layout. Although the animation + // engine can handle this usecase, it looks obtrusive. + animate = !changesItemGridLayout(m_layouter->size(), + m_layouter->itemSize(), + margin); + m_layouter->setItemMargin(margin); + } + + if (m_grouped) { + updateGroupHeaderHeight(); + } + + if (animate && previousOption.maxTextSize != option.maxTextSize) { + // Animating a change of the maximum text size just results in expensive + // temporary eliding and clipping operations and does not look good visually. + animate = false; + } + + QHashIterator it(m_visibleItems); + while (it.hasNext()) { + it.next(); + it.value()->setStyleOption(option); + } + + m_sizeHintResolver->clearCache(); + m_layouter->markAsDirty(); + doLayout(animate ? Animation : NoAnimation); + + if (m_itemSize.isEmpty()) { + updatePreferredColumnWidths(); + } + + onStyleOptionChanged(option, previousOption); +} + +void KItemListView::setScrollOrientation(Qt::Orientation orientation) +{ + const Qt::Orientation previousOrientation = m_layouter->scrollOrientation(); + if (orientation == previousOrientation) { + return; + } + + m_layouter->setScrollOrientation(orientation); + m_animation->setScrollOrientation(orientation); + m_sizeHintResolver->clearCache(); + + if (m_grouped) { + QMutableHashIterator it (m_visibleGroups); + while (it.hasNext()) { + it.next(); + it.value()->setScrollOrientation(orientation); + } + updateGroupHeaderHeight(); + + } + + doLayout(NoAnimation); + + onScrollOrientationChanged(orientation, previousOrientation); + emit scrollOrientationChanged(orientation, previousOrientation); +} + +Qt::Orientation KItemListView::scrollOrientation() const +{ + return m_layouter->scrollOrientation(); +} + +KItemListWidgetCreatorBase* KItemListView::defaultWidgetCreator() const +{ + return 0; +} + +KItemListGroupHeaderCreatorBase* KItemListView::defaultGroupHeaderCreator() const +{ + return 0; } void KItemListView::initializeItemListWidget(KItemListWidget* item) @@ -699,6 +849,11 @@ void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, co Q_UNUSED(previous); } +void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) +{ + Q_UNUSED(supportsExpanding); +} + void KItemListView::onTransactionBegin() { } @@ -710,7 +865,7 @@ void KItemListView::onTransactionEnd() bool KItemListView::event(QEvent* event) { // Forward all events to the controller and handle them there - if (m_controller && m_controller->processEvent(event, transform())) { + if (!m_editingRole && m_controller && m_controller->processEvent(event, transform())) { event->accept(); return true; } @@ -768,13 +923,17 @@ QList KItemListView::visibleItemListWidgets() const void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) { - updateVisibleRolesSizes(itemRanges); + if (m_itemSize.isEmpty()) { + updatePreferredColumnWidths(itemRanges); + } const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { beginTransaction(); } + m_layouter->markAsDirty(); + int previouslyInsertedCount = 0; foreach (const KItemRange& range, itemRanges) { // range.index is related to the model before anything has been inserted. @@ -808,10 +967,15 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) for (int i = itemsToMove.count() - 1; i >= 0; --i) { KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]); Q_ASSERT(widget); - setWidgetIndex(widget, widget->index() + count); + const int newIndex = widget->index() + count; + if (hasMultipleRanges) { + setWidgetIndex(widget, newIndex); + } else { + // Try to animate the moving of the item + moveWidgetToIndex(widget, newIndex); + } } - m_layouter->markAsDirty(); if (m_model->count() == count && m_activeTransactions == 0) { // Check whether a scrollbar is required to show the inserted items. In this case // the size of the layouter will be decreased before calling doLayout(): This prevents @@ -842,23 +1006,52 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) } if (hasMultipleRanges) { +#ifndef QT_NO_DEBUG + // Important: Don't read any m_layouter-property inside the for-loop in case if + // multiple ranges are given! m_layouter accesses m_sizeHintResolver which is + // updated in each loop-cycle and has only a consistent state after the loop. + Q_ASSERT(m_layouter->isDirty()); +#endif m_endTransactionAnimationHint = NoAnimation; endTransaction(); + updateSiblingsInformation(); } + + if (m_grouped && (hasMultipleRanges || itemRanges.first().count < m_model->count())) { + // In case if items of the same group have been inserted before an item that + // currently represents the first item of the group, the group header of + // this item must be removed. + updateVisibleGroupHeaders(); + } + + if (useAlternateBackgrounds()) { + updateAlternateBackgrounds(); + } } void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) { - updateVisibleRolesSizes(); + 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) { beginTransaction(); } + 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) { @@ -870,7 +1063,8 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) 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) { @@ -908,11 +1102,15 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) KItemListWidget* widget = m_visibleItems.value(i); if (widget) { const int newIndex = i - count; - setWidgetIndex(widget, newIndex); + if (hasMultipleRanges) { + setWidgetIndex(widget, newIndex); + } else { + // Try to animate the moving of the item + moveWidgetToIndex(widget, newIndex); + } } } - m_layouter->markAsDirty(); if (!hasMultipleRanges) { // The decrease-layout-size optimization in KItemListView::slotItemsInserted() // assumes an updated geometry. If items are removed during an active transaction, @@ -931,10 +1129,26 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) } if (hasMultipleRanges) { +#ifndef QT_NO_DEBUG + // Important: Don't read any m_layouter-property inside the for-loop in case if + // multiple ranges are given! m_layouter accesses m_sizeHintResolver which is + // updated in each loop-cycle and has only a consistent state after the loop. + Q_ASSERT(m_layouter->isDirty()); +#endif m_endTransactionAnimationHint = NoAnimation; endTransaction(); updateSiblingsInformation(); } + + if (m_grouped && (hasMultipleRanges || m_model->count() > 0)) { + // In case if the first item of a group has been removed, the group header + // must be applied to the next visible item. + updateVisibleGroupHeaders(); + } + + if (useAlternateBackgrounds()) { + updateAlternateBackgrounds(); + } } void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList& movedToIndexes) @@ -953,9 +1167,6 @@ void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList KItemListWidget* widget = m_visibleItems.value(index); if (widget) { updateWidgetProperties(widget, index); - if (m_grouped) { - updateGroupHeaderForWidget(widget); - } initializeItemListWidget(widget); } } @@ -968,8 +1179,8 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles) { const bool updateSizeHints = itemSizeHintUpdateRequired(roles); - if (updateSizeHints) { - updateVisibleRolesSizes(itemRanges); + if (updateSizeHints && m_itemSize.isEmpty()) { + updatePreferredColumnWidths(itemRanges); } foreach (const KItemRange& itemRange, itemRanges) { @@ -1001,6 +1212,7 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, doLayout(NoAnimation); } } + QAccessible::updateAccessibility(this, 0, QAccessible::TableModelChanged); } void KItemListView::slotGroupedSortingChanged(bool current) @@ -1020,6 +1232,13 @@ void KItemListView::slotGroupedSortingChanged(bool current) Q_ASSERT(m_visibleGroups.isEmpty()); } + if (useAlternateBackgrounds()) { + // Changing the group mode requires to update the alternate backgrounds + // as with the enabled group mode the altering is done on base of the first + // group item. + updateAlternateBackgrounds(); + } + updateSiblingsInformation(); doLayout(NoAnimation); } @@ -1049,15 +1268,14 @@ void KItemListView::slotCurrentChanged(int current, int previous) KItemListWidget* previousWidget = m_visibleItems.value(previous, 0); if (previousWidget) { - Q_ASSERT(previousWidget->isCurrent()); previousWidget->setCurrent(false); } KItemListWidget* currentWidget = m_visibleItems.value(current, 0); if (currentWidget) { - Q_ASSERT(!currentWidget->isCurrent()); currentWidget->setCurrent(true); } + QAccessible::updateAccessibility(this, current+1, QAccessible::Focus); } void KItemListView::slotSelectionChanged(const QSet& current, const QSet& previous) @@ -1090,7 +1308,7 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, // by m_visibleWidgets and must be deleted manually after the animation has // been finished. recycleGroupHeaderForWidget(itemListWidget); - m_widgetCreator->recycle(itemListWidget); + widgetCreator()->recycle(itemListWidget); break; } @@ -1136,40 +1354,34 @@ void KItemListView::slotRubberBandActivationChanged(bool active) update(); } -void KItemListView::slotVisibleRoleWidthChanged(const QByteArray& role, - qreal currentWidth, - qreal previousWidth) +void KItemListView::slotHeaderColumnWidthChanged(const QByteArray& role, + qreal currentWidth, + qreal previousWidth) { + Q_UNUSED(role); + Q_UNUSED(currentWidth); Q_UNUSED(previousWidth); - m_useHeaderWidths = true; + m_headerWidget->setAutomaticColumnResizing(false); + applyColumnWidthsFromHeader(); + doLayout(NoAnimation); +} - if (m_visibleRolesSizes.contains(role)) { - QSizeF roleSize = m_visibleRolesSizes.value(role); - roleSize.setWidth(currentWidth); - m_visibleRolesSizes.insert(role, roleSize); - m_stretchedVisibleRolesSizes.insert(role, roleSize); +void KItemListView::slotHeaderColumnMoved(const QByteArray& role, + int currentIndex, + int previousIndex) +{ + Q_ASSERT(m_visibleRoles[previousIndex] == role); - // Apply the new size to the layouter - QSizeF dynamicItemSize = m_itemSize; - if (dynamicItemSize.width() < 0) { - const qreal requiredWidth = visibleRolesSizesWidthSum(); - dynamicItemSize.setWidth(qMax(size().width(), requiredWidth)); - } - if (dynamicItemSize.height() < 0) { - const qreal requiredHeight = visibleRolesSizesHeightSum(); - dynamicItemSize.setHeight(qMax(size().height(), requiredHeight)); - } + const QList previous = m_visibleRoles; - m_layouter->setItemSize(dynamicItemSize); + QList current = m_visibleRoles; + current.removeAt(previousIndex); + current.insert(currentIndex, role); - // Update the role sizes for all visible widgets - foreach (KItemListWidget* widget, visibleItemListWidgets()) { - widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); - } + setVisibleRoles(current); - doLayout(NoAnimation); - } + emit visibleRolesChanged(current, previous); } void KItemListView::triggerAutoScrolling() @@ -1226,9 +1438,9 @@ void KItemListView::triggerAutoScrolling() const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset); setScrollOffset(newScrollOffset); - // Trigger the autoscroll timer which will periodically call - // triggerAutoScrolling() - m_autoScrollTimer->start(RepeatingAutoScrollDelay); + // Trigger the autoscroll timer which will periodically call + // triggerAutoScrolling() + m_autoScrollTimer->start(RepeatingAutoScrollDelay); } void KItemListView::slotGeometryOfGroupHeaderParentChanged() @@ -1240,6 +1452,22 @@ void KItemListView::slotGeometryOfGroupHeaderParentChanged() updateGroupHeaderLayout(widget); } +void KItemListView::slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value) +{ + disconnectRoleEditingSignals(index); + + emit roleEditingCanceled(index, role, value); + m_editingRole = false; +} + +void KItemListView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value) +{ + disconnectRoleEditingSignals(index); + + emit roleEditingFinished(index, role, value); + m_editingRole = false; +} + void KItemListView::setController(KItemListController* controller) { if (m_controller != controller) { @@ -1287,8 +1515,10 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); } + m_sizeHintResolver->clearCache(); + m_model = model; - m_layouter->setModel(model); + m_layouter->setModel(model); m_grouped = model->groupedSorting(); if (m_model) { @@ -1306,6 +1536,12 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); + + const int itemCount = m_model->count(); + if (itemCount > 0) { + m_sizeHintResolver->itemsInserted(0, itemCount); + slotItemsInserted(KItemRangeList() << KItemRange(0, itemCount)); + } } onModelChanged(model, previous); @@ -1377,10 +1613,8 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha const int oldIndex = reusableItems.takeLast(); widget = m_visibleItems.value(oldIndex); setWidgetIndex(widget, i); - - if (m_grouped) { - updateGroupHeaderForWidget(widget); - } + updateWidgetProperties(widget, i); + initializeItemListWidget(widget); } else { // No reusable KItemListWidget instance is available, create a new one widget = createWidget(i); @@ -1411,11 +1645,16 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha } if (animate) { + if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) { + m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); + applyNewPos = false; + } + const bool itemsRemoved = (changedCount < 0); const bool itemsInserted = (changedCount > 0); if (itemsRemoved && (i >= changedIndex + changedCount + 1)) { // The item is located after the removed items. Animate the moving of the position. - applyNewPos = !moveWidget(widget, itemBounds); + applyNewPos = !moveWidget(widget, newPos); } else if (itemsInserted && i >= changedIndex) { // The item is located after the first inserted item if (i <= changedIndex + changedCount - 1) { @@ -1429,11 +1668,11 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha // The item was already there before, so animate the moving of the position. // No moving animation is done if the item is animated by a create animation: This // prevents a "move animation mess" when inserting several ranges in parallel. - applyNewPos = !moveWidget(widget, itemBounds); + applyNewPos = !moveWidget(widget, newPos); } } else if (!itemsRemoved && !itemsInserted && !wasHidden) { // The size of the view might have been changed. Animate the moving of the position. - applyNewPos = !moveWidget(widget, itemBounds); + applyNewPos = !moveWidget(widget, newPos); } } else { m_animation->stop(widget); @@ -1463,6 +1702,11 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha widget->resize(itemBounds.size()); } } + + // Updating the cell-information must be done as last step: The decision whether the + // moving-animation should be started at all is based on the previous cell-information. + const Cell cell(m_layouter->itemColumn(i), m_layouter->itemRow(i)); + m_visibleCells.insert(i, cell); } // Delete invisible KItemListWidget instances that have not been reused @@ -1517,7 +1761,7 @@ QList KItemListView::recycleInvisibleItems(int firstVisibleIndex, } else { widget->setVisible(false); items.append(index); - + if (m_grouped) { recycleGroupHeaderForWidget(widget); } @@ -1528,29 +1772,30 @@ QList KItemListView::recycleInvisibleItems(int firstVisibleIndex, return items; } -bool KItemListView::moveWidget(KItemListWidget* widget,const QRectF& itemBounds) +bool KItemListView::moveWidget(KItemListWidget* widget,const QPointF& newPos) { - const QPointF oldPos = widget->pos(); - const QPointF newPos = itemBounds.topLeft(); - if (oldPos == newPos) { + if (widget->pos() == newPos) { return false; } - - bool startMovingAnim = m_itemSize.isEmpty() || widget->size() != itemBounds.size(); - if (!startMovingAnim) { + + bool startMovingAnim = false; + + if (m_itemSize.isEmpty()) { + // The items are not aligned in a grid but either as columns or rows. + startMovingAnim = true; + } else { // When having a grid the moving-animation should only be started, if it is done within // one row in the vertical scroll-orientation or one column in the horizontal scroll-orientation. // Otherwise instead of a moving-animation a create-animation on the new position will be used // instead. This is done to prevent overlapping (and confusing) moving-animations. - const QSizeF itemMargin = m_layouter->itemMargin(); - const qreal xMax = m_itemSize.width() + itemMargin.width(); - const qreal yMax = m_itemSize.height() + itemMargin.height(); - qreal xDiff = qAbs(oldPos.x() - newPos.x()); - qreal yDiff = qAbs(oldPos.y() - newPos.y()); - if (scrollOrientation() == Qt::Vertical) { - startMovingAnim = (xDiff > yDiff && yDiff < yMax); - } else { - startMovingAnim = (yDiff > xDiff && xDiff < xMax); + const int index = widget->index(); + const Cell cell = m_visibleCells.value(index); + if (cell.column >= 0 && cell.row >= 0) { + if (scrollOrientation() == Qt::Vertical) { + startMovingAnim = (cell.row == m_layouter->itemRow(index)); + } else { + startMovingAnim = (cell.column == m_layouter->itemColumn(index)); + } } } @@ -1593,16 +1838,12 @@ void KItemListView::emitOffsetChanges() KItemListWidget* KItemListView::createWidget(int index) { - KItemListWidget* widget = m_widgetCreator->create(this); + KItemListWidget* widget = widgetCreator()->create(this); widget->setFlag(QGraphicsItem::ItemStacksBehindParent); - updateWidgetProperties(widget, index); m_visibleItems.insert(index, widget); - - if (m_grouped) { - updateGroupHeaderForWidget(widget); - } - + m_visibleCells.insert(index, Cell()); + updateWidgetProperties(widget, index); initializeItemListWidget(widget); return widget; } @@ -1613,18 +1854,39 @@ void KItemListView::recycleWidget(KItemListWidget* widget) recycleGroupHeaderForWidget(widget); } - m_visibleItems.remove(widget->index()); - m_widgetCreator->recycle(widget); + const int index = widget->index(); + m_visibleItems.remove(index); + m_visibleCells.remove(index); + + widgetCreator()->recycle(widget); } void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) { const int oldIndex = widget->index(); m_visibleItems.remove(oldIndex); - updateWidgetProperties(widget, index); + m_visibleCells.remove(oldIndex); + m_visibleItems.insert(index, widget); + m_visibleCells.insert(index, Cell()); - initializeItemListWidget(widget); + widget->setIndex(index); +} + +void KItemListView::moveWidgetToIndex(KItemListWidget* widget, int index) +{ + const int oldIndex = widget->index(); + const Cell oldCell = m_visibleCells.value(oldIndex); + + setWidgetIndex(widget, index); + + const Cell newCell(m_layouter->itemColumn(index), m_layouter->itemRow(index)); + const bool vertical = (scrollOrientation() == Qt::Vertical); + const bool updateCell = (vertical && oldCell.row == newCell.row) || + (!vertical && oldCell.column == newCell.column); + if (updateCell) { + m_visibleCells.insert(index, newCell); + } } void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType) @@ -1639,18 +1901,22 @@ void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType) void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) { widget->setVisibleRoles(m_visibleRoles); - widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); + updateWidgetColumnWidths(widget); widget->setStyleOption(m_styleOption); const KItemListSelectionManager* selectionManager = m_controller->selectionManager(); widget->setCurrent(index == selectionManager->currentItem()); widget->setSelected(selectionManager->isSelected(index)); widget->setHovered(false); - widget->setAlternatingBackgroundColors(false); widget->setEnabledSelectionToggle(enabledSelectionToggles()); widget->setIndex(index); widget->setData(m_model->data(index)); widget->setSiblingsInformation(QBitArray()); + updateAlternateBackgroundForWidget(widget); + + if (m_grouped) { + updateGroupHeaderForWidget(widget); + } } void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) @@ -1666,34 +1932,22 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) } const QList > groups = model()->groups(); - if (groups.isEmpty()) { + if (groups.isEmpty() || !groupHeaderCreator()) { return; } KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); if (!groupHeader) { - groupHeader = m_groupHeaderCreator->create(this); + groupHeader = groupHeaderCreator()->create(this); groupHeader->setParentItem(widget); m_visibleGroups.insert(widget, groupHeader); connect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged())); } Q_ASSERT(groupHeader->parentItem() == widget); - // Determine the shown data for the header by doing a binary - // search in the groups-list - int min = 0; - int max = groups.count() - 1; - int mid = 0; - do { - mid = (min + max) / 2; - if (index > groups.at(mid).first) { - min = mid + 1; - } else { - max = mid - 1; - } - } while (groups.at(mid).first != index && min <= max); - - groupHeader->setData(groups.at(mid).second); + const int groupIndex = groupIndexForItem(index); + Q_ASSERT(groupIndex >= 0); + groupHeader->setData(groups.at(groupIndex).second); groupHeader->setRole(model()->sortRole()); groupHeader->setStyleOption(m_styleOption); groupHeader->setScrollOrientation(scrollOrientation()); @@ -1717,8 +1971,10 @@ void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) // In the vertical scroll orientation the group header should always span // the whole width no matter which temporary position the parent widget // has. In this case the x-position and width will be adjusted manually. - groupHeader->setPos(-widget->x(), -groupHeaderRect.height()); - groupHeader->resize(size().width(), groupHeaderRect.size().height()); + const qreal x = -widget->x() - itemOffset(); + const qreal width = maximumItemOffset(); + groupHeader->setPos(x, -groupHeaderRect.height()); + groupHeader->resize(width, groupHeaderRect.size().height()); } else { groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y()); groupHeader->resize(groupHeaderRect.size()); @@ -1730,7 +1986,7 @@ void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) KItemListGroupHeader* header = m_visibleGroups.value(widget); if (header) { header->setParentItem(0); - m_groupHeaderCreator->recycle(header); + groupHeaderCreator()->recycle(header); m_visibleGroups.remove(widget); disconnect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged())); } @@ -1748,25 +2004,147 @@ void KItemListView::updateVisibleGroupHeaders() } } -QHash KItemListView::headerRolesWidths() const +int KItemListView::groupIndexForItem(int index) const { - QHash rolesWidths; + Q_ASSERT(m_grouped); - QHashIterator it(m_stretchedVisibleRolesSizes); + const QList > groups = model()->groups(); + if (groups.isEmpty()) { + return -1; + } + + int min = 0; + int max = groups.count() - 1; + int mid = 0; + do { + mid = (min + max) / 2; + if (index > groups[mid].first) { + min = mid + 1; + } else { + max = mid - 1; + } + } while (groups[mid].first != index && min <= max); + + if (min > max) { + while (groups[mid].first > index && mid > 0) { + --mid; + } + } + + return mid; +} + +void KItemListView::updateAlternateBackgrounds() +{ + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - rolesWidths.insert(it.key(), it.value().width()); + updateAlternateBackgroundForWidget(it.value()); } +} - return rolesWidths; +void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget) +{ + bool enabled = useAlternateBackgrounds(); + if (enabled) { + const int index = widget->index(); + enabled = (index & 0x1) > 0; + if (m_grouped) { + const int groupIndex = groupIndexForItem(index); + if (groupIndex >= 0) { + const QList > groups = model()->groups(); + const int indexOfFirstGroupItem = groups[groupIndex].first; + const int relativeIndex = index - indexOfFirstGroupItem; + enabled = (relativeIndex & 0x1) > 0; + } + } + } + widget->setAlternateBackground(enabled); } -void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges) +bool KItemListView::useAlternateBackgrounds() const { - if (!m_itemSize.isEmpty() || m_useHeaderWidths) { - return; + return m_itemSize.isEmpty() && m_visibleRoles.count() > 1; +} + +QHash KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const +{ + QElapsedTimer timer; + timer.start(); + + QHash 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. + const KItemListWidgetCreatorBase* creator = widgetCreator(); + 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 = creator->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 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 it(m_visibleItems); + while (it.hasNext()) { + it.next(); + updateWidgetColumnWidths(it.value()); + } +} + +void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget) +{ + foreach (const QByteArray& role, m_visibleRoles) { + widget->setColumnWidth(role, m_headerWidget->columnWidth(role)); + } +} + +void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges) +{ + Q_ASSERT(m_itemSize.isEmpty()); const int itemCount = m_model->count(); int rangesItemCount = 0; foreach (const KItemRange& range, itemRanges) { @@ -1774,139 +2152,118 @@ void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges) } if (itemCount == rangesItemCount) { - m_visibleRolesSizes = visibleRolesSizes(itemRanges); - if (m_header) { - // Assure the the sizes are not smaller than the minimum defined by the header - // TODO: Currently only implemented for a top-aligned header - const qreal minHeaderRoleWidth = m_header->minimumRoleWidth(); - QMutableHashIterator it (m_visibleRolesSizes); - while (it.hasNext()) { - it.next(); - const QSizeF& size = it.value(); - if (size.width() < minHeaderRoleWidth) { - const QSizeF newSize(minHeaderRoleWidth, size.height()); - m_visibleRolesSizes.insert(it.key(), newSize); - } - } + const QHash 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 sizes of the sub ranges - // already fit into the available sizes and hence no + // 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 updatedSizes = visibleRolesSizes(itemRanges); - QHashIterator it(updatedSizes); + const QHash updatedWidths = preferredColumnWidths(itemRanges); + QHashIterator it(updatedWidths); while (it.hasNext()) { it.next(); const QByteArray& role = it.key(); - const QSizeF& updatedSize = it.value(); - const QSizeF currentSize = m_visibleRolesSizes.value(role); - if (updatedSize.width() > currentSize.width() || updatedSize.height() > currentSize.height()) { - m_visibleRolesSizes.insert(role, updatedSize); - updateRequired = true; + const qreal updatedWidth = it.value(); + const qreal currentWidth = m_headerWidget->preferredColumnWidth(role); + if (updatedWidth > currentWidth) { + 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; } } - updateStretchedVisibleRolesSizes(); + if (m_headerWidget->automaticColumnResizing()) { + applyAutomaticColumnWidths(); + } } -void KItemListView::updateVisibleRolesSizes() +void KItemListView::updatePreferredColumnWidths() { - if (!m_model) { - return; - } - - const int itemCount = m_model->count(); - if (itemCount > 0) { - updateVisibleRolesSizes(KItemRangeList() << KItemRange(0, itemCount)); + if (m_model) { + updatePreferredColumnWidths(KItemRangeList() << KItemRange(0, m_model->count())); } } -void KItemListView::updateStretchedVisibleRolesSizes() +void KItemListView::applyAutomaticColumnWidths() { - if (!m_itemSize.isEmpty() || m_useHeaderWidths || m_visibleRoles.isEmpty()) { + Q_ASSERT(m_itemSize.isEmpty()); + Q_ASSERT(m_headerWidget->automaticColumnResizing()); + if (m_visibleRoles.isEmpty()) { return; } // Calculate the maximum size of an item by considering the // visible role sizes and apply them to the layouter. If the - // size does not use the available view-size it the size of the + // size does not use the available view-size the size of the // first role will get stretched. - m_stretchedVisibleRolesSizes = m_visibleRolesSizes; - const QByteArray role = m_visibleRoles.first(); - QSizeF firstRoleSize = m_stretchedVisibleRolesSizes.value(role); + foreach (const QByteArray& role, m_visibleRoles) { + const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role); + m_headerWidget->setColumnWidth(role, preferredWidth); + } + + const QByteArray firstRole = m_visibleRoles.first(); + qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole); QSizeF dynamicItemSize = m_itemSize; - if (dynamicItemSize.width() <= 0) { - const qreal requiredWidth = visibleRolesSizesWidthSum(); - const qreal availableWidth = size().width(); - if (requiredWidth < availableWidth) { - // Stretch the first role to use the whole width for the item - firstRoleSize.rwidth() += availableWidth - requiredWidth; - m_stretchedVisibleRolesSizes.insert(role, firstRoleSize); + 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 && m_visibleRoles.count() > 1) { + // 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.setWidth(qMax(requiredWidth, availableWidth)); - } - if (dynamicItemSize.height() <= 0) { - const qreal requiredHeight = visibleRolesSizesHeightSum(); - const qreal availableHeight = size().height(); - if (requiredHeight < availableHeight) { - // Stretch the first role to use the whole height for the item - firstRoleSize.rheight() += availableHeight - requiredHeight; - m_stretchedVisibleRolesSizes.insert(role, firstRoleSize); - } - dynamicItemSize.setHeight(qMax(requiredHeight, availableHeight)); + m_headerWidget->setColumnWidth(firstRole, shrinkedFirstColumnWidth); + requiredWidth -= firstColumnWidth - shrinkedFirstColumnWidth; } - m_layouter->setItemSize(dynamicItemSize); + dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth); - if (m_header) { - m_header->setVisibleRolesWidths(headerRolesWidths()); - m_header->resize(dynamicItemSize.width(), m_header->size().height()); - } + m_layouter->setItemSize(dynamicItemSize); // Update the role sizes for all visible widgets - foreach (KItemListWidget* widget, visibleItemListWidgets()) { - widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); - } -} - -qreal KItemListView::visibleRolesSizesWidthSum() const -{ - qreal widthSum = 0; - QHashIterator it(m_visibleRolesSizes); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - widthSum += it.value().width(); + updateWidgetColumnWidths(it.value()); } - return widthSum; } -qreal KItemListView::visibleRolesSizesHeightSum() const +qreal KItemListView::columnWidthsSum() const { - qreal heightSum = 0; - QHashIterator it(m_visibleRolesSizes); - while (it.hasNext()) { - it.next(); - heightSum += it.value().height(); + qreal widthsSum = 0; + foreach (const QByteArray& role, m_visibleRoles) { + widthsSum += m_headerWidget->columnWidth(role); } - return heightSum; + return widthsSum; } QRectF KItemListView::headerBoundaries() const { - return m_header ? m_header->geometry() : QRectF(); + return m_headerWidget->isVisible() ? m_headerWidget->geometry() : QRectF(); } bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, @@ -1916,7 +2273,7 @@ bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, if (newItemSize.isEmpty() || newGridSize.isEmpty()) { return false; } - + if (m_layouter->scrollOrientation() == Qt::Vertical) { const qreal itemWidth = m_layouter->itemSize().width(); if (itemWidth > 0) { @@ -1929,7 +2286,7 @@ bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, m_layouter->itemMargin().width()); return oldColumnCount != newColumnCount; } - } + } } else { const qreal itemHeight = m_layouter->itemSize().height(); if (itemHeight > 0) { @@ -1942,18 +2299,24 @@ bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, m_layouter->itemMargin().height()); return oldRowCount != newRowCount; } - } + } } - + return false; } bool KItemListView::animateChangedItemCount(int changedItemCount) const { + if (m_itemSize.isEmpty()) { + // We have only columns or only rows, but no grid: An animation is usually + // welcome when inserting or removing items. + return !supportsItemExpanding(); + } + if (m_layouter->size().isEmpty() || m_layouter->itemSize().isEmpty()) { return false; } - + const int maximum = (scrollOrientation() == Qt::Vertical) ? m_layouter->size().width() / m_layouter->itemSize().width() : m_layouter->size().height() / m_layouter->itemSize().height(); @@ -1974,11 +2337,57 @@ bool KItemListView::scrollBarRequired(const QSizeF& size) const : maxOffset > size.width(); } +int KItemListView::showDropIndicator(const QPointF& pos) +{ + QHashIterator it(m_visibleItems); + while (it.hasNext()) { + it.next(); + const KItemListWidget* widget = it.value(); + + const QPointF mappedPos = widget->mapFromItem(this, pos); + const QRectF rect = itemRect(widget->index()); + if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) { + if (m_model->supportsDropping(widget->index())) { + const int gap = qMax(4, m_styleOption.padding); + if (mappedPos.y() >= gap && mappedPos.y() <= rect.height() - gap) { + return -1; + } + } + + const bool isAboveItem = (mappedPos.y () < rect.height() / 2); + const qreal y = isAboveItem ? rect.top() : rect.bottom(); + + const QRectF draggingInsertIndicator(rect.left(), y, rect.width(), 1); + if (m_dropIndicator != draggingInsertIndicator) { + m_dropIndicator = draggingInsertIndicator; + update(); + } + + int index = widget->index(); + if (!isAboveItem) { + ++index; + } + return index; + } + } + + const QRectF firstItemRect = itemRect(firstVisibleIndex()); + return (pos.y() <= firstItemRect.top()) ? 0 : -1; +} + +void KItemListView::hideDropIndicator() +{ + if (!m_dropIndicator.isNull()) { + m_dropIndicator = QRectF(); + update(); + } +} + void KItemListView::updateGroupHeaderHeight() { qreal groupHeaderHeight = m_styleOption.fontMetrics.height(); qreal groupHeaderMargin = 0; - + if (scrollOrientation() == Qt::Horizontal) { // The vertical margin above and below the header should be // equal to the horizontal margin, not the vertical margin @@ -1986,10 +2395,10 @@ void KItemListView::updateGroupHeaderHeight() groupHeaderHeight += 2 * m_styleOption.horizontalMargin; groupHeaderMargin = m_styleOption.horizontalMargin; } else if (m_itemSize.isEmpty()){ - groupHeaderHeight += 2 * m_styleOption.padding; + groupHeaderHeight += 4 * m_styleOption.padding; groupHeaderMargin = m_styleOption.iconSize / 2; } else { - groupHeaderHeight += 2 * m_styleOption.padding; + groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin; groupHeaderMargin = m_styleOption.iconSize / 4; } m_layouter->setGroupHeaderHeight(groupHeaderHeight); @@ -2000,7 +2409,7 @@ void KItemListView::updateGroupHeaderHeight() void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) { - if (!supportsItemExpanding()) { + if (!supportsItemExpanding() || !m_model) { return; } @@ -2125,6 +2534,17 @@ bool KItemListView::hasSiblingSuccessor(int index) const return hasSuccessor; } +void KItemListView::disconnectRoleEditingSignals(int index) +{ + KItemListWidget* widget = m_visibleItems.value(index); + if (!widget) { + return; + } + + widget->disconnect(SIGNAL(roleEditingCanceled(int,QByteArray,QVariant)), this); + widget->disconnect(SIGNAL(roleEditingFinished(int,QByteArray,QVariant)), this); +} + int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) { int inc = 0;