┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/kitemviews
diff options
context:
space:
mode:
Diffstat (limited to 'src/kitemviews')
-rw-r--r--src/kitemviews/kfileitemlistview.cpp423
-rw-r--r--src/kitemviews/kfileitemlistview.h97
-rw-r--r--src/kitemviews/kfileitemlistwidget.cpp728
-rw-r--r--src/kitemviews/kfileitemlistwidget.h118
-rw-r--r--src/kitemviews/kfileitemmodel.cpp868
-rw-r--r--src/kitemviews/kfileitemmodel.h192
-rw-r--r--src/kitemviews/kfileitemmodelrolesupdater.cpp765
-rw-r--r--src/kitemviews/kfileitemmodelrolesupdater.h177
-rw-r--r--src/kitemviews/kitemlistcontainer.cpp191
-rw-r--r--src/kitemviews/kitemlistcontainer.h70
-rw-r--r--src/kitemviews/kitemlistcontroller.cpp281
-rw-r--r--src/kitemviews/kitemlistcontroller.h118
-rw-r--r--src/kitemviews/kitemlistgroupheader.cpp58
-rw-r--r--src/kitemviews/kitemlistgroupheader.h45
-rw-r--r--src/kitemviews/kitemlistselectionmanager.cpp87
-rw-r--r--src/kitemviews/kitemlistselectionmanager.h69
-rw-r--r--src/kitemviews/kitemlistsizehintresolver.cpp86
-rw-r--r--src/kitemviews/kitemlistsizehintresolver_p.h50
-rw-r--r--src/kitemviews/kitemliststyleoption.cpp37
-rw-r--r--src/kitemviews/kitemliststyleoption.h41
-rw-r--r--src/kitemviews/kitemlistview.cpp1124
-rw-r--r--src/kitemviews/kitemlistview.h360
-rw-r--r--src/kitemviews/kitemlistviewanimation.cpp241
-rw-r--r--src/kitemviews/kitemlistviewanimation_p.h79
-rw-r--r--src/kitemviews/kitemlistviewlayouter.cpp474
-rw-r--r--src/kitemviews/kitemlistviewlayouter_p.h126
-rw-r--r--src/kitemviews/kitemlistwidget.cpp260
-rw-r--r--src/kitemviews/kitemlistwidget.h134
-rw-r--r--src/kitemviews/kitemmodelbase.cpp113
-rw-r--r--src/kitemviews/kitemmodelbase.h145
-rw-r--r--src/kitemviews/kpixmapmodifier.cpp388
-rw-r--r--src/kitemviews/kpixmapmodifier_p.h37
32 files changed, 7982 insertions, 0 deletions
diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp
new file mode 100644
index 000000000..a7217d30c
--- /dev/null
+++ b/src/kitemviews/kfileitemlistview.cpp
@@ -0,0 +1,423 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kfileitemlistview.h"
+
+#include "kitemlistgroupheader.h"
+#include "kfileitemmodelrolesupdater.h"
+#include "kfileitemlistwidget.h"
+#include "kfileitemmodel.h"
+#include <KLocale>
+#include <KStringHandler>
+
+#include <KDebug>
+
+#include <QTextLine>
+#include <QTimer>
+
+#define KFILEITEMLISTVIEW_DEBUG
+
+namespace {
+ const int ShortInterval = 50;
+ const int LongInterval = 300;
+}
+
+KFileItemListView::KFileItemListView(QGraphicsWidget* parent) :
+ KItemListView(parent),
+ m_itemLayout(IconsLayout),
+ m_modelRolesUpdater(0),
+ m_updateVisibleIndexRangeTimer(0),
+ m_updateIconSizeTimer(0),
+ m_minimumRolesWidths()
+{
+ setScrollOrientation(Qt::Vertical);
+ setWidgetCreator(new KItemListWidgetCreator<KFileItemListWidget>());
+ setGroupHeaderCreator(new KItemListGroupHeaderCreator<KItemListGroupHeader>());
+
+ m_updateVisibleIndexRangeTimer = new QTimer(this);
+ m_updateVisibleIndexRangeTimer->setSingleShot(true);
+ m_updateVisibleIndexRangeTimer->setInterval(ShortInterval);
+ connect(m_updateVisibleIndexRangeTimer, SIGNAL(timeout()), this, SLOT(updateVisibleIndexRange()));
+
+ m_updateIconSizeTimer = new QTimer(this);
+ m_updateIconSizeTimer->setSingleShot(true);
+ m_updateIconSizeTimer->setInterval(ShortInterval);
+ connect(m_updateIconSizeTimer, SIGNAL(timeout()), this, SLOT(updateIconSize()));
+
+ updateMinimumRolesWidths();
+}
+
+KFileItemListView::~KFileItemListView()
+{
+ delete widgetCreator();
+ delete groupHeaderCreator();
+
+ delete m_modelRolesUpdater;
+ m_modelRolesUpdater = 0;
+}
+
+void KFileItemListView::setPreviewsShown(bool show)
+{
+ if (m_modelRolesUpdater) {
+ m_modelRolesUpdater->setPreviewShown(show);
+ }
+}
+
+bool KFileItemListView::previewsShown() const
+{
+ return m_modelRolesUpdater->isPreviewShown();
+}
+
+void KFileItemListView::setItemLayout(Layout layout)
+{
+ if (m_itemLayout != layout) {
+ m_itemLayout = layout;
+ updateLayoutOfVisibleItems();
+ }
+}
+
+KFileItemListView::Layout KFileItemListView::itemLayout() const
+{
+ return m_itemLayout;
+}
+
+QSizeF KFileItemListView::itemSizeHint(int index) const
+{
+ const QHash<QByteArray, QVariant> values = model()->data(index);
+ const KItemListStyleOption& option = styleOption();
+ const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
+
+ switch (m_itemLayout) {
+ case IconsLayout: {
+ const QString text = KStringHandler::preProcessWrap(values["name"].toString());
+
+ const qreal maxWidth = itemSize().width() - 2 * option.margin;
+ int textLinesCount = 0;
+ QTextLine line;
+
+ // Calculate the number of lines required for wrapping the name
+ QTextOption textOption(Qt::AlignHCenter);
+ textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+
+ QTextLayout layout(text, option.font);
+ layout.setTextOption(textOption);
+ layout.beginLayout();
+ while ((line = layout.createLine()).isValid()) {
+ line.setLineWidth(maxWidth);
+ line.naturalTextWidth();
+ ++textLinesCount;
+ }
+ layout.endLayout();
+
+ // Add one line for each additional information
+ textLinesCount += additionalRolesCount;
+
+ const qreal height = textLinesCount * option.fontMetrics.height() +
+ option.iconSize +
+ option.margin * 4;
+ return QSizeF(itemSize().width(), height);
+ }
+
+ case CompactLayout: {
+ // For each row exactly one role is shown. Calculate the maximum required width that is necessary
+ // to show all roles without horizontal clipping.
+ qreal maximumRequiredWidth = 0.0;
+ QHashIterator<QByteArray, int> it(visibleRoles());
+ while (it.hasNext()) {
+ it.next();
+ const QByteArray& role = it.key();
+ const QString text = values[role].toString();
+ const qreal requiredWidth = option.fontMetrics.width(text);
+ maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth);
+ }
+
+ const qreal width = option.margin * 4 + option.iconSize + maximumRequiredWidth;
+ const qreal height = option.margin * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * option.fontMetrics.height());
+ return QSizeF(width, height);
+ }
+
+ case DetailsLayout: {
+ // The width will be determined dynamically by KFileItemListView::visibleRoleSizes()
+ const qreal height = option.margin * 2 + qMax(option.iconSize, option.fontMetrics.height());
+ return QSizeF(-1, height);
+ }
+
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+
+ return QSize();
+}
+
+QHash<QByteArray, QSizeF> KFileItemListView::visibleRoleSizes() const
+{
+ QElapsedTimer timer;
+ timer.start();
+
+ QHash<QByteArray, QSizeF> sizes;
+
+ const int itemCount = model()->count();
+ for (int i = 0; i < itemCount; ++i) {
+ QHashIterator<QByteArray, int> it(visibleRoles());
+ while (it.hasNext()) {
+ it.next();
+ const QByteArray& visibleRole = it.key();
+
+ QSizeF maxSize = sizes.value(visibleRole, QSizeF(0, 0));
+
+ const QSizeF itemSize = visibleRoleSizeHint(i, visibleRole);
+ maxSize = maxSize.expandedTo(itemSize);
+ sizes.insert(visibleRole, maxSize);
+ }
+
+ if (i > 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.
+ #ifdef KFILEITEMLISTVIEW_DEBUG
+ kDebug() << "Timer exceeded, stopped after" << i << "items";
+ #endif
+ break;
+ }
+ }
+
+#ifdef KFILEITEMLISTVIEW_DEBUG
+ kDebug() << "[TIME] Calculated dynamic item size for " << itemCount << "items:" << timer.elapsed();
+#endif
+ return sizes;
+}
+
+void KFileItemListView::initializeItemListWidget(KItemListWidget* item)
+{
+ KFileItemListWidget* fileItemListWidget = static_cast<KFileItemListWidget*>(item);
+
+ switch (m_itemLayout) {
+ case IconsLayout: fileItemListWidget->setLayout(KFileItemListWidget::IconsLayout); break;
+ case CompactLayout: fileItemListWidget->setLayout(KFileItemListWidget::CompactLayout); break;
+ case DetailsLayout: fileItemListWidget->setLayout(KFileItemListWidget::DetailsLayout); break;
+ default: Q_ASSERT(false); break;
+ }
+}
+
+void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
+{
+ Q_UNUSED(previous);
+ Q_ASSERT(qobject_cast<KFileItemModel*>(current));
+
+ if (m_modelRolesUpdater) {
+ delete m_modelRolesUpdater;
+ }
+
+ m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this);
+ const int size = styleOption().iconSize;
+ m_modelRolesUpdater->setIconSize(QSize(size, size));
+}
+
+void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+ updateLayoutOfVisibleItems();
+}
+
+void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+ triggerVisibleIndexRangeUpdate();
+}
+
+void KFileItemListView::onOffsetChanged(qreal current, qreal previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+ triggerVisibleIndexRangeUpdate();
+}
+
+void KFileItemListView::onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous)
+{
+ Q_UNUSED(previous);
+
+ Q_ASSERT(qobject_cast<KFileItemModel*>(model()));
+ KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model());
+
+ // KFileItemModel does not distinct between "visible" and "invisible" roles.
+ // Add all roles that are mandatory for having a working KFileItemListView:
+ QSet<QByteArray> keys = current.keys().toSet();
+ QSet<QByteArray> roles = keys;
+ roles.insert("iconPixmap");
+ roles.insert("iconName");
+ roles.insert("name"); // TODO: just don't allow to disable it
+ roles.insert("isDir");
+ if (m_itemLayout == DetailsLayout) {
+ roles.insert("isExpanded");
+ roles.insert("expansionLevel");
+ }
+
+ fileItemModel->setRoles(roles);
+
+ m_modelRolesUpdater->setRoles(keys);
+}
+
+void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+ triggerIconSizeUpdate();
+}
+
+void KFileItemListView::onTransactionBegin()
+{
+ m_modelRolesUpdater->setPaused(true);
+}
+
+void KFileItemListView::onTransactionEnd()
+{
+ // Only unpause the model-roles-updater if no timer is active. If one
+ // timer is still active the model-roles-updater will be unpaused later as
+ // soon as the timer has been exceeded.
+ const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() ||
+ m_updateIconSizeTimer->isActive();
+ if (!timerActive) {
+ m_modelRolesUpdater->setPaused(false);
+ }
+}
+
+void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event)
+{
+ KItemListView::resizeEvent(event);
+ triggerVisibleIndexRangeUpdate();
+}
+
+void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
+{
+ KItemListView::slotItemsRemoved(itemRanges);
+ updateTimersInterval();
+}
+
+void KFileItemListView::triggerVisibleIndexRangeUpdate()
+{
+ m_modelRolesUpdater->setPaused(true);
+ m_updateVisibleIndexRangeTimer->start();
+}
+
+void KFileItemListView::updateVisibleIndexRange()
+{
+ if (!m_modelRolesUpdater) {
+ return;
+ }
+
+ const int index = firstVisibleIndex();
+ const int count = lastVisibleIndex() - index + 1;
+ m_modelRolesUpdater->setVisibleIndexRange(index, count);
+
+ if (m_updateIconSizeTimer->isActive()) {
+ // If the icon-size update is pending do an immediate update
+ // of the icon-size before unpausing m_modelRolesUpdater. This prevents
+ // an unnecessary expensive recreation of all previews afterwards.
+ m_updateIconSizeTimer->stop();
+ const KItemListStyleOption& option = styleOption();
+ m_modelRolesUpdater->setIconSize(QSize(option.iconSize, option.iconSize));
+ }
+
+ m_modelRolesUpdater->setPaused(isTransactionActive());
+ updateTimersInterval();
+}
+
+void KFileItemListView::triggerIconSizeUpdate()
+{
+ m_modelRolesUpdater->setPaused(true);
+ m_updateIconSizeTimer->start();
+}
+
+void KFileItemListView::updateIconSize()
+{
+ if (!m_modelRolesUpdater) {
+ return;
+ }
+
+ const KItemListStyleOption& option = styleOption();
+ m_modelRolesUpdater->setIconSize(QSize(option.iconSize, option.iconSize));
+
+ if (m_updateVisibleIndexRangeTimer->isActive()) {
+ // If the visibility-index-range update is pending do an immediate update
+ // of the range before unpausing m_modelRolesUpdater. This prevents
+ // an unnecessary expensive recreation of all previews afterwards.
+ m_updateVisibleIndexRangeTimer->stop();
+ const int index = firstVisibleIndex();
+ const int count = lastVisibleIndex() - index + 1;
+ m_modelRolesUpdater->setVisibleIndexRange(index, count);
+ }
+
+ m_modelRolesUpdater->setPaused(isTransactionActive());
+ updateTimersInterval();
+}
+
+QSizeF KFileItemListView::visibleRoleSizeHint(int index, const QByteArray& role) const
+{
+ const KItemListStyleOption& option = styleOption();
+
+ qreal width = m_minimumRolesWidths.value(role, 0);
+ const qreal height = option.margin * 2 + option.fontMetrics.height();
+
+ const QVariant value = model()->data(index).value(role);
+ const QString text = value.toString();
+ if (!text.isEmpty()) {
+ width = qMax(width, qreal(option.margin * 2 + option.fontMetrics.width(text)));
+ }
+
+ return QSizeF(width, height);
+}
+
+void KFileItemListView::updateLayoutOfVisibleItems()
+{
+ foreach (KItemListWidget* widget, visibleItemListWidgets()) {
+ initializeItemListWidget(widget);
+ }
+ triggerVisibleIndexRangeUpdate();
+}
+
+void KFileItemListView::updateTimersInterval()
+{
+ if (!model()) {
+ return;
+ }
+
+ // The ShortInterval is used for cases like switching the directory: If the
+ // model is empty and filled later the creation of the previews should be done
+ // as soon as possible. The LongInterval is used when the model already contains
+ // items and assures that operations like zooming don't result in too many temporary
+ // recreations of the previews.
+
+ const int interval = (model()->count() <= 0) ? ShortInterval : LongInterval;
+ m_updateVisibleIndexRangeTimer->setInterval(interval);
+ m_updateIconSizeTimer->setInterval(interval);
+}
+
+void KFileItemListView::updateMinimumRolesWidths()
+{
+ m_minimumRolesWidths.clear();
+
+ const KItemListStyleOption& option = styleOption();
+ const QString sizeText = QLatin1String("888888") + i18nc("@item:intable", "items");
+ m_minimumRolesWidths.insert("size", option.fontMetrics.width(sizeText));
+}
+
+#include "kfileitemlistview.moc"
diff --git a/src/kitemviews/kfileitemlistview.h b/src/kitemviews/kfileitemlistview.h
new file mode 100644
index 000000000..acd03ed22
--- /dev/null
+++ b/src/kitemviews/kfileitemlistview.h
@@ -0,0 +1,97 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KFILEITEMLISTVIEW_H
+#define KFILEITEMLISTVIEW_H
+
+#include <libdolphin_export.h>
+
+#include <kitemviews/kitemlistview.h>
+
+class KFileItemModelRolesUpdater;
+class QTimer;
+
+class LIBDOLPHINPRIVATE_EXPORT KFileItemListView : public KItemListView
+{
+ Q_OBJECT
+
+public:
+ enum Layout
+ {
+ IconsLayout,
+ CompactLayout,
+ DetailsLayout
+ };
+
+ KFileItemListView(QGraphicsWidget* parent = 0);
+ virtual ~KFileItemListView();
+
+ void setPreviewsShown(bool show);
+ bool previewsShown() const;
+
+ void setItemLayout(Layout layout);
+ Layout itemLayout() const;
+
+ virtual QSizeF itemSizeHint(int index) const;
+ virtual QHash<QByteArray, QSizeF> visibleRoleSizes() const;
+
+protected:
+ virtual void initializeItemListWidget(KItemListWidget* item);
+ virtual void onModelChanged(KItemModelBase* current, KItemModelBase* previous);
+ virtual void onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous);
+ virtual void onItemSizeChanged(const QSizeF& current, const QSizeF& previous);
+ virtual void onOffsetChanged(qreal current, qreal previous);
+ virtual void onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous);
+ virtual void onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
+ virtual void onTransactionBegin();
+ virtual void onTransactionEnd();
+ virtual void resizeEvent(QGraphicsSceneResizeEvent* event);
+
+protected slots:
+ virtual void slotItemsRemoved(const KItemRangeList& itemRanges);
+
+private slots:
+ void triggerVisibleIndexRangeUpdate();
+ void updateVisibleIndexRange();
+
+ void triggerIconSizeUpdate();
+ void updateIconSize();
+
+private:
+ QSizeF visibleRoleSizeHint(int index, const QByteArray& role) const;
+ void updateLayoutOfVisibleItems();
+ void updateTimersInterval();
+ void updateMinimumRolesWidths();
+
+private:
+ Layout m_itemLayout;
+
+ KFileItemModelRolesUpdater* m_modelRolesUpdater;
+ QTimer* m_updateVisibleIndexRangeTimer;
+ QTimer* m_updateIconSizeTimer;
+
+ // Cache for calculating visibleRoleSizes() in a fast way
+ QHash<QByteArray, int> m_minimumRolesWidths;
+
+ friend class KFileItemListViewTest; // For unit testing
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp
new file mode 100644
index 000000000..4abc4d115
--- /dev/null
+++ b/src/kitemviews/kfileitemlistwidget.cpp
@@ -0,0 +1,728 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kfileitemlistwidget.h"
+
+#include "kfileitemmodel.h"
+#include "kitemlistview.h"
+#include "kpixmapmodifier_p.h"
+
+#include <KIcon>
+#include <KIconEffect>
+#include <KIconLoader>
+#include <KLocale>
+#include <KStringHandler>
+#include <KDebug>
+
+#include <QFontMetricsF>
+#include <QGraphicsSceneResizeEvent>
+#include <QPainter>
+#include <QTextLayout>
+#include <QTextLine>
+
+//#define KFILEITEMLISTWIDGET_DEBUG
+
+KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) :
+ KItemListWidget(parent),
+ m_isDir(false),
+ m_dirtyLayout(true),
+ m_dirtyContent(true),
+ m_dirtyContentRoles(),
+ m_layout(IconsLayout),
+ m_pixmapPos(),
+ m_pixmap(),
+ m_scaledPixmapSize(),
+ m_hoverPixmapRect(),
+ m_hoverPixmap(),
+ m_textPos(),
+ m_text(),
+ m_textsBoundingRect(),
+ m_sortedVisibleRoles(),
+ m_expansionArea(),
+ m_additionalInfoTextColor()
+{
+ for (int i = 0; i < TextIdCount; ++i) {
+ m_text[i].setTextFormat(Qt::PlainText);
+ m_text[i].setPerformanceHint(QStaticText::AggressiveCaching);
+ }
+}
+
+KFileItemListWidget::~KFileItemListWidget()
+{
+}
+
+void KFileItemListWidget::setLayout(Layout layout)
+{
+ if (m_layout != layout) {
+ m_layout = layout;
+ m_dirtyLayout = true;
+ update();
+ }
+}
+
+KFileItemListWidget::Layout KFileItemListWidget::layout() const
+{
+ return m_layout;
+}
+
+void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+ KItemListWidget::paint(painter, option, widget);
+
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ if (m_dirtyContent || m_dirtyLayout) {
+ const_cast<KFileItemListWidget*>(this)->updateCache();
+ }
+
+ if (m_isDir && !m_expansionArea.isEmpty()) {
+ QStyleOption arrowOption;
+ arrowOption.rect = m_expansionArea.toRect();
+ const QStyle::PrimitiveElement arrow = data()["isExpanded"].toBool()
+ ? QStyle::PE_IndicatorArrowDown : QStyle::PE_IndicatorArrowRight;
+ style()->drawPrimitive(arrow, &arrowOption, painter);
+ }
+
+ const bool isHovered = (hoverOpacity() > 0.0);
+
+ const KItemListStyleOption& itemListStyleOption = styleOption();
+ if (isHovered) {
+ // Blend the unhovered and hovered pixmap if the hovering
+ // animation is ongoing
+ if (hoverOpacity() < 1.0) {
+ drawPixmap(painter, m_pixmap);
+ }
+
+ const qreal opacity = painter->opacity();
+ painter->setOpacity(hoverOpacity() * opacity);
+ drawPixmap(painter, m_hoverPixmap);
+
+ // Draw the hover background for the text
+ QRectF textsBoundingRect = m_textsBoundingRect;
+ const qreal marginDiff = itemListStyleOption.margin / 2;
+ textsBoundingRect.adjust(marginDiff, marginDiff, -marginDiff, -marginDiff);
+ painter->setOpacity(hoverOpacity() * opacity * 0.1);
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(itemListStyleOption.palette.text());
+ painter->drawRoundedRect(textsBoundingRect, 4, 4);
+
+ painter->setOpacity(opacity);
+ } else {
+ drawPixmap(painter, m_pixmap);
+ }
+
+ painter->setFont(itemListStyleOption.font);
+ painter->setPen(itemListStyleOption.palette.text().color());
+ painter->drawStaticText(m_textPos[Name], m_text[Name]);
+
+ painter->setPen(m_additionalInfoTextColor);
+ for (int i = Name + 1; i < TextIdCount; ++i) {
+ painter->drawStaticText(m_textPos[i], m_text[i]);
+ }
+
+#ifdef KFILEITEMLISTWIDGET_DEBUG
+ painter->setPen(Qt::red);
+ painter->setBrush(Qt::NoBrush);
+ painter->drawText(QPointF(0, itemListStyleOption.fontMetrics.height()), QString::number(index()));
+ painter->drawRect(rect());
+#endif
+}
+
+bool KFileItemListWidget::contains(const QPointF& point) const
+{
+ return KItemListWidget::contains(point) || m_textsBoundingRect.contains(point);
+}
+
+QRectF KFileItemListWidget::hoverBoundingRect() const
+{
+ QRectF bounds = m_hoverPixmapRect;
+ const qreal margin = styleOption().margin;
+ bounds.adjust(-margin, -margin, margin, margin);
+ return bounds;
+}
+
+QRectF KFileItemListWidget::expansionToggleRect() const
+{
+ return m_isDir ? m_expansionArea : QRectF();
+}
+
+void KFileItemListWidget::dataChanged(const QHash<QByteArray, QVariant>& current,
+ const QSet<QByteArray>& roles)
+{
+ KItemListWidget::dataChanged(current, roles);
+ m_dirtyContent = true;
+
+ QSet<QByteArray> dirtyRoles;
+ if (roles.isEmpty()) {
+ dirtyRoles = visibleRoles().keys().toSet();
+ dirtyRoles.insert("iconPixmap");
+ dirtyRoles.insert("iconName");
+ } else {
+ dirtyRoles = roles;
+ }
+
+ QSetIterator<QByteArray> it(dirtyRoles);
+ while (it.hasNext()) {
+ const QByteArray& role = it.next();
+ m_dirtyContentRoles.insert(role);
+ }
+}
+
+void KFileItemListWidget::visibleRolesChanged(const QHash<QByteArray, int>& current,
+ const QHash<QByteArray, int>& previous)
+{
+ KItemListWidget::visibleRolesChanged(current, previous);
+ m_dirtyLayout = true;
+
+ // Cache the roles sorted into m_sortedVisibleRoles:
+ const int visibleRolesCount = current.count();
+ m_sortedVisibleRoles.clear();
+ m_sortedVisibleRoles.reserve(visibleRolesCount);
+ for (int i = 0; i < visibleRolesCount; ++i) {
+ m_sortedVisibleRoles.append(QByteArray());
+ }
+
+ QHashIterator<QByteArray, int> it(current);
+ while (it.hasNext()) {
+ it.next();
+
+ const int index = it.value();
+ if (index < 0 || index >= visibleRolesCount || !m_sortedVisibleRoles.at(index).isEmpty()) {
+ kWarning() << "The visible roles have an invalid sort order.";
+ break;
+ }
+
+ const QByteArray& role = it.key();
+ m_sortedVisibleRoles[index] = role;
+ }
+}
+
+void KFileItemListWidget::visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current,
+ const QHash<QByteArray, QSizeF>& previous)
+{
+ KItemListWidget::visibleRolesSizesChanged(current, previous);
+ m_dirtyLayout = true;
+}
+
+void KFileItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
+ const KItemListStyleOption& previous)
+{
+ KItemListWidget::styleOptionChanged(current, previous);
+
+ // For the color of the additional info the inactive text color
+ // is not used as this might lead to unreadable text for some color schemes. Instead
+ // the text color is slightly mixed with the background color.
+ const QColor c1 = current.palette.text().color();
+ const QColor c2 = current.palette.background().color();
+ const int p1 = 70;
+ const int p2 = 100 - p1;
+ m_additionalInfoTextColor = QColor((c1.red() * p1 + c2.red() * p2) / 100,
+ (c1.green() * p1 + c2.green() * p2) / 100,
+ (c1.blue() * p1 + c2.blue() * p2) / 100);
+
+ m_dirtyLayout = true;
+}
+
+void KFileItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event)
+{
+ KItemListWidget::resizeEvent(event);
+ m_dirtyLayout = true;
+}
+
+void KFileItemListWidget::updateCache()
+{
+ if (index() < 0) {
+ return;
+ }
+
+ m_isDir = data()["isDir"].toBool();
+
+ updateExpansionArea();
+ updateTextsCache();
+ updatePixmapCache();
+
+ m_dirtyLayout = false;
+ m_dirtyContent = false;
+ m_dirtyContentRoles.clear();
+}
+
+void KFileItemListWidget::updateExpansionArea()
+{
+ if (m_layout == DetailsLayout) {
+ const QHash<QByteArray, QVariant> values = data();
+ Q_ASSERT(values.contains("expansionLevel"));
+ const KItemListStyleOption& option = styleOption();
+ const int expansionLevel = values.value("expansionLevel", 0).toInt();
+
+ const qreal widgetHeight = size().height();
+ const qreal expansionLevelSize = KIconLoader::SizeSmall;
+ const qreal x = option.margin + expansionLevel * widgetHeight;
+ const qreal y = (widgetHeight - expansionLevelSize) / 2;
+ m_expansionArea = QRectF(x, y, expansionLevelSize, expansionLevelSize);
+ } else {
+ m_expansionArea = QRectF();
+ }
+}
+
+void KFileItemListWidget::updatePixmapCache()
+{
+ // Precondition: Requires already updated m_textPos values to calculate
+ // the remaining height when the alignment is vertical.
+
+ const bool iconOnTop = (m_layout == IconsLayout);
+ const KItemListStyleOption& option = styleOption();
+ const int iconHeight = option.iconSize;
+
+ const QHash<QByteArray, QVariant> values = data();
+ const QSizeF widgetSize = size();
+
+ int scaledIconHeight = 0;
+ if (iconOnTop) {
+ scaledIconHeight = static_cast<int>(m_textPos[Name].y() - 3 * option.margin);
+ } else {
+ const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1;
+ const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height();
+ scaledIconHeight = (requiredTextHeight < iconHeight) ? widgetSize.height() - 2 * option.margin : iconHeight;
+ }
+
+ bool updatePixmap = (iconHeight != m_pixmap.height());
+ if (!updatePixmap && m_dirtyContent) {
+ updatePixmap = m_dirtyContentRoles.isEmpty()
+ || m_dirtyContentRoles.contains("iconPixmap")
+ || m_dirtyContentRoles.contains("iconName");
+ }
+
+ if (updatePixmap) {
+ m_pixmap = values["iconPixmap"].value<QPixmap>();
+ if (m_pixmap.isNull()) {
+ // Use the icon that fits to the MIME-type
+ QString iconName = values["iconName"].toString();
+ if (iconName.isEmpty()) {
+ // The icon-name has not been not resolved by KFileItemModelRolesUpdater,
+ // use a generic icon as fallback
+ iconName = QLatin1String("unknown");
+ }
+ m_pixmap = pixmapForIcon(iconName, iconHeight);
+ m_hoverPixmapRect.setSize(m_pixmap.size());
+ } else if (m_pixmap.size() != QSize(iconHeight, iconHeight)) {
+ // A custom pixmap has been applied. Assure that the pixmap
+ // is scaled to the available size.
+ const bool scale = m_pixmap.width() > iconHeight || m_pixmap.height() > iconHeight ||
+ (m_pixmap.width() < iconHeight && m_pixmap.height() < iconHeight);
+ if (scale) {
+ KPixmapModifier::scale(m_pixmap, QSize(iconHeight, iconHeight));
+ }
+ m_hoverPixmapRect.setSize(m_pixmap.size());
+
+ // To simplify the handling of scaling the original pixmap
+ // will be embedded into a square pixmap.
+ QPixmap squarePixmap(iconHeight, iconHeight);
+ squarePixmap.fill(Qt::transparent);
+
+ QPainter painter(&squarePixmap);
+ if (iconOnTop) {
+ const int x = (iconHeight - m_pixmap.width()) / 2; // Center horizontally
+ const int y = iconHeight - m_pixmap.height(); // Align on bottom
+ painter.drawPixmap(x, y, m_pixmap);
+ } else {
+ const int x = iconHeight - m_pixmap.width(); // Align right
+ const int y = (iconHeight - m_pixmap.height()) / 2; // Center vertically
+ painter.drawPixmap(x, y, m_pixmap);
+ }
+
+ m_pixmap = squarePixmap;
+ } else {
+ m_hoverPixmapRect.setSize(m_pixmap.size());
+ }
+
+ Q_ASSERT(m_pixmap.height() == iconHeight);
+ }
+
+ m_scaledPixmapSize = QSize(scaledIconHeight, scaledIconHeight);
+
+ if (iconOnTop) {
+ m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2);
+ } else {
+ m_pixmapPos.setX(m_textPos[Name].x() - 2 * option.margin - scaledIconHeight);
+ }
+ m_pixmapPos.setY(option.margin);
+
+ // Center the hover rectangle horizontally and align it on bottom
+ const qreal x = m_pixmapPos.x() + (m_scaledPixmapSize.width() - m_hoverPixmapRect.width()) / 2.0;
+ const qreal y = m_pixmapPos.y() + m_scaledPixmapSize.height() - m_hoverPixmapRect.height();
+ m_hoverPixmapRect.moveTopLeft(QPointF(x, y));
+
+ // Prepare the pixmap that is used when the item gets hovered
+ if (option.state & QStyle::State_MouseOver) {
+ m_hoverPixmap = m_pixmap;
+ KIconEffect* effect = KIconLoader::global()->iconEffect();
+ // In the KIconLoader terminology, active = hover.
+ if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) {
+ m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState);
+ } else {
+ m_hoverPixmap = m_pixmap;
+ }
+ } else if (hoverOpacity() <= 0.0) {
+ // No hover animation is ongoing. Clear m_hoverPixmap to save memory.
+ m_hoverPixmap = QPixmap();
+ }
+}
+
+void KFileItemListWidget::updateTextsCache()
+{
+ QTextOption textOption;
+ switch (m_layout) {
+ case IconsLayout:
+ textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+ textOption.setAlignment(Qt::AlignHCenter);
+ break;
+ case CompactLayout:
+ case DetailsLayout:
+ textOption.setAlignment(Qt::AlignLeft);
+ textOption.setWrapMode(QTextOption::NoWrap);
+ break;
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+
+ for (int i = 0; i < TextIdCount; ++i) {
+ m_text[i].setText(QString());
+ m_text[i].setTextOption(textOption);
+ }
+
+ switch (m_layout) {
+ case IconsLayout: updateIconsLayoutTextCache(); break;
+ case CompactLayout: updateCompactLayoutTextCache(); break;
+ case DetailsLayout: updateDetailsLayoutTextCache(); break;
+ default: Q_ASSERT(false); break;
+ }
+}
+
+void KFileItemListWidget::updateIconsLayoutTextCache()
+{
+ // +------+
+ // | Icon |
+ // +------+
+ //
+ // Name role that
+ // might get wrapped above
+ // several lines.
+ // Additional role 1
+ // Additional role 2
+
+ const QHash<QByteArray, QVariant> values = data();
+
+ const KItemListStyleOption& option = styleOption();
+ const qreal maxWidth = size().width() - 2 * option.margin;
+ const qreal widgetHeight = size().height();
+ const qreal fontHeight = option.fontMetrics.height();
+
+ // Initialize properties for the "name" role. It will be used as anchor
+ // for initializing the position of the other roles.
+ m_text[Name].setText(KStringHandler::preProcessWrap(values["name"].toString()));
+
+ // Calculate the number of lines required for the name and the required width
+ int textLinesCountForName = 0;
+ qreal requiredWidthForName = 0;
+ QTextLine line;
+
+ QTextLayout layout(m_text[Name].text(), option.font);
+ layout.setTextOption(m_text[Name].textOption());
+ layout.beginLayout();
+ while ((line = layout.createLine()).isValid()) {
+ line.setLineWidth(maxWidth);
+ requiredWidthForName = qMax(requiredWidthForName, line.naturalTextWidth());
+ ++textLinesCountForName;
+ }
+ layout.endLayout();
+
+ // Use one line for each additional information
+ int textLinesCount = textLinesCountForName;
+ const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
+ textLinesCount += additionalRolesCount;
+
+ m_text[Name].setTextWidth(maxWidth);
+ m_textPos[Name] = QPointF(option.margin, widgetHeight - textLinesCount * fontHeight - option.margin);
+ m_textsBoundingRect = QRectF(option.margin + (maxWidth - requiredWidthForName) / 2,
+ m_textPos[Name].y(),
+ requiredWidthForName,
+ m_text[Name].size().height());
+
+ // Calculate the position for each additional information
+ qreal y = m_textPos[Name].y() + textLinesCountForName * fontHeight;
+ foreach (const QByteArray& role, m_sortedVisibleRoles) {
+ const TextId textId = roleTextId(role);
+ if (textId == Name) {
+ continue;
+ }
+
+ const QString text = roleText(textId, values[role]);
+ m_text[textId].setText(text);
+
+ qreal requiredWidth = 0;
+
+ QTextLayout layout(text, option.font);
+ layout.setTextOption(m_text[textId].textOption());
+ layout.beginLayout();
+ QTextLine textLine = layout.createLine();
+ if (textLine.isValid()) {
+ textLine.setLineWidth(maxWidth);
+ requiredWidth = textLine.naturalTextWidth();
+ if (textLine.textLength() < text.length()) {
+ // TODO: QFontMetrics::elidedText() works different regarding the given width
+ // in comparison to QTextLine::setLineWidth(). It might happen that the text does
+ // not get elided although it does not fit into the given width. As workaround
+ // the margin is substracted.
+ const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth - option.margin);
+ m_text[textId].setText(elidedText);
+ }
+ }
+ layout.endLayout();
+
+ m_textPos[textId] = QPointF(option.margin, y);
+ m_text[textId].setTextWidth(maxWidth);
+
+ const QRectF textBoundingRect(option.margin + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight);
+ m_textsBoundingRect |= textBoundingRect;
+
+ y += fontHeight;
+ }
+
+ // Add a margin to the text bounding rectangle
+ const qreal margin = option.margin;
+ m_textsBoundingRect.adjust(-margin, -margin, margin, margin);
+}
+
+void KFileItemListWidget::updateCompactLayoutTextCache()
+{
+ // +------+ Name role
+ // | Icon | Additional role 1
+ // +------+ Additional role 2
+
+ const QHash<QByteArray, QVariant> values = data();
+
+ const KItemListStyleOption& option = styleOption();
+ const qreal widgetHeight = size().height();
+ const qreal fontHeight = option.fontMetrics.height();
+ const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * fontHeight;
+ const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.margin : option.iconSize;
+
+ qreal maximumRequiredTextWidth = 0;
+ const qreal x = option.margin * 3 + scaledIconSize;
+ qreal y = (widgetHeight - textLinesHeight) / 2;
+ const qreal maxWidth = size().width() - x - option.margin;
+ foreach (const QByteArray& role, m_sortedVisibleRoles) {
+ const TextId textId = roleTextId(role);
+
+ const QString text = roleText(textId, values[role]);
+ m_text[textId].setText(text);
+
+ qreal requiredWidth = option.fontMetrics.width(text);
+ if (requiredWidth > maxWidth) {
+ requiredWidth = maxWidth;
+ const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
+ m_text[textId].setText(elidedText);
+ }
+
+ m_textPos[textId] = QPointF(x, y);
+ m_text[textId].setTextWidth(maxWidth);
+
+ maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth);
+
+ y += fontHeight;
+ }
+
+ m_textsBoundingRect = QRectF(x - option.margin, 0, maximumRequiredTextWidth + 2 * option.margin, widgetHeight);
+}
+
+void KFileItemListWidget::updateDetailsLayoutTextCache()
+{
+ // Precondition: Requires already updated m_expansionArea
+ // to determine the left position.
+
+ // +------+
+ // | Icon | Name role Additional role 1 Additional role 2
+ // +------+
+ m_textsBoundingRect = QRectF();
+
+ const KItemListStyleOption& option = styleOption();
+ const QHash<QByteArray, QVariant> values = data();
+
+ const qreal widgetHeight = size().height();
+ const int scaledIconSize = widgetHeight - 2 * option.margin;
+ const int fontHeight = option.fontMetrics.height();
+
+ qreal x = m_expansionArea.right() + option.margin * 3 + scaledIconSize;
+ const qreal y = qMax(qreal(option.margin), (widgetHeight - fontHeight) / 2);
+
+ foreach (const QByteArray& role, m_sortedVisibleRoles) {
+ const TextId textId = roleTextId(role);
+
+ const QString text = roleText(textId, values[role]);
+ m_text[textId].setText(text);
+
+ const qreal requiredWidth = option.fontMetrics.width(text);
+ m_textPos[textId] = QPointF(x, y);
+
+ const qreal columnWidth = visibleRolesSizes().value(role, QSizeF(0, 0)).width();
+ x += columnWidth;
+
+ switch (textId) {
+ case Name: {
+ m_textsBoundingRect = QRectF(m_textPos[textId].x() - option.margin, 0,
+ requiredWidth + 2 * option.margin, size().height());
+
+ // The column after the name should always be aligned on the same x-position independent
+ // from the expansion-level shown in the name column
+ x -= m_expansionArea.right();
+ break;
+ }
+ case Size:
+ // The values for the size should be right aligned
+ m_textPos[textId].rx() += columnWidth - requiredWidth - 2 * option.margin;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+QString KFileItemListWidget::roleText(TextId textId, const QVariant& roleValue) const
+{
+ QString text;
+
+ switch (textId) {
+ case Name:
+ case Permissions:
+ case Owner:
+ case Group:
+ case Type:
+ case Destination:
+ case Path:
+ text = roleValue.toString();
+ break;
+
+ case Size: {
+ if (data().value("isDir").toBool()) {
+ // The item represents a directory. Show the number of sub directories
+ // instead of the file size of the directory.
+ if (!roleValue.isNull()) {
+ const KIO::filesize_t size = roleValue.value<KIO::filesize_t>();
+ text = i18ncp("@item:intable", "%1 item", "%1 items", size);
+ }
+ } else {
+ const KIO::filesize_t size = roleValue.value<KIO::filesize_t>();
+ text = KIO::convertSize(size);
+ }
+ break;
+ }
+
+ case Date: {
+ const QDateTime dateTime = roleValue.toDateTime();
+ text = KGlobal::locale()->formatDateTime(dateTime);
+ break;
+ }
+
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+
+ return text;
+}
+
+void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap)
+{
+ const bool isHiddenItem = m_text[Name].text().startsWith(QLatin1Char('.'));
+ qreal opacity;
+ if (isHiddenItem) {
+ opacity = painter->opacity();
+ painter->setOpacity(opacity * 0.3);
+ }
+
+ if (m_scaledPixmapSize != pixmap.size()) {
+ QPixmap scaledPixmap = pixmap;
+ KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize);
+ painter->drawPixmap(m_pixmapPos, scaledPixmap);
+
+#ifdef KFILEITEMLISTWIDGET_DEBUG
+ painter->setPen(Qt::green);
+ painter->drawRect(QRectF(m_pixmapPos, QSizeF(scaledPixmap.size())));
+#endif
+ } else {
+ painter->drawPixmap(m_pixmapPos, pixmap);
+ }
+
+ if (isHiddenItem) {
+ painter->setOpacity(opacity);
+ }
+}
+
+QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size)
+{
+ const KIcon icon(name);
+
+ int requestedSize;
+ if (size <= KIconLoader::SizeSmall) {
+ requestedSize = KIconLoader::SizeSmall;
+ } else if (size <= KIconLoader::SizeSmallMedium) {
+ requestedSize = KIconLoader::SizeSmallMedium;
+ } else if (size <= KIconLoader::SizeMedium) {
+ requestedSize = KIconLoader::SizeMedium;
+ } else if (size <= KIconLoader::SizeLarge) {
+ requestedSize = KIconLoader::SizeLarge;
+ } else if (size <= KIconLoader::SizeHuge) {
+ requestedSize = KIconLoader::SizeHuge;
+ } else if (size <= KIconLoader::SizeEnormous) {
+ requestedSize = KIconLoader::SizeEnormous;
+ } else if (size <= KIconLoader::SizeEnormous * 2) {
+ requestedSize = KIconLoader::SizeEnormous * 2;
+ } else {
+ requestedSize = size;
+ }
+
+ QPixmap pixmap = icon.pixmap(requestedSize, requestedSize);
+ if (requestedSize != size) {
+ KPixmapModifier::scale(pixmap, QSize(size, size));
+ }
+
+ return pixmap;
+}
+
+KFileItemListWidget::TextId KFileItemListWidget::roleTextId(const QByteArray& role)
+{
+ static QHash<QByteArray, TextId> rolesHash;
+ if (rolesHash.isEmpty()) {
+ rolesHash.insert("name", Name);
+ rolesHash.insert("size", Size);
+ rolesHash.insert("date", Date);
+ rolesHash.insert("permissions", Permissions);
+ rolesHash.insert("owner", Owner);
+ rolesHash.insert("group", Group);
+ rolesHash.insert("type", Type);
+ rolesHash.insert("destination", Destination);
+ rolesHash.insert("path", Path);
+ }
+
+ return rolesHash.value(role);
+}
+
+#include "kfileitemlistwidget.moc"
diff --git a/src/kitemviews/kfileitemlistwidget.h b/src/kitemviews/kfileitemlistwidget.h
new file mode 100644
index 000000000..3ce953106
--- /dev/null
+++ b/src/kitemviews/kfileitemlistwidget.h
@@ -0,0 +1,118 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KFILEITEMLISTWIDGET_H
+#define KFILEITEMLISTWIDGET_H
+
+#include <libdolphin_export.h>
+
+#include <kitemviews/kitemlistwidget.h>
+
+#include <QPixmap>
+#include <QPointF>
+#include <QStaticText>
+
+class LIBDOLPHINPRIVATE_EXPORT KFileItemListWidget : public KItemListWidget
+{
+ Q_OBJECT
+
+public:
+ enum Layout
+ {
+ IconsLayout,
+ CompactLayout,
+ DetailsLayout
+ };
+
+ KFileItemListWidget(QGraphicsItem* parent);
+ virtual ~KFileItemListWidget();
+
+ void setLayout(Layout layout);
+ Layout layout() const;
+
+ virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
+
+ virtual bool contains(const QPointF& point) const;
+ virtual QRectF hoverBoundingRect() const;
+ virtual QRectF expansionToggleRect() const;
+
+protected:
+ virtual void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>());
+ virtual void visibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous);
+ virtual void visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current, const QHash<QByteArray, QSizeF>& previous);
+ virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
+ virtual void resizeEvent(QGraphicsSceneResizeEvent* event);
+
+private:
+ enum TextId {
+ Name,
+ Size,
+ Date,
+ Permissions,
+ Owner,
+ Group,
+ Type,
+ Destination,
+ Path,
+ TextIdCount // Mandatory last entry
+ };
+
+ void updateCache();
+ void updateExpansionArea();
+ void updatePixmapCache();
+
+ void updateTextsCache();
+ void updateIconsLayoutTextCache();
+ void updateCompactLayoutTextCache();
+ void updateDetailsLayoutTextCache();
+
+ QString roleText(TextId textId, const QVariant& roleValue) const;
+
+ void drawPixmap(QPainter* painter, const QPixmap& pixmap);
+
+ static QPixmap pixmapForIcon(const QString& name, int size);
+ static TextId roleTextId(const QByteArray& role);
+
+private:
+ bool m_isDir;
+ bool m_dirtyLayout;
+ bool m_dirtyContent;
+ QSet<QByteArray> m_dirtyContentRoles;
+
+ Layout m_layout;
+ QPointF m_pixmapPos;
+ QPixmap m_pixmap;
+ QSize m_scaledPixmapSize;
+
+ QRectF m_hoverPixmapRect;
+ QPixmap m_hoverPixmap;
+
+ QPointF m_textPos[TextIdCount];
+ QStaticText m_text[TextIdCount];
+ QRectF m_textsBoundingRect;
+
+ QList<QByteArray> m_sortedVisibleRoles;
+
+ QRectF m_expansionArea;
+ QColor m_additionalInfoTextColor;
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp
new file mode 100644
index 000000000..b191abab6
--- /dev/null
+++ b/src/kitemviews/kfileitemmodel.cpp
@@ -0,0 +1,868 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kfileitemmodel.h"
+
+#include <KDirLister>
+#include <KLocale>
+#include <KStringHandler>
+#include <KDebug>
+
+#include <QTimer>
+
+#define KFILEITEMMODEL_DEBUG
+
+KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) :
+ KItemModelBase(QByteArray(), "name", parent),
+ m_dirLister(dirLister),
+ m_naturalSorting(true),
+ m_sortFoldersFirst(true),
+ m_groupRole(NoRole),
+ m_sortRole(NameRole),
+ m_caseSensitivity(Qt::CaseInsensitive),
+ m_sortedItems(),
+ m_items(),
+ m_data(),
+ m_requestRole(),
+ m_minimumUpdateIntervalTimer(0),
+ m_maximumUpdateIntervalTimer(0),
+ m_pendingItemsToInsert(),
+ m_pendingItemsToDelete(),
+ m_rootExpansionLevel(-1)
+{
+ resetRoles();
+ m_requestRole[NameRole] = true;
+ m_requestRole[IsDirRole] = true;
+
+ Q_ASSERT(dirLister);
+
+ connect(dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled()));
+ connect(dirLister, SIGNAL(completed()), this, SLOT(slotCompleted()));
+ connect(dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList)));
+ connect(dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
+ connect(dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
+ connect(dirLister, SIGNAL(clear(KUrl)), this, SLOT(slotClear(KUrl)));
+
+ // Although the layout engine of KItemListView is fast it is very inefficient to e.g.
+ // emit 50 itemsInserted()-signals each 100 ms. m_minimumUpdateIntervalTimer assures that updates
+ // are done in 1 second intervals for equal operations.
+ m_minimumUpdateIntervalTimer = new QTimer(this);
+ m_minimumUpdateIntervalTimer->setInterval(1000);
+ m_minimumUpdateIntervalTimer->setSingleShot(true);
+ connect(m_minimumUpdateIntervalTimer, SIGNAL(timeout()), this, SLOT(dispatchPendingItems()));
+
+ // For slow KIO-slaves like used for searching it makes sense to show results periodically even
+ // before the completed() or canceled() signal has been emitted.
+ m_maximumUpdateIntervalTimer = new QTimer(this);
+ m_maximumUpdateIntervalTimer->setInterval(2000);
+ m_maximumUpdateIntervalTimer->setSingleShot(true);
+ connect(m_maximumUpdateIntervalTimer, SIGNAL(timeout()), this, SLOT(dispatchPendingItems()));
+
+ Q_ASSERT(m_minimumUpdateIntervalTimer->interval() <= m_maximumUpdateIntervalTimer->interval());
+}
+
+KFileItemModel::~KFileItemModel()
+{
+}
+
+int KFileItemModel::count() const
+{
+ return m_data.count();
+}
+
+QHash<QByteArray, QVariant> KFileItemModel::data(int index) const
+{
+ if (index >= 0 && index < count()) {
+ return m_data.at(index);
+ }
+ return QHash<QByteArray, QVariant>();
+}
+
+bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& values)
+{
+ if (index >= 0 && index < count()) {
+ QHash<QByteArray, QVariant> currentValue = m_data.at(index);
+
+ QSet<QByteArray> changedRoles;
+ QHashIterator<QByteArray, QVariant> it(values);
+ while (it.hasNext()) {
+ it.next();
+ const QByteArray role = it.key();
+ const QVariant value = it.value();
+
+ if (currentValue[role] != value) {
+ currentValue[role] = value;
+ changedRoles.insert(role);
+ }
+ }
+
+ if (!changedRoles.isEmpty()) {
+ m_data[index] = currentValue;
+ emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
+ }
+
+ return true;
+ }
+ return false;
+}
+
+bool KFileItemModel::supportsGrouping() const
+{
+ return true;
+}
+
+bool KFileItemModel::supportsSorting() const
+{
+ return true;
+}
+
+KFileItem KFileItemModel::fileItem(int index) const
+{
+ if (index >= 0 && index < count()) {
+ return m_sortedItems.at(index);
+ }
+
+ return KFileItem();
+}
+
+int KFileItemModel::index(const KFileItem& item) const
+{
+ if (item.isNull()) {
+ return -1;
+ }
+
+ return m_items.value(item, -1);
+}
+
+void KFileItemModel::clear()
+{
+ slotClear();
+}
+
+void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
+{
+ if (count() > 0) {
+ const bool supportedExpanding = m_requestRole[IsExpandedRole] && m_requestRole[ExpansionLevelRole];
+ const bool willSupportExpanding = roles.contains("isExpanded") && roles.contains("expansionLevel");
+ if (supportedExpanding && !willSupportExpanding) {
+ // No expanding is supported anymore. Take care to delete all items that have an expansion level
+ // that is not 0 (and hence are part of an expanded item).
+ removeExpandedItems();
+ }
+ }
+
+ resetRoles();
+ QSetIterator<QByteArray> it(roles);
+ while (it.hasNext()) {
+ const QByteArray& role = it.next();
+ m_requestRole[roleIndex(role)] = true;
+ }
+
+ if (count() > 0) {
+ // Update m_data with the changed requested roles
+ const int maxIndex = count() - 1;
+ for (int i = 0; i <= maxIndex; ++i) {
+ m_data[i] = retrieveData(m_sortedItems.at(i));
+ }
+
+ kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!";
+ emit itemsChanged(KItemRangeList() << KItemRange(0, count()), QSet<QByteArray>());
+ }
+}
+
+QSet<QByteArray> KFileItemModel::roles() const
+{
+ QSet<QByteArray> roles;
+ for (int i = 0; i < RolesCount; ++i) {
+ if (m_requestRole[i]) {
+ switch (i) {
+ case NoRole: break;
+ case NameRole: roles.insert("name"); break;
+ case SizeRole: roles.insert("size"); break;
+ case DateRole: roles.insert("date"); break;
+ case PermissionsRole: roles.insert("permissions"); break;
+ case OwnerRole: roles.insert("owner"); break;
+ case GroupRole: roles.insert("group"); break;
+ case TypeRole: roles.insert("type"); break;
+ case DestinationRole: roles.insert("destination"); break;
+ case PathRole: roles.insert("path"); break;
+ case IsDirRole: roles.insert("isDir"); break;
+ case IsExpandedRole: roles.insert("isExpanded"); break;
+ case ExpansionLevelRole: roles.insert("expansionLevel"); break;
+ default: Q_ASSERT(false); break;
+ }
+ }
+ }
+ return roles;
+}
+
+bool KFileItemModel::setExpanded(int index, bool expanded)
+{
+ if (isExpanded(index) == expanded || index < 0 || index >= count()) {
+ return false;
+ }
+
+ QHash<QByteArray, QVariant> values;
+ values.insert("isExpanded", expanded);
+ if (!setData(index, values)) {
+ return false;
+ }
+
+ if (expanded) {
+ const KUrl url = m_sortedItems.at(index).url();
+ KDirLister* dirLister = m_dirLister.data();
+ if (dirLister) {
+ dirLister->openUrl(url, KDirLister::Keep);
+ return true;
+ }
+ } else {
+ KFileItemList itemsToRemove;
+ const int expansionLevel = data(index)["expansionLevel"].toInt();
+ ++index;
+ while (index < count() && data(index)["expansionLevel"].toInt() > expansionLevel) {
+ itemsToRemove.append(m_sortedItems.at(index));
+ ++index;
+ }
+ removeItems(itemsToRemove);
+ return true;
+ }
+
+ return false;
+}
+
+bool KFileItemModel::isExpanded(int index) const
+{
+ if (index >= 0 && index < count()) {
+ return m_data.at(index).value("isExpanded").toBool();
+ }
+ return false;
+}
+
+bool KFileItemModel::isExpandable(int index) const
+{
+ if (index >= 0 && index < count()) {
+ return m_sortedItems.at(index).isDir();
+ }
+ return false;
+}
+
+void KFileItemModel::onGroupRoleChanged(const QByteArray& current, const QByteArray& previous)
+{
+ Q_UNUSED(previous);
+ m_groupRole = roleIndex(current);
+}
+
+void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous)
+{
+ Q_UNUSED(previous);
+ const int itemCount = count();
+ if (itemCount <= 0) {
+ return;
+ }
+
+ m_sortRole = roleIndex(current);
+
+ KFileItemList sortedItems = m_sortedItems;
+ m_sortedItems.clear();
+ m_items.clear();
+ m_data.clear();
+ emit itemsRemoved(KItemRangeList() << KItemRange(0, itemCount));
+
+ sort(sortedItems.begin(), sortedItems.end());
+ int index = 0;
+ foreach (const KFileItem& item, sortedItems) {
+ m_sortedItems.append(item);
+ m_items.insert(item, index);
+ m_data.append(retrieveData(item));
+
+ ++index;
+ }
+
+ emit itemsInserted(KItemRangeList() << KItemRange(0, itemCount));
+}
+
+void KFileItemModel::slotCompleted()
+{
+ if (m_minimumUpdateIntervalTimer->isActive()) {
+ // dispatchPendingItems() will be called when the timer
+ // has been expired.
+ return;
+ }
+
+ dispatchPendingItems();
+ m_minimumUpdateIntervalTimer->start();
+}
+
+void KFileItemModel::slotCanceled()
+{
+ m_minimumUpdateIntervalTimer->stop();
+ m_maximumUpdateIntervalTimer->stop();
+ dispatchPendingItems();
+}
+
+void KFileItemModel::slotNewItems(const KFileItemList& items)
+{
+ if (!m_pendingItemsToDelete.isEmpty()) {
+ removeItems(m_pendingItemsToDelete);
+ m_pendingItemsToDelete.clear();
+ }
+ m_pendingItemsToInsert.append(items);
+
+ if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) {
+ // Assure that items get dispatched if no completed() or canceled() signal is
+ // emitted during the maximum update interval.
+ m_maximumUpdateIntervalTimer->start();
+ }
+}
+
+void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
+{
+ if (!m_pendingItemsToInsert.isEmpty()) {
+ insertItems(m_pendingItemsToInsert);
+ m_pendingItemsToInsert.clear();
+ }
+ m_pendingItemsToDelete.append(items);
+}
+
+void KFileItemModel::slotClear()
+{
+#ifdef KFILEITEMMODEL_DEBUG
+ kDebug() << "Clearing all items";
+#endif
+
+ m_minimumUpdateIntervalTimer->stop();
+ m_maximumUpdateIntervalTimer->stop();
+ m_pendingItemsToInsert.clear();
+ m_pendingItemsToDelete.clear();
+
+ m_rootExpansionLevel = -1;
+
+ const int removedCount = m_data.count();
+ if (removedCount > 0) {
+ m_sortedItems.clear();
+ m_items.clear();
+ m_data.clear();
+ emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
+ }
+}
+
+void KFileItemModel::slotClear(const KUrl& url)
+{
+ Q_UNUSED(url);
+}
+
+void KFileItemModel::dispatchPendingItems()
+{
+ if (!m_pendingItemsToInsert.isEmpty()) {
+ Q_ASSERT(m_pendingItemsToDelete.isEmpty());
+ insertItems(m_pendingItemsToInsert);
+ m_pendingItemsToInsert.clear();
+ } else if (!m_pendingItemsToDelete.isEmpty()) {
+ Q_ASSERT(m_pendingItemsToInsert.isEmpty());
+ removeItems(m_pendingItemsToDelete);
+ m_pendingItemsToDelete.clear();
+ }
+}
+
+void KFileItemModel::insertItems(const KFileItemList& items)
+{
+ if (items.isEmpty()) {
+ return;
+ }
+
+#ifdef KFILEITEMMODEL_DEBUG
+ QElapsedTimer timer;
+ timer.start();
+ kDebug() << "===========================================================";
+ kDebug() << "Inserting" << items.count() << "items";
+#endif
+
+ KFileItemList sortedItems = items;
+ sort(sortedItems.begin(), sortedItems.end());
+
+#ifdef KFILEITEMMODEL_DEBUG
+ kDebug() << "[TIME] Sorting:" << timer.elapsed();
+#endif
+
+ KItemRangeList itemRanges;
+ int targetIndex = 0;
+ int sourceIndex = 0;
+ int insertedAtIndex = -1;
+ int insertedCount = 0;
+ while (sourceIndex < sortedItems.count()) {
+ // Find target index from m_items to insert the current item
+ // in a sorted order
+ const int previousTargetIndex = targetIndex;
+ while (targetIndex < m_sortedItems.count()) {
+ if (!lessThan(m_sortedItems.at(targetIndex), sortedItems.at(sourceIndex))) {
+ break;
+ }
+ ++targetIndex;
+ }
+
+ if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) {
+ itemRanges << KItemRange(insertedAtIndex, insertedCount);
+ insertedAtIndex = targetIndex;
+ insertedCount = 0;
+ }
+
+ // Insert item at the position targetIndex
+ const KFileItem item = sortedItems.at(sourceIndex);
+ m_sortedItems.insert(targetIndex, item);
+ m_data.insert(targetIndex, retrieveData(item));
+ // m_items will be inserted after the loop (see comment below)
+ ++insertedCount;
+
+ if (insertedAtIndex < 0) {
+ insertedAtIndex = targetIndex;
+ }
+ ++targetIndex;
+ ++sourceIndex;
+ }
+
+ // The indexes of all m_items must be adjusted, not only the index
+ // of the new items
+ for (int i = 0; i < m_sortedItems.count(); ++i) {
+ m_items.insert(m_sortedItems.at(i), i);
+ }
+
+ itemRanges << KItemRange(insertedAtIndex, insertedCount);
+ emit itemsInserted(itemRanges);
+
+#ifdef KFILEITEMMODEL_DEBUG
+ kDebug() << "[TIME] Inserting of" << items.count() << "items:" << timer.elapsed();
+#endif
+}
+
+void KFileItemModel::removeItems(const KFileItemList& items)
+{
+ if (items.isEmpty()) {
+ return;
+ }
+
+#ifdef KFILEITEMMODEL_DEBUG
+ kDebug() << "Removing " << items.count() << "items";
+#endif
+
+ KFileItemList sortedItems = items;
+ sort(sortedItems.begin(), sortedItems.end());
+
+ QList<int> indexesToRemove;
+ indexesToRemove.reserve(items.count());
+
+ // Calculate the item ranges that will get deleted
+ KItemRangeList itemRanges;
+ int removedAtIndex = -1;
+ int removedCount = 0;
+ int targetIndex = 0;
+ foreach (const KFileItem& itemToRemove, sortedItems) {
+ const int previousTargetIndex = targetIndex;
+ while (targetIndex < m_sortedItems.count()) {
+ if (m_sortedItems.at(targetIndex) == itemToRemove) {
+ break;
+ }
+ ++targetIndex;
+ }
+ if (targetIndex >= m_sortedItems.count()) {
+ kWarning() << "Item that should be deleted has not been found!";
+ return;
+ }
+
+ if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) {
+ itemRanges << KItemRange(removedAtIndex, removedCount);
+ removedAtIndex = targetIndex;
+ removedCount = 0;
+ }
+
+ indexesToRemove.append(targetIndex);
+ if (removedAtIndex < 0) {
+ removedAtIndex = targetIndex;
+ }
+ ++removedCount;
+ ++targetIndex;
+ }
+
+ // Delete the items
+ for (int i = indexesToRemove.count() - 1; i >= 0; --i) {
+ const int indexToRemove = indexesToRemove.at(i);
+ m_items.remove(m_sortedItems.at(indexToRemove));
+ m_sortedItems.removeAt(indexToRemove);
+ m_data.removeAt(indexToRemove);
+ }
+
+ // The indexes of all m_items must be adjusted, not only the index
+ // of the removed items
+ for (int i = 0; i < m_sortedItems.count(); ++i) {
+ m_items.insert(m_sortedItems.at(i), i);
+ }
+
+ if (count() <= 0) {
+ m_rootExpansionLevel = -1;
+ }
+
+ itemRanges << KItemRange(removedAtIndex, removedCount);
+ emit itemsRemoved(itemRanges);
+}
+
+void KFileItemModel::removeExpandedItems()
+{
+
+ KFileItemList expandedItems;
+
+ const int maxIndex = m_data.count() - 1;
+ for (int i = 0; i <= maxIndex; ++i) {
+ if (m_data.at(i).value("expansionLevel").toInt() > 0) {
+ const KFileItem fileItem = m_sortedItems.at(i);
+ expandedItems.append(fileItem);
+ }
+ }
+
+ // The m_rootExpansionLevel may not get reset before all items with
+ // a bigger expansionLevel have been removed.
+ Q_ASSERT(m_rootExpansionLevel >= 0);
+ removeItems(expandedItems);
+
+ m_rootExpansionLevel = -1;
+}
+
+void KFileItemModel::resetRoles()
+{
+ for (int i = 0; i < RolesCount; ++i) {
+ m_requestRole[i] = false;
+ }
+}
+
+KFileItemModel::Role KFileItemModel::roleIndex(const QByteArray& role) const
+{
+ static QHash<QByteArray, Role> rolesHash;
+ if (rolesHash.isEmpty()) {
+ rolesHash.insert("name", NameRole);
+ rolesHash.insert("size", SizeRole);
+ rolesHash.insert("date", DateRole);
+ rolesHash.insert("permissions", PermissionsRole);
+ rolesHash.insert("owner", OwnerRole);
+ rolesHash.insert("group", GroupRole);
+ rolesHash.insert("type", TypeRole);
+ rolesHash.insert("destination", DestinationRole);
+ rolesHash.insert("path", PathRole);
+ rolesHash.insert("isDir", IsDirRole);
+ rolesHash.insert("isExpanded", IsExpandedRole);
+ rolesHash.insert("expansionLevel", ExpansionLevelRole);
+ }
+ return rolesHash.value(role, NoRole);
+}
+
+QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item) const
+{
+ // It is important to insert only roles that are fast to retrieve. E.g.
+ // KFileItem::iconName() can be very expensive if the MIME-type is unknown
+ // and hence will be retrieved asynchronously by KFileItemModelRolesUpdater.
+ QHash<QByteArray, QVariant> data;
+ data.insert("iconPixmap", QPixmap());
+
+ const bool isDir = item.isDir();
+ if (m_requestRole[IsDirRole]) {
+ data.insert("isDir", isDir);
+ }
+
+ if (m_requestRole[NameRole]) {
+ data.insert("name", item.name());
+ }
+
+ if (m_requestRole[SizeRole]) {
+ if (isDir) {
+ data.insert("size", QVariant());
+ } else {
+ data.insert("size", item.size());
+ }
+ }
+
+ if (m_requestRole[DateRole]) {
+ // Don't use KFileItem::timeString() as this is too expensive when
+ // having several thousands of items. Instead the formatting of the
+ // date-time will be done on-demand by the view when the date will be shown.
+ const KDateTime dateTime = item.time(KFileItem::ModificationTime);
+ data.insert("date", dateTime.dateTime());
+ }
+
+ if (m_requestRole[PermissionsRole]) {
+ data.insert("permissions", item.permissionsString());
+ }
+
+ if (m_requestRole[OwnerRole]) {
+ data.insert("owner", item.user());
+ }
+
+ if (m_requestRole[GroupRole]) {
+ data.insert("group", item.group());
+ }
+
+ if (m_requestRole[DestinationRole]) {
+ QString destination = item.linkDest();
+ if (destination.isEmpty()) {
+ destination = i18nc("@item:intable", "No destination");
+ }
+ data.insert("destination", destination);
+ }
+
+ if (m_requestRole[PathRole]) {
+ data.insert("path", item.localPath());
+ }
+
+ if (m_requestRole[IsExpandedRole]) {
+ data.insert("isExpanded", false);
+ }
+
+ if (m_requestRole[ExpansionLevelRole]) {
+ if (m_rootExpansionLevel < 0) {
+ KDirLister* dirLister = m_dirLister.data();
+ if (dirLister) {
+ const QString rootDir = dirLister->url().directory(KUrl::AppendTrailingSlash);
+ m_rootExpansionLevel = rootDir.count('/');
+ }
+ }
+ const QString dir = item.url().directory(KUrl::AppendTrailingSlash);
+ const int level = dir.count('/') - m_rootExpansionLevel - 1;
+ data.insert("expansionLevel", level);
+ }
+
+ if (item.isMimeTypeKnown()) {
+ data.insert("iconName", item.iconName());
+
+ if (m_requestRole[TypeRole]) {
+ data.insert("type", item.mimeComment());
+ }
+ }
+
+ return data;
+}
+
+bool KFileItemModel::lessThan(const KFileItem& a, const KFileItem& b) const
+{
+ int result = 0;
+
+ if (m_rootExpansionLevel >= 0) {
+ result = expansionLevelsCompare(a, b);
+ if (result != 0) {
+ // The items have parents with different expansion levels
+ return result < 0;
+ }
+ }
+
+ if (m_sortFoldersFirst) {
+ const bool isDirA = a.isDir();
+ const bool isDirB = b.isDir();
+ if (isDirA && !isDirB) {
+ return true;
+ } else if (!isDirA && isDirB) {
+ return false;
+ }
+ }
+
+ switch (m_sortRole) {
+ case NameRole: {
+ result = stringCompare(a.text(), b.text());
+ if (result == 0) {
+ // KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used
+ result = stringCompare(a.name(m_caseSensitivity == Qt::CaseInsensitive),
+ b.name(m_caseSensitivity == Qt::CaseInsensitive));
+ }
+ break;
+ }
+
+ case DateRole: {
+ const KDateTime dateTimeA = a.time(KFileItem::ModificationTime);
+ const KDateTime dateTimeB = b.time(KFileItem::ModificationTime);
+ if (dateTimeA < dateTimeB) {
+ result = -1;
+ } else if (dateTimeA > dateTimeB) {
+ result = +1;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (result == 0) {
+ // It must be assured that the sort order is always unique even if two values have been
+ // equal. In this case a comparison of the URL is done which is unique in all cases
+ // within KDirLister.
+ result = QString::compare(a.url().url(), b.url().url(), Qt::CaseSensitive);
+ }
+
+ return result < 0;
+}
+
+void KFileItemModel::sort(const KFileItemList::iterator& startIterator, const KFileItemList::iterator& endIterator)
+{
+ KFileItemList::iterator start = startIterator;
+ KFileItemList::iterator end = endIterator;
+
+ // The implementation is based on qSortHelper() from qalgorithms.h
+ // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+ // In opposite to qSort() it allows to use a member-function for the comparison of elements.
+ while (1) {
+ int span = int(end - start);
+ if (span < 2) {
+ return;
+ }
+
+ --end;
+ KFileItemList::iterator low = start, high = end - 1;
+ KFileItemList::iterator pivot = start + span / 2;
+
+ if (lessThan(*end, *start)) {
+ qSwap(*end, *start);
+ }
+ if (span == 2) {
+ return;
+ }
+
+ if (lessThan(*pivot, *start)) {
+ qSwap(*pivot, *start);
+ }
+ if (lessThan(*end, *pivot)) {
+ qSwap(*end, *pivot);
+ }
+ if (span == 3) {
+ return;
+ }
+
+ qSwap(*pivot, *end);
+
+ while (low < high) {
+ while (low < high && lessThan(*low, *end)) {
+ ++low;
+ }
+
+ while (high > low && lessThan(*end, *high)) {
+ --high;
+ }
+ if (low < high) {
+ qSwap(*low, *high);
+ ++low;
+ --high;
+ } else {
+ break;
+ }
+ }
+
+ if (lessThan(*low, *end)) {
+ ++low;
+ }
+
+ qSwap(*end, *low);
+ sort(start, low);
+
+ start = low + 1;
+ ++end;
+ }
+}
+
+int KFileItemModel::stringCompare(const QString& a, const QString& b) const
+{
+ // Taken from KDirSortFilterProxyModel (kdelibs/kfile/kdirsortfilterproxymodel.*)
+ // Copyright (C) 2006 by Peter Penz <[email protected]>
+ // Copyright (C) 2006 by Dominic Battre <[email protected]>
+ // Copyright (C) 2006 by Martin Pool <[email protected]>
+
+ if (m_caseSensitivity == Qt::CaseInsensitive) {
+ const int result = m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseInsensitive)
+ : QString::compare(a, b, Qt::CaseInsensitive);
+ if (result != 0) {
+ // Only return the result, if the strings are not equal. If they are equal by a case insensitive
+ // comparison, still a deterministic sort order is required. A case sensitive
+ // comparison is done as fallback.
+ return result;
+ }
+ }
+
+ return m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseSensitive)
+ : QString::compare(a, b, Qt::CaseSensitive);
+}
+
+int KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b) const
+{
+ const KUrl urlA = a.url();
+ const KUrl urlB = b.url();
+ if (urlA.directory() == urlB.directory()) {
+ // Both items have the same directory as parent
+ return 0;
+ }
+
+ // Check whether one item is the parent of the other item
+ if (urlA.isParentOf(urlB)) {
+ return -1;
+ } else if (urlB.isParentOf(urlA)) {
+ return +1;
+ }
+
+ // Determine the maximum common path of both items and
+ // remember the index in 'index'
+ const QString pathA = urlA.path();
+ const QString pathB = urlB.path();
+
+ const int maxIndex = qMin(pathA.length(), pathB.length()) - 1;
+ int index = 0;
+ while (index <= maxIndex && pathA.at(index) == pathB.at(index)) {
+ ++index;
+ }
+ if (index > maxIndex) {
+ index = maxIndex;
+ }
+ while (pathA.at(index) != QLatin1Char('/') && index > 0) {
+ --index;
+ }
+
+ // Determine the first sub-path after the common path and
+ // check whether it represents a directory or already a file
+ bool isDirA = true;
+ const QString subPathA = subPath(a, pathA, index, &isDirA);
+ bool isDirB = true;
+ const QString subPathB = subPath(b, pathB, index, &isDirB);
+
+ if (isDirA && !isDirB) {
+ return -1;
+ } else if (!isDirA && isDirB) {
+ return +1;
+ }
+
+ return stringCompare(subPathA, subPathB);
+}
+
+QString KFileItemModel::subPath(const KFileItem& item,
+ const QString& itemPath,
+ int start,
+ bool* isDir) const
+{
+ Q_ASSERT(isDir);
+ const int pathIndex = itemPath.indexOf('/', start + 1);
+ *isDir = (pathIndex > 0) || item.isDir();
+ return itemPath.mid(start, pathIndex - start);
+}
+
+bool KFileItemModel::useMaximumUpdateInterval() const
+{
+ const KDirLister* dirLister = m_dirLister.data();
+ return dirLister && !dirLister->url().isLocalFile();
+}
+
+#include "kfileitemmodel.moc"
diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h
new file mode 100644
index 000000000..e6c89d744
--- /dev/null
+++ b/src/kitemviews/kfileitemmodel.h
@@ -0,0 +1,192 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KFILEITEMMODEL_H
+#define KFILEITEMMODEL_H
+
+#include <libdolphin_export.h>
+#include <KFileItemList>
+#include <KUrl>
+#include <kitemviews/kitemmodelbase.h>
+
+#include <QHash>
+
+class KDirLister;
+class QTimer;
+
+/**
+ * @brief KItemModelBase implementation for KFileItems.
+ *
+ * KFileItemModel is connected with one KDirLister. Each time the KDirLister
+ * emits new items, removes items or changes items the model gets synchronized.
+ *
+ * KFileItemModel supports sorting and grouping of items. Additional roles that
+ * are not part of KFileItem can be added with KFileItemModel::setData().
+ *
+ * Also the recursive expansion of sub-directories is supported by
+ * KFileItemModel::setExpanded().
+ */
+class LIBDOLPHINPRIVATE_EXPORT KFileItemModel : public KItemModelBase
+{
+ Q_OBJECT
+
+public:
+ explicit KFileItemModel(KDirLister* dirLister, QObject* parent = 0);
+ virtual ~KFileItemModel();
+
+ virtual int count() const;
+ virtual QHash<QByteArray, QVariant> data(int index) const;
+ virtual bool setData(int index, const QHash<QByteArray, QVariant> &values);
+
+ /**
+ * @return True
+ * @reimp
+ */
+ virtual bool supportsGrouping() const;
+
+ /**
+ * @return True
+ * @reimp
+ */
+ virtual bool supportsSorting() const;
+
+ /**
+ * @return The file-item for the index \a index. If the index is in a valid
+ * range it is assured that the file-item is not null. The runtime
+ * complexity of this call is O(1).
+ */
+ KFileItem fileItem(int index) const;
+
+ /**
+ * @return The index for the file-item \a item. -1 is returned if no file-item
+ * is found or if the file-item is null. The runtime
+ * complexity of this call is O(1).
+ */
+ int index(const KFileItem& item) const;
+
+ /**
+ * Clears all items of the model.
+ */
+ void clear();
+
+ // TODO: "name" + "isDir" is default in ctor
+ void setRoles(const QSet<QByteArray>& roles);
+ QSet<QByteArray> roles() const;
+
+ bool setExpanded(int index, bool expanded);
+ bool isExpanded(int index) const;
+ bool isExpandable(int index) const;
+
+protected:
+ virtual void onGroupRoleChanged(const QByteArray& current, const QByteArray& previous);
+ virtual void onSortRoleChanged(const QByteArray& current, const QByteArray& previous);
+
+private slots:
+ void slotCompleted();
+ void slotCanceled();
+ void slotNewItems(const KFileItemList& items);
+ void slotItemsDeleted(const KFileItemList& items);
+ void slotClear();
+ void slotClear(const KUrl& url);
+
+ void dispatchPendingItems();
+
+private:
+ void insertItems(const KFileItemList& items);
+ void removeItems(const KFileItemList& items);
+
+ void removeExpandedItems();
+
+ enum Role {
+ NoRole,
+ NameRole,
+ SizeRole,
+ DateRole,
+ PermissionsRole,
+ OwnerRole,
+ GroupRole,
+ TypeRole,
+ DestinationRole,
+ PathRole,
+ IsDirRole,
+ IsExpandedRole,
+ ExpansionLevelRole,
+ RolesCount // Mandatory last entry
+ };
+
+ void resetRoles();
+
+ Role roleIndex(const QByteArray& role) const;
+
+ QHash<QByteArray, QVariant> retrieveData(const KFileItem& item) const;
+
+ bool lessThan(const KFileItem& a, const KFileItem& b) const;
+ void sort(const KFileItemList::iterator& start, const KFileItemList::iterator& end);
+ int stringCompare(const QString& a, const QString& b) const;
+
+ /**
+ * Compares the expansion level of both items. The "expansion level" is defined
+ * by the number of parent directories. However simply comparing just the numbers
+ * is not sufficient, it is also important to check the hierarchy for having
+ * a correct order like shown in a tree.
+ */
+ int expansionLevelsCompare(const KFileItem& a, const KFileItem& b) const;
+
+ /**
+ * Helper method for expansionLevelCompare().
+ */
+ QString subPath(const KFileItem& item,
+ const QString& itemPath,
+ int start,
+ bool* isDir) const;
+
+ bool useMaximumUpdateInterval() const;
+
+private:
+ QWeakPointer<KDirLister> m_dirLister;
+
+ bool m_naturalSorting;
+ bool m_sortFoldersFirst;
+
+ Role m_groupRole;
+ Role m_sortRole;
+ Qt::CaseSensitivity m_caseSensitivity;
+
+ KFileItemList m_sortedItems; // Allows O(1) access for KFileItemModel::fileItem(int index)
+ QHash<KFileItem, int> m_items; // Allows O(1) access for KFileItemModel::index(const KFileItem& item)
+ QList<QHash<QByteArray, QVariant> > m_data;
+
+ bool m_requestRole[RolesCount];
+
+ QTimer* m_minimumUpdateIntervalTimer;
+ QTimer* m_maximumUpdateIntervalTimer;
+ KFileItemList m_pendingItemsToInsert;
+ KFileItemList m_pendingItemsToDelete;
+
+ // Stores the smallest expansion level of the root-URL. Is required to calculate
+ // the "expansionLevel" role in an efficient way. A value < 0 indicates that
+ // it has not been initialized yet.
+ mutable int m_rootExpansionLevel;
+
+ friend class KFileItemModelTest; // For unit testing
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp
new file mode 100644
index 000000000..325d08e18
--- /dev/null
+++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp
@@ -0,0 +1,765 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kfileitemmodelrolesupdater.h"
+
+#include "kfileitemmodel.h"
+#include "kpixmapmodifier_p.h"
+
+#include <KConfig>
+#include <KConfigGroup>
+#include <KDebug>
+#include <KFileItem>
+#include <KGlobal>
+#include <KIO/PreviewJob>
+#include <QPainter>
+#include <QPixmap>
+#include <QElapsedTimer>
+#include <QTimer>
+
+// Required includes for subDirectoriesCount():
+#ifdef Q_WS_WIN
+ #include <QDir>
+#else
+ #include <dirent.h>
+ #include <QFile>
+#endif
+
+#define KFILEITEMMODELROLESUPDATER_DEBUG
+
+namespace {
+ const int MaxResolveItemsCount = 100;
+}
+
+KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) :
+ QObject(parent),
+ m_paused(false),
+ m_previewChangedDuringPausing(false),
+ m_iconSizeChangedDuringPausing(false),
+ m_rolesChangedDuringPausing(false),
+ m_previewShown(false),
+ m_clearPreviews(false),
+ m_model(model),
+ m_iconSize(),
+ m_firstVisibleIndex(0),
+ m_lastVisibleIndex(-1),
+ m_roles(),
+ m_enabledPlugins(),
+ m_pendingVisibleItems(),
+ m_pendingInvisibleItems(),
+ m_previewJobs(),
+ m_resolvePendingRolesTimer(0)
+{
+ Q_ASSERT(model);
+
+ const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
+ m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
+ << "directorythumbnail"
+ << "imagethumbnail"
+ << "jpegthumbnail");
+
+ connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
+ this, SLOT(slotItemsInserted(KItemRangeList)));
+ connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
+ this, SLOT(slotItemsRemoved(KItemRangeList)));
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+
+ // A timer with a minimal timeout is used to merge several triggerPendingRolesResolving() calls
+ // to only one call of resolvePendingRoles().
+ m_resolvePendingRolesTimer = new QTimer(this);
+ m_resolvePendingRolesTimer->setInterval(1);
+ m_resolvePendingRolesTimer->setSingleShot(true);
+ connect(m_resolvePendingRolesTimer, SIGNAL(timeout()), this, SLOT(resolvePendingRoles()));
+}
+
+KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
+{
+}
+
+void KFileItemModelRolesUpdater::setIconSize(const QSize& size)
+{
+ if (size != m_iconSize) {
+ m_iconSize = size;
+ if (m_paused) {
+ m_iconSizeChangedDuringPausing = true;
+ } else if (m_previewShown) {
+ // An icon size change requires the regenerating of
+ // all previews
+ sortAndResolveAllRoles();
+ } else {
+ sortAndResolvePendingRoles();
+ }
+ }
+}
+
+QSize KFileItemModelRolesUpdater::iconSize() const
+{
+ return m_iconSize;
+}
+
+void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
+{
+ if (index < 0) {
+ index = 0;
+ }
+ if (count < 0) {
+ count = 0;
+ }
+
+ if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) {
+ // The range has not been changed
+ return;
+ }
+
+ m_firstVisibleIndex = index;
+ m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
+
+ if (hasPendingRoles() && !m_paused) {
+ sortAndResolvePendingRoles();
+ }
+}
+
+void KFileItemModelRolesUpdater::setPreviewShown(bool show)
+{
+ if (show == m_previewShown) {
+ return;
+ }
+
+ m_previewShown = show;
+ if (!show) {
+ m_clearPreviews = true;
+ }
+
+ if (m_paused) {
+ m_previewChangedDuringPausing = true;
+ } else {
+ sortAndResolveAllRoles();
+ }
+}
+
+bool KFileItemModelRolesUpdater::isPreviewShown() const
+{
+ return m_previewShown;
+}
+
+void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list)
+{
+ m_enabledPlugins = list;
+}
+
+void KFileItemModelRolesUpdater::setPaused(bool paused)
+{
+ if (paused == m_paused) {
+ return;
+ }
+
+ m_paused = paused;
+ if (paused) {
+ if (hasPendingRoles()) {
+ foreach (KJob* job, m_previewJobs) {
+ job->kill();
+ }
+ Q_ASSERT(m_previewJobs.isEmpty());
+ }
+ } else {
+ const bool resolveAll = (m_iconSizeChangedDuringPausing && m_previewShown) ||
+ (m_previewChangedDuringPausing && !m_previewShown) ||
+ m_rolesChangedDuringPausing;
+ if (resolveAll) {
+ sortAndResolveAllRoles();
+ } else {
+ sortAndResolvePendingRoles();
+ }
+
+ m_iconSizeChangedDuringPausing = false;
+ m_previewChangedDuringPausing = false;
+ m_rolesChangedDuringPausing = false;
+ }
+}
+
+void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
+{
+ if (roles.count() == m_roles.count()) {
+ bool isEqual = true;
+ foreach (const QByteArray& role, roles) {
+ if (!m_roles.contains(role)) {
+ isEqual = false;
+ break;
+ }
+ }
+ if (isEqual) {
+ return;
+ }
+ }
+
+ m_roles = roles;
+
+ if (m_paused) {
+ m_rolesChangedDuringPausing = true;
+ } else {
+ sortAndResolveAllRoles();
+ }
+}
+
+QSet<QByteArray> KFileItemModelRolesUpdater::roles() const
+{
+ return m_roles;
+}
+
+bool KFileItemModelRolesUpdater::isPaused() const
+{
+ return m_paused;
+}
+
+QStringList KFileItemModelRolesUpdater::enabledPlugins() const
+{
+ return m_enabledPlugins;
+}
+
+void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges)
+{
+ // If no valid index range is given assume that all items are visible.
+ // A cleanup will be done later as soon as the index range has been set.
+ const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
+
+ if (hasValidIndexRange) {
+ // Move all current pending visible items that are not visible anymore
+ // to the pending invisible items.
+ QSetIterator<KFileItem> it(m_pendingVisibleItems);
+ while (it.hasNext()) {
+ const KFileItem item = it.next();
+ const int index = m_model->index(item);
+ if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) {
+ m_pendingVisibleItems.remove(item);
+ m_pendingInvisibleItems.insert(item);
+ }
+ }
+ }
+
+ int rangesCount = 0;
+
+ foreach (const KItemRange& range, itemRanges) {
+ rangesCount += range.count;
+
+ // Add the inserted items to the pending visible and invisible items
+ const int lastIndex = range.index + range.count - 1;
+ for (int i = range.index; i <= lastIndex; ++i) {
+ const KFileItem item = m_model->fileItem(i);
+ if (!hasValidIndexRange || (i >= m_firstVisibleIndex && i <= m_lastVisibleIndex)) {
+ m_pendingVisibleItems.insert(item);
+ } else {
+ m_pendingInvisibleItems.insert(item);
+ }
+ }
+ }
+
+ triggerPendingRolesResolving(rangesCount);
+}
+
+void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges)
+{
+ Q_UNUSED(itemRanges);
+ m_firstVisibleIndex = 0;
+ m_lastVisibleIndex = -1;
+ if (hasPendingRoles() && m_model->count() <= 0) {
+ resetPendingRoles();
+ }
+}
+
+void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
+ const QSet<QByteArray>& roles)
+{
+ Q_UNUSED(itemRanges);
+ Q_UNUSED(roles);
+ // TODO
+}
+
+void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
+{
+ m_pendingVisibleItems.remove(item);
+ m_pendingInvisibleItems.remove(item);
+
+ const int index = m_model->index(item);
+ if (index < 0) {
+ return;
+ }
+
+ QPixmap scaledPixmap = pixmap;
+
+ const QString mimeType = item.mimetype();
+ const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
+ const QString mimeTypeGroup = mimeType.left(slashIndex);
+ if (mimeTypeGroup == QLatin1String("image")) {
+ KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
+ } else {
+ KPixmapModifier::scale(scaledPixmap, m_iconSize);
+ }
+
+ QHash<QByteArray, QVariant> data = rolesData(item);
+ data.insert("iconPixmap", scaledPixmap);
+
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ m_model->setData(index, data);
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+}
+
+void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
+{
+ m_pendingVisibleItems.remove(item);
+ m_pendingInvisibleItems.remove(item);
+
+ const bool clearPreviews = m_clearPreviews;
+ m_clearPreviews = true;
+ applyResolvedRoles(item, ResolveAll);
+ m_clearPreviews = clearPreviews;
+}
+
+void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job)
+{
+#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
+ kDebug() << "Preview job finished. Pending visible:" << m_pendingVisibleItems.count() << "invisible:" << m_pendingInvisibleItems.count();
+#endif
+
+ m_previewJobs.removeOne(job);
+ if (!m_previewJobs.isEmpty() || !hasPendingRoles()) {
+ return;
+ }
+
+ const KFileItemList visibleItems = sortedItems(m_pendingVisibleItems);
+ const KFileItemList invisibleItems = itemSubSet(m_pendingInvisibleItems, MaxResolveItemsCount - visibleItems.count());
+ startPreviewJob(visibleItems + invisibleItems);
+}
+
+void KFileItemModelRolesUpdater::resolvePendingRoles()
+{
+ int resolvedCount = 0;
+
+ const bool hasSlowRoles = m_previewShown
+ || m_roles.contains("size")
+ || m_roles.contains("type");
+ const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll;
+
+ // Resolving the MIME type can be expensive. Assure that not more than 200 ms are
+ // spend for resolving them synchronously. Usually this is more than enough to determine
+ // all visible items, but there are corner cases where this limit gets easily exceeded.
+ const int MaxTime = 200;
+ QElapsedTimer timer;
+ timer.start();
+
+ // Resolve the MIME type of all visible items
+ QSetIterator<KFileItem> visibleIt(m_pendingVisibleItems);
+ while (visibleIt.hasNext()) {
+ const KFileItem item = visibleIt.next();
+ applyResolvedRoles(item, resolveHint);
+ if (!hasSlowRoles) {
+ Q_ASSERT(!m_pendingInvisibleItems.contains(item));
+ // All roles have been resolved already by applyResolvedRoles()
+ m_pendingVisibleItems.remove(item);
+ }
+ ++resolvedCount;
+
+ if (timer.elapsed() > MaxTime) {
+ break;
+ }
+ }
+
+ // Resolve the MIME type of the invisible items at least until the timeout
+ // has been exceeded or the maximum number of items has been reached
+ KFileItemList invisibleItems;
+ if (m_lastVisibleIndex >= 0) {
+ // The visible range is valid, don't care about the order how the MIME
+ // type of invisible items get resolved
+ invisibleItems = m_pendingInvisibleItems.toList();
+ } else {
+ // The visible range is temporary invalid (e.g. happens when loading
+ // a directory) so take care to sort the currently invisible items where
+ // a part will get visible later
+ invisibleItems = sortedItems(m_pendingInvisibleItems);
+ }
+
+ int index = 0;
+ while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxTime) {
+ const KFileItem item = invisibleItems.at(index);
+ applyResolvedRoles(item, resolveHint);
+
+ if (!hasSlowRoles) {
+ // All roles have been resolved already by applyResolvedRoles()
+ m_pendingInvisibleItems.remove(item);
+ }
+ ++index;
+ ++resolvedCount;
+ }
+
+ if (m_previewShown) {
+ KFileItemList items = sortedItems(m_pendingVisibleItems);
+ items += invisibleItems;
+ startPreviewJob(items);
+ } else {
+ QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
+ }
+
+#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
+ if (timer.elapsed() > MaxTime) {
+ kDebug() << "Maximum time exceeded, skipping items... Remaining visible:" << m_pendingVisibleItems.count()
+ << "invisible:" << m_pendingInvisibleItems.count();
+ }
+ kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed();
+#endif
+}
+
+void KFileItemModelRolesUpdater::resolveNextPendingRoles()
+{
+ if (m_paused) {
+ return;
+ }
+
+ if (m_previewShown) {
+ // The preview has been turned on since the last run. Skip
+ // resolving further pending roles as this is done as soon
+ // as a preview has been received.
+ return;
+ }
+
+ int resolvedCount = 0;
+ bool changed = false;
+ for (int i = 0; i <= 1; ++i) {
+ QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
+ QSetIterator<KFileItem> it(pendingItems);
+ while (it.hasNext() && !changed && resolvedCount < MaxResolveItemsCount) {
+ const KFileItem item = it.next();
+ pendingItems.remove(item);
+ changed = applyResolvedRoles(item, ResolveAll);
+ ++resolvedCount;
+ }
+ }
+
+ if (hasPendingRoles()) {
+ QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
+ } else {
+ m_clearPreviews = false;
+ }
+
+#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
+ static int callCount = 0;
+ ++callCount;
+ if (callCount % 100 == 0) {
+ kDebug() << "Remaining visible roles to resolve:" << m_pendingVisibleItems.count()
+ << "invisible:" << m_pendingInvisibleItems.count();
+ }
+#endif
+}
+
+void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items)
+{
+ if (items.count() <= 0 || m_paused) {
+ return;
+ }
+
+ // PreviewJob internally caches items always with the size of
+ // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
+ // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
+ // do a downscaling anyhow because of the frame, so in this case only the provided
+ // cache sizes are requested.
+ const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
+ ? QSize(256, 256) : QSize(128, 128);
+
+ KJob* job;
+ if (items.count() <= MaxResolveItemsCount) {
+ job = KIO::filePreview(items, cacheSize, &m_enabledPlugins);
+ } else {
+ KFileItemList itemsSubSet;
+ for (int i = 0; i <= MaxResolveItemsCount; ++i) {
+ itemsSubSet.append(items.at(i));
+ }
+ job = KIO::filePreview(itemsSubSet, cacheSize, &m_enabledPlugins);
+ }
+
+ connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
+ this, SLOT(slotGotPreview(const KFileItem&, const QPixmap&)));
+ connect(job, SIGNAL(failed(KFileItem)),
+ this, SLOT(slotPreviewFailed(KFileItem)));
+ connect(job, SIGNAL(finished(KJob*)),
+ this, SLOT(slotPreviewJobFinished(KJob*)));
+
+ m_previewJobs.append(job);
+}
+
+
+bool KFileItemModelRolesUpdater::hasPendingRoles() const
+{
+ return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty();
+}
+
+void KFileItemModelRolesUpdater::resetPendingRoles()
+{
+ m_pendingVisibleItems.clear();
+ m_pendingInvisibleItems.clear();
+
+ foreach (KJob* job, m_previewJobs) {
+ job->kill();
+ }
+ Q_ASSERT(m_previewJobs.isEmpty());
+}
+
+void KFileItemModelRolesUpdater::triggerPendingRolesResolving(int count)
+{
+ Q_ASSERT(count <= m_model->count());
+ if (count == m_model->count()) {
+ // When initially loading a directory a synchronous resolving prevents a minor
+ // flickering when opening directories. This is also fine from a performance point
+ // of view as it is assured in resolvePendingRoles() to never block the event-loop
+ // for more than 200 ms.
+ resolvePendingRoles();
+ } else {
+ // Items have been added. This can be done in several small steps within one loop
+ // because of the sorting and hence may not trigger any expensive operation.
+ m_resolvePendingRolesTimer->start();
+ }
+}
+
+void KFileItemModelRolesUpdater::sortAndResolveAllRoles()
+{
+ if (m_paused) {
+ return;
+ }
+
+ resetPendingRoles();
+ Q_ASSERT(m_pendingVisibleItems.isEmpty());
+ Q_ASSERT(m_pendingInvisibleItems.isEmpty());
+
+ if (m_model->count() <= 0) {
+ return;
+ }
+
+ // Determine all visible items
+ Q_ASSERT(m_firstVisibleIndex >= 0);
+ for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
+ const KFileItem item = m_model->fileItem(i);
+ if (!item.isNull()) {
+ m_pendingVisibleItems.insert(item);
+ }
+ }
+
+ // Determine all invisible items
+ for (int i = 0; i < m_firstVisibleIndex; ++i) {
+ const KFileItem item = m_model->fileItem(i);
+ if (!item.isNull()) {
+ m_pendingInvisibleItems.insert(item);
+ }
+ }
+ for (int i = m_lastVisibleIndex + 1; i < m_model->count(); ++i) {
+ const KFileItem item = m_model->fileItem(i);
+ if (!item.isNull()) {
+ m_pendingInvisibleItems.insert(item);
+ }
+ }
+
+ triggerPendingRolesResolving(m_pendingVisibleItems.count() +
+ m_pendingInvisibleItems.count());
+}
+
+void KFileItemModelRolesUpdater::sortAndResolvePendingRoles()
+{
+ Q_ASSERT(!m_paused);
+ if (m_model->count() <= 0) {
+ return;
+ }
+
+ // If no valid index range is given assume that all items are visible.
+ // A cleanup will be done later as soon as the index range has been set.
+ const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
+
+ // Trigger a preview generation of all pending items. Assure that the visible
+ // pending items get generated first.
+ QSet<KFileItem> pendingItems;
+ pendingItems += m_pendingVisibleItems;
+ pendingItems += m_pendingInvisibleItems;
+
+ resetPendingRoles();
+ Q_ASSERT(m_pendingVisibleItems.isEmpty());
+ Q_ASSERT(m_pendingInvisibleItems.isEmpty());
+
+ QSetIterator<KFileItem> it(pendingItems);
+ while (it.hasNext()) {
+ const KFileItem item = it.next();
+ if (item.isNull()) {
+ continue;
+ }
+
+ const int index = m_model->index(item);
+ if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
+ m_pendingVisibleItems.insert(item);
+ } else {
+ m_pendingInvisibleItems.insert(item);
+ }
+ }
+
+ triggerPendingRolesResolving(m_pendingVisibleItems.count() +
+ m_pendingInvisibleItems.count());
+}
+
+bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
+{
+ const bool resolveAll = (hint == ResolveAll);
+
+ bool mimeTypeChanged = false;
+ if (!item.isMimeTypeKnown()) {
+ item.determineMimeType();
+ mimeTypeChanged = true;
+ }
+
+ if (mimeTypeChanged || resolveAll || m_clearPreviews) {
+ const int index = m_model->index(item);
+ if (index < 0) {
+ return false;
+ }
+
+ QHash<QByteArray, QVariant> data;
+ if (resolveAll) {
+ data = rolesData(item);
+ }
+
+ if (mimeTypeChanged || m_clearPreviews) {
+ data.insert("iconName", item.iconName());
+ }
+ if (m_clearPreviews) {
+ data.insert("iconPixmap", QString());
+ }
+
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ m_model->setData(index, data);
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ return true;
+ }
+
+ return false;
+}
+
+QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const
+{
+ QHash<QByteArray, QVariant> data;
+
+ if (m_roles.contains("size")) {
+ if (item.isDir() && item.isLocalFile()) {
+ const QString path = item.localPath();
+ const int count = subDirectoriesCount(path);
+ if (count >= 0) {
+ data.insert("size", KIO::filesize_t(count));
+ }
+ }
+ }
+
+ if (m_roles.contains("type")) {
+ data.insert("type", item.mimeComment());
+ }
+
+ return data;
+}
+
+KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet<KFileItem>& items) const
+{
+ KFileItemList itemList;
+ if (items.isEmpty()) {
+ return itemList;
+ }
+
+#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
+ QElapsedTimer timer;
+ timer.start();
+#endif
+
+ QList<int> indexes;
+ indexes.reserve(items.count());
+
+ QSetIterator<KFileItem> it(items);
+ while (it.hasNext()) {
+ const KFileItem item = it.next();
+ const int index = m_model->index(item);
+ indexes.append(index);
+ }
+ qSort(indexes);
+
+ itemList.reserve(items.count());
+ foreach (int index, indexes) {
+ itemList.append(m_model->fileItem(index));
+ }
+
+#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
+ kDebug() << "[TIME] Sorting of items:" << timer.elapsed();
+#endif
+ return itemList;
+}
+
+KFileItemList KFileItemModelRolesUpdater::itemSubSet(const QSet<KFileItem>& items, int count)
+{
+ KFileItemList itemList;
+
+ int index = 0;
+ QSetIterator<KFileItem> it(items);
+ while (it.hasNext() && index < count) {
+ const KFileItem item = it.next();
+ if (item.isNull()) {
+ continue;
+ }
+ itemList.append(item);
+ ++index;
+ }
+
+ return itemList;
+}
+
+int KFileItemModelRolesUpdater::subDirectoriesCount(const QString& path)
+{
+#ifdef Q_WS_WIN
+ QDir dir(path);
+ return dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
+#else
+ // Taken from kdelibs/kio/kio/kdirmodel.cpp
+ // Copyright (C) 2006 David Faure <[email protected]>
+
+ int count = -1;
+ DIR* dir = ::opendir(QFile::encodeName(path));
+ if (dir) {
+ count = 0;
+ struct dirent *dirEntry = 0;
+ while ((dirEntry = ::readdir(dir))) {
+ if (dirEntry->d_name[0] == '.') {
+ if (dirEntry->d_name[1] == '\0') {
+ // Skip "."
+ continue;
+ }
+ if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
+ // Skip ".."
+ continue;
+ }
+ }
+ ++count;
+ }
+ ::closedir(dir);
+ }
+ return count;
+#endif
+}
+
+#include "kfileitemmodelrolesupdater.moc"
diff --git a/src/kitemviews/kfileitemmodelrolesupdater.h b/src/kitemviews/kfileitemmodelrolesupdater.h
new file mode 100644
index 000000000..4931f9d64
--- /dev/null
+++ b/src/kitemviews/kfileitemmodelrolesupdater.h
@@ -0,0 +1,177 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KFILEITEMMODELROLESUPDATER_H
+#define KFILEITEMMODELROLESUPDATER_H
+
+#include <libdolphin_export.h>
+
+#include <KFileItem>
+#include <kitemviews/kitemmodelbase.h>
+
+#include <QObject>
+#include <QSet>
+#include <QSize>
+#include <QStringList>
+
+class KFileItemModel;
+class KJob;
+class QPixmap;
+class QTimer;
+
+/**
+ * @brief Resolves expensive roles asynchronously and applies them to the KFileItemModel.
+ *
+ * KFileItemModel only resolves roles that are inexpensive like e.g. the file name or
+ * the permissions. Creating previews or determining the MIME-type can be quite expensive
+ * and KFileItemModelRolesUpdater takes care to update such roles asynchronously.
+ */
+class LIBDOLPHINPRIVATE_EXPORT KFileItemModelRolesUpdater : public QObject
+{
+ Q_OBJECT
+
+public:
+ KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent = 0);
+ virtual ~KFileItemModelRolesUpdater();
+
+ void setIconSize(const QSize& size);
+ QSize iconSize() const;
+
+ /**
+ * Sets the range of items that are visible currently. The roles
+ * of visible items are resolved first.
+ */
+ void setVisibleIndexRange(int index, int count);
+
+ /**
+ * If \a show is set to true, the "iconPixmap" role will be filled with a preview
+ * of the file. If \a show is false the MIME type icon will be used for the "iconPixmap"
+ * role.
+ */
+ void setPreviewShown(bool show);
+ bool isPreviewShown() const;
+
+ /**
+ * If \a paused is set to true the asynchronous resolving of roles will be paused.
+ * State changes during pauses like changing the icon size or the preview-shown
+ * will be remembered and handled after unpausing.
+ */
+ void setPaused(bool paused);
+ bool isPaused() const;
+
+ /**
+ * Sets the roles that should be resolved asynchronously.
+ */
+ void setRoles(const QSet<QByteArray>& roles);
+ QSet<QByteArray> roles() const;
+
+ /**
+ * Sets the list of enabled thumbnail plugins.
+ * Per default all plugins enabled in the KConfigGroup "PreviewSettings"
+ * are used.
+ *
+ * Note that this method doesn't cause already generated previews
+ * to be regenerated.
+ *
+ * For a list of available plugins, call KServiceTypeTrader::self()->query("ThumbCreator").
+ *
+ * @see enabledPlugins
+ */
+ void setEnabledPlugins(const QStringList& list);
+
+ /**
+ * Returns the list of enabled thumbnail plugins.
+ * @see setEnabledPlugins
+ */
+ QStringList enabledPlugins() const;
+
+private slots:
+ void slotItemsInserted(const KItemRangeList& itemRanges);
+ void slotItemsRemoved(const KItemRangeList& itemRanges);
+ void slotItemsChanged(const KItemRangeList& itemRanges,
+ const QSet<QByteArray>& roles);
+
+ void slotGotPreview(const KFileItem& item, const QPixmap& pixmap);
+ void slotPreviewFailed(const KFileItem& item);
+
+ /**
+ * Is invoked when the preview job has been finished and
+ * removes the job from the m_previewJobs list.
+ */
+ void slotPreviewJobFinished(KJob* job);
+
+ void resolvePendingRoles();
+ void resolveNextPendingRoles();
+
+private:
+ void startPreviewJob(const KFileItemList& items);
+
+ bool hasPendingRoles() const;
+ void resetPendingRoles();
+ void triggerPendingRolesResolving(int count);
+ void sortAndResolveAllRoles();
+ void sortAndResolvePendingRoles();
+
+ enum ResolveHint {
+ ResolveFast,
+ ResolveAll
+ };
+ bool applyResolvedRoles(const KFileItem& item, ResolveHint hint);
+ QHash<QByteArray, QVariant> rolesData(const KFileItem& item) const;
+
+ KFileItemList sortedItems(const QSet<KFileItem>& items) const;
+
+ static KFileItemList itemSubSet(const QSet<KFileItem>& items, int count);
+ static int subDirectoriesCount(const QString& path);
+
+private:
+ // Property for setPaused()/isPaused().
+ bool m_paused;
+
+ // Property changes during pausing must be remembered to be able
+ // to react when unpausing again:
+ bool m_previewChangedDuringPausing;
+ bool m_iconSizeChangedDuringPausing;
+ bool m_rolesChangedDuringPausing;
+
+ // Property for setPreviewShown()/previewShown().
+ bool m_previewShown;
+
+ // True if the role "iconPixmap" should be cleared when resolving the next
+ // role with resolveRole(). Is necessary if the preview gets disabled
+ // during the roles-updater has been paused by setPaused().
+ bool m_clearPreviews;
+
+ KFileItemModel* m_model;
+ QSize m_iconSize;
+ int m_firstVisibleIndex;
+ int m_lastVisibleIndex;
+ QSet<QByteArray> m_roles;
+ QStringList m_enabledPlugins;
+
+ QSet<KFileItem> m_pendingVisibleItems;
+ QSet<KFileItem> m_pendingInvisibleItems;
+ QList<KJob*> m_previewJobs;
+
+ QTimer* m_resolvePendingRolesTimer;
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kitemlistcontainer.cpp b/src/kitemviews/kitemlistcontainer.cpp
new file mode 100644
index 000000000..09fb505d6
--- /dev/null
+++ b/src/kitemviews/kitemlistcontainer.cpp
@@ -0,0 +1,191 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistcontainer.h"
+
+#include "kitemlistcontroller.h"
+#include "kitemlistview.h"
+#include "kitemmodelbase.h"
+
+#include <QGraphicsScene>
+#include <QGraphicsView>
+#include <QScrollBar>
+#include <QStyle>
+
+#include <KDebug>
+
+class KItemListContainerViewport : public QGraphicsView
+{
+public:
+ KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent)
+ : QGraphicsView(scene, parent)
+ {
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setViewportMargins(0, 0, 0, 0);
+ setFrameShape(QFrame::NoFrame);
+ }
+
+ void scrollContentsBy(int dx, int dy)
+ {
+ Q_UNUSED(dx);
+ Q_UNUSED(dy);
+ // Do nothing. This prevents that e.g. the wheel-event
+ // results in a moving of the scene items.
+ }
+};
+
+KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* parent) :
+ QAbstractScrollArea(parent),
+ m_controller(controller)
+{
+ Q_ASSERT(controller);
+ controller->setParent(this);
+ initialize();
+}
+
+KItemListContainer::KItemListContainer(QWidget* parent) :
+ QAbstractScrollArea(parent),
+ m_controller(0)
+{
+ initialize();
+}
+
+KItemListContainer::~KItemListContainer()
+{
+}
+
+KItemListController* KItemListContainer::controller() const
+{
+ return m_controller;
+}
+
+void KItemListContainer::showEvent(QShowEvent* event)
+{
+ QAbstractScrollArea::showEvent(event);
+ updateGeometries();
+}
+
+void KItemListContainer::resizeEvent(QResizeEvent* event)
+{
+ QAbstractScrollArea::resizeEvent(event);
+ updateGeometries();
+}
+
+void KItemListContainer::scrollContentsBy(int dx, int dy)
+{
+ KItemListView* view = m_controller->view();
+ if (!view) {
+ return;
+ }
+
+ const qreal currentOffset = view->offset();
+ const qreal offsetDiff = (view->scrollOrientation() == Qt::Vertical) ? dy : dx;
+ view->setOffset(currentOffset - offsetDiff);
+}
+
+void KItemListContainer::slotModelChanged(KItemModelBase* current, KItemModelBase* previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemListContainer::slotViewChanged(KItemListView* current, KItemListView* previous)
+{
+ QGraphicsScene* scene = static_cast<QGraphicsView*>(viewport())->scene();
+ if (previous) {
+ scene->removeItem(previous);
+ disconnect(previous, SIGNAL(offsetChanged(int,int)), this, SLOT(updateScrollBars()));
+ disconnect(previous, SIGNAL(maximumOffsetChanged(int,int)), this, SLOT(updateScrollBars()));
+ }
+ if (current) {
+ scene->addItem(current);
+ connect(previous, SIGNAL(offsetChanged(int,int)), this, SLOT(updateScrollBars()));
+ connect(current, SIGNAL(maximumOffsetChanged(int,int)), this, SLOT(updateScrollBars()));
+ }
+}
+
+void KItemListContainer::updateScrollBars()
+{
+ const QSizeF size = m_controller->view()->size();
+
+ if (m_controller->view()->scrollOrientation() == Qt::Vertical) {
+ QScrollBar* scrollBar = verticalScrollBar();
+ const int value = m_controller->view()->offset();
+ const int maximum = qMax(0, int(m_controller->view()->maximumOffset() - size.height()));
+ scrollBar->setPageStep(size.height());
+ scrollBar->setMinimum(0);
+ scrollBar->setMaximum(maximum);
+ scrollBar->setValue(value);
+ horizontalScrollBar()->setMaximum(0);
+ } else {
+ QScrollBar* scrollBar = horizontalScrollBar();
+ const int value = m_controller->view()->offset();
+ const int maximum = qMax(0, int(m_controller->view()->maximumOffset() - size.width()));
+ scrollBar->setPageStep(size.width());
+ scrollBar->setMinimum(0);
+ scrollBar->setMaximum(maximum);
+ scrollBar->setValue(value);
+ verticalScrollBar()->setMaximum(0);
+ }
+}
+
+void KItemListContainer::updateGeometries()
+{
+ QRect rect = geometry();
+
+ int widthDec = frameWidth() * 2;
+ if (verticalScrollBar()->isVisible()) {
+ widthDec += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+ }
+
+ int heightDec = frameWidth() * 2;
+ if (horizontalScrollBar()->isVisible()) {
+ heightDec += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+ }
+
+ rect.adjust(0, 0, -widthDec, -heightDec);
+
+ m_controller->view()->setGeometry(QRect(0, 0, rect.width(), rect.height()));
+
+ static_cast<KItemListContainerViewport*>(viewport())->scene()->setSceneRect(0, 0, rect.width(), rect.height());
+ static_cast<KItemListContainerViewport*>(viewport())->viewport()->setGeometry(QRect(0, 0, rect.width(), rect.height()));
+
+ updateScrollBars();
+}
+
+void KItemListContainer::initialize()
+{
+ if (!m_controller) {
+ m_controller = new KItemListController(this);
+ }
+
+ connect(m_controller, SIGNAL(modelChanged(KItemModelBase*,KItemModelBase*)),
+ this, SLOT(slotModelChanged(KItemModelBase*,KItemModelBase*)));
+ connect(m_controller, SIGNAL(viewChanged(KItemListView*,KItemListView*)),
+ this, SLOT(slotViewChanged(KItemListView*,KItemListView*)));
+
+ QGraphicsView* graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this);
+ setViewport(graphicsView);
+}
+
+#include "kitemlistcontainer.moc"
diff --git a/src/kitemviews/kitemlistcontainer.h b/src/kitemviews/kitemlistcontainer.h
new file mode 100644
index 000000000..83044c4f8
--- /dev/null
+++ b/src/kitemviews/kitemlistcontainer.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTCONTAINER_H
+#define KITEMLISTCONTAINER_H
+
+#include <libdolphin_export.h>
+
+#include <QAbstractScrollArea>
+
+class KItemListController;
+class KItemListView;
+class KItemModelBase;
+
+/**
+ * @brief Provides a QWidget based scrolling view for a KItemListController.
+ *
+ * @see KItemListController
+ */
+class LIBDOLPHINPRIVATE_EXPORT KItemListContainer : public QAbstractScrollArea
+{
+ Q_OBJECT
+
+public:
+ explicit KItemListContainer(KItemListController* controller, QWidget* parent = 0);
+ KItemListContainer(QWidget* parent = 0);
+ virtual ~KItemListContainer();
+
+ KItemListController* controller() const;
+
+protected:
+ virtual void showEvent(QShowEvent* event);
+ virtual void resizeEvent(QResizeEvent* event);
+ virtual void scrollContentsBy(int dx, int dy);
+
+private slots:
+ void slotModelChanged(KItemModelBase* current, KItemModelBase* previous);
+ void slotViewChanged(KItemListView* current, KItemListView* previous);
+ void updateScrollBars();
+
+private:
+ void initialize();
+ void updateGeometries();
+
+private:
+ KItemListController* m_controller;
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp
new file mode 100644
index 000000000..b709e5606
--- /dev/null
+++ b/src/kitemviews/kitemlistcontroller.cpp
@@ -0,0 +1,281 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistcontroller.h"
+
+#include "kitemlistview.h"
+#include "kitemlistselectionmanager.h"
+
+#include <QEvent>
+#include <QGraphicsSceneEvent>
+#include <QTransform>
+
+#include <KDebug>
+
+KItemListController::KItemListController(QObject* parent) :
+ QObject(parent),
+ m_selectionBehavior(NoSelection),
+ m_model(0),
+ m_view(0),
+ m_selectionManager(new KItemListSelectionManager(this))
+{
+}
+
+KItemListController::~KItemListController()
+{
+}
+
+void KItemListController::setModel(KItemModelBase* model)
+{
+ if (m_model == model) {
+ return;
+ }
+
+ KItemModelBase* oldModel = m_model;
+ m_model = model;
+
+ if (m_view) {
+ m_view->setModel(m_model);
+ }
+
+ emit modelChanged(m_model, oldModel);
+}
+
+KItemModelBase* KItemListController::model() const
+{
+ return m_model;
+}
+
+KItemListSelectionManager* KItemListController::selectionManager() const
+{
+ return m_selectionManager;
+}
+
+void KItemListController::setView(KItemListView* view)
+{
+ if (m_view == view) {
+ return;
+ }
+
+ KItemListView* oldView = m_view;
+ m_view = view;
+
+ if (m_view) {
+ m_view->setController(this);
+ m_view->setModel(m_model);
+ }
+
+ emit viewChanged(m_view, oldView);
+}
+
+KItemListView* KItemListController::view() const
+{
+ return m_view;
+}
+
+void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
+{
+ m_selectionBehavior = behavior;
+}
+
+KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
+{
+ return m_selectionBehavior;
+}
+
+bool KItemListController::showEvent(QShowEvent* event)
+{
+ Q_UNUSED(event);
+ return false;
+}
+
+bool KItemListController::hideEvent(QHideEvent* event)
+{
+ Q_UNUSED(event);
+ return false;
+}
+
+bool KItemListController::keyPressEvent(QKeyEvent* event)
+{
+ Q_UNUSED(event);
+ return false;
+}
+
+bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
+{
+ Q_UNUSED(event);
+ return false;
+}
+
+bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
+{
+ if (m_view) {
+ const QPointF pos = transform.map(event->pos());
+ const int index = m_view->itemAt(pos);
+ if (index >= 0) {
+ bool emitItemClicked = true;
+ if (event->button() & Qt::LeftButton) {
+ if (m_view->isAboveExpansionToggle(index, pos)) {
+ emit itemExpansionToggleClicked(index);
+ emitItemClicked = false;
+ }
+ }
+
+ if (emitItemClicked) {
+ emit itemClicked(index, event->button());
+ }
+ }
+ }
+
+ return false;
+}
+
+bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(transform);
+ return false;
+}
+
+bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
+{
+ if (!event) {
+ return false;
+ }
+
+ switch (event->type()) {
+// case QEvent::FocusIn:
+// case QEvent::FocusOut:
+// return focusEvent(static_cast<QFocusEvent*>(event));
+ case QEvent::KeyPress:
+ return keyPressEvent(static_cast<QKeyEvent*>(event));
+ case QEvent::InputMethod:
+ return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
+ case QEvent::GraphicsSceneMousePress:
+ return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneMouseMove:
+ return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneMouseRelease:
+ return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneWheel:
+ return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneDragEnter:
+ return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneDragLeave:
+ return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneDragMove:
+ return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneDrop:
+ return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneHoverEnter:
+ return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneHoverMove:
+ return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneHoverLeave:
+ return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
+ case QEvent::GraphicsSceneResize:
+ return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
+ default:
+ break;
+ }
+
+ return false;
+}
+
+#include "kitemlistcontroller.moc"
diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h
new file mode 100644
index 000000000..4407e3445
--- /dev/null
+++ b/src/kitemviews/kitemlistcontroller.h
@@ -0,0 +1,118 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTCONTROLLER_H
+#define KITEMLISTCONTROLLER_H
+
+#include <libdolphin_export.h>
+
+#include <QObject>
+
+class KItemModelBase;
+class KItemListSelectionManager;
+class KItemListView;
+class QGraphicsSceneHoverEvent;
+class QGraphicsSceneDragDropEvent;
+class QGraphicsSceneMouseEvent;
+class QGraphicsSceneResizeEvent;
+class QGraphicsSceneWheelEvent;
+class QHideEvent;
+class QInputMethodEvent;
+class QKeyEvent;
+class QShowEvent;
+class QTransform;
+
+/**
+ * @brief Controls the view, model and selection of an item-list.
+ *
+ * For a working item-list it is mandatory to set a compatible view and model
+ * with KItemListController::setView() and KItemListController::setModel().
+ *
+ * @see KItemListView
+ * @see KItemModelBase
+ * @see KItemListSelectionManager
+ */
+class LIBDOLPHINPRIVATE_EXPORT KItemListController : public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(SelectionBehavior)
+ Q_PROPERTY(KItemModelBase* model READ model WRITE setModel)
+ Q_PROPERTY(KItemListView *view READ view WRITE setView)
+ Q_PROPERTY(SelectionBehavior selectionBehavior READ selectionBehavior WRITE setSelectionBehavior)
+
+public:
+ enum SelectionBehavior {
+ NoSelection,
+ SingleSelection,
+ MultiSelection
+ };
+
+ KItemListController(QObject* parent = 0);
+ virtual ~KItemListController();
+
+ void setModel(KItemModelBase* model);
+ KItemModelBase* model() const;
+
+ void setView(KItemListView* view);
+ KItemListView* view() const;
+
+ KItemListSelectionManager* selectionManager() const;
+
+ void setSelectionBehavior(SelectionBehavior behavior);
+ SelectionBehavior selectionBehavior() const;
+
+ virtual bool showEvent(QShowEvent* event);
+ virtual bool hideEvent(QHideEvent* event);
+ virtual bool keyPressEvent(QKeyEvent* event);
+ virtual bool inputMethodEvent(QInputMethodEvent* event);
+ virtual bool mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform);
+ virtual bool mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform);
+ virtual bool mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform);
+ virtual bool mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform);
+ virtual bool dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform);
+ virtual bool dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform);
+ virtual bool dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform);
+ virtual bool dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform);
+ virtual bool hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform);
+ virtual bool hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform);
+ virtual bool hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform);
+ virtual bool wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform);
+ virtual bool resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform);
+ virtual bool processEvent(QEvent* event, const QTransform& transform);
+
+signals:
+ void itemClicked(int index, Qt::MouseButton button);
+ void itemExpansionToggleClicked(int index);
+
+ void modelChanged(KItemModelBase* current, KItemModelBase* previous);
+ void viewChanged(KItemListView* current, KItemListView* previous);
+
+private:
+ SelectionBehavior m_selectionBehavior;
+ KItemModelBase* m_model;
+ KItemListView* m_view;
+ KItemListSelectionManager* m_selectionManager;
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kitemlistgroupheader.cpp b/src/kitemviews/kitemlistgroupheader.cpp
new file mode 100644
index 000000000..4db253293
--- /dev/null
+++ b/src/kitemviews/kitemlistgroupheader.cpp
@@ -0,0 +1,58 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistgroupheader.h"
+
+#include "kitemlistview.h"
+
+#include <QPainter>
+
+#include <KDebug>
+
+KItemListGroupHeader::KItemListGroupHeader(QGraphicsWidget* parent) :
+ QGraphicsWidget(parent, 0)
+{
+}
+
+KItemListGroupHeader::~KItemListGroupHeader()
+{
+}
+
+QSizeF KItemListGroupHeader::sizeHint(Qt::SizeHint which, const QSizeF& constraint) const
+{
+ Q_UNUSED(which);
+ Q_UNUSED(constraint);
+ return QSizeF();
+}
+
+void KItemListGroupHeader::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+ Q_UNUSED(option);
+ Q_UNUSED(widget);
+ painter->setPen(Qt::darkGreen);
+ painter->setBrush(QColor(0, 255, 0, 50));
+ painter->drawRect(rect());
+
+ //painter->drawText(rect(), QString::number(m_index));
+}
+
+#include "kitemlistgroupheader.moc"
diff --git a/src/kitemviews/kitemlistgroupheader.h b/src/kitemviews/kitemlistgroupheader.h
new file mode 100644
index 000000000..135fd5e5f
--- /dev/null
+++ b/src/kitemviews/kitemlistgroupheader.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTGROUPHEADER_H
+#define KITEMLISTGROUPHEADER_H
+
+#include <libdolphin_export.h>
+
+#include <QGraphicsWidget>
+
+class KItemListView;
+
+class LIBDOLPHINPRIVATE_EXPORT KItemListGroupHeader : public QGraphicsWidget
+{
+ Q_OBJECT
+
+public:
+ KItemListGroupHeader(QGraphicsWidget* parent = 0);
+ virtual ~KItemListGroupHeader();
+
+ void setIndex(int index);
+ int index() const;
+
+ virtual QSizeF sizeHint(Qt::SizeHint which = Qt::PreferredSize, const QSizeF& constraint = QSizeF()) const;
+ virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
+};
+#endif
+
+
diff --git a/src/kitemviews/kitemlistselectionmanager.cpp b/src/kitemviews/kitemlistselectionmanager.cpp
new file mode 100644
index 000000000..6fe9ed818
--- /dev/null
+++ b/src/kitemviews/kitemlistselectionmanager.cpp
@@ -0,0 +1,87 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistselectionmanager.h"
+
+#include "kitemmodelbase.h"
+
+KItemListSelectionManager::KItemListSelectionManager(QObject* parent) :
+ QObject(parent),
+ m_currentItem(-1),
+ m_anchorItem(-1),
+ m_model(0)
+{
+}
+
+KItemListSelectionManager::~KItemListSelectionManager()
+{
+}
+
+void KItemListSelectionManager::setCurrentItem(int current)
+{
+ const int previous = m_currentItem;
+ if (m_model && current < m_model->count()) {
+ m_currentItem = current;
+ } else {
+ m_currentItem = -1;
+ }
+
+ if (m_currentItem != previous) {
+ emit currentChanged(m_currentItem, previous);
+ }
+}
+
+int KItemListSelectionManager::currentItem() const
+{
+ return m_currentItem;
+}
+
+void KItemListSelectionManager::setAnchorItem(int anchor)
+{
+ const int previous = m_anchorItem;
+ if (m_model && anchor < m_model->count()) {
+ m_anchorItem = anchor;
+ } else {
+ m_anchorItem = -1;
+ }
+
+ if (m_anchorItem != previous) {
+ emit anchorChanged(m_anchorItem, previous);
+ }
+}
+
+int KItemListSelectionManager::anchorItem() const
+{
+ return m_anchorItem;
+}
+
+KItemModelBase* KItemListSelectionManager::model() const
+{
+ return m_model;
+}
+
+void KItemListSelectionManager::setModel(KItemModelBase* model)
+{
+ m_model = model;
+}
+
+#include "kitemlistselectionmanager.moc"
diff --git a/src/kitemviews/kitemlistselectionmanager.h b/src/kitemviews/kitemlistselectionmanager.h
new file mode 100644
index 000000000..5c8e84614
--- /dev/null
+++ b/src/kitemviews/kitemlistselectionmanager.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTSELECTIONMANAGER_H
+#define KITEMLISTSELECTIONMANAGER_H
+
+#include <libdolphin_export.h>
+
+#include <QObject>
+
+class KItemModelBase;
+
+class LIBDOLPHINPRIVATE_EXPORT KItemListSelectionManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum SelectionMode {
+ Select,
+ Deselect,
+ Toggle
+ };
+
+ KItemListSelectionManager(QObject* parent = 0);
+ virtual ~KItemListSelectionManager();
+
+ void setCurrentItem(int current);
+ int currentItem() const;
+
+ void setAnchorItem(int anchor);
+ int anchorItem() const;
+
+ KItemModelBase* model() const;
+
+signals:
+ void currentChanged(int current, int previous);
+ void anchorChanged(int anchor, int previous);
+
+protected:
+ void setModel(KItemModelBase* model);
+
+private:
+ int m_currentItem;
+ int m_anchorItem;
+ KItemModelBase* m_model;
+
+ friend class KItemListController;
+};
+
+#endif
diff --git a/src/kitemviews/kitemlistsizehintresolver.cpp b/src/kitemviews/kitemlistsizehintresolver.cpp
new file mode 100644
index 000000000..00eb79bdd
--- /dev/null
+++ b/src/kitemviews/kitemlistsizehintresolver.cpp
@@ -0,0 +1,86 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistsizehintresolver_p.h"
+
+#include <kitemviews/kitemlistview.h>
+#include <KDebug>
+
+KItemListSizeHintResolver::KItemListSizeHintResolver(const KItemListView* itemListView) :
+ m_itemListView(itemListView),
+ m_sizeHintCache()
+{
+}
+
+KItemListSizeHintResolver::~KItemListSizeHintResolver()
+{
+}
+
+QSizeF KItemListSizeHintResolver::sizeHint(int index) const
+{
+ QSizeF size = m_sizeHintCache.at(index);
+ if (size.isEmpty()) {
+ size = m_itemListView->itemSizeHint(index);
+ m_sizeHintCache[index] = size;
+ }
+ return size;
+}
+
+void KItemListSizeHintResolver::itemsInserted(int index, int count)
+{
+ const int currentCount = m_sizeHintCache.count();
+ m_sizeHintCache.reserve(currentCount + count);
+ while (count > 0) {
+ m_sizeHintCache.insert(index, QSizeF());
+ ++index;
+ --count;
+ }
+}
+
+void KItemListSizeHintResolver::itemsRemoved(int index, int count)
+{
+ const QList<QSizeF>::iterator begin = m_sizeHintCache.begin() + index;
+ const QList<QSizeF>::iterator end = begin + count;
+ m_sizeHintCache.erase(begin, end);
+}
+
+void KItemListSizeHintResolver::itemsMoved(int from, int to, int count)
+{
+ Q_UNUSED(from);
+ Q_UNUSED(to);
+ Q_UNUSED(count);
+}
+
+void KItemListSizeHintResolver::itemsChanged(int index, int count, const QSet<QByteArray>& roles)
+{
+ Q_UNUSED(roles);
+ while (count) {
+ m_sizeHintCache[index] = QSizeF();
+ ++index;
+ --count;
+ }
+}
+
+void KItemListSizeHintResolver::clearCache()
+{
+ const int count = m_sizeHintCache.count();
+ for (int i = 0; i < count; ++i) {
+ m_sizeHintCache[i] = QSizeF();
+ }
+}
diff --git a/src/kitemviews/kitemlistsizehintresolver_p.h b/src/kitemviews/kitemlistsizehintresolver_p.h
new file mode 100644
index 000000000..f4f65eccb
--- /dev/null
+++ b/src/kitemviews/kitemlistsizehintresolver_p.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTSIZEHINTRESOLVER_H
+#define KITEMLISTSIZEHINTRESOLVER_H
+
+#include <libdolphin_export.h>
+
+#include <QByteArray>
+#include <QList>
+#include <QSizeF>
+
+class KItemListView;
+
+class LIBDOLPHINPRIVATE_EXPORT KItemListSizeHintResolver
+{
+public:
+ KItemListSizeHintResolver(const KItemListView* itemListView);
+ virtual ~KItemListSizeHintResolver();
+ QSizeF sizeHint(int index) const;
+
+ void itemsInserted(int index, int count);
+ void itemsRemoved(int index, int count);
+ void itemsMoved(int from, int to, int count);
+ void itemsChanged(int index, int count, const QSet<QByteArray>& roles);
+
+ void clearCache();
+
+private:
+ const KItemListView* m_itemListView;
+ mutable QList<QSizeF> m_sizeHintCache;
+};
+
+#endif
diff --git a/src/kitemviews/kitemliststyleoption.cpp b/src/kitemviews/kitemliststyleoption.cpp
new file mode 100644
index 000000000..261dfc07b
--- /dev/null
+++ b/src/kitemviews/kitemliststyleoption.cpp
@@ -0,0 +1,37 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemliststyleoption.h"
+
+KItemListStyleOption::KItemListStyleOption() :
+ QStyleOption(QStyleOption::Version, QStyleOption::SO_CustomBase + 1)
+{
+}
+
+KItemListStyleOption::KItemListStyleOption(const KItemListStyleOption& other) :
+ QStyleOption(other)
+{
+ margin = other.margin;
+ iconSize = other.iconSize;
+ font = other.font;
+}
+
+KItemListStyleOption::~KItemListStyleOption()
+{
+}
diff --git a/src/kitemviews/kitemliststyleoption.h b/src/kitemviews/kitemliststyleoption.h
new file mode 100644
index 000000000..d181204d7
--- /dev/null
+++ b/src/kitemviews/kitemliststyleoption.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTSTYLEOPTION_H
+#define KITEMLISTSTYLEOPTION_H
+
+#include <libdolphin_export.h>
+
+#include <QFont>
+#include <QStyleOption>
+
+class LIBDOLPHINPRIVATE_EXPORT KItemListStyleOption : public QStyleOption
+{
+public:
+ KItemListStyleOption();
+ KItemListStyleOption(const KItemListStyleOption& other);
+ virtual ~KItemListStyleOption();
+
+ int margin;
+ int iconSize;
+ QFont font;
+};
+#endif
+
+
diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp
new file mode 100644
index 000000000..a0897420c
--- /dev/null
+++ b/src/kitemviews/kitemlistview.cpp
@@ -0,0 +1,1124 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistview.h"
+
+#include "kitemlistcontroller.h"
+#include "kitemlistgroupheader.h"
+#include "kitemlistselectionmanager.h"
+#include "kitemlistsizehintresolver_p.h"
+#include "kitemlistviewlayouter_p.h"
+#include "kitemlistviewanimation_p.h"
+#include "kitemlistwidget.h"
+
+#include <KDebug>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPropertyAnimation>
+#include <QStyle>
+#include <QTimer>
+
+KItemListView::KItemListView(QGraphicsWidget* parent) :
+ QGraphicsWidget(parent),
+ m_grouped(false),
+ m_activeTransactions(0),
+ m_itemSize(),
+ m_controller(0),
+ m_model(0),
+ m_visibleRoles(),
+ m_visibleRolesSizes(),
+ m_widgetCreator(0),
+ m_groupHeaderCreator(0),
+ m_styleOption(),
+ m_visibleItems(),
+ m_visibleGroups(),
+ m_sizeHintResolver(0),
+ m_layouter(0),
+ m_animation(0),
+ m_layoutTimer(0),
+ m_oldOffset(0),
+ m_oldMaximumOffset(0)
+{
+ setAcceptHoverEvents(true);
+
+ m_sizeHintResolver = new KItemListSizeHintResolver(this);
+
+ m_layouter = new KItemListViewLayouter(this);
+ m_layouter->setSizeHintResolver(m_sizeHintResolver);
+
+ m_animation = new KItemListViewAnimation(this);
+ connect(m_animation, SIGNAL(finished(QGraphicsWidget*, KItemListViewAnimation::AnimationType)),
+ this, SLOT(slotAnimationFinished(QGraphicsWidget*, KItemListViewAnimation::AnimationType)));
+
+ m_layoutTimer = new QTimer(this);
+ m_layoutTimer->setInterval(300);
+ m_layoutTimer->setSingleShot(true);
+ connect(m_layoutTimer, SIGNAL(timeout()), this, SLOT(slotLayoutTimerFinished()));
+}
+
+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();
+ updateLayout();
+ onScrollOrientationChanged(orientation, previousOrientation);
+}
+
+Qt::Orientation KItemListView::scrollOrientation() const
+{
+ return m_layouter->scrollOrientation();
+}
+
+void KItemListView::setItemSize(const QSizeF& itemSize)
+{
+ const QSizeF previousSize = m_itemSize;
+ if (itemSize == previousSize) {
+ return;
+ }
+
+ m_itemSize = itemSize;
+
+ if (!markVisibleRolesSizesAsDirty()) {
+ if (itemSize.width() < previousSize.width() || itemSize.height() < previousSize.height()) {
+ prepareLayoutForIncreasedItemCount(itemSize, ItemSize);
+ } else {
+ m_layouter->setItemSize(itemSize);
+ }
+ }
+
+ m_sizeHintResolver->clearCache();
+ updateLayout();
+ onItemSizeChanged(itemSize, previousSize);
+}
+
+QSizeF KItemListView::itemSize() const
+{
+ return m_itemSize;
+}
+
+void KItemListView::setOffset(qreal offset)
+{
+ if (offset < 0) {
+ offset = 0;
+ }
+
+ const qreal previousOffset = m_layouter->offset();
+ if (offset == previousOffset) {
+ return;
+ }
+
+ m_layouter->setOffset(offset);
+ m_animation->setOffset(offset);
+ if (!m_layoutTimer->isActive()) {
+ doLayout(NoAnimation, 0, 0);
+ update();
+ }
+ onOffsetChanged(offset, previousOffset);
+}
+
+qreal KItemListView::offset() const
+{
+ return m_layouter->offset();
+}
+
+qreal KItemListView::maximumOffset() const
+{
+ return m_layouter->maximumOffset();
+}
+
+void KItemListView::setVisibleRoles(const QHash<QByteArray, int>& roles)
+{
+ const QHash<QByteArray, int> previousRoles = m_visibleRoles;
+ m_visibleRoles = roles;
+
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ KItemListWidget* widget = it.value();
+ widget->setVisibleRoles(roles);
+ widget->setVisibleRolesSizes(m_visibleRolesSizes);
+ }
+
+ m_sizeHintResolver->clearCache();
+ m_layouter->markAsDirty();
+ onVisibleRolesChanged(roles, previousRoles);
+
+ markVisibleRolesSizesAsDirty();
+ updateLayout();
+}
+
+QHash<QByteArray, int> KItemListView::visibleRoles() const
+{
+ return m_visibleRoles;
+}
+
+KItemListController* KItemListView::controller() const
+{
+ return m_controller;
+}
+
+KItemModelBase* KItemListView::model() const
+{
+ return m_model;
+}
+
+void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator)
+{
+ m_widgetCreator = widgetCreator;
+}
+
+KItemListWidgetCreatorBase* KItemListView::widgetCreator() const
+{
+ return m_widgetCreator;
+}
+
+void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator)
+{
+ m_groupHeaderCreator = groupHeaderCreator;
+}
+
+KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const
+{
+ return m_groupHeaderCreator;
+}
+
+void KItemListView::setStyleOption(const KItemListStyleOption& option)
+{
+ const KItemListStyleOption previousOption = m_styleOption;
+ m_styleOption = option;
+
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ it.value()->setStyleOption(option);
+ }
+
+ m_sizeHintResolver->clearCache();
+ updateLayout();
+ onStyleOptionChanged(option, previousOption);
+}
+
+const KItemListStyleOption& KItemListView::styleOption() const
+{
+ return m_styleOption;
+}
+
+void KItemListView::setGeometry(const QRectF& rect)
+{
+ QGraphicsWidget::setGeometry(rect);
+ if (!m_model) {
+ return;
+ }
+
+ if (m_itemSize.isEmpty()) {
+ m_layouter->setItemSize(QSizeF());
+ }
+
+ if (m_model->count() > 0) {
+ prepareLayoutForIncreasedItemCount(rect.size(), LayouterSize);
+ } else {
+ m_layouter->setSize(rect.size());
+ }
+
+ m_layoutTimer->start();
+}
+
+int KItemListView::itemAt(const QPointF& pos) const
+{
+ if (!m_model) {
+ return -1;
+ }
+
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+
+ const KItemListWidget* widget = it.value();
+ const QPointF mappedPos = widget->mapFromItem(this, pos);
+ if (widget->contains(mappedPos)) {
+ return it.key();
+ }
+ }
+
+ return -1;
+}
+
+bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
+{
+ Q_UNUSED(index);
+ Q_UNUSED(pos);
+ return false;
+}
+
+bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const
+{
+ const KItemListWidget* widget = m_visibleItems.value(index);
+ if (widget) {
+ const QRectF expansionToggleRect = widget->expansionToggleRect();
+ if (!expansionToggleRect.isEmpty()) {
+ const QPointF mappedPos = widget->mapFromItem(this, pos);
+ return expansionToggleRect.contains(mappedPos);
+ }
+ }
+ return false;
+}
+
+int KItemListView::firstVisibleIndex() const
+{
+ return m_layouter->firstVisibleIndex();
+}
+
+int KItemListView::lastVisibleIndex() const
+{
+ return m_layouter->lastVisibleIndex();
+}
+
+QSizeF KItemListView::itemSizeHint(int index) const
+{
+ Q_UNUSED(index);
+ return itemSize();
+}
+
+QHash<QByteArray, QSizeF> KItemListView::visibleRoleSizes() const
+{
+ return QHash<QByteArray, QSizeF>();
+}
+
+void KItemListView::beginTransaction()
+{
+ ++m_activeTransactions;
+ if (m_activeTransactions == 1) {
+ onTransactionBegin();
+ }
+}
+
+void KItemListView::endTransaction()
+{
+ --m_activeTransactions;
+ if (m_activeTransactions < 0) {
+ m_activeTransactions = 0;
+ kWarning() << "Mismatch between beginTransaction()/endTransaction()";
+ }
+
+ if (m_activeTransactions == 0) {
+ onTransactionEnd();
+ updateLayout();
+ }
+}
+
+bool KItemListView::isTransactionActive() const
+{
+ return m_activeTransactions > 0;
+}
+
+void KItemListView::initializeItemListWidget(KItemListWidget* item)
+{
+ Q_UNUSED(item);
+}
+
+void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemListView::onOffsetChanged(qreal current, qreal previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemListView::onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemListView::onTransactionBegin()
+{
+}
+
+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())) {
+ event->accept();
+ return true;
+ }
+ return QGraphicsWidget::event(event);
+}
+
+void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+ event->accept();
+}
+
+void KItemListView::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
+{
+ if (!m_model) {
+ return;
+ }
+
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+
+ KItemListWidget* widget = it.value();
+ KItemListStyleOption styleOption = widget->styleOption();
+ const QPointF mappedPos = widget->mapFromItem(this, event->pos());
+
+ const bool hovered = widget->contains(mappedPos) &&
+ !widget->expansionToggleRect().contains(mappedPos) &&
+ !widget->selectionToggleRect().contains(mappedPos);
+ if (hovered) {
+ if (!(styleOption.state & QStyle::State_MouseOver)) {
+ styleOption.state |= QStyle::State_MouseOver;
+ widget->setStyleOption(styleOption);
+ }
+ } else if (styleOption.state & QStyle::State_MouseOver) {
+ styleOption.state &= ~QStyle::State_MouseOver;
+ widget->setStyleOption(styleOption);
+ }
+ }
+}
+
+void KItemListView::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
+{
+ Q_UNUSED(event);
+
+ if (!m_model) {
+ return;
+ }
+
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+
+ KItemListWidget* widget = it.value();
+ KItemListStyleOption styleOption = widget->styleOption();
+ if (styleOption.state & QStyle::State_MouseOver) {
+ styleOption.state &= ~QStyle::State_MouseOver;
+ widget->setStyleOption(styleOption);
+ }
+ }
+}
+
+QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
+{
+ return m_visibleItems.values();
+}
+
+void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
+{
+ markVisibleRolesSizesAsDirty();
+
+ const bool hasMultipleRanges = (itemRanges.count() > 1);
+ if (hasMultipleRanges) {
+ beginTransaction();
+ }
+
+ foreach (const KItemRange& range, itemRanges) {
+ const int index = range.index;
+ const int count = range.count;
+ if (index < 0 || count <= 0) {
+ kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
+ continue;
+ }
+
+ m_sizeHintResolver->itemsInserted(index, count);
+
+ // Determine which visible items must be moved
+ QList<int> itemsToMove;
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ const int visibleItemIndex = it.key();
+ if (visibleItemIndex >= index) {
+ itemsToMove.append(visibleItemIndex);
+ }
+ }
+
+ // Update the indexes of all KItemListWidget instances that are located
+ // after the inserted items. It is important to adjust the indexes in the order
+ // from the highest index to the lowest index to prevent overlaps when setting the new index.
+ qSort(itemsToMove);
+ for (int i = itemsToMove.count() - 1; i >= 0; --i) {
+ KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]);
+ Q_ASSERT(widget);
+ setWidgetIndex(widget, widget->index() + count);
+ }
+
+ m_layouter->markAsDirty();
+ if (m_model->count() == count && maximumOffset() > size().height()) {
+ kDebug() << "Scrollbar required, skipping layout";
+ const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+ QSizeF layouterSize = m_layouter->size();
+ if (scrollOrientation() == Qt::Vertical) {
+ layouterSize.rwidth() -= scrollBarExtent;
+ } else {
+ layouterSize.rheight() -= scrollBarExtent;
+ }
+ m_layouter->setSize(layouterSize);
+ }
+
+ if (!hasMultipleRanges) {
+ doLayout(Animation, index, count);
+ update();
+ }
+
+ if (m_controller) {
+ KItemListSelectionManager* selectionManager = m_controller->selectionManager();
+ const int current = selectionManager->currentItem();
+ if (current < 0) {
+ selectionManager->setCurrentItem(0);
+ } else if (current >= index) {
+ selectionManager->setCurrentItem(current + count);
+ }
+ }
+ }
+
+ if (hasMultipleRanges) {
+ endTransaction();
+ }
+}
+
+void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
+{
+ markVisibleRolesSizesAsDirty();
+
+ const bool hasMultipleRanges = (itemRanges.count() > 1);
+ if (hasMultipleRanges) {
+ beginTransaction();
+ }
+
+ for (int i = itemRanges.count() - 1; i >= 0; --i) {
+ const KItemRange& range = itemRanges.at(i);
+ const int index = range.index;
+ const int count = range.count;
+ if (index < 0 || count <= 0) {
+ kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
+ continue;
+ }
+
+ m_sizeHintResolver->itemsRemoved(index, count);
+
+ const int firstRemovedIndex = index;
+ const int lastRemovedIndex = index + count - 1;
+ const int lastIndex = m_model->count() + count - 1;
+
+ // Remove all KItemListWidget instances that got deleted
+ for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
+ KItemListWidget* widget = m_visibleItems.value(i);
+ if (!widget) {
+ continue;
+ }
+
+ m_animation->stop(widget);
+ // Stopping the animation might lead to recycling the widget if
+ // it is invisible (see slotAnimationFinished()).
+ // Check again whether it is still visible:
+ if (!m_visibleItems.contains(i)) {
+ continue;
+ }
+
+ if (m_model->count() == 0) {
+ // For performance reasons no animation is done when all items have
+ // been removed.
+ recycleWidget(widget);
+ } else {
+ // Animate the removing of the items. Special case: When removing an item there
+ // is no valid model index available anymore. For the
+ // remove-animation the item gets removed from m_visibleItems but the widget
+ // will stay alive until the animation has been finished and will
+ // be recycled (deleted) in KItemListView::slotAnimationFinished().
+ m_visibleItems.remove(i);
+ widget->setIndex(-1);
+ m_animation->start(widget, KItemListViewAnimation::DeleteAnimation);
+ }
+ }
+
+ // Update the indexes of all KItemListWidget instances that are located
+ // after the deleted items
+ for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) {
+ KItemListWidget* widget = m_visibleItems.value(i);
+ if (widget) {
+ const int newIndex = i - count;
+ setWidgetIndex(widget, newIndex);
+ }
+ }
+
+ m_layouter->markAsDirty();
+ if (!hasMultipleRanges) {
+ doLayout(Animation, index, -count);
+ update();
+ }
+
+ /*KItemListSelectionManager* selectionManager = m_controller->selectionManager();
+ const int current = selectionManager->currentItem();
+ if (count() <= 0) {
+ selectionManager->setCurrentItem(-1);
+ } else if (current >= index) {
+ selectionManager->setCurrentItem(current + count);
+ }*/
+ }
+
+ if (hasMultipleRanges) {
+ endTransaction();
+ }
+}
+
+void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
+ const QSet<QByteArray>& roles)
+{
+ foreach (const KItemRange& itemRange, itemRanges) {
+ const int index = itemRange.index;
+ const int count = itemRange.count;
+
+ m_sizeHintResolver->itemsChanged(index, count, roles);
+
+ const int lastIndex = index + count - 1;
+ for (int i = index; i <= lastIndex; ++i) {
+ KItemListWidget* widget = m_visibleItems.value(i);
+ if (widget) {
+ widget->setData(m_model->data(i), roles);
+ }
+ }
+ }
+}
+
+void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
+ KItemListViewAnimation::AnimationType type)
+{
+ KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
+ Q_ASSERT(itemListWidget);
+
+ switch (type) {
+ case KItemListViewAnimation::DeleteAnimation: {
+ // As we recycle the widget in this case it is important to assure that no
+ // other animation has been started. This is a convention in KItemListView and
+ // not a requirement defined by KItemListViewAnimation.
+ Q_ASSERT(!m_animation->isStarted(itemListWidget));
+
+ // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
+ // by m_visibleWidgets and must be deleted manually after the animation has
+ // been finished.
+ KItemListGroupHeader* header = m_visibleGroups.value(itemListWidget);
+ if (header) {
+ m_groupHeaderCreator->recycle(header);
+ m_visibleGroups.remove(itemListWidget);
+ }
+ m_widgetCreator->recycle(itemListWidget);
+ break;
+ }
+
+ case KItemListViewAnimation::CreateAnimation:
+ case KItemListViewAnimation::MovingAnimation:
+ case KItemListViewAnimation::ResizeAnimation: {
+ const int index = itemListWidget->index();
+ const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
+ (index > m_layouter->lastVisibleIndex());
+ if (invisible && !m_animation->isStarted(itemListWidget)) {
+ recycleWidget(itemListWidget);
+ }
+ break;
+ }
+
+ default: break;
+ }
+}
+
+void KItemListView::slotLayoutTimerFinished()
+{
+ m_layouter->setSize(geometry().size());
+ doLayout(Animation, 0, 0);
+}
+
+void KItemListView::setController(KItemListController* controller)
+{
+ if (m_controller != controller) {
+ KItemListController* previous = m_controller;
+ m_controller = controller;
+ onControllerChanged(controller, previous);
+ }
+}
+
+void KItemListView::setModel(KItemModelBase* model)
+{
+ if (m_model == model) {
+ return;
+ }
+
+ KItemModelBase* previous = m_model;
+
+ if (m_model) {
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
+ this, SLOT(slotItemsInserted(KItemRangeList)));
+ disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
+ this, SLOT(slotItemsRemoved(KItemRangeList)));
+ }
+
+ m_model = model;
+ m_layouter->setModel(model);
+ m_grouped = !model->groupRole().isEmpty();
+
+ if (m_model) {
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
+ this, SLOT(slotItemsInserted(KItemRangeList)));
+ connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
+ this, SLOT(slotItemsRemoved(KItemRangeList)));
+ }
+
+ onModelChanged(model, previous);
+}
+
+void KItemListView::updateLayout()
+{
+ doLayout(Animation, 0, 0);
+ update();
+}
+
+void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
+{
+ if (m_layoutTimer->isActive()) {
+ kDebug() << "Stopping layout timer, synchronous layout requested";
+ m_layoutTimer->stop();
+ }
+
+ if (m_model->count() < 0 || m_activeTransactions > 0) {
+ return;
+ }
+
+ applyDynamicItemSize();
+
+ const int firstVisibleIndex = m_layouter->firstVisibleIndex();
+ const int lastVisibleIndex = m_layouter->lastVisibleIndex();
+ if (firstVisibleIndex < 0) {
+ emitOffsetChanges();
+ return;
+ }
+
+ // Do a sanity check of the offset-property: When properties of the itemlist-view have been changed
+ // it might be possible that the maximum offset got changed too. Assure that the full visible range
+ // is still shown if the maximum offset got decreased.
+ const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height();
+ const qreal maxOffsetToShowFullRange = maximumOffset() - visibleOffsetRange;
+ if (offset() > maxOffsetToShowFullRange) {
+ m_layouter->setOffset(qMax(qreal(0), maxOffsetToShowFullRange));
+ }
+
+ // Determine all items that are completely invisible and might be
+ // reused for items that just got (at least partly) visible.
+ // Items that do e.g. an animated moving of their position are not
+ // marked as invisible: This assures that a scrolling inside the view
+ // can be done without breaking an animation.
+ QList<int> reusableItems;
+ QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ KItemListWidget* widget = it.value();
+ const int index = widget->index();
+ const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex);
+ if (invisible && !m_animation->isStarted(widget)) {
+ widget->setVisible(false);
+ reusableItems.append(index);
+ }
+ }
+
+ // Assure that for each visible item a KItemListWidget is available. KItemListWidget
+ // instances from invisible items are reused. If no reusable items are
+ // found then new KItemListWidget instances get created.
+ const bool animate = (hint == Animation);
+ for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
+ bool applyNewPos = true;
+ bool wasHidden = false;
+
+ const QRectF itemBounds = m_layouter->itemBoundingRect(i);
+ const QPointF newPos = itemBounds.topLeft();
+ KItemListWidget* widget = m_visibleItems.value(i);
+ if (!widget) {
+ wasHidden = true;
+ if (!reusableItems.isEmpty()) {
+ // Reuse a KItemListWidget instance from an invisible item
+ const int oldIndex = reusableItems.takeLast();
+ widget = m_visibleItems.value(oldIndex);
+ setWidgetIndex(widget, i);
+ } else {
+ // No reusable KItemListWidget instance is available, create a new one
+ widget = createWidget(i);
+ }
+ widget->resize(itemBounds.size());
+
+ if (animate && changedCount < 0) {
+ // Items have been deleted, move the created item to the
+ // imaginary old position.
+ const QRectF itemBoundingRect = m_layouter->itemBoundingRect(i - changedCount);
+ if (itemBoundingRect.isEmpty()) {
+ const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical)
+ ? QPointF(0, size().height()) : QPointF(size().width(), 0);
+ widget->setPos(invisibleOldPos);
+ } else {
+ widget->setPos(itemBoundingRect.topLeft());
+ }
+ applyNewPos = false;
+ }
+ } else if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
+ applyNewPos = false;
+ }
+
+ if (animate) {
+ 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.
+ m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
+ applyNewPos = false;
+ } else if (itemsInserted && i >= changedIndex) {
+ // The item is located after the first inserted item
+ if (i <= changedIndex + changedCount - 1) {
+ // The item is an inserted item. Animate the appearing of the item.
+ // For performance reasons no animation is done when changedCount is equal
+ // to all available items.
+ if (changedCount < m_model->count()) {
+ m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
+ }
+ } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
+ // 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.
+ m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
+ applyNewPos = false;
+ }
+ } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
+ // The size of the view might have been changed. Animate the moving of the position.
+ m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
+ applyNewPos = false;
+ }
+ }
+
+ if (applyNewPos) {
+ widget->setPos(newPos);
+ }
+
+ Q_ASSERT(widget->index() == i);
+ widget->setVisible(true);
+
+ if (widget->size() != itemBounds.size()) {
+ m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size());
+ }
+ }
+
+ // Delete invisible KItemListWidget instances that have not been reused
+ foreach (int index, reusableItems) {
+ recycleWidget(m_visibleItems.value(index));
+ }
+
+ emitOffsetChanges();
+}
+
+void KItemListView::emitOffsetChanges()
+{
+ const int newOffset = m_layouter->offset();
+ if (m_oldOffset != newOffset) {
+ emit offsetChanged(newOffset, m_oldOffset);
+ m_oldOffset = newOffset;
+ }
+
+ const int newMaximumOffset = m_layouter->maximumOffset();
+ if (m_oldMaximumOffset != newMaximumOffset) {
+ emit maximumOffsetChanged(newMaximumOffset, m_oldMaximumOffset);
+ m_oldMaximumOffset = newMaximumOffset;
+ }
+}
+
+KItemListWidget* KItemListView::createWidget(int index)
+{
+ KItemListWidget* widget = m_widgetCreator->create(this);
+ widget->setVisibleRoles(m_visibleRoles);
+ widget->setVisibleRolesSizes(m_visibleRolesSizes);
+ widget->setStyleOption(m_styleOption);
+ widget->setIndex(index);
+ widget->setData(m_model->data(index));
+ m_visibleItems.insert(index, widget);
+
+ if (m_grouped) {
+ if (m_layouter->isFirstGroupItem(index)) {
+ KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
+ header->setPos(0, -50);
+ header->resize(50, 50);
+ m_visibleGroups.insert(widget, header);
+ }
+ }
+
+ initializeItemListWidget(widget);
+ return widget;
+}
+
+void KItemListView::recycleWidget(KItemListWidget* widget)
+{
+ if (m_grouped) {
+ KItemListGroupHeader* header = m_visibleGroups.value(widget);
+ if (header) {
+ m_groupHeaderCreator->recycle(header);
+ m_visibleGroups.remove(widget);
+ }
+ }
+
+ m_visibleItems.remove(widget->index());
+ m_widgetCreator->recycle(widget);
+}
+
+void KItemListView::setWidgetIndex(KItemListWidget* widget, int index)
+{
+ if (m_grouped) {
+ bool createHeader = m_layouter->isFirstGroupItem(index);
+ KItemListGroupHeader* header = m_visibleGroups.value(widget);
+ if (header) {
+ if (createHeader) {
+ createHeader = false;
+ } else {
+ m_groupHeaderCreator->recycle(header);
+ m_visibleGroups.remove(widget);
+ }
+ }
+
+ if (createHeader) {
+ KItemListGroupHeader* header = m_groupHeaderCreator->create(widget);
+ header->setPos(0, -50);
+ header->resize(50, 50);
+ m_visibleGroups.insert(widget, header);
+ }
+ }
+
+ const int oldIndex = widget->index();
+ m_visibleItems.remove(oldIndex);
+ widget->setVisibleRoles(m_visibleRoles);
+ widget->setVisibleRolesSizes(m_visibleRolesSizes);
+ widget->setStyleOption(m_styleOption);
+ widget->setIndex(index);
+ widget->setData(m_model->data(index));
+ m_visibleItems.insert(index, widget);
+
+ initializeItemListWidget(widget);
+}
+
+void KItemListView::prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType)
+{
+ // Calculate the first visible index and last visible index for the current size
+ const int currentFirst = m_layouter->firstVisibleIndex();
+ const int currentLast = m_layouter->lastVisibleIndex();
+
+ const QSizeF currentSize = (sizeType == LayouterSize) ? m_layouter->size() : m_layouter->itemSize();
+
+ // Calculate the first visible index and last visible index for the new size
+ setLayouterSize(size, sizeType);
+ const int newFirst = m_layouter->firstVisibleIndex();
+ const int newLast = m_layouter->lastVisibleIndex();
+
+ if ((currentFirst != newFirst) || (currentLast != newLast)) {
+ // At least one index has been changed. Assure that widgets for all possible
+ // visible items get created so that a move-animation can be started later.
+ const int maxVisibleItems = m_layouter->maximumVisibleItems();
+ int minFirst = qMin(newFirst, currentFirst);
+ const int maxLast = qMax(newLast, currentLast);
+
+ if (maxLast - minFirst + 1 < maxVisibleItems) {
+ // Increasing the size might result in a smaller KItemListView::offset().
+ // Decrease the first visible index in a way that at least the maximum
+ // visible items are shown.
+ minFirst = qMax(0, maxLast - maxVisibleItems + 1);
+ }
+
+ if (maxLast - minFirst > maxVisibleItems + maxVisibleItems / 2) {
+ // The creating of widgets is quite expensive. Assure that never more
+ // than 50 % of the maximum visible items get created for the animations.
+ return;
+ }
+
+ setLayouterSize(currentSize, sizeType);
+ for (int i = minFirst; i <= maxLast; ++i) {
+ if (!m_visibleItems.contains(i)) {
+ KItemListWidget* widget = createWidget(i);
+ const QPointF pos = m_layouter->itemBoundingRect(i).topLeft();
+ widget->setPos(pos);
+ }
+ }
+ setLayouterSize(size, sizeType);
+ }
+}
+
+void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType)
+{
+ switch (sizeType) {
+ case LayouterSize: m_layouter->setSize(size); break;
+ case ItemSize: m_layouter->setItemSize(size); break;
+ default: break;
+ }
+}
+
+bool KItemListView::markVisibleRolesSizesAsDirty()
+{
+ const bool dirty = m_itemSize.isEmpty();
+ if (dirty) {
+ m_visibleRolesSizes.clear();
+ m_layouter->setItemSize(QSizeF());
+ }
+ return dirty;
+}
+
+void KItemListView::applyDynamicItemSize()
+{
+ if (!m_itemSize.isEmpty()) {
+ return;
+ }
+
+ if (m_visibleRolesSizes.isEmpty()) {
+ m_visibleRolesSizes = visibleRoleSizes();
+ foreach (KItemListWidget* widget, visibleItemListWidgets()) {
+ widget->setVisibleRolesSizes(m_visibleRolesSizes);
+ }
+ }
+
+ if (m_layouter->itemSize().isEmpty()) {
+ qreal requiredWidth = 0;
+ qreal requiredHeight = 0;
+
+ QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
+ while (it.hasNext()) {
+ it.next();
+ const QSizeF& visibleRoleSize = it.value();
+ requiredWidth += visibleRoleSize.width();
+ requiredHeight += visibleRoleSize.height();
+ }
+
+ QSizeF dynamicItemSize = m_itemSize;
+ if (dynamicItemSize.width() <= 0) {
+ dynamicItemSize.setWidth(qMax(requiredWidth, size().width()));
+ }
+ if (dynamicItemSize.height() <= 0) {
+ dynamicItemSize.setHeight(qMax(requiredHeight, size().height()));
+ }
+
+ m_layouter->setItemSize(dynamicItemSize);
+ }
+}
+
+KItemListCreatorBase::~KItemListCreatorBase()
+{
+ qDeleteAll(m_recycleableWidgets);
+ qDeleteAll(m_createdWidgets);
+}
+
+void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget)
+{
+ m_createdWidgets.insert(widget);
+}
+
+void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
+{
+ Q_ASSERT(m_createdWidgets.contains(widget));
+ m_createdWidgets.remove(widget);
+
+ if (m_recycleableWidgets.count() < 100) {
+ m_recycleableWidgets.append(widget);
+ widget->setVisible(false);
+ } else {
+ delete widget;
+ }
+}
+
+QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget()
+{
+ if (m_recycleableWidgets.isEmpty()) {
+ return 0;
+ }
+
+ QGraphicsWidget* widget = m_recycleableWidgets.takeLast();
+ m_createdWidgets.insert(widget);
+ return widget;
+}
+
+KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
+{
+}
+
+void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget)
+{
+ widget->setOpacity(1.0);
+ pushRecycleableWidget(widget);
+}
+
+KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
+{
+}
+
+void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header)
+{
+ header->setOpacity(1.0);
+ pushRecycleableWidget(header);
+}
+
+#include "kitemlistview.moc"
diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h
new file mode 100644
index 000000000..829736a09
--- /dev/null
+++ b/src/kitemviews/kitemlistview.h
@@ -0,0 +1,360 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTVIEW_H
+#define KITEMLISTVIEW_H
+
+#include <libdolphin_export.h>
+
+#include <kitemviews/kitemliststyleoption.h>
+#include <kitemviews/kitemlistviewanimation_p.h>
+#include <kitemviews/kitemlistwidget.h>
+#include <kitemviews/kitemmodelbase.h>
+#include <QGraphicsWidget>
+
+class KItemListController;
+class KItemListWidgetCreatorBase;
+class KItemListGroupHeader;
+class KItemListGroupHeaderCreatorBase;
+class KItemListSizeHintResolver;
+class KItemListViewAnimation;
+class KItemListViewLayouter;
+class KItemListWidget;
+class KItemListViewCreatorBase;
+class QTimer;
+
+/**
+ * @brief Represents the view of an item-list.
+ *
+ * The view is responsible for showing the items of the model within
+ * a GraphicsItem. Each visible item is represented by a KItemListWidget.
+ *
+ * The created view must be applied to the KItemListController with
+ * KItemListController::setView(). For showing a custom model it is not
+ * mandatory to derive from KItemListView, all that is necessary is
+ * to set a widget-creator that is capable to create KItemListWidgets
+ * showing the model items. A widget-creator can be set with
+ * KItemListView::setWidgetCreator().
+ *
+ * @see KItemListWidget
+ * @see KItemModelBase
+ */
+class LIBDOLPHINPRIVATE_EXPORT KItemListView : public QGraphicsWidget
+{
+ Q_OBJECT
+
+public:
+ KItemListView(QGraphicsWidget* parent = 0);
+ virtual ~KItemListView();
+
+ void setScrollOrientation(Qt::Orientation orientation);
+ Qt::Orientation scrollOrientation() const;
+
+ void setItemSize(const QSizeF& size);
+ QSizeF itemSize() const;
+
+ // TODO: add note that offset is not checked against maximumOffset, only against 0.
+ void setOffset(qreal offset);
+ qreal offset() const;
+
+ qreal maximumOffset() const;
+
+ /**
+ * Sets the visible roles to \p roles. The integer-value defines
+ * the order of the visible role: Smaller values are ordered first.
+ */
+ void setVisibleRoles(const QHash<QByteArray, int>& roles);
+ QHash<QByteArray, int> visibleRoles() const;
+
+ /**
+ * @return Controller of the item-list. The controller gets
+ * initialized by KItemListController::setView() and will
+ * result in calling KItemListController::onControllerChanged().
+ */
+ KItemListController* controller() const;
+
+ /**
+ * @return Model of the item-list. The model gets
+ * initialized by KItemListController::setView() and will
+ * result in calling KItemListController::onModelChanged().
+ */
+ KItemModelBase* model() const;
+
+ /**
+ * Sets the creator that creates a widget showing the
+ * content of one model-item. Usually it is sufficient
+ * to implement a custom widget X derived from KItemListWidget and
+ * set the creator by:
+ * <code>
+ * itemListView->setWidgetCreator(new KItemListWidgetCreator<X>());
+ * </code>
+ * Note that the ownership of the widget creator is not transferred to
+ * the item-list view: One instance of a widget creator might get shared
+ * by several item-list view instances.
+ **/
+ void setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator);
+ KItemListWidgetCreatorBase* widgetCreator() const;
+
+ void setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator);
+ KItemListGroupHeaderCreatorBase* groupHeaderCreator() const;
+
+ void setStyleOption(const KItemListStyleOption& option);
+ const KItemListStyleOption& styleOption() const;
+
+ virtual void setGeometry(const QRectF& rect);
+
+ int itemAt(const QPointF& pos) const;
+ bool isAboveSelectionToggle(int index, const QPointF& pos) const;
+ bool isAboveExpansionToggle(int index, const QPointF& pos) const;
+
+ int firstVisibleIndex() const;
+ int lastVisibleIndex() const;
+
+ virtual QSizeF itemSizeHint(int index) const;
+ virtual QHash<QByteArray, QSizeF> visibleRoleSizes() const;
+
+ void beginTransaction();
+ void endTransaction();
+ bool isTransactionActive() const;
+
+signals:
+ void offsetChanged(int current, int previous);
+ void maximumOffsetChanged(int current, int previous);
+
+protected:
+ virtual void initializeItemListWidget(KItemListWidget* item);
+
+ virtual void onControllerChanged(KItemListController* current, KItemListController* previous);
+ virtual void onModelChanged(KItemModelBase* current, KItemModelBase* previous);
+
+ virtual void onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous);
+ virtual void onItemSizeChanged(const QSizeF& current, const QSizeF& previous);
+ virtual void onOffsetChanged(qreal current, qreal previous);
+ virtual void onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous);
+ virtual void onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
+
+ virtual void onTransactionBegin();
+ virtual void onTransactionEnd();
+
+ virtual bool event(QEvent* event);
+ virtual void mousePressEvent(QGraphicsSceneMouseEvent* event);
+ virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event);
+ virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event);
+
+ QList<KItemListWidget*> visibleItemListWidgets() const;
+
+protected slots:
+ virtual void slotItemsInserted(const KItemRangeList& itemRanges);
+ virtual void slotItemsRemoved(const KItemRangeList& itemRanges);
+ virtual void slotItemsChanged(const KItemRangeList& itemRanges,
+ const QSet<QByteArray>& roles);
+
+private slots:
+ void slotAnimationFinished(QGraphicsWidget* widget,
+ KItemListViewAnimation::AnimationType type);
+ void slotLayoutTimerFinished();
+
+private:
+ enum LayoutAnimationHint
+ {
+ NoAnimation,
+ Animation
+ };
+
+ enum SizeType
+ {
+ LayouterSize,
+ ItemSize
+ };
+
+ void setController(KItemListController* controller);
+ void setModel(KItemModelBase* model);
+
+ void updateLayout();
+ void doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount);
+ void doGroupHeadersLayout(LayoutAnimationHint hint, int changedIndex, int changedCount);
+ void emitOffsetChanges();
+
+ KItemListWidget* createWidget(int index);
+ void recycleWidget(KItemListWidget* widget);
+ void setWidgetIndex(KItemListWidget* widget, int index);
+
+ /**
+ * Helper method for setGeometry() and setItemSize(): Calling both methods might result
+ * in a changed number of visible items. To assure that currently invisible items can
+ * get animated from the old position to the new position prepareLayoutForIncreasedItemCount()
+ * takes care to create all item widgets that are visible with the old or the new size.
+ * @param size Size of the layouter or the item dependent on \p sizeType.
+ * @param sizeType LayouterSize: KItemListLayouter::setSize() is used.
+ * ItemSize: KItemListLayouter::setItemSize() is used.
+ */
+ void prepareLayoutForIncreasedItemCount(const QSizeF& size, SizeType sizeType);
+
+ /**
+ * Helper method for prepareLayoutForIncreasedItemCount().
+ */
+ void setLayouterSize(const QSizeF& size, SizeType sizeType);
+
+ /**
+ * Marks the visible roles as dirty so that they will get updated when doing the next
+ * layout. The visible roles will only get marked as dirty if an empty item-size is
+ * given.
+ * @return True if the visible roles have been marked as dirty.
+ */
+ bool markVisibleRolesSizesAsDirty();
+
+ /**
+ * Updates the m_visibleRoleSizes property and applies the dynamic
+ * size to the layouter.
+ */
+ void applyDynamicItemSize();
+
+private:
+ bool m_grouped;
+ int m_activeTransactions; // Counter for beginTransaction()/endTransaction()
+
+ QSizeF m_itemSize;
+ KItemListController* m_controller;
+ KItemModelBase* m_model;
+ QHash<QByteArray, int> m_visibleRoles;
+ QHash<QByteArray, QSizeF> m_visibleRolesSizes;
+ KItemListWidgetCreatorBase* m_widgetCreator;
+ KItemListGroupHeaderCreatorBase* m_groupHeaderCreator;
+ KItemListStyleOption m_styleOption;
+
+ QHash<int, KItemListWidget*> m_visibleItems;
+ QHash<KItemListWidget*, KItemListGroupHeader*> m_visibleGroups;
+
+ int m_scrollBarExtent;
+ KItemListSizeHintResolver* m_sizeHintResolver;
+ KItemListViewLayouter* m_layouter;
+ KItemListViewAnimation* m_animation;
+
+ QTimer* m_layoutTimer; // Triggers an asynchronous doLayout() call.
+ int m_oldOffset;
+ int m_oldMaximumOffset;
+
+ friend class KItemListController;
+};
+
+/**
+ * Allows to do a fast logical creation and deletion of QGraphicsWidgets
+ * by recycling existing QGraphicsWidgets instances. Is used by
+ * KItemListWidgetCreatorBase and KItemListGroupHeaderCreatorBase.
+ * @internal
+ */
+class LIBDOLPHINPRIVATE_EXPORT KItemListCreatorBase
+{
+public:
+ virtual ~KItemListCreatorBase();
+
+protected:
+ void addCreatedWidget(QGraphicsWidget* widget);
+ void pushRecycleableWidget(QGraphicsWidget* widget);
+ QGraphicsWidget* popRecycleableWidget();
+
+private:
+ QSet<QGraphicsWidget*> m_createdWidgets;
+ QList<QGraphicsWidget*> m_recycleableWidgets;
+};
+
+/**
+ * @brief Base class for creating KItemListWidgets.
+ *
+ * It is recommended that applications simply use the KItemListWidgetCreator-template class.
+ * For a custom implementation the methods create() and recyle() must be reimplemented.
+ * The intention of the widget creator is to prevent repetitive and expensive instantiations and
+ * deletions of KItemListWidgets by recycling existing widget instances.
+ */
+class LIBDOLPHINPRIVATE_EXPORT KItemListWidgetCreatorBase : public KItemListCreatorBase
+{
+public:
+ virtual ~KItemListWidgetCreatorBase();
+ virtual KItemListWidget* create(KItemListView* view) = 0;
+ virtual void recycle(KItemListWidget* widget);
+};
+
+template <class T>
+class LIBDOLPHINPRIVATE_EXPORT KItemListWidgetCreator : public KItemListWidgetCreatorBase
+{
+public:
+ virtual ~KItemListWidgetCreator();
+ virtual KItemListWidget* create(KItemListView* view);
+};
+
+template <class T>
+KItemListWidgetCreator<T>::~KItemListWidgetCreator()
+{
+}
+
+template <class T>
+KItemListWidget* KItemListWidgetCreator<T>::create(KItemListView* view)
+{
+ KItemListWidget* widget = static_cast<KItemListWidget*>(popRecycleableWidget());
+ if (!widget) {
+ widget = new T(view);
+ addCreatedWidget(widget);
+ }
+ return widget;
+}
+
+/**
+ * @brief Base class for creating KItemListGroupHeaders.
+ *
+ * It is recommended that applications simply use the KItemListGroupHeaderCreator-template class.
+ * For a custom implementation the methods create() and recyle() must be reimplemented.
+ * The intention of the group-header creator is to prevent repetitive and expensive instantiations and
+ * deletions of KItemListGroupHeaders by recycling existing header instances.
+ */
+class LIBDOLPHINPRIVATE_EXPORT KItemListGroupHeaderCreatorBase : public KItemListCreatorBase
+{
+public:
+ virtual ~KItemListGroupHeaderCreatorBase();
+ virtual KItemListGroupHeader* create(QGraphicsWidget* parent) = 0;
+ virtual void recycle(KItemListGroupHeader* header);
+};
+
+template <class T>
+class LIBDOLPHINPRIVATE_EXPORT KItemListGroupHeaderCreator : public KItemListGroupHeaderCreatorBase
+{
+public:
+ virtual ~KItemListGroupHeaderCreator();
+ virtual KItemListGroupHeader* create(QGraphicsWidget* parent);
+};
+
+template <class T>
+KItemListGroupHeaderCreator<T>::~KItemListGroupHeaderCreator()
+{
+}
+
+template <class T>
+KItemListGroupHeader* KItemListGroupHeaderCreator<T>::create(QGraphicsWidget* parent)
+{
+ KItemListGroupHeader* widget = static_cast<KItemListGroupHeader*>(popRecycleableWidget());
+ if (!widget) {
+ widget = new T(parent);
+ addCreatedWidget(widget);
+ }
+ return widget;
+}
+
+#endif
diff --git a/src/kitemviews/kitemlistviewanimation.cpp b/src/kitemviews/kitemlistviewanimation.cpp
new file mode 100644
index 000000000..449d557f9
--- /dev/null
+++ b/src/kitemviews/kitemlistviewanimation.cpp
@@ -0,0 +1,241 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistviewanimation_p.h"
+
+#include "kitemlistview.h"
+
+#include <KDebug>
+
+#include <QGraphicsWidget>
+#include <QPropertyAnimation>
+
+KItemListViewAnimation::KItemListViewAnimation(QObject* parent) :
+ QObject(parent),
+ m_scrollOrientation(Qt::Vertical),
+ m_offset(0),
+ m_animation()
+{
+}
+
+KItemListViewAnimation::~KItemListViewAnimation()
+{
+ for (int type = 0; type < AnimationTypeCount; ++type) {
+ qDeleteAll(m_animation[type]);
+ }
+}
+
+void KItemListViewAnimation::setScrollOrientation(Qt::Orientation orientation)
+{
+ m_scrollOrientation = orientation;
+}
+
+Qt::Orientation KItemListViewAnimation::scrollOrientation() const
+{
+ return m_scrollOrientation;
+}
+
+void KItemListViewAnimation::setOffset(qreal offset)
+{
+ const qreal diff = m_offset - offset;
+ m_offset = offset;
+
+ // The change of the offset requires that the position of all
+ // animated QGraphicsWidgets get adjusted. An exception is made
+ // for the delete animation that should just fade away on the
+ // existing position.
+ for (int type = 0; type < AnimationTypeCount; ++type) {
+ if (type == DeleteAnimation) {
+ continue;
+ }
+
+ QHashIterator<QGraphicsWidget*, QPropertyAnimation*> it(m_animation[type]);
+ while (it.hasNext()) {
+ it.next();
+
+ QGraphicsWidget* widget = it.key();
+ QPropertyAnimation* propertyAnim = it.value();
+
+ QPointF currentPos = widget->pos();
+ if (m_scrollOrientation == Qt::Vertical) {
+ currentPos.ry() += diff;
+ } else {
+ currentPos.rx() += diff;
+ }
+
+ if (type == MovingAnimation) {
+ // Stop the animation, calculate the moved start- and end-value
+ // and restart the animation for the remaining duration.
+ const int remainingDuration = propertyAnim->duration()
+ - propertyAnim->currentTime();
+
+ const bool block = propertyAnim->signalsBlocked();
+ propertyAnim->blockSignals(true);
+ propertyAnim->stop();
+
+ QPointF endPos = propertyAnim->endValue().toPointF();
+ if (m_scrollOrientation == Qt::Vertical) {
+ endPos.ry() += diff;
+ } else {
+ endPos.rx() += diff;
+ }
+
+ propertyAnim->setDuration(remainingDuration);
+ propertyAnim->setStartValue(currentPos);
+ propertyAnim->setEndValue(endPos);
+ propertyAnim->start();
+ propertyAnim->blockSignals(block);
+ } else {
+ widget->setPos(currentPos);
+ }
+ }
+ }
+}
+
+qreal KItemListViewAnimation::offset() const
+{
+ return m_offset;
+}
+
+void KItemListViewAnimation::start(QGraphicsWidget* widget, AnimationType type, const QVariant& endValue)
+{
+ stop(widget, type);
+
+ const int duration = 200;
+ QPropertyAnimation* propertyAnim = 0;
+
+ switch (type) {
+ case MovingAnimation: {
+ const QPointF newPos = endValue.toPointF();
+ if (newPos == widget->pos()) {
+ return;
+ }
+
+ propertyAnim = new QPropertyAnimation(widget, "pos");
+ propertyAnim->setDuration(duration);
+ propertyAnim->setEndValue(newPos);
+ break;
+ }
+
+ case CreateAnimation: {
+ propertyAnim = new QPropertyAnimation(widget, "opacity");
+ propertyAnim->setEasingCurve(QEasingCurve::InQuart);
+ propertyAnim->setDuration(duration);
+ propertyAnim->setStartValue(0.0);
+ propertyAnim->setEndValue(1.0);
+ break;
+ }
+
+ case DeleteAnimation: {
+ propertyAnim = new QPropertyAnimation(widget, "opacity");
+ propertyAnim->setEasingCurve(QEasingCurve::OutQuart);
+ propertyAnim->setDuration(duration);
+ propertyAnim->setStartValue(1.0);
+ propertyAnim->setEndValue(0.0);
+ break;
+ }
+
+ case ResizeAnimation: {
+ const QSizeF newSize = endValue.toSizeF();
+ if (newSize == widget->size()) {
+ return;
+ }
+
+ propertyAnim = new QPropertyAnimation(widget, "size");
+ propertyAnim->setDuration(duration);
+ propertyAnim->setEndValue(newSize);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ Q_ASSERT(propertyAnim);
+ connect(propertyAnim, SIGNAL(finished()), this, SLOT(slotFinished()));
+ m_animation[type].insert(widget, propertyAnim);
+
+ propertyAnim->start();
+}
+
+void KItemListViewAnimation::stop(QGraphicsWidget* widget, AnimationType type)
+{
+ QPropertyAnimation* propertyAnim = m_animation[type].value(widget);
+ if (propertyAnim) {
+ propertyAnim->stop();
+
+ switch (type) {
+ case MovingAnimation: break;
+ case CreateAnimation: widget->setOpacity(1.0); break;
+ case DeleteAnimation: widget->setOpacity(0.0); break;
+ case ResizeAnimation: break;
+ default: break;
+ }
+
+ m_animation[type].remove(widget);
+ delete propertyAnim;
+
+ emit finished(widget, type);
+ }
+}
+
+void KItemListViewAnimation::stop(QGraphicsWidget* widget)
+{
+ for (int type = 0; type < AnimationTypeCount; ++type) {
+ stop(widget, static_cast<AnimationType>(type));
+ }
+}
+
+bool KItemListViewAnimation::isStarted(QGraphicsWidget *widget, AnimationType type) const
+{
+ return m_animation[type].value(widget);
+}
+
+bool KItemListViewAnimation::isStarted(QGraphicsWidget* widget) const
+{
+ for (int type = 0; type < AnimationTypeCount; ++type) {
+ if (isStarted(widget, static_cast<AnimationType>(type))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void KItemListViewAnimation::slotFinished()
+{
+ QPropertyAnimation* finishedAnim = qobject_cast<QPropertyAnimation*>(sender());
+ for (int type = 0; type < AnimationTypeCount; ++type) {
+ QHashIterator<QGraphicsWidget*, QPropertyAnimation*> it(m_animation[type]);
+ while (it.hasNext()) {
+ it.next();
+ QPropertyAnimation* propertyAnim = it.value();
+ if (propertyAnim == finishedAnim) {
+ QGraphicsWidget* widget = it.key();
+ m_animation[type].remove(widget);
+ finishedAnim->deleteLater();
+
+ emit finished(widget, static_cast<AnimationType>(type));
+ return;
+ }
+ }
+ }
+ Q_ASSERT(false);
+}
+
+#include "kitemlistviewanimation_p.moc"
diff --git a/src/kitemviews/kitemlistviewanimation_p.h b/src/kitemviews/kitemlistviewanimation_p.h
new file mode 100644
index 000000000..0bf54d296
--- /dev/null
+++ b/src/kitemviews/kitemlistviewanimation_p.h
@@ -0,0 +1,79 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTVIEWANIMATION_H
+#define KITEMLISTVIEWANIMATION_H
+
+#include <libdolphin_export.h>
+
+#include <QHash>
+#include <QObject>
+#include <QVariant>
+
+class KItemListView;
+class QGraphicsWidget;
+class QPointF;
+class QPropertyAnimation;
+
+class LIBDOLPHINPRIVATE_EXPORT KItemListViewAnimation : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum AnimationType {
+ MovingAnimation,
+ CreateAnimation,
+ DeleteAnimation,
+ ResizeAnimation
+ };
+
+ KItemListViewAnimation(QObject* parent = 0);
+ virtual ~KItemListViewAnimation();
+
+ void setScrollOrientation(Qt::Orientation orientation);
+ Qt::Orientation scrollOrientation() const;
+
+ void setOffset(qreal offset);
+ qreal offset() const;
+
+ void start(QGraphicsWidget* widget, AnimationType type, const QVariant& endValue = QVariant());
+
+ void stop(QGraphicsWidget* widget, AnimationType type);
+ void stop(QGraphicsWidget* widget);
+
+ bool isStarted(QGraphicsWidget *widget, AnimationType type) const;
+ bool isStarted(QGraphicsWidget* widget) const;
+
+signals:
+ void finished(QGraphicsWidget* widget, KItemListViewAnimation::AnimationType type);
+
+private slots:
+ void slotFinished();
+
+private:
+ enum { AnimationTypeCount = 4 };
+
+ Qt::Orientation m_scrollOrientation;
+ qreal m_offset;
+ QHash<QGraphicsWidget*, QPropertyAnimation*> m_animation[AnimationTypeCount];
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kitemlistviewlayouter.cpp b/src/kitemviews/kitemlistviewlayouter.cpp
new file mode 100644
index 000000000..7d420e093
--- /dev/null
+++ b/src/kitemviews/kitemlistviewlayouter.cpp
@@ -0,0 +1,474 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistviewlayouter_p.h"
+
+#include "kitemmodelbase.h"
+#include "kitemlistsizehintresolver_p.h"
+
+#include <KDebug>
+
+#define KITEMLISTVIEWLAYOUTER_DEBUG
+
+namespace {
+ // TODO
+ const int HeaderHeight = 50;
+};
+
+KItemListViewLayouter::KItemListViewLayouter(QObject* parent) :
+ QObject(parent),
+ m_dirty(true),
+ m_visibleIndexesDirty(true),
+ m_grouped(false),
+ m_scrollOrientation(Qt::Vertical),
+ m_size(),
+ m_itemSize(128, 128),
+ m_model(0),
+ m_sizeHintResolver(0),
+ m_offset(0),
+ m_maximumOffset(0),
+ m_firstVisibleIndex(-1),
+ m_lastVisibleIndex(-1),
+ m_firstVisibleGroupIndex(-1),
+ m_columnWidth(0),
+ m_xPosInc(0),
+ m_columnCount(0),
+ m_groups(),
+ m_groupIndexes(),
+ m_itemBoundingRects()
+{
+}
+
+KItemListViewLayouter::~KItemListViewLayouter()
+{
+}
+
+void KItemListViewLayouter::setScrollOrientation(Qt::Orientation orientation)
+{
+ if (m_scrollOrientation != orientation) {
+ m_scrollOrientation = orientation;
+ m_dirty = true;
+ }
+}
+
+Qt::Orientation KItemListViewLayouter::scrollOrientation() const
+{
+ return m_scrollOrientation;
+}
+
+void KItemListViewLayouter::setSize(const QSizeF& size)
+{
+ if (m_size != size) {
+ m_size = size;
+ m_dirty = true;
+ }
+}
+
+QSizeF KItemListViewLayouter::size() const
+{
+ return m_size;
+}
+
+void KItemListViewLayouter::setItemSize(const QSizeF& size)
+{
+ if (m_itemSize != size) {
+ m_itemSize = size;
+ m_dirty = true;
+ }
+}
+
+QSizeF KItemListViewLayouter::itemSize() const
+{
+ return m_itemSize;
+}
+
+void KItemListViewLayouter::setOffset(qreal offset)
+{
+ if (m_offset != offset) {
+ m_offset = offset;
+ m_visibleIndexesDirty = true;
+ }
+}
+
+qreal KItemListViewLayouter::offset() const
+{
+ return m_offset;
+}
+
+void KItemListViewLayouter::setModel(const KItemModelBase* model)
+{
+ if (m_model != model) {
+ m_model = model;
+ m_dirty = true;
+ }
+}
+
+const KItemModelBase* KItemListViewLayouter::model() const
+{
+ return m_model;
+}
+
+void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver* sizeHintResolver)
+{
+ if (m_sizeHintResolver != sizeHintResolver) {
+ m_sizeHintResolver = sizeHintResolver;
+ m_dirty = true;
+ }
+}
+
+const KItemListSizeHintResolver* KItemListViewLayouter::sizeHintResolver() const
+{
+ return m_sizeHintResolver;
+}
+
+qreal KItemListViewLayouter::maximumOffset() const
+{
+ const_cast<KItemListViewLayouter*>(this)->doLayout();
+ return m_maximumOffset;
+}
+
+int KItemListViewLayouter::firstVisibleIndex() const
+{
+ const_cast<KItemListViewLayouter*>(this)->doLayout();
+ return m_firstVisibleIndex;
+}
+
+int KItemListViewLayouter::lastVisibleIndex() const
+{
+ const_cast<KItemListViewLayouter*>(this)->doLayout();
+ return m_lastVisibleIndex;
+}
+
+QRectF KItemListViewLayouter::itemBoundingRect(int index) const
+{
+ const_cast<KItemListViewLayouter*>(this)->doLayout();
+ if (index < 0 || index >= m_itemBoundingRects.count()) {
+ return QRectF();
+ }
+
+ if (m_scrollOrientation == Qt::Horizontal) {
+ // Rotate the logical direction which is always vertical by 90°
+ // to get the physical horizontal direction
+ const QRectF& b = m_itemBoundingRects[index];
+ QRectF bounds(b.y(), b.x(), b.height(), b.width());
+ QPointF pos = bounds.topLeft();
+ pos.rx() -= m_offset;
+ bounds.moveTo(pos);
+ return bounds;
+ }
+
+ QRectF bounds = m_itemBoundingRects[index];
+ QPointF pos = bounds.topLeft();
+ pos.ry() -= m_offset;
+ bounds.moveTo(pos);
+ return bounds;
+}
+
+int KItemListViewLayouter::maximumVisibleItems() const
+{
+ const_cast<KItemListViewLayouter*>(this)->doLayout();
+
+ const int height = static_cast<int>(m_size.height());
+ const int rowHeight = static_cast<int>(m_itemSize.height());
+ int rows = height / rowHeight;
+ if (height % rowHeight != 0) {
+ ++rows;
+ }
+
+ return rows * m_columnCount;
+}
+
+bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const
+{
+ return m_groupIndexes.contains(itemIndex);
+}
+
+void KItemListViewLayouter::markAsDirty()
+{
+ m_dirty = true;
+}
+
+void KItemListViewLayouter::doLayout()
+{
+ if (m_dirty) {
+#ifdef KITEMLISTVIEWLAYOUTER_DEBUG
+ QElapsedTimer timer;
+ timer.start();
+#endif
+
+ m_visibleIndexesDirty = true;
+
+ QSizeF itemSize = m_itemSize;
+ QSizeF size = m_size;
+
+ const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
+ if (horizontalScrolling) {
+ itemSize.setWidth(m_itemSize.height());
+ itemSize.setHeight(m_itemSize.width());
+ size.setWidth(m_size.height());
+ size.setHeight(m_size.width());
+ }
+
+ m_columnWidth = itemSize.width();
+ m_columnCount = qMax(1, int(size.width() / m_columnWidth));
+ m_xPosInc = 0;
+
+ const int itemCount = m_model->count();
+ if (itemCount > m_columnCount) {
+ // Apply the unused width equally to each column
+ const qreal unusedWidth = size.width() - m_columnCount * m_columnWidth;
+ const qreal columnInc = unusedWidth / (m_columnCount + 1);
+ m_columnWidth += columnInc;
+ m_xPosInc += columnInc;
+ }
+
+ int rowCount = itemCount / m_columnCount;
+ if (itemCount % m_columnCount != 0) {
+ ++rowCount;
+ }
+
+ m_itemBoundingRects.reserve(itemCount);
+
+ qreal y = 0;
+ int rowIndex = 0;
+
+ int index = 0;
+ while (index < itemCount) {
+ qreal x = m_xPosInc;
+ qreal maxItemHeight = itemSize.height();
+
+ int column = 0;
+ while (index < itemCount && column < m_columnCount) {
+ qreal requiredItemHeight = itemSize.height();
+ if (m_sizeHintResolver) {
+ const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index);
+ const qreal sizeHintHeight = horizontalScrolling ? sizeHint.width() : sizeHint.height();
+ if (sizeHintHeight > requiredItemHeight) {
+ requiredItemHeight = sizeHintHeight;
+ }
+ }
+
+ const QRectF bounds(x, y, itemSize.width(), requiredItemHeight);
+ if (index < m_itemBoundingRects.count()) {
+ m_itemBoundingRects[index] = bounds;
+ } else {
+ m_itemBoundingRects.append(bounds);
+ }
+
+ maxItemHeight = qMax(maxItemHeight, requiredItemHeight);
+ x += m_columnWidth;
+ ++index;
+ ++column;
+ }
+
+ y += maxItemHeight;
+ ++rowIndex;
+ }
+ if (m_itemBoundingRects.count() > itemCount) {
+ m_itemBoundingRects.erase(m_itemBoundingRects.begin() + itemCount,
+ m_itemBoundingRects.end());
+ }
+
+ m_maximumOffset = (itemCount > 0) ? m_itemBoundingRects.last().bottom() : 0;
+
+ m_grouped = !m_model->groupRole().isEmpty();
+ /*if (m_grouped) {
+ createGroupHeaders();
+
+ const int lastGroupItemCount = m_model->count() - m_groups.last().firstItemIndex;
+ m_maximumOffset = m_groups.last().y + (lastGroupItemCount / m_columnCount) * m_rowHeight;
+ if (lastGroupItemCount % m_columnCount != 0) {
+ m_maximumOffset += m_rowHeight;
+ }
+ } else {*/
+ // m_maximumOffset = m_minimumRowHeight * rowCount;
+ //}
+
+#ifdef KITEMLISTVIEWLAYOUTER_DEBUG
+ kDebug() << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed();
+#endif
+ m_dirty = false;
+ }
+
+ if (m_grouped) {
+ updateGroupedVisibleIndexes();
+ } else {
+ updateVisibleIndexes();
+ }
+}
+
+void KItemListViewLayouter::updateVisibleIndexes()
+{
+ if (!m_visibleIndexesDirty) {
+ return;
+ }
+
+ Q_ASSERT(!m_grouped);
+ Q_ASSERT(!m_dirty);
+
+ if (m_model->count() <= 0) {
+ m_firstVisibleIndex = -1;
+ m_lastVisibleIndex = -1;
+ m_visibleIndexesDirty = false;
+ return;
+ }
+
+ const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
+ const int minimumHeight = horizontalScrolling ? m_itemSize.width()
+ : m_itemSize.height();
+
+ // Calculate the first visible index:
+ // 1. Guess the index by using the minimum row height
+ const int maxIndex = m_model->count() - 1;
+ m_firstVisibleIndex = int(m_offset / minimumHeight) * m_columnCount;
+
+ // 2. Decrease the index by checking the real row heights
+ int prevRowIndex = m_firstVisibleIndex - m_columnCount;
+ while (prevRowIndex > maxIndex) {
+ prevRowIndex -= m_columnCount;
+ }
+
+ while (prevRowIndex >= 0 && m_itemBoundingRects[prevRowIndex].bottom() >= m_offset) {
+ m_firstVisibleIndex = prevRowIndex;
+ prevRowIndex -= m_columnCount;
+ }
+ m_firstVisibleIndex = qBound(0, m_firstVisibleIndex, maxIndex);
+
+ // Calculate the last visible index
+ const int visibleHeight = horizontalScrolling ? m_size.width() : m_size.height();
+ const qreal bottom = m_offset + visibleHeight;
+ m_lastVisibleIndex = m_firstVisibleIndex; // first visible row, first column
+ int nextRowIndex = m_lastVisibleIndex + m_columnCount;
+ while (nextRowIndex <= maxIndex && m_itemBoundingRects[nextRowIndex].y() <= bottom) {
+ m_lastVisibleIndex = nextRowIndex;
+ nextRowIndex += m_columnCount;
+ }
+ m_lastVisibleIndex += m_columnCount - 1; // move it to the last column
+ m_lastVisibleIndex = qBound(0, m_lastVisibleIndex, maxIndex);
+
+ m_visibleIndexesDirty = false;
+}
+
+void KItemListViewLayouter::updateGroupedVisibleIndexes()
+{
+ if (!m_visibleIndexesDirty) {
+ return;
+ }
+
+ Q_ASSERT(m_grouped);
+ Q_ASSERT(!m_dirty);
+
+ if (m_model->count() <= 0) {
+ m_firstVisibleIndex = -1;
+ m_lastVisibleIndex = -1;
+ m_visibleIndexesDirty = false;
+ return;
+ }
+
+ // Find the first visible group
+ const int lastGroupIndex = m_groups.count() - 1;
+ int groupIndex = lastGroupIndex;
+ for (int i = 1; i < m_groups.count(); ++i) {
+ if (m_groups[i].y >= m_offset) {
+ groupIndex = i - 1;
+ break;
+ }
+ }
+
+ // Calculate the first visible index
+ qreal groupY = m_groups[groupIndex].y;
+ m_firstVisibleIndex = m_groups[groupIndex].firstItemIndex;
+ const int invisibleRowCount = int(m_offset - groupY) / int(m_itemSize.height());
+ m_firstVisibleIndex += invisibleRowCount * m_columnCount;
+ if (groupIndex + 1 <= lastGroupIndex) {
+ // Check whether the calculated first visible index remains inside the current
+ // group. If this is not the case let the first element of the next group be the first
+ // visible index.
+ const int nextGroupIndex = m_groups[groupIndex + 1].firstItemIndex;
+ if (m_firstVisibleIndex > nextGroupIndex) {
+ m_firstVisibleIndex = nextGroupIndex;
+ }
+ }
+
+ m_firstVisibleGroupIndex = groupIndex;
+
+ const int maxIndex = m_model->count() - 1;
+ m_firstVisibleIndex = qBound(0, m_firstVisibleIndex, maxIndex);
+
+ // Calculate the last visible index: Find group where the last visible item is shown.
+ const qreal visibleBottom = m_offset + m_size.height(); // TODO: respect Qt::Horizontal alignment
+ while ((groupIndex < lastGroupIndex) && (m_groups[groupIndex + 1].y < visibleBottom)) {
+ ++groupIndex;
+ }
+
+ groupY = m_groups[groupIndex].y;
+ m_lastVisibleIndex = m_groups[groupIndex].firstItemIndex;
+ const int availableHeight = static_cast<int>(visibleBottom - groupY);
+ int visibleRowCount = availableHeight / int(m_itemSize.height());
+ if (availableHeight % int(m_itemSize.height()) != 0) {
+ ++visibleRowCount;
+ }
+ m_lastVisibleIndex += visibleRowCount * m_columnCount - 1;
+
+ if (groupIndex + 1 <= lastGroupIndex) {
+ // Check whether the calculate last visible index remains inside the current group.
+ // If this is not the case let the last element of this group be the last visible index.
+ const int nextGroupIndex = m_groups[groupIndex + 1].firstItemIndex;
+ if (m_lastVisibleIndex >= nextGroupIndex) {
+ m_lastVisibleIndex = nextGroupIndex - 1;
+ }
+ }
+ //Q_ASSERT(m_lastVisibleIndex < m_model->count());
+ m_lastVisibleIndex = qBound(0, m_lastVisibleIndex, maxIndex);
+
+ m_visibleIndexesDirty = false;
+}
+
+void KItemListViewLayouter::createGroupHeaders()
+{
+ m_groups.clear();
+ m_groupIndexes.clear();
+
+ // TODO:
+ QList<int> numbers;
+ numbers << 0 << 5 << 6 << 13 << 20 << 25 << 30 << 35 << 50;
+
+ qreal y = 0;
+ for (int i = 0; i < numbers.count(); ++i) {
+ if (i > 0) {
+ const int previousGroupItemCount = numbers[i] - m_groups.last().firstItemIndex;
+ int previousGroupRowCount = previousGroupItemCount / m_columnCount;
+ if (previousGroupItemCount % m_columnCount != 0) {
+ ++previousGroupRowCount;
+ }
+ const qreal previousGroupHeight = previousGroupRowCount * m_itemSize.height();
+ y += previousGroupHeight;
+ }
+ y += HeaderHeight;
+
+ ItemGroup itemGroup;
+ itemGroup.firstItemIndex = numbers[i];
+ itemGroup.y = y;
+
+ m_groups.append(itemGroup);
+ m_groupIndexes.insert(itemGroup.firstItemIndex);
+ }
+}
+
+#include "kitemlistviewlayouter_p.moc"
diff --git a/src/kitemviews/kitemlistviewlayouter_p.h b/src/kitemviews/kitemlistviewlayouter_p.h
new file mode 100644
index 000000000..775e9ff85
--- /dev/null
+++ b/src/kitemviews/kitemlistviewlayouter_p.h
@@ -0,0 +1,126 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTVIEWLAYOUTER_H
+#define KITEMLISTVIEWLAYOUTER_H
+
+#include <libdolphin_export.h>
+
+#include <QObject>
+#include <QRectF>
+#include <QSet>
+#include <QSizeF>
+
+class KItemModelBase;
+class KItemListSizeHintResolver;
+
+class LIBDOLPHINPRIVATE_EXPORT KItemListViewLayouter : public QObject
+{
+ Q_OBJECT
+
+public:
+ KItemListViewLayouter(QObject* parent = 0);
+ virtual ~KItemListViewLayouter();
+
+ void setScrollOrientation(Qt::Orientation orientation);
+ Qt::Orientation scrollOrientation() const;
+
+ void setSize(const QSizeF& size);
+ QSizeF size() const;
+
+ void setItemSize(const QSizeF& size);
+ QSizeF itemSize() const;
+
+ // TODO: add note that offset can be < 0 or > maximumOffset!
+ void setOffset(qreal offset);
+ qreal offset() const;
+
+ void setModel(const KItemModelBase* model);
+ const KItemModelBase* model() const;
+
+ void setSizeHintResolver(const KItemListSizeHintResolver* sizeHintResolver);
+ const KItemListSizeHintResolver* sizeHintResolver() const;
+
+ qreal maximumOffset() const;
+
+ // TODO: mention that return value is -1 if count == 0
+ int firstVisibleIndex() const;
+
+ // TODO: mention that return value is -1 if count == 0
+ int lastVisibleIndex() const;
+
+ QRectF itemBoundingRect(int index) const;
+
+ int maximumVisibleItems() const;
+
+ /**
+ * @return True if the item with the index \p itemIndex
+ * is the first item within a group.
+ */
+ bool isFirstGroupItem(int itemIndex) const;
+
+ void markAsDirty();
+
+private:
+ void doLayout();
+
+ void updateVisibleIndexes();
+ void updateGroupedVisibleIndexes();
+ void createGroupHeaders();
+
+private:
+ bool m_dirty;
+ bool m_visibleIndexesDirty;
+ bool m_grouped;
+
+ Qt::Orientation m_scrollOrientation;
+ QSizeF m_size;
+
+ QSizeF m_itemSize;
+ const KItemModelBase* m_model;
+ const KItemListSizeHintResolver* m_sizeHintResolver;
+
+ qreal m_offset;
+ qreal m_maximumOffset;
+
+ int m_firstVisibleIndex;
+ int m_lastVisibleIndex;
+
+ int m_firstVisibleGroupIndex;
+
+ qreal m_columnWidth;
+ qreal m_xPosInc;
+ int m_columnCount;
+
+ struct ItemGroup {
+ int firstItemIndex;
+ qreal y;
+ };
+ QList<ItemGroup> m_groups;
+
+ // Stores all item indexes that are the first item of a group.
+ // Assures fast access for KItemListViewLayouter::isFirstGroupItem().
+ QSet<int> m_groupIndexes;
+
+ QList<QRectF> m_itemBoundingRects;
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp
new file mode 100644
index 000000000..3f08d9f7a
--- /dev/null
+++ b/src/kitemviews/kitemlistwidget.cpp
@@ -0,0 +1,260 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemlistwidget.h"
+
+#include "kitemlistview.h"
+#include "kitemmodelbase.h"
+
+#include <KDebug>
+
+#include <QPainter>
+#include <QPropertyAnimation>
+#include <QStyle>
+
+KItemListWidget::KItemListWidget(QGraphicsItem* parent) :
+ QGraphicsWidget(parent, 0),
+ m_index(-1),
+ m_data(),
+ m_visibleRoles(),
+ m_visibleRolesSizes(),
+ m_styleOption(),
+ m_hoverOpacity(0),
+ m_hoverCache(0),
+ m_hoverAnimation(0)
+{
+}
+
+KItemListWidget::~KItemListWidget()
+{
+ clearCache();
+}
+
+void KItemListWidget::setIndex(int index)
+{
+ if (m_index != index) {
+ if (m_hoverAnimation) {
+ m_hoverAnimation->stop();
+ m_hoverOpacity = 0;
+ }
+ clearCache();
+
+ m_index = index;
+ }
+}
+
+int KItemListWidget::index() const
+{
+ return m_index;
+}
+
+void KItemListWidget::setData(const QHash<QByteArray, QVariant>& data,
+ const QSet<QByteArray>& roles)
+{
+ clearCache();
+ if (roles.isEmpty()) {
+ m_data = data;
+ dataChanged(m_data);
+ } else {
+ foreach (const QByteArray& role, roles) {
+ m_data[role] = data[role];
+ }
+ dataChanged(m_data, roles);
+ }
+}
+
+QHash<QByteArray, QVariant> KItemListWidget::data() const
+{
+ return m_data;
+}
+
+void KItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+ Q_UNUSED(option);
+ if (m_hoverOpacity <= 0.0) {
+ return;
+ }
+
+ const QRect hoverBounds = hoverBoundingRect().toRect();
+ if (!m_hoverCache) {
+ m_hoverCache = new QPixmap(hoverBounds.size());
+ m_hoverCache->fill(Qt::transparent);
+
+ QPainter pixmapPainter(m_hoverCache);
+
+ QStyleOptionViewItemV4 viewItemOption;
+ viewItemOption.initFrom(widget);
+ viewItemOption.rect = QRect(0, 0, hoverBounds.width(), hoverBounds.height());
+ viewItemOption.state = QStyle::State_Enabled | QStyle::State_MouseOver;
+ viewItemOption.viewItemPosition = QStyleOptionViewItemV4::OnlyOne;
+
+ widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &viewItemOption, &pixmapPainter, widget);
+ }
+
+ const qreal opacity = painter->opacity();
+ painter->setOpacity(m_hoverOpacity * opacity);
+ painter->drawPixmap(hoverBounds.topLeft(), *m_hoverCache);
+ painter->setOpacity(opacity);
+}
+
+void KItemListWidget::setVisibleRoles(const QHash<QByteArray, int>& roles)
+{
+ const QHash<QByteArray, int> previousRoles = m_visibleRoles;
+ m_visibleRoles = roles;
+ visibleRolesChanged(roles, previousRoles);
+}
+
+QHash<QByteArray, int> KItemListWidget::visibleRoles() const
+{
+ return m_visibleRoles;
+}
+
+void KItemListWidget::setVisibleRolesSizes(const QHash<QByteArray, QSizeF> rolesSizes)
+{
+ const QHash<QByteArray, QSizeF> previousRolesSizes = m_visibleRolesSizes;
+ m_visibleRolesSizes = rolesSizes;
+ visibleRolesSizesChanged(rolesSizes, previousRolesSizes);
+}
+
+QHash<QByteArray, QSizeF> KItemListWidget::visibleRolesSizes() const
+{
+ return m_visibleRolesSizes;
+}
+
+void KItemListWidget::setStyleOption(const KItemListStyleOption& option)
+{
+ const KItemListStyleOption previous = m_styleOption;
+ if (m_index >= 0) {
+ clearCache();
+
+ const bool wasHovered = (previous.state & QStyle::State_MouseOver);
+ m_styleOption = option;
+ const bool isHovered = (m_styleOption.state & QStyle::State_MouseOver);
+
+ if (wasHovered != isHovered) {
+ // The hovering state has been changed. Assure that a fade-animation
+ // is done to the new state.
+ if (!m_hoverAnimation) {
+ m_hoverAnimation = new QPropertyAnimation(this, "hoverOpacity", this);
+ m_hoverAnimation->setDuration(200);
+ }
+ m_hoverAnimation->stop();
+
+ if (!wasHovered && isHovered) {
+ m_hoverAnimation->setEndValue(1.0);
+ } else {
+ Q_ASSERT(wasHovered && !isHovered);
+ m_hoverAnimation->setEndValue(0.0);
+ }
+
+ m_hoverAnimation->start();
+ }
+ } else {
+ m_styleOption = option;
+ }
+
+ styleOptionChanged(option, previous);
+}
+
+const KItemListStyleOption& KItemListWidget::styleOption() const
+{
+ return m_styleOption;
+}
+
+bool KItemListWidget::contains(const QPointF& point) const
+{
+ return hoverBoundingRect().contains(point) ||
+ expansionToggleRect().contains(point) ||
+ selectionToggleRect().contains(point);
+}
+
+QRectF KItemListWidget::hoverBoundingRect() const
+{
+ return QRectF(QPointF(0, 0), size());
+}
+
+QRectF KItemListWidget::selectionToggleRect() const
+{
+ return QRectF();
+}
+
+QRectF KItemListWidget::expansionToggleRect() const
+{
+ return QRectF();
+}
+
+void KItemListWidget::dataChanged(const QHash<QByteArray, QVariant>& current,
+ const QSet<QByteArray>& roles)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(roles);
+ update();
+}
+
+void KItemListWidget::visibleRolesChanged(const QHash<QByteArray, int>& current,
+ const QHash<QByteArray, int>& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+ update();
+}
+
+void KItemListWidget::visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current,
+ const QHash<QByteArray, QSizeF>& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+ update();
+}
+
+void KItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
+ const KItemListStyleOption& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+ update();
+}
+
+void KItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event)
+{
+ QGraphicsWidget::resizeEvent(event);
+ clearCache();
+}
+
+qreal KItemListWidget::hoverOpacity() const
+{
+ return m_hoverOpacity;
+}
+
+void KItemListWidget::setHoverOpacity(qreal opacity)
+{
+ m_hoverOpacity = opacity;
+ update();
+}
+
+void KItemListWidget::clearCache()
+{
+ delete m_hoverCache;
+ m_hoverCache = 0;
+}
+
+#include "kitemlistwidget.moc"
diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h
new file mode 100644
index 000000000..eb2ebf455
--- /dev/null
+++ b/src/kitemviews/kitemlistwidget.h
@@ -0,0 +1,134 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMLISTWIDGET_H
+#define KITEMLISTWIDGET_H
+
+#include <libdolphin_export.h>
+
+#include <kitemviews/kitemliststyleoption.h>
+
+#include <QGraphicsWidget>
+
+class QPropertyAnimation;
+
+/**
+ * @brief Widget that shows a visible item from the model.
+ *
+ * For showing an item from a custom model it is required to at least overwrite KItemListWidget::paint().
+ * All properties are set by KItemListView, for each property there is a corresponding
+ * virtual protected method that allows to react on property changes.
+ */
+class LIBDOLPHINPRIVATE_EXPORT KItemListWidget : public QGraphicsWidget
+{
+ Q_OBJECT
+
+public:
+ KItemListWidget(QGraphicsItem* parent);
+ virtual ~KItemListWidget();
+
+ void setIndex(int index);
+ int index() const;
+
+ void setData(const QHash<QByteArray, QVariant>& data, const QSet<QByteArray>& roles = QSet<QByteArray>());
+ QHash<QByteArray, QVariant> data() const;
+
+ /**
+ * Draws the hover-bounding-rectangle if the item is hovered. Overwrite this method
+ * to show the data of the custom model provided by KItemListWidget::data().
+ * @reimp
+ */
+ virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
+
+ /**
+ * Sets the visible roles to \p roles. The integer-value defines
+ * the order of the visible role: Smaller values are ordered first.
+ */
+ void setVisibleRoles(const QHash<QByteArray, int>& roles);
+ QHash<QByteArray, int> visibleRoles() const;
+
+ void setVisibleRolesSizes(const QHash<QByteArray, QSizeF> rolesSizes);
+ QHash<QByteArray, QSizeF> visibleRolesSizes() const;
+
+ void setStyleOption(const KItemListStyleOption& option);
+ const KItemListStyleOption& styleOption() const;
+
+ /**
+ * @return True if \a point is inside KItemListWidget::hoverBoundingRect(),
+ * KItemListWidget::selectionToggleRect() or KItemListWidget::expansionToggleRect().
+ * @reimp
+ */
+ virtual bool contains(const QPointF& point) const;
+
+ /**
+ * @return Bounding rectangle for the area that acts as hovering-area. Per default
+ * the bounding rectangle of the KItemListWidget is returned.
+ */
+ virtual QRectF hoverBoundingRect() const;
+
+ /**
+ * @return Rectangle for the selection-toggle that is used to select or deselect an item.
+ * Per default an empty rectangle is returned which means that no selection-toggle
+ * is available.
+ */
+ virtual QRectF selectionToggleRect() const;
+
+ /**
+ * @return Rectangle for the expansion-toggle that is used to open a sub-tree of the model.
+ * Per default an empty rectangle is returned which means that no opening of sub-trees
+ * is supported.
+ */
+ virtual QRectF expansionToggleRect() const;
+
+protected:
+ virtual void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>());
+ virtual void visibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous);
+ virtual void visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current, const QHash<QByteArray, QSizeF>& previous);
+ virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
+ virtual void resizeEvent(QGraphicsSceneResizeEvent* event);
+
+ /**
+ * @return The current opacity of the hover-animation. When implementing a custom painting-code for a hover-state
+ * this opacity value should be respected.
+ */
+ qreal hoverOpacity() const;
+
+private:
+ void setHoverOpacity(qreal opacity);
+ void clearCache();
+
+private:
+ Q_PROPERTY(qreal hoverOpacity READ hoverOpacity WRITE setHoverOpacity)
+
+ int m_index;
+ QHash<QByteArray, QVariant> m_data;
+ QHash<QByteArray, int> m_visibleRoles;
+ QHash<QByteArray, QSizeF> m_visibleRolesSizes;
+ KItemListStyleOption m_styleOption;
+
+ qreal m_hoverOpacity;
+ mutable QPixmap* m_hoverCache;
+ QPropertyAnimation* m_hoverAnimation;
+};
+#endif
+
+
diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp
new file mode 100644
index 000000000..63e771ef7
--- /dev/null
+++ b/src/kitemviews/kitemmodelbase.cpp
@@ -0,0 +1,113 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "kitemmodelbase.h"
+
+KItemRange::KItemRange(int index, int count) :
+ index(index),
+ count(count)
+{
+}
+
+KItemModelBase::KItemModelBase(QObject* parent) :
+ QObject(parent),
+ m_groupRole(),
+ m_sortRole()
+{
+}
+
+KItemModelBase::KItemModelBase(const QByteArray& groupRole, const QByteArray& sortRole, QObject* parent) :
+ QObject(parent),
+ m_groupRole(groupRole),
+ m_sortRole(sortRole)
+{
+}
+
+KItemModelBase::~KItemModelBase()
+{
+}
+
+bool KItemModelBase::setData(int index, const QHash<QByteArray, QVariant> &values)
+{
+ Q_UNUSED(index);
+ Q_UNUSED(values);
+ return false;
+}
+
+bool KItemModelBase::supportsGrouping() const
+{
+ return false;
+}
+
+void KItemModelBase::setGroupRole(const QByteArray& role)
+{
+ if (supportsGrouping() && role != m_groupRole) {
+ const QByteArray previous = m_groupRole;
+ m_groupRole = role;
+ onGroupRoleChanged(role, previous);
+ emit groupRoleChanged(role, previous);
+ }
+}
+
+QByteArray KItemModelBase::groupRole() const
+{
+ return m_groupRole;
+}
+
+bool KItemModelBase::supportsSorting() const
+{
+ return false;
+}
+
+void KItemModelBase::setSortRole(const QByteArray& role)
+{
+ if (supportsSorting() && role != m_sortRole) {
+ const QByteArray previous = m_sortRole;
+ m_sortRole = role;
+ onSortRoleChanged(role, previous);
+ emit sortRoleChanged(role, previous);
+ }
+}
+
+QByteArray KItemModelBase::sortRole() const
+{
+ return m_sortRole;
+}
+
+QString KItemModelBase::roleDescription(const QByteArray& role) const
+{
+ return role;
+}
+
+void KItemModelBase::onGroupRoleChanged(const QByteArray& current, const QByteArray& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+void KItemModelBase::onSortRoleChanged(const QByteArray& current, const QByteArray& previous)
+{
+ Q_UNUSED(current);
+ Q_UNUSED(previous);
+}
+
+#include "kitemmodelbase.moc"
diff --git a/src/kitemviews/kitemmodelbase.h b/src/kitemviews/kitemmodelbase.h
new file mode 100644
index 000000000..4eb96c8fd
--- /dev/null
+++ b/src/kitemviews/kitemmodelbase.h
@@ -0,0 +1,145 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * Based on the Itemviews NG project from Trolltech Labs: *
+ * http://qt.gitorious.org/qt-labs/itemviews-ng *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KITEMMODELBASE_H
+#define KITEMMODELBASE_H
+
+#include <libdolphin_export.h>
+
+#include <QHash>
+#include <QObject>
+#include <QSet>
+#include <QVariant>
+
+struct KItemRange
+{
+ KItemRange(int index, int count);
+ int index;
+ int count;
+};
+typedef QList<KItemRange> KItemRangeList;
+
+/**
+ * @brief Base class for model implementations used by KItemListView and KItemListController.
+ *
+ * A item-model consists of a variable number of items. The number of items
+ * is given by KItemModelBase::count(). The data of an item is accessed by a unique index
+ * with KItemModelBase::data(). The indexes are integer-values counting from 0 to the
+ * KItemModelBase::count() - 1.
+ *
+ * One item consists of a variable number of role/value-pairs.
+ *
+ * A model can optionally provide sorting- and/or grouping-capabilities.
+ */
+class LIBDOLPHINPRIVATE_EXPORT KItemModelBase : public QObject
+{
+ Q_OBJECT
+
+public:
+ KItemModelBase(QObject* parent = 0);
+ KItemModelBase(const QByteArray& groupRole, const QByteArray& sortRole, QObject* parent = 0);
+ virtual ~KItemModelBase();
+
+ /** @return The number of items. */
+ virtual int count() const = 0;
+
+ virtual QHash<QByteArray, QVariant> data(int index) const = 0;
+
+ /**
+ * Sets the data for the item at \a index to the given \a values. Returns true
+ * if the data was set on the item; returns false otherwise.
+ *
+ * The default implementation does not set the data, and will always return
+ * false.
+ */
+ virtual bool setData(int index, const QHash<QByteArray, QVariant>& values);
+
+ /**
+ * @return True if the model supports grouping of data. Per default false is returned.
+ * If the model should support grouping it is necessary to overwrite
+ * this method to return true and to implement KItemModelBase::onGroupRoleChanged().
+ */
+ virtual bool supportsGrouping() const;
+
+ /**
+ * Sets the group-role to \a role. The method KItemModelBase::onGroupRoleChanged() will be
+ * called so that model-implementations can react on the group-role change. Afterwards the
+ * signal groupRoleChanged() will be emitted.
+ */
+ void setGroupRole(const QByteArray& role);
+ QByteArray groupRole() const;
+
+ /**
+ * @return True if the model supports sorting of data. Per default false is returned.
+ * If the model should support sorting it is necessary to overwrite
+ * this method to return true and to implement KItemModelBase::onSortRoleChanged().
+ */
+ virtual bool supportsSorting() const;
+
+ /**
+ * Sets the sor-role to \a role. The method KItemModelBase::onSortRoleChanged() will be
+ * called so that model-implementations can react on the sort-role change. Afterwards the
+ * signal sortRoleChanged() will be emitted.
+ */
+ void setSortRole(const QByteArray& role);
+ QByteArray sortRole() const;
+
+ virtual QString roleDescription(const QByteArray& role) const;
+
+signals:
+ void itemsInserted(const KItemRangeList& itemRanges);
+ void itemsRemoved(const KItemRangeList& itemRanges);
+ void itemsMoved(const KItemRangeList& itemRanges);
+ void itemsChanged(const KItemRangeList& itemRanges, const QSet<QByteArray>& roles);
+
+ void groupRoleChanged(const QByteArray& current, const QByteArray& previous);
+ void sortRoleChanged(const QByteArray& current, const QByteArray& previous);
+
+protected:
+ /**
+ * Is invoked if the group role has been changed by KItemModelBase::setGroupRole(). Allows
+ * to react on the changed group role before the signal groupRoleChanged() will be emitted.
+ * The implementation must assure that the items are sorted in a way that they are grouped
+ * by the role given by \a current. Usually the most efficient way is to emit a
+ * itemsRemoved() signal for all items, reorder the items internally and to emit a
+ * itemsInserted() signal afterwards.
+ */
+ virtual void onGroupRoleChanged(const QByteArray& current, const QByteArray& previous);
+
+ /**
+ * Is invoked if the sort role has been changed by KItemModelBase::setSortRole(). Allows
+ * to react on the changed sort role before the signal sortRoleChanged() will be emitted.
+ * The implementation must assure that the items are sorted by the role given by \a current.
+ * Usually the most efficient way is to emit a
+ * itemsRemoved() signal for all items, reorder the items internally and to emit a
+ * itemsInserted() signal afterwards.
+ */
+ virtual void onSortRoleChanged(const QByteArray& current, const QByteArray& previous);
+
+private:
+ QByteArray m_groupRole;
+ QByteArray m_sortRole;
+};
+
+#endif
+
+
diff --git a/src/kitemviews/kpixmapmodifier.cpp b/src/kitemviews/kpixmapmodifier.cpp
new file mode 100644
index 000000000..e210f0bf5
--- /dev/null
+++ b/src/kitemviews/kpixmapmodifier.cpp
@@ -0,0 +1,388 @@
+//krazy:exclude=copyright (email of Maxim is missing)
+/*
+ This file is a part of the KDE project
+
+ Copyright © 2006 Zack Rusin <[email protected]>
+ Copyright © 2006-2007, 2008 Fredrik Höglund <[email protected]>
+
+ The stack blur algorithm was invented by Mario Klingemann <[email protected]>
+
+ This implementation is based on the version in Anti-Grain Geometry Version 2.4,
+ Copyright © 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "kpixmapmodifier_p.h"
+
+#include <QImage>
+#include <QPainter>
+#include <QPixmap>
+#include <QSize>
+
+#include <KDebug>
+
+#include <config-X11.h> // for HAVE_XRENDER
+#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
+# include <QX11Info>
+# include <X11/Xlib.h>
+# include <X11/extensions/Xrender.h>
+#endif
+
+static const quint32 stackBlur8Mul[255] =
+{
+ 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
+ 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
+ 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
+ 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
+ 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
+ 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
+ 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
+ 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
+ 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
+ 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
+ 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
+ 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
+ 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
+ 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
+ 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
+ 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259
+};
+
+static const quint32 stackBlur8Shr[255] =
+{
+ 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
+ 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
+};
+
+static void blurHorizontal(QImage& image, unsigned int* stack, int div, int radius)
+{
+ int stackindex;
+ int stackstart;
+
+ quint32 * const pixels = reinterpret_cast<quint32 *>(image.bits());
+ quint32 pixel;
+
+ int w = image.width();
+ int h = image.height();
+ int wm = w - 1;
+
+ unsigned int mulSum = stackBlur8Mul[radius];
+ unsigned int shrSum = stackBlur8Shr[radius];
+
+ unsigned int sum, sumIn, sumOut;
+
+ for (int y = 0; y < h; y++) {
+ sum = 0;
+ sumIn = 0;
+ sumOut = 0;
+
+ const int yw = y * w;
+ pixel = pixels[yw];
+ for (int i = 0; i <= radius; i++) {
+ stack[i] = qAlpha(pixel);
+
+ sum += stack[i] * (i + 1);
+ sumOut += stack[i];
+ }
+
+ for (int i = 1; i <= radius; i++) {
+ pixel = pixels[yw + qMin(i, wm)];
+
+ unsigned int* stackpix = &stack[i + radius];
+ *stackpix = qAlpha(pixel);
+
+ sum += *stackpix * (radius + 1 - i);
+ sumIn += *stackpix;
+ }
+
+ stackindex = radius;
+ for (int x = 0, i = yw; x < w; x++) {
+ pixels[i++] = (((sum * mulSum) >> shrSum) << 24) & 0xff000000;
+
+ sum -= sumOut;
+
+ stackstart = stackindex + div - radius;
+ if (stackstart >= div) {
+ stackstart -= div;
+ }
+
+ unsigned int* stackpix = &stack[stackstart];
+
+ sumOut -= *stackpix;
+
+ pixel = pixels[yw + qMin(x + radius + 1, wm)];
+
+ *stackpix = qAlpha(pixel);
+
+ sumIn += *stackpix;
+ sum += sumIn;
+
+ if (++stackindex >= div) {
+ stackindex = 0;
+ }
+
+ stackpix = &stack[stackindex];
+
+ sumOut += *stackpix;
+ sumIn -= *stackpix;
+ }
+ }
+}
+
+static void blurVertical(QImage& image, unsigned int* stack, int div, int radius)
+{
+ int stackindex;
+ int stackstart;
+
+ quint32 * const pixels = reinterpret_cast<quint32 *>(image.bits());
+ quint32 pixel;
+
+ int w = image.width();
+ int h = image.height();
+ int hm = h - 1;
+
+ int mul_sum = stackBlur8Mul[radius];
+ int shr_sum = stackBlur8Shr[radius];
+
+ unsigned int sum, sumIn, sumOut;
+
+ for (int x = 0; x < w; x++) {
+ sum = 0;
+ sumIn = 0;
+ sumOut = 0;
+
+ pixel = pixels[x];
+ for (int i = 0; i <= radius; i++) {
+ stack[i] = qAlpha(pixel);
+
+ sum += stack[i] * (i + 1);
+ sumOut += stack[i];
+ }
+
+ for (int i = 1; i <= radius; i++) {
+ pixel = pixels[qMin(i, hm) * w + x];
+
+ unsigned int* stackpix = &stack[i + radius];
+ *stackpix = qAlpha(pixel);
+
+ sum += *stackpix * (radius + 1 - i);
+ sumIn += *stackpix;
+ }
+
+ stackindex = radius;
+ for (int y = 0, i = x; y < h; y++, i += w) {
+ pixels[i] = (((sum * mul_sum) >> shr_sum) << 24) & 0xff000000;
+
+ sum -= sumOut;
+
+ stackstart = stackindex + div - radius;
+ if (stackstart >= div)
+ stackstart -= div;
+
+ unsigned int* stackpix = &stack[stackstart];
+
+ sumOut -= *stackpix;
+
+ pixel = pixels[qMin(y + radius + 1, hm) * w + x];
+
+ *stackpix = qAlpha(pixel);
+
+ sumIn += *stackpix;
+ sum += sumIn;
+
+ if (++stackindex >= div) {
+ stackindex = 0;
+ }
+
+ stackpix = &stack[stackindex];
+
+ sumOut += *stackpix;
+ sumIn -= *stackpix;
+ }
+ }
+}
+
+static void stackBlur(QImage& image, float radius)
+{
+ radius = qRound(radius);
+
+ int div = int(radius * 2) + 1;
+ unsigned int* stack = new unsigned int[div];
+
+ blurHorizontal(image, stack, div, radius);
+ blurVertical(image, stack, div, radius);
+
+ delete [] stack;
+}
+
+static void shadowBlur(QImage& image, float radius, const QColor& color)
+{
+ if (radius < 0) {
+ return;
+ }
+
+ if (radius > 0) {
+ stackBlur(image, radius);
+ }
+
+ // Correct the color and opacity of the shadow
+ QPainter p(&image);
+ p.setCompositionMode(QPainter::CompositionMode_SourceIn);
+ p.fillRect(image.rect(), color);
+}
+
+namespace {
+ /** Helper class for drawing frames for KPixmapModifier::applyFrame(). */
+ class TileSet
+ {
+ public:
+ enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 };
+
+ enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide,
+ RightSide, BottomLeftCorner, BottomSide, BottomRightCorner,
+ NumTiles };
+
+ TileSet()
+ {
+ QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
+
+ QPainter p(&image);
+ p.setCompositionMode(QPainter::CompositionMode_Source);
+ p.fillRect(image.rect(), Qt::transparent);
+ p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
+ p.end();
+
+ shadowBlur(image, 3, Qt::black);
+
+ QPixmap pixmap = QPixmap::fromImage(image);
+ m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8);
+ m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8);
+ m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8);
+ m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8);
+ m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8);
+ m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8);
+ m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8);
+ m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
+ }
+
+ void paint(QPainter* p, const QRect& r)
+ {
+ p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
+ if (r.width() - 16 > 0) {
+ p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
+ }
+ p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
+ if (r.height() - 16 > 0) {
+ p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]);
+ p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
+ }
+ p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
+ if (r.width() - 16 > 0) {
+ p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
+ }
+ p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
+
+ const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1,
+ -(RightMargin + 1), -(BottomMargin + 1));
+ p->fillRect(contentRect, Qt::transparent);
+ }
+
+ QPixmap m_tiles[NumTiles];
+ };
+}
+
+void KPixmapModifier::scale(QPixmap& pixmap, const QSize& scaledSize)
+{
+#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
+ // Assume that the texture size limit is 2048x2048
+ if ((pixmap.width() <= 2048) && (pixmap.height() <= 2048) && pixmap.x11PictureHandle()) {
+ QSize scaledPixmapSize = pixmap.size();
+ scaledPixmapSize.scale(scaledSize, Qt::KeepAspectRatio);
+
+ const qreal factor = scaledPixmapSize.width() / qreal(pixmap.width());
+
+ XTransform xform = {{
+ { XDoubleToFixed(1 / factor), 0, 0 },
+ { 0, XDoubleToFixed(1 / factor), 0 },
+ { 0, 0, XDoubleToFixed(1) }
+ }};
+
+ QPixmap scaledPixmap(scaledPixmapSize);
+ scaledPixmap.fill(Qt::transparent);
+
+ Display* dpy = QX11Info::display();
+
+ XRenderPictureAttributes attr;
+ attr.repeat = RepeatPad;
+ XRenderChangePicture(dpy, pixmap.x11PictureHandle(), CPRepeat, &attr);
+
+ XRenderSetPictureFilter(dpy, pixmap.x11PictureHandle(), FilterBilinear, 0, 0);
+ XRenderSetPictureTransform(dpy, pixmap.x11PictureHandle(), &xform);
+ XRenderComposite(dpy, PictOpOver, pixmap.x11PictureHandle(), None, scaledPixmap.x11PictureHandle(),
+ 0, 0, 0, 0, 0, 0, scaledPixmap.width(), scaledPixmap.height());
+ pixmap = scaledPixmap;
+ } else {
+ pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::FastTransformation);
+ }
+#else
+ pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::FastTransformation);
+#endif
+}
+
+void KPixmapModifier::applyFrame(QPixmap& icon, const QSize& scaledSize)
+{
+ static TileSet tileSet;
+
+ // Resize the icon to the maximum size minus the space required for the frame
+ const QSize size(scaledSize.width() - TileSet::LeftMargin - TileSet::RightMargin,
+ scaledSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
+ scale(icon, size);
+
+ QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin,
+ icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin);
+ framedIcon.fill(Qt::transparent);
+
+ QPainter painter;
+ painter.begin(&framedIcon);
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ tileSet.paint(&painter, framedIcon.rect());
+ painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+ painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon);
+
+ icon = framedIcon;
+}
+
diff --git a/src/kitemviews/kpixmapmodifier_p.h b/src/kitemviews/kpixmapmodifier_p.h
new file mode 100644
index 000000000..bca5e442a
--- /dev/null
+++ b/src/kitemviews/kpixmapmodifier_p.h
@@ -0,0 +1,37 @@
+/***************************************************************************
+ * Copyright (C) 2011 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef KPIXMAPMODIFIER_H
+#define KPIXMAPMODIFIER_H
+
+#include <libdolphin_export.h>
+
+class QPixmap;
+class QSize;
+
+class LIBDOLPHINPRIVATE_EXPORT KPixmapModifier
+{
+public:
+ static void scale(QPixmap& pixmap, const QSize& scaledSize);
+ static void applyFrame(QPixmap& icon, const QSize& scaledSize);
+};
+
+#endif
+
+