#include "kitemlistview.h"
#include <KDebug>
+#include "kitemlistcontainer.h"
#include "kitemlistcontroller.h"
#include "kitemlistheader.h"
#include "kitemlistselectionmanager.h"
#include <QStyleOptionRubberBand>
#include <QTimer>
+#include <algorithm>
+
+#include "kitemlistviewaccessible.h"
+
namespace {
// Time in ms until reaching the autoscroll margin triggers
// an initial autoscrolling
const int RepeatingAutoScrollDelay = 1000 / 60;
}
+#ifndef QT_NO_ACCESSIBILITY
+QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
+{
+ Q_UNUSED(key)
+
+ if (KItemListContainer* container = qobject_cast<KItemListContainer*>(object)) {
+ return new KItemListContainerAccessible(container);
+ } else if (KItemListView* view = qobject_cast<KItemListView*>(object)) {
+ return new KItemListViewAccessible(view);
+ }
+
+ return 0;
+}
+#endif
+
KItemListView::KItemListView(QGraphicsWidget* parent) :
QGraphicsWidget(parent),
m_enabledSelectionToggles(false),
m_headerWidget->setVisible(false);
m_header = new KItemListHeader(this);
+
+#ifndef QT_NO_ACCESSIBILITY
+ QAccessible::installFactory(accessibleInterfaceFactory);
+#endif
+
}
KItemListView::~KItemListView()
return m_layouter->maximumItemOffset();
}
+int KItemListView::maximumVisibleItems() const
+{
+ return m_layouter->maximumVisibleItems();
+}
+
void KItemListView::setVisibleRoles(const QList<QByteArray>& roles)
{
const QList<QByteArray> previousRoles = m_visibleRoles;
}
}
+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<int, KItemListWidget*> it(m_visibleItems);
}
}
+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)
{
const QSizeF previousSize = m_itemSize;
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.
}
previouslyInsertedCount += count;
- m_sizeHintResolver->itemsInserted(index, count);
-
// Determine which visible items must be moved
QList<int> itemsToMove;
QHashIterator<int, KItemListWidget*> it(m_visibleItems);
}
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();
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];
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<int> 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;
}
}
// 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);
}
}
}
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();
void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList<int>& movedToIndexes)
{
- m_sizeHintResolver->itemsMoved(itemRange.index, itemRange.count);
+ m_sizeHintResolver->itemsMoved(itemRange, movedToIndexes);
m_layouter->markAsDirty();
if (m_controller) {
doLayout(NoAnimation);
}
}
+ QAccessible::updateAccessibility(this, 0, QAccessible::TableModelChanged);
+}
+
+void KItemListView::slotGroupsChanged()
+{
+ updateVisibleGroupHeaders();
+ doLayout(NoAnimation);
+ updateSiblingsInformation();
}
void KItemListView::slotGroupedSortingChanged(bool current)
if (m_grouped) {
updateGroupHeaderHeight();
} else {
- // Clear all visible headers
- QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> 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<KItemListWidget*, KItemListGroupHeader*> it(m_visibleGroups);
while (it.hasNext()) {
it.next();
recycleGroupHeaderForWidget(it.key());
if (currentWidget) {
currentWidget->setCurrent(true);
}
+ QAccessible::updateAccessibility(this, current+1, QAccessible::Focus);
}
void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
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()
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;
}
this, SLOT(slotItemsRemoved(KItemRangeList)));
disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
+ 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);
this, SLOT(slotItemsRemoved(KItemRangeList)));
connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
+ 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)),
const int itemCount = m_model->count();
if (itemCount > 0) {
- m_sizeHintResolver->itemsInserted(0, itemCount);
slotItemsInserted(KItemRangeList() << KItemRange(0, itemCount));
}
}
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) {
}
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) {
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.
const QPointF mappedPos = widget->mapFromItem(this, pos);
const QRectF rect = itemRect(widget->index());
if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) {
- const qreal y = (mappedPos.y () < rect.height() / 2) ?
- rect.top() : rect.bottom();
+ 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();
}
- return widget->index();
+
+ int index = widget->index();
+ if (!isAboveItem) {
+ ++index;
+ }
+ return index;
}
}
- return -1;
+ const QRectF firstItemRect = itemRect(firstVisibleIndex());
+ return (pos.y() <= firstItemRect.top()) ? 0 : -1;
}
void KItemListView::hideDropIndicator()
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;