diff options
| author | Peter Penz <[email protected]> | 2011-07-30 20:13:09 +0200 |
|---|---|---|
| committer | Peter Penz <[email protected]> | 2011-07-30 20:13:41 +0200 |
| commit | f23e9496f303995557b744c03178f5dbd9b35016 (patch) | |
| tree | 1139c4340ac173718d1fa847e0124d6175781fd9 /src/kitemviews/kfileitemlistview.cpp | |
| parent | 69e4007e5e330f2ca87c0176a186967b5ca156e8 (diff) | |
Merged very early alpha-version of Dolphin 2.0
Dolphin 2.0 will get a new view-engine with the
following improvements:
- Better performance
- Animated transitions
- No clipped filenames due to dynamic item-sizes
- Grouping support for all view-modes
- Non-rectangular selection areas
- Simplified code for better maintenance
More details will be provided in a blog-entry during
the next days.
Please note that the code is in a very
early alpha-stage and although the most tricky parts
have been implemented already very basic things like
drag and drop or selections have not been pushed yet.
Those things are rather trivial to implement but this
still will take some time.
Diffstat (limited to 'src/kitemviews/kfileitemlistview.cpp')
| -rw-r--r-- | src/kitemviews/kfileitemlistview.cpp | 423 |
1 files changed, 423 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" |
