X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/aacf20282d9b7d78bda93ba946cdcf2e0fee5692..6bfa0ccfc701df5af5f043fce3168b3840858212:/src/kitemviews/kitemlistview.cpp diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 4bcdab105..d8edcfc50 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -23,6 +23,7 @@ #include "kitemlistview.h" #include +#include "kitemlistcontainer.h" #include "kitemlistcontroller.h" #include "kitemlistheader.h" #include "kitemlistselectionmanager.h" @@ -36,12 +37,17 @@ #include #include +#include #include #include #include #include #include +#include + +#include "kitemlistviewaccessible.h" + namespace { // Time in ms until reaching the autoscroll margin triggers // an initial autoscrolling @@ -51,6 +57,21 @@ 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), @@ -83,7 +104,8 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_autoScrollIncrement(0), m_autoScrollTimer(0), m_header(0), - m_headerWidget(0) + m_headerWidget(0), + m_dropIndicator() { setAcceptHoverEvents(true); @@ -108,6 +130,11 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_headerWidget->setVisible(false); m_header = new KItemListHeader(this); + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::installFactory(accessibleInterfaceFactory); +#endif + } KItemListView::~KItemListView() @@ -183,6 +210,11 @@ qreal KItemListView::maximumItemOffset() const return m_layouter->maximumItemOffset(); } +int KItemListView::maximumVisibleItems() const +{ + return m_layouter->maximumVisibleItems(); +} + void KItemListView::setVisibleRoles(const QList& roles) { const QList previousRoles = m_visibleRoles; @@ -363,6 +395,15 @@ void KItemListView::setGeometry(const QRectF& rect) } } +qreal KItemListView::verticalPageStep() const +{ + qreal headerHeight = 0; + if (m_headerWidget->isVisible()) { + headerHeight = m_headerWidget->size().height(); + } + return size().height() - headerHeight; +} + int KItemListView::itemAt(const QPointF& pos) const { QHashIterator it(m_visibleItems); @@ -566,8 +607,21 @@ KItemListHeader* KItemListView::header() const 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) @@ -608,6 +662,32 @@ 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); + } +} + +QVariant KItemListView::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == QGraphicsItem::ItemSceneHasChanged && scene()) { + if (!scene()->views().isEmpty()) { + m_styleOption.palette = scene()->views().at(0)->palette(); + } + } + return QGraphicsItem::itemChange(change, value); } void KItemListView::setItemSize(const QSizeF& size) @@ -880,6 +960,8 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) m_layouter->markAsDirty(); + m_sizeHintResolver->itemsInserted(itemRanges); + int previouslyInsertedCount = 0; foreach (const KItemRange& range, itemRanges) { // range.index is related to the model before anything has been inserted. @@ -893,8 +975,6 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) } previouslyInsertedCount += count; - m_sizeHintResolver->itemsInserted(index, count); - // Determine which visible items must be moved QList itemsToMove; QHashIterator it(m_visibleItems); @@ -922,13 +1002,6 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) } } - // 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. - if (m_grouped && index + count < m_model->count()) { - updateGroupHeaderForWidget(m_visibleItems.value(index + count)); - } - 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 @@ -959,17 +1032,19 @@ 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(); } @@ -990,10 +1065,7 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) m_layouter->markAsDirty(); - int removedItemsCount = 0; - for (int i = 0; i < itemRanges.count(); ++i) { - removedItemsCount += itemRanges[i].count; - } + m_sizeHintResolver->itemsRemoved(itemRanges); for (int i = itemRanges.count() - 1; i >= 0; --i) { const KItemRange& range = itemRanges[i]; @@ -1004,17 +1076,19 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) continue; } - m_sizeHintResolver->itemsRemoved(index, count); - const int firstRemovedIndex = index; const int lastRemovedIndex = index + count - 1; - const int lastIndex = m_model->count() - 1 + removedItemsCount; - removedItemsCount -= count; + + // Remeber which items have to be moved because they are behind the removed range. + QVector itemsToMove; // Remove all KItemListWidget instances that got deleted - for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) { - KItemListWidget* widget = m_visibleItems.value(i); - if (!widget) { + foreach (KItemListWidget* widget, m_visibleItems) { + const int i = widget->index(); + if (i < firstRemovedIndex) { + continue; + } else if (i > lastRemovedIndex) { + itemsToMove.append(i); continue; } @@ -1042,26 +1116,21 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) } // Update the indexes of all KItemListWidget instances that are located - // after the deleted items - for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) { + // after the deleted items. It is important to update them in ascending + // order to prevent overlaps when setting the new index. + std::sort(itemsToMove.begin(), itemsToMove.end()); + foreach (int i, itemsToMove) { KItemListWidget* widget = m_visibleItems.value(i); - if (widget) { - const int newIndex = i - count; - if (hasMultipleRanges) { - setWidgetIndex(widget, newIndex); - } else { - // Try to animate the moving of the item - moveWidgetToIndex(widget, newIndex); - } + Q_ASSERT(widget); + const int newIndex = i - count; + if (hasMultipleRanges) { + setWidgetIndex(widget, newIndex); + } else { + // Try to animate the moving of the item + moveWidgetToIndex(widget, newIndex); } } - // In case if the first item of a group has been removed, the group header - // must be applied to the next visible item. - if (m_grouped && index < m_model->count()) { - updateGroupHeaderForWidget(m_visibleItems.value(index)); - } - if (!hasMultipleRanges) { // The decrease-layout-size optimization in KItemListView::slotItemsInserted() // assumes an updated geometry. If items are removed during an active transaction, @@ -1080,17 +1149,17 @@ 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(); } @@ -1098,7 +1167,7 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList& movedToIndexes) { - m_sizeHintResolver->itemsMoved(itemRange.index, itemRange.count); + m_sizeHintResolver->itemsMoved(itemRange, movedToIndexes); m_layouter->markAsDirty(); if (m_controller) { @@ -1157,6 +1226,14 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, doLayout(NoAnimation); } } + QAccessible::updateAccessibility(this, 0, QAccessible::TableModelChanged); +} + +void KItemListView::slotGroupsChanged() +{ + updateVisibleGroupHeaders(); + doLayout(NoAnimation); + updateSiblingsInformation(); } void KItemListView::slotGroupedSortingChanged(bool current) @@ -1167,8 +1244,10 @@ void KItemListView::slotGroupedSortingChanged(bool current) if (m_grouped) { updateGroupHeaderHeight(); } else { - // Clear all visible headers - QMutableHashIterator it (m_visibleGroups); + // Clear all visible headers. Note that the QHashIterator takes a copy of + // m_visibleGroups. Therefore, it remains valid even if items are removed + // from m_visibleGroups in recycleGroupHeaderForWidget(). + QHashIterator it(m_visibleGroups); while (it.hasNext()) { it.next(); recycleGroupHeaderForWidget(it.key()); @@ -1219,6 +1298,7 @@ void KItemListView::slotCurrentChanged(int current, int previous) if (currentWidget) { currentWidget->setCurrent(true); } + QAccessible::updateAccessibility(this, current+1, QAccessible::Focus); } void KItemListView::slotSelectionChanged(const QSet& current, const QSet& previous) @@ -1381,9 +1461,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() @@ -1397,12 +1477,16 @@ void KItemListView::slotGeometryOfGroupHeaderParentChanged() 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; } @@ -1446,15 +1530,17 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotItemsRemoved(KItemRangeList))); disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), this, SLOT(slotItemsMoved(KItemRange,QList))); + disconnect(m_model, SIGNAL(groupsChanged()), + this, SLOT(slotGroupsChanged())); disconnect(m_model, SIGNAL(groupedSortingChanged(bool)), this, SLOT(slotGroupedSortingChanged(bool))); disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); - } - m_sizeHintResolver->clearCache(); + m_sizeHintResolver->itemsRemoved(KItemRangeList() << KItemRange(0, m_model->count())); + } m_model = model; m_layouter->setModel(model); @@ -1469,6 +1555,8 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotItemsRemoved(KItemRangeList))); connect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), this, SLOT(slotItemsMoved(KItemRange,QList))); + connect(m_model, SIGNAL(groupsChanged()), + this, SLOT(slotGroupsChanged())); connect(m_model, SIGNAL(groupedSortingChanged(bool)), this, SLOT(slotGroupedSortingChanged(bool))); connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), @@ -1478,7 +1566,6 @@ void KItemListView::setModel(KItemModelBase* model) const int itemCount = m_model->count(); if (itemCount > 0) { - m_sizeHintResolver->itemsInserted(0, itemCount); slotItemsInserted(KItemRangeList() << KItemRange(0, itemCount)); } } @@ -1561,18 +1648,22 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha widget->resize(itemBounds.size()); if (animate && changedCount < 0) { - // Items have been deleted, move the created item to the - // imaginary old position. They will get animated to the new position - // later. - const QRectF itemRect = m_layouter->itemRect(i - changedCount); - if (itemRect.isEmpty()) { - const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical) - ? QPointF(0, size().height()) : QPointF(size().width(), 0); - widget->setPos(invisibleOldPos); - } else { - widget->setPos(itemRect.topLeft()); + // Items have been deleted. + if (i >= changedIndex) { + // The item is located behind the removed range. Move the + // created item to the imaginary old position outside the + // view. It will get animated to the new position later. + const int previousIndex = i - changedCount; + const QRectF itemRect = m_layouter->itemRect(previousIndex); + if (itemRect.isEmpty()) { + const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical) + ? QPointF(0, size().height()) : QPointF(size().width(), 0); + widget->setPos(invisibleOldPos); + } else { + widget->setPos(itemRect.topLeft()); + } + applyNewPos = false; } - applyNewPos = false; } if (supportsExpanding && changedCount == 0) { @@ -1584,9 +1675,14 @@ 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)) { + if (itemsRemoved && (i >= changedIndex)) { // The item is located after the removed items. Animate the moving of the position. applyNewPos = !moveWidget(widget, newPos); } else if (itemsInserted && i >= changedIndex) { @@ -1716,7 +1812,7 @@ bool KItemListView::moveWidget(KItemListWidget* widget,const QPointF& newPos) if (m_itemSize.isEmpty()) { // The items are not aligned in a grid but either as columns or rows. - startMovingAnim = !supportsItemExpanding(); + 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. @@ -1905,8 +2001,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()); @@ -2269,6 +2367,53 @@ 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())) { + // Keep 30% of the rectangle as the gap instead of always having a fixed gap + const int gap = qMax(4.0, 0.3 * rect.height()); + 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(); @@ -2420,6 +2565,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;