diff options
| author | Peter Penz <[email protected]> | 2012-04-21 21:28:16 +0200 |
|---|---|---|
| committer | Peter Penz <[email protected]> | 2012-04-21 21:32:42 +0200 |
| commit | ae4d11d918938fd9087f2035dac247969c1f2313 (patch) | |
| tree | 0303667797c81814b46b9ed5ed20b48ef31f2d71 /src/kitemviews | |
| parent | 47d7cdffdd2d2c04067a5088eaeff67add53dde3 (diff) | |
Prepare view-engine for non-KFileItem usecase
Up to now the view-engine only provided a model-implementation that
supports file-items. The view-engine always had been designed to be able
to work with any kind of model, so now a KStandardItemModel is available.
The plan is to convert the places panel to the new view-engine. It should
be no problem to fix this until the feature freeze - in the worst case
the places-panel code could be reverted while still keeping the
KStandardItemModel changes.
Diffstat (limited to 'src/kitemviews')
23 files changed, 2577 insertions, 1698 deletions
diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp index c5c444875..7bcc781fa 100644 --- a/src/kitemviews/kfileitemlistview.cpp +++ b/src/kitemviews/kfileitemlistview.cpp @@ -43,8 +43,7 @@ namespace { } KFileItemListView::KFileItemListView(QGraphicsWidget* parent) : - KItemListView(parent), - m_itemLayout(IconsLayout), + KStandardItemListView(parent), m_modelRolesUpdater(0), m_updateVisibleIndexRangeTimer(0), m_updateIconSizeTimer(0) @@ -52,8 +51,6 @@ KFileItemListView::KFileItemListView(QGraphicsWidget* parent) : setAcceptDrops(true); setScrollOrientation(Qt::Vertical); - setWidgetCreator(new KItemListWidgetCreator<KFileItemListWidget>()); - setGroupHeaderCreator(new KItemListGroupHeaderCreator<KFileItemListGroupHeader>()); m_updateVisibleIndexRangeTimer = new QTimer(this); m_updateVisibleIndexRangeTimer->setSingleShot(true); @@ -65,25 +62,26 @@ KFileItemListView::KFileItemListView(QGraphicsWidget* parent) : m_updateIconSizeTimer->setInterval(ShortInterval); connect(m_updateIconSizeTimer, SIGNAL(timeout()), this, SLOT(updateIconSize())); - setVisibleRoles(QList<QByteArray>() << "name"); + setVisibleRoles(QList<QByteArray>() << "text"); } KFileItemListView::~KFileItemListView() { - // The group headers are children of the widgets created by - // widgetCreator(). So it is mandatory to delete the group headers - // first. - delete groupHeaderCreator(); - delete widgetCreator(); - delete m_modelRolesUpdater; m_modelRolesUpdater = 0; } void KFileItemListView::setPreviewsShown(bool show) { - if (m_modelRolesUpdater) { + if (!m_modelRolesUpdater) { + return; + } + + if (m_modelRolesUpdater->previewsShown() != show) { + beginTransaction(); m_modelRolesUpdater->setPreviewsShown(show); + onPreviewsShownChanged(show); + endTransaction(); } } @@ -104,26 +102,6 @@ bool KFileItemListView::enlargeSmallPreviews() const return m_modelRolesUpdater ? m_modelRolesUpdater->enlargeSmallPreviews() : false; } -void KFileItemListView::setItemLayout(Layout layout) -{ - if (m_itemLayout != layout) { - const bool updateRoles = (m_itemLayout == DetailsLayout || layout == DetailsLayout); - m_itemLayout = layout; - if (updateRoles) { - // The details-layout requires some invisible roles that - // must be added to the model if the new layout is "details". - // If the old layout was "details" the roles will get removed. - applyRolesToModel(); - } - updateLayoutOfVisibleItems(); - } -} - -KFileItemListView::Layout KFileItemListView::itemLayout() const -{ - return m_itemLayout; -} - void KFileItemListView::setEnabledPlugins(const QStringList& list) { if (m_modelRolesUpdater) { @@ -207,52 +185,53 @@ QPixmap KFileItemListView::createDragPixmap(const QSet<int>& indexes) const return dragPixmap; } -void KFileItemListView::initializeItemListWidget(KItemListWidget* item) +KItemListWidgetCreatorBase* KFileItemListView::defaultWidgetCreator() const { - 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; - } + return new KItemListWidgetCreator<KFileItemListWidget>(); +} - fileItemListWidget->setSupportsItemExpanding(supportsItemExpanding()); +KItemListGroupHeaderCreatorBase* KFileItemListView::defaultGroupHeaderCreator() const +{ + return new KItemListGroupHeaderCreator<KFileItemListGroupHeader>(); } -bool KFileItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const +void KFileItemListView::onPreviewsShownChanged(bool shown) { - // Even if the icons have a different size they are always aligned within - // the area defined by KItemStyleOption.iconSize and hence result in no - // change of the item-size. - const bool containsIconName = changedRoles.contains("iconName"); - const bool containsIconPixmap = changedRoles.contains("iconPixmap"); - const int count = changedRoles.count(); + Q_UNUSED(shown); +} - const bool iconChanged = (containsIconName && containsIconPixmap && count == 2) || - (containsIconName && count == 1) || - (containsIconPixmap && count == 1); - return !iconChanged; +void KFileItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous) +{ + if (previous == DetailsLayout || current == DetailsLayout) { + // The details-layout requires some invisible roles that + // must be added to the model if the new layout is "details". + // If the old layout was "details" the roles will get removed. + applyRolesToModel(); + } + KStandardItemListView::onItemLayoutChanged(current, previous); + triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous) { - Q_UNUSED(previous); Q_ASSERT(qobject_cast<KFileItemModel*>(current)); + KStandardItemListView::onModelChanged(current, previous); delete m_modelRolesUpdater; - m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this); - m_modelRolesUpdater->setIconSize(availableIconSize()); + m_modelRolesUpdater = 0; - applyRolesToModel(); + if (current) { + m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this); + m_modelRolesUpdater->setIconSize(availableIconSize()); + + applyRolesToModel(); + } } void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) { - Q_UNUSED(current); - Q_UNUSED(previous); - updateLayoutOfVisibleItems(); + KStandardItemListView::onScrollOrientationChanged(current, previous); + triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous) @@ -264,39 +243,42 @@ void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& p void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + KStandardItemListView::onScrollOffsetChanged(current, previous); triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + KStandardItemListView::onVisibleRolesChanged(current, previous); applyRolesToModel(); } void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + KStandardItemListView::onStyleOptionChanged(current, previous); triggerIconSizeUpdate(); } void KFileItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) { - Q_UNUSED(supportsExpanding); applyRolesToModel(); - updateLayoutOfVisibleItems(); + KStandardItemListView::onSupportsItemExpandingChanged(supportsExpanding); + triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onTransactionBegin() { - m_modelRolesUpdater->setPaused(true); + if (m_modelRolesUpdater) { + m_modelRolesUpdater->setPaused(true); + } } void KFileItemListView::onTransactionEnd() { + if (!m_modelRolesUpdater) { + return; + } + // 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. @@ -309,13 +291,13 @@ void KFileItemListView::onTransactionEnd() void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event) { - KItemListView::resizeEvent(event); + KStandardItemListView::resizeEvent(event); triggerVisibleIndexRangeUpdate(); } void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) { - KItemListView::slotItemsRemoved(itemRanges); + KStandardItemListView::slotItemsRemoved(itemRanges); updateTimersInterval(); } @@ -326,7 +308,7 @@ void KFileItemListView::slotSortRoleChanged(const QByteArray& current, const QBy applyRolesToModel(); } - KItemListView::slotSortRoleChanged(current, previous); + KStandardItemListView::slotSortRoleChanged(current, previous); } void KFileItemListView::triggerVisibleIndexRangeUpdate() @@ -391,18 +373,6 @@ void KFileItemListView::updateIconSize() updateTimersInterval(); } -void KFileItemListView::updateLayoutOfVisibleItems() -{ - if (!model()) { - return; - } - - foreach (KItemListWidget* widget, visibleItemListWidgets()) { - initializeItemListWidget(widget); - } - triggerVisibleIndexRangeUpdate(); -} - void KFileItemListView::updateTimersInterval() { if (!model()) { @@ -434,7 +404,7 @@ void KFileItemListView::applyRolesToModel() QSet<QByteArray> roles = visibleRoles().toSet(); roles.insert("iconPixmap"); roles.insert("iconName"); - roles.insert("name"); + roles.insert("text"); roles.insert("isDir"); if (supportsItemExpanding()) { roles.insert("isExpanded"); @@ -453,7 +423,7 @@ QSize KFileItemListView::availableIconSize() const { const KItemListStyleOption& option = styleOption(); const int iconSize = option.iconSize; - if (m_itemLayout == IconsLayout) { + if (itemLayout() == IconsLayout) { const int maxIconWidth = itemSize().width() - 2 * option.padding; return QSize(maxIconWidth, iconSize); } diff --git a/src/kitemviews/kfileitemlistview.h b/src/kitemviews/kfileitemlistview.h index 8f7ca9063..c8a3385fd 100644 --- a/src/kitemviews/kfileitemlistview.h +++ b/src/kitemviews/kfileitemlistview.h @@ -22,7 +22,7 @@ #include <libdolphin_export.h> -#include <kitemviews/kitemlistview.h> +#include <kitemviews/kstandarditemlistview.h> class KFileItemModelRolesUpdater; class QTimer; @@ -36,18 +36,11 @@ class QTimer; * KItemListView::setWidgetCreator() and KItemListView::setGroupHeaderCreator() * to apply customized generators. */ -class LIBDOLPHINPRIVATE_EXPORT KFileItemListView : public KItemListView +class LIBDOLPHINPRIVATE_EXPORT KFileItemListView : public KStandardItemListView { Q_OBJECT public: - enum Layout - { - IconsLayout, - CompactLayout, - DetailsLayout - }; - KFileItemListView(QGraphicsWidget* parent = 0); virtual ~KFileItemListView(); @@ -62,9 +55,6 @@ public: void setEnlargeSmallPreviews(bool enlarge); bool enlargeSmallPreviews() const; - void setItemLayout(Layout layout); - Layout itemLayout() const; - /** * Sets the list of enabled thumbnail plugins that are used for previews. * Per default all plugins enabled in the KConfigGroup "PreviewSettings" @@ -86,8 +76,10 @@ public: virtual QPixmap createDragPixmap(const QSet<int>& indexes) const; protected: - virtual void initializeItemListWidget(KItemListWidget* item); - virtual bool itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const; + virtual KItemListWidgetCreatorBase* defaultWidgetCreator() const; + virtual KItemListGroupHeaderCreatorBase* defaultGroupHeaderCreator() const; + virtual void onPreviewsShownChanged(bool shown); + virtual void onItemLayoutChanged(ItemLayout current, ItemLayout 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); @@ -111,7 +103,6 @@ private slots: void updateIconSize(); private: - void updateLayoutOfVisibleItems(); void updateTimersInterval(); /** @@ -130,8 +121,6 @@ private: QSize availableIconSize() const; private: - Layout m_itemLayout; - KFileItemModelRolesUpdater* m_modelRolesUpdater; QTimer* m_updateVisibleIndexRangeTimer; QTimer* m_updateIconSizeTimer; diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp index 83df9da70..a5a4f9c0b 100644 --- a/src/kitemviews/kfileitemlistwidget.cpp +++ b/src/kitemviews/kfileitemlistwidget.cpp @@ -19,1202 +19,31 @@ #include "kfileitemlistwidget.h" -#include "kfileitemlistview.h" -#include "kfileitemmodel.h" - -#include <KIcon> -#include <KIconEffect> -#include <KIconLoader> -#include <KLocale> -#include <kratingpainter.h> -#include <KStringHandler> #include <KDebug> +#include <KGlobal> +#include <KLocale> +#include <KIO/MetaData> +#include <QDateTime> -#include "private/kfileitemclipboard.h" -#include "private/kitemlistroleeditor.h" -#include "private/kpixmapmodifier.h" - -#include <QFontMetricsF> -#include <QGraphicsScene> -#include <QGraphicsSceneResizeEvent> -#include <QGraphicsView> -#include <QPainter> -#include <QStyleOption> -#include <QTextLayout> -#include <QTextLine> - -// #define KFILEITEMLISTWIDGET_DEBUG - -KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) : - KItemListWidget(parent), - m_isCut(false), - m_isHidden(false), - m_isExpandable(false), - m_supportsItemExpanding(false), - m_dirtyLayout(true), - m_dirtyContent(true), - m_dirtyContentRoles(), - m_layout(IconsLayout), - m_pixmapPos(), - m_pixmap(), - m_scaledPixmapSize(), - m_iconRect(), - m_hoverPixmap(), - m_textInfo(), - m_textRect(), - m_sortedVisibleRoles(), - m_expansionArea(), - m_customTextColor(), - m_additionalInfoTextColor(), - m_overlay(), - m_rating(), - m_roleEditor(0) -{ -} - -KFileItemListWidget::~KFileItemListWidget() -{ - qDeleteAll(m_textInfo); - m_textInfo.clear(); - - delete m_roleEditor; -} - -void KFileItemListWidget::setLayout(Layout layout) -{ - if (m_layout != layout) { - m_layout = layout; - m_dirtyLayout = true; - updateAdditionalInfoTextColor(); - update(); - } -} - -KFileItemListWidget::Layout KFileItemListWidget::layout() const -{ - return m_layout; -} - -void KFileItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding) -{ - if (m_supportsItemExpanding != supportsItemExpanding) { - m_supportsItemExpanding = supportsItemExpanding; - m_dirtyLayout = true; - update(); - } -} - -bool KFileItemListWidget::supportsItemExpanding() const -{ - return m_supportsItemExpanding; -} - -void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) -{ - const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing(); - - KItemListWidget::paint(painter, option, widget); - - if (!m_expansionArea.isEmpty()) { - drawSiblingsInformation(painter); - } - - 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); - painter->setOpacity(opacity); - } else { - drawPixmap(painter, m_pixmap); - } - - painter->setFont(itemListStyleOption.font); - painter->setPen(textColor()); - const TextInfo* textInfo = m_textInfo.value("name"); - painter->drawStaticText(textInfo->pos, textInfo->staticText); - - bool clipAdditionalInfoBounds = false; - if (m_supportsItemExpanding) { - // Prevent a possible overlapping of the additional-information texts - // with the icon. This can happen if the user has minimized the width - // of the name-column to a very small value. - const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding; - if (textInfo->pos.x() + columnWidth("name") > minX) { - clipAdditionalInfoBounds = true; - painter->save(); - painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip); - } - } - - painter->setPen(m_additionalInfoTextColor); - painter->setFont(itemListStyleOption.font); - - for (int i = 1; i < m_sortedVisibleRoles.count(); ++i) { - const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles[i]); - painter->drawStaticText(textInfo->pos, textInfo->staticText); - } - - if (!m_rating.isNull()) { - const TextInfo* ratingTextInfo = m_textInfo.value("rating"); - QPointF pos = ratingTextInfo->pos; - const Qt::Alignment align = ratingTextInfo->staticText.textOption().alignment(); - if (align & Qt::AlignHCenter) { - pos.rx() += (size().width() - m_rating.width()) / 2; - } - painter->drawPixmap(pos, m_rating); - } - - if (clipAdditionalInfoBounds) { - painter->restore(); - } - -#ifdef KFILEITEMLISTWIDGET_DEBUG - painter->setBrush(Qt::NoBrush); - painter->setPen(Qt::green); - painter->drawRect(m_iconRect); - - painter->setPen(Qt::red); - painter->drawText(QPointF(0, itemListStyleOption.fontMetrics.height()), QString::number(index())); - painter->drawRect(rect()); -#endif -} - -QRectF KFileItemListWidget::iconRect() const -{ - const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing(); - return m_iconRect; -} - -QRectF KFileItemListWidget::textRect() const -{ - const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing(); - return m_textRect; -} - -QRectF KFileItemListWidget::textFocusRect() const -{ - // In the compact- and details-layout a larger textRect() is returned to be aligned - // with the iconRect(). This is useful to have a larger selection/hover-area - // when having a quite large icon size but only one line of text. Still the - // focus rectangle should be shown as narrow as possible around the text. - - const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing(); - - switch (m_layout) { - case CompactLayout: { - QRectF rect = m_textRect; - const TextInfo* topText = m_textInfo.value(m_sortedVisibleRoles.first()); - const TextInfo* bottomText = m_textInfo.value(m_sortedVisibleRoles.last()); - rect.setTop(topText->pos.y()); - rect.setBottom(bottomText->pos.y() + bottomText->staticText.size().height()); - return rect; - } - - case DetailsLayout: { - QRectF rect = m_textRect; - const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles.first()); - rect.setTop(textInfo->pos.y()); - rect.setBottom(textInfo->pos.y() + textInfo->staticText.size().height()); - return rect; - } - - default: - break; - } - - return m_textRect; -} - -QRectF KFileItemListWidget::expansionToggleRect() const -{ - const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing(); - return m_isExpandable ? m_expansionArea : QRectF(); -} - -QRectF KFileItemListWidget::selectionToggleRect() const -{ - const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing(); - - const int iconHeight = styleOption().iconSize; - - int toggleSize = KIconLoader::SizeSmall; - if (iconHeight >= KIconLoader::SizeEnormous) { - toggleSize = KIconLoader::SizeMedium; - } else if (iconHeight >= KIconLoader::SizeLarge) { - toggleSize = KIconLoader::SizeSmallMedium; - } - - QPointF pos = iconRect().topLeft(); - - // If the selection toggle has a very small distance to the - // widget borders, the size of the selection toggle will get - // increased to prevent an accidental clicking of the item - // when trying to hit the toggle. - const int widgetHeight = size().height(); - const int widgetWidth = size().width(); - const int minMargin = 2; - - if (toggleSize + minMargin * 2 >= widgetHeight) { - pos.rx() -= (widgetHeight - toggleSize) / 2; - toggleSize = widgetHeight; - pos.setY(0); - } - if (toggleSize + minMargin * 2 >= widgetWidth) { - pos.ry() -= (widgetWidth - toggleSize) / 2; - toggleSize = widgetWidth; - pos.setX(0); - } - - return QRectF(pos, QSizeF(toggleSize, toggleSize)); -} - -QSizeF KFileItemListWidget::itemSizeHint(int index, const KItemListView* view) -{ - const QHash<QByteArray, QVariant> values = view->model()->data(index); - const KItemListStyleOption& option = view->styleOption(); - const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); - - switch (static_cast<const KFileItemListView*>(view)->itemLayout()) { - case IconsLayout: { - const QString text = KStringHandler::preProcessWrap(values["name"].toString()); - - const qreal itemWidth = view->itemSize().width(); - const qreal maxWidth = itemWidth - 2 * option.padding; - QTextLine line; - - // Calculate the number of lines required for wrapping the name - QTextOption textOption(Qt::AlignHCenter); - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - - qreal textHeight = 0; - QTextLayout layout(text, option.font); - layout.setTextOption(textOption); - layout.beginLayout(); - while ((line = layout.createLine()).isValid()) { - line.setLineWidth(maxWidth); - line.naturalTextWidth(); - textHeight += line.height(); - } - layout.endLayout(); - - // Add one line for each additional information - textHeight += additionalRolesCount * option.fontMetrics.lineSpacing(); - - const qreal maxTextHeight = option.maxTextSize.height(); - if (maxTextHeight > 0 && textHeight > maxTextHeight) { - textHeight = maxTextHeight; - } - - return QSizeF(itemWidth, textHeight + option.iconSize + option.padding * 3); - } - - 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; - - foreach (const QByteArray& role, view->visibleRoles()) { - const QString text = KFileItemListWidget::roleText(role, values); - const qreal requiredWidth = option.fontMetrics.width(text); - maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth); - } - - qreal width = option.padding * 4 + option.iconSize + maximumRequiredWidth; - const qreal maxWidth = option.maxTextSize.width(); - if (maxWidth > 0 && width > maxWidth) { - width = maxWidth; - } - const qreal height = option.padding * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * option.fontMetrics.lineSpacing()); - return QSizeF(width, height); - } - - case DetailsLayout: { - const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height()); - return QSizeF(-1, height); - } - - default: - Q_ASSERT(false); - break; - } - - return QSize(); -} - -qreal KFileItemListWidget::preferredRoleColumnWidth(const QByteArray& role, - int index, - const KItemListView* view) -{ - - const QHash<QByteArray, QVariant> values = view->model()->data(index); - const KItemListStyleOption& option = view->styleOption(); - - const QString text = KFileItemListWidget::roleText(role, values); - qreal width = columnPadding(option); - - if (role == "rating") { - width += preferredRatingSize(option).width(); - } else { - width += option.fontMetrics.width(text); - - if (role == "name") { - // Increase the width by the expansion-toggle and the current expansion level - const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); - width += option.padding + (expandedParentsCount + 1) * view->itemSize().height() + KIconLoader::SizeSmall; - - // Increase the width by the required space for the icon - width += option.padding * 2 + option.iconSize; - } - } - - return width; -} - -void KFileItemListWidget::invalidateCache() -{ - m_dirtyLayout = true; - m_dirtyContent = true; -} - -void KFileItemListWidget::refreshCache() -{ -} - -void KFileItemListWidget::setTextColor(const QColor& color) -{ - if (color != m_customTextColor) { - m_customTextColor = color; - updateAdditionalInfoTextColor(); - update(); - } -} - -QColor KFileItemListWidget::textColor() const -{ - if (m_customTextColor.isValid() && !isSelected()) { - return m_customTextColor; - } - - const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; - const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : QPalette::Text; - return styleOption().palette.brush(group, role).color(); -} - -void KFileItemListWidget::setOverlay(const QPixmap& overlay) -{ - m_overlay = overlay; - m_dirtyContent = true; - update(); -} - -QPixmap KFileItemListWidget::overlay() const -{ - return m_overlay; -} - -void KFileItemListWidget::dataChanged(const QHash<QByteArray, QVariant>& current, - const QSet<QByteArray>& roles) -{ - Q_UNUSED(current); - - m_dirtyContent = true; - - QSet<QByteArray> dirtyRoles; - if (roles.isEmpty()) { - dirtyRoles = visibleRoles().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 QList<QByteArray>& current, - const QList<QByteArray>& previous) -{ - Q_UNUSED(previous); - m_sortedVisibleRoles = current; - m_dirtyLayout = true; -} - -void KFileItemListWidget::columnWidthChanged(const QByteArray& role, - qreal current, - qreal previous) -{ - Q_UNUSED(role); - Q_UNUSED(current); - Q_UNUSED(previous); - m_dirtyLayout = true; -} - -void KFileItemListWidget::styleOptionChanged(const KItemListStyleOption& current, - const KItemListStyleOption& previous) -{ - Q_UNUSED(current); - Q_UNUSED(previous); - updateAdditionalInfoTextColor(); - m_dirtyLayout = true; -} - -void KFileItemListWidget::hoveredChanged(bool hovered) -{ - Q_UNUSED(hovered); - m_dirtyLayout = true; -} - -void KFileItemListWidget::selectedChanged(bool selected) -{ - Q_UNUSED(selected); - updateAdditionalInfoTextColor(); -} - -void KFileItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous) -{ - Q_UNUSED(current); - Q_UNUSED(previous); - m_dirtyLayout = true; -} - -void KFileItemListWidget::editedRoleChanged(const QByteArray& current, const QByteArray& previous) -{ - Q_UNUSED(previous); - - QGraphicsView* parent = scene()->views()[0]; - if (current.isEmpty() || !parent || current != "name") { - if (m_roleEditor) { - emit roleEditingCanceled(index(), current, data().value(current)); - m_roleEditor->deleteLater(); - m_roleEditor = 0; - } - return; - } - - Q_ASSERT(!m_roleEditor); - - const TextInfo* textInfo = m_textInfo.value("name"); - - m_roleEditor = new KItemListRoleEditor(parent); - m_roleEditor->setIndex(index()); - m_roleEditor->setRole(current); - - const QString text = data().value(current).toString(); - m_roleEditor->setPlainText(text); - - QTextOption textOption = textInfo->staticText.textOption(); - m_roleEditor->document()->setDefaultTextOption(textOption); - - // Select the text without MIME-type extension - int selectionLength = text.length(); - - const QString extension = KMimeType::extractKnownExtension(text); - if (!extension.isEmpty()) { - selectionLength -= extension.length() + 1; - } - - if (selectionLength > 0) { - QTextCursor cursor = m_roleEditor->textCursor(); - cursor.movePosition(QTextCursor::StartOfBlock); - cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectionLength); - m_roleEditor->setTextCursor(cursor); - } - - connect(m_roleEditor, SIGNAL(roleEditingCanceled(int,QByteArray,QVariant)), - this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant))); - connect(m_roleEditor, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)), - this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant))); - - // Adjust the geometry of the editor - QRectF rect = roleEditingRect(current); - const int frameWidth = m_roleEditor->frameWidth(); - rect.adjust(-frameWidth, -frameWidth, frameWidth, frameWidth); - rect.translate(pos()); - if (rect.right() > parent->width()) { - rect.setWidth(parent->width() - rect.left()); - } - if (rect.bottom() > parent->height()) { - rect.setHeight(parent->height() - rect.top()); - } - m_roleEditor->setGeometry(rect.toRect()); - m_roleEditor->show(); - m_roleEditor->setFocus(); -} - -void KFileItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) -{ - if (m_roleEditor) { - setEditedRole(QByteArray()); - Q_ASSERT(!m_roleEditor); - } - - KItemListWidget::resizeEvent(event); - - m_dirtyLayout = true; -} - -void KFileItemListWidget::showEvent(QShowEvent* event) -{ - KItemListWidget::showEvent(event); - - // Listen to changes of the clipboard to mark the item as cut/uncut - KFileItemClipboard* clipboard = KFileItemClipboard::instance(); - - const KUrl itemUrl = data().value("url").value<KUrl>(); - m_isCut = clipboard->isCut(itemUrl); - - connect(clipboard, SIGNAL(cutItemsChanged()), - this, SLOT(slotCutItemsChanged())); -} - -void KFileItemListWidget::hideEvent(QHideEvent* event) -{ - disconnect(KFileItemClipboard::instance(), SIGNAL(cutItemsChanged()), - this, SLOT(slotCutItemsChanged())); - - KItemListWidget::hideEvent(event); -} - -void KFileItemListWidget::slotCutItemsChanged() -{ - const KUrl itemUrl = data().value("url").value<KUrl>(); - const bool isCut = KFileItemClipboard::instance()->isCut(itemUrl); - if (m_isCut != isCut) { - m_isCut = isCut; - m_pixmap = QPixmap(); - m_dirtyContent = true; - update(); - } -} - -void KFileItemListWidget::slotRoleEditingCanceled(int index, - const QByteArray& role, - const QVariant& value) -{ - m_roleEditor->deleteLater(); - m_roleEditor = 0; - emit roleEditingCanceled(index, role, value); - setEditedRole(QByteArray()); -} - -void KFileItemListWidget::slotRoleEditingFinished(int index, - const QByteArray& role, - const QVariant& value) -{ - m_roleEditor->deleteLater(); - m_roleEditor = 0; - emit roleEditingFinished(index, role, value); - setEditedRole(QByteArray()); -} - -void KFileItemListWidget::triggerCacheRefreshing() -{ - if ((!m_dirtyContent && !m_dirtyLayout) || index() < 0) { - return; - } - - refreshCache(); - - const QHash<QByteArray, QVariant> values = data(); - m_isExpandable = m_supportsItemExpanding && values["isExpandable"].toBool(); - m_isHidden = values["name"].toString().startsWith(QLatin1Char('.')); - - updateExpansionArea(); - updateTextsCache(); - updatePixmapCache(); - - m_dirtyLayout = false; - m_dirtyContent = false; - m_dirtyContentRoles.clear(); -} - -void KFileItemListWidget::updateExpansionArea() -{ - if (m_supportsItemExpanding) { - const QHash<QByteArray, QVariant> values = data(); - Q_ASSERT(values.contains("expandedParentsCount")); - const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); - if (expandedParentsCount >= 0) { - const qreal widgetHeight = size().height(); - const qreal inc = (widgetHeight - KIconLoader::SizeSmall) / 2; - const qreal x = expandedParentsCount * widgetHeight + inc; - const qreal y = inc; - m_expansionArea = QRectF(x, y, KIconLoader::SizeSmall, KIconLoader::SizeSmall); - return; - } - } - - m_expansionArea = QRectF(); -} - -void KFileItemListWidget::updatePixmapCache() -{ - // Precondition: Requires already updated m_textPos values to calculate - // the remaining height when the alignment is vertical. - - const QSizeF widgetSize = size(); - const bool iconOnTop = (m_layout == IconsLayout); - const KItemListStyleOption& option = styleOption(); - const qreal padding = option.padding; - - const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : option.iconSize; - const int maxIconHeight = option.iconSize; - - const QHash<QByteArray, QVariant> values = data(); - - bool updatePixmap = (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight); - if (!updatePixmap && m_dirtyContent) { - updatePixmap = m_dirtyContentRoles.isEmpty() - || m_dirtyContentRoles.contains("iconPixmap") - || m_dirtyContentRoles.contains("iconName") - || m_dirtyContentRoles.contains("iconOverlays"); - } - - 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, maxIconHeight); - } else if (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight) { - // A custom pixmap has been applied. Assure that the pixmap - // is scaled to the maximum available size. - KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight)); - } - - const QStringList overlays = values["iconOverlays"].toStringList(); - - // Strangely KFileItem::overlays() returns empty string-values, so - // we need to check first whether an overlay must be drawn at all. - // It is more efficient to do it here, as KIconLoader::drawOverlays() - // assumes that an overlay will be drawn and has some additional - // setup time. - foreach (const QString& overlay, overlays) { - if (!overlay.isEmpty()) { - // There is at least one overlay, draw all overlays above m_pixmap - // and cancel the check - KIconLoader::global()->drawOverlays(overlays, m_pixmap, KIconLoader::Desktop); - break; - } - } - - if (m_isCut) { - applyCutEffect(m_pixmap); - } - - if (m_isHidden) { - applyHiddenEffect(m_pixmap); - } - } - - if (!m_overlay.isNull()) { - QPainter painter(&m_pixmap); - painter.drawPixmap(0, m_pixmap.height() - m_overlay.height(), m_overlay); - } - - int scaledIconSize = 0; - if (iconOnTop) { - const TextInfo* textInfo = m_textInfo.value("name"); - scaledIconSize = static_cast<int>(textInfo->pos.y() - 2 * padding); - } else { - const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1; - const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height(); - scaledIconSize = (requiredTextHeight < maxIconHeight) ? - widgetSize.height() - 2 * padding : maxIconHeight; - } - - const int maxScaledIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : scaledIconSize; - const int maxScaledIconHeight = scaledIconSize; - - m_scaledPixmapSize = m_pixmap.size(); - m_scaledPixmapSize.scale(maxScaledIconWidth, maxScaledIconHeight, Qt::KeepAspectRatio); - - if (iconOnTop) { - // Center horizontally and align on bottom within the icon-area - m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2); - m_pixmapPos.setY(padding + scaledIconSize - m_scaledPixmapSize.height()); - } else { - // Center horizontally and vertically within the icon-area - const TextInfo* textInfo = m_textInfo.value("name"); - m_pixmapPos.setX(textInfo->pos.x() - 2 * padding - - (scaledIconSize + m_scaledPixmapSize.width()) / 2); - m_pixmapPos.setY(padding - + (scaledIconSize - m_scaledPixmapSize.height()) / 2); - } - - m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)); - - // Prepare the pixmap that is used when the item gets hovered - if (isHovered()) { - 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; - } - - qDeleteAll(m_textInfo); - m_textInfo.clear(); - for (int i = 0; i < m_sortedVisibleRoles.count(); ++i) { - TextInfo* textInfo = new TextInfo(); - textInfo->staticText.setTextFormat(Qt::PlainText); - textInfo->staticText.setPerformanceHint(QStaticText::AggressiveCaching); - textInfo->staticText.setTextOption(textOption); - m_textInfo.insert(m_sortedVisibleRoles[i], textInfo); - } - - switch (m_layout) { - case IconsLayout: updateIconsLayoutTextCache(); break; - case CompactLayout: updateCompactLayoutTextCache(); break; - case DetailsLayout: updateDetailsLayoutTextCache(); break; - default: Q_ASSERT(false); break; - } - - const TextInfo* ratingTextInfo = m_textInfo.value("rating"); - if (ratingTextInfo) { - // The text of the rating-role has been set to empty to get - // replaced by a rating-image showing the rating as stars. - const KItemListStyleOption& option = styleOption(); - QSizeF ratingSize = preferredRatingSize(option); - - const qreal availableWidth = (m_layout == DetailsLayout) - ? columnWidth("rating") - columnPadding(option) - : m_textRect.width(); - if (ratingSize.width() > availableWidth) { - ratingSize.rwidth() = availableWidth; - } - m_rating = QPixmap(ratingSize.toSize()); - m_rating.fill(Qt::transparent); - - QPainter painter(&m_rating); - const QRect rect(0, 0, m_rating.width(), m_rating.height()); - const int rating = data().value("rating").toInt(); - KRatingPainter::paintRating(&painter, rect, Qt::AlignJustify | Qt::AlignVCenter, rating); - } else if (!m_rating.isNull()) { - m_rating = QPixmap(); - } -} - -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 padding = option.padding; - const qreal maxWidth = size().width() - 2 * padding; - const qreal widgetHeight = size().height(); - const qreal lineSpacing = option.fontMetrics.lineSpacing(); - - // Initialize properties for the "name" role. It will be used as anchor - // for initializing the position of the other roles. - TextInfo* nameTextInfo = m_textInfo.value("name"); - const QString nameText = KStringHandler::preProcessWrap(values["name"].toString()); - nameTextInfo->staticText.setText(nameText); - - // Calculate the number of lines required for the name and the required width - qreal nameWidth = 0; - qreal nameHeight = 0; - QTextLine line; - - const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0); - const int maxNameLines = (option.maxTextSize.height() / int(lineSpacing)) - additionalRolesCount; - - QTextLayout layout(nameTextInfo->staticText.text(), option.font); - layout.setTextOption(nameTextInfo->staticText.textOption()); - layout.beginLayout(); - int nameLineIndex = 0; - while ((line = layout.createLine()).isValid()) { - line.setLineWidth(maxWidth); - nameWidth = qMax(nameWidth, line.naturalTextWidth()); - nameHeight += line.height(); - - ++nameLineIndex; - if (nameLineIndex == maxNameLines) { - // The maximum number of textlines has been reached. If this is - // the case provide an elided text if necessary. - const int textLength = line.textStart() + line.textLength(); - if (textLength < nameText.length()) { - // Elide the last line of the text - QString lastTextLine = nameText.mid(line.textStart(), line.textLength()); - lastTextLine = option.fontMetrics.elidedText(lastTextLine, - Qt::ElideRight, - line.naturalTextWidth() - 1); - const QString elidedText = nameText.left(line.textStart()) + lastTextLine; - nameTextInfo->staticText.setText(elidedText); - } - break; - } - } - layout.endLayout(); - - // Use one line for each additional information - nameTextInfo->staticText.setTextWidth(maxWidth); - nameTextInfo->pos = QPointF(padding, widgetHeight - - nameHeight - - additionalRolesCount * lineSpacing - - padding); - m_textRect = QRectF(padding + (maxWidth - nameWidth) / 2, - nameTextInfo->pos.y(), - nameWidth, - nameHeight); - - // Calculate the position for each additional information - qreal y = nameTextInfo->pos.y() + nameHeight; - foreach (const QByteArray& role, m_sortedVisibleRoles) { - if (role == "name") { - continue; - } - - const QString text = roleText(role, values); - TextInfo* textInfo = m_textInfo.value(role); - textInfo->staticText.setText(text); - - qreal requiredWidth = 0; - - QTextLayout layout(text, option.font); - QTextOption textOption; - textOption.setWrapMode(QTextOption::NoWrap); - layout.setTextOption(textOption); - - layout.beginLayout(); - QTextLine textLine = layout.createLine(); - if (textLine.isValid()) { - textLine.setLineWidth(maxWidth); - requiredWidth = textLine.naturalTextWidth(); - if (requiredWidth > maxWidth) { - const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth); - textInfo->staticText.setText(elidedText); - requiredWidth = option.fontMetrics.width(elidedText); - } - } - layout.endLayout(); - - textInfo->pos = QPointF(padding, y); - textInfo->staticText.setTextWidth(maxWidth); - - const QRectF textRect(padding + (maxWidth - requiredWidth) / 2, y, requiredWidth, lineSpacing); - m_textRect |= textRect; - - y += lineSpacing; - } - - // Add a padding to the text rectangle - m_textRect.adjust(-padding, -padding, padding, padding); -} - -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 lineSpacing = option.fontMetrics.lineSpacing(); - const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * lineSpacing; - const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.padding : option.iconSize; - - qreal maximumRequiredTextWidth = 0; - const qreal x = option.padding * 3 + scaledIconSize; - qreal y = qRound((widgetHeight - textLinesHeight) / 2); - const qreal maxWidth = size().width() - x - option.padding; - foreach (const QByteArray& role, m_sortedVisibleRoles) { - const QString text = roleText(role, values); - TextInfo* textInfo = m_textInfo.value(role); - textInfo->staticText.setText(text); - - qreal requiredWidth = option.fontMetrics.width(text); - if (requiredWidth > maxWidth) { - requiredWidth = maxWidth; - const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth); - textInfo->staticText.setText(elidedText); - } - - textInfo->pos = QPointF(x, y); - textInfo->staticText.setTextWidth(maxWidth); - - maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth); - - y += lineSpacing; - } - - m_textRect = QRectF(x - option.padding, 0, maximumRequiredTextWidth + 2 * option.padding, 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_textRect = QRectF(); - - const KItemListStyleOption& option = styleOption(); - const QHash<QByteArray, QVariant> values = data(); - - const qreal widgetHeight = size().height(); - const int scaledIconSize = widgetHeight - 2 * option.padding; - const int fontHeight = option.fontMetrics.height(); - - const qreal columnWidthInc = columnPadding(option); - qreal firstColumnInc = scaledIconSize; - if (m_supportsItemExpanding) { - firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2; - } else { - firstColumnInc += option.padding; - } - - qreal x = firstColumnInc; - const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2); - - foreach (const QByteArray& role, m_sortedVisibleRoles) { - const RoleType type = roleType(role); - - QString text = roleText(role, values); - - // Elide the text in case it does not fit into the available column-width - qreal requiredWidth = option.fontMetrics.width(text); - const qreal roleWidth = columnWidth(role); - qreal availableTextWidth = roleWidth - columnWidthInc; - if (type == Name) { - availableTextWidth -= firstColumnInc; - } - - if (requiredWidth > availableTextWidth) { - text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth); - requiredWidth = option.fontMetrics.width(text); - } - - TextInfo* textInfo = m_textInfo.value(role); - textInfo->staticText.setText(text); - textInfo->pos = QPointF(x + columnWidthInc / 2, y); - x += roleWidth; - - switch (type) { - case Name: { - const qreal textWidth = option.extendedSelectionRegion - ? size().width() - textInfo->pos.x() - : requiredWidth + 2 * option.padding; - m_textRect = QRectF(textInfo->pos.x() - option.padding, 0, - textWidth, 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 -= firstColumnInc; - break; - } - case Size: - // The values for the size should be right aligned - textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc; - break; - - default: - break; - } - } -} - -void KFileItemListWidget::updateAdditionalInfoTextColor() -{ - QColor c1; - if (m_customTextColor.isValid()) { - c1 = m_customTextColor; - } else if (isSelected() && m_layout != DetailsLayout) { - c1 = styleOption().palette.highlightedText().color(); - } else { - c1 = styleOption().palette.text().color(); - } - - // 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 c1 is slightly mixed with the background color. - const QColor c2 = styleOption().palette.base().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); -} - -void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap) -{ - if (m_scaledPixmapSize != pixmap.size()) { - QPixmap scaledPixmap = pixmap; - KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize); - painter->drawPixmap(m_pixmapPos, scaledPixmap); - -#ifdef KFILEITEMLISTWIDGET_DEBUG - painter->setPen(Qt::blue); - painter->drawRect(QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize))); -#endif - } else { - painter->drawPixmap(m_pixmapPos, pixmap); - } -} - -void KFileItemListWidget::drawSiblingsInformation(QPainter* painter) -{ - const int siblingSize = size().height(); - const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2; - QRect siblingRect(x, 0, siblingSize, siblingSize); - - QStyleOption option; - bool isItemSibling = true; - - const QBitArray siblings = siblingsInformation(); - for (int i = siblings.count() - 1; i >= 0; --i) { - option.rect = siblingRect; - option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None; - - if (isItemSibling) { - option.state |= QStyle::State_Item; - if (m_isExpandable) { - option.state |= QStyle::State_Children; - } - if (data()["isExpanded"].toBool()) { - option.state |= QStyle::State_Open; - } - isItemSibling = false; - } - - style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter); - - siblingRect.translate(-siblingRect.width(), 0); - } -} - -QRectF KFileItemListWidget::roleEditingRect(const QByteArray& role) const -{ - const TextInfo* textInfo = m_textInfo.value(role); - if (!textInfo) { - return QRectF(); - } - - QRectF rect(textInfo->pos, textInfo->staticText.size()); - if (m_layout == DetailsLayout) { - rect.setWidth(columnWidth(role) - rect.x()); - } - - return rect; -} - -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; -} - -void KFileItemListWidget::applyCutEffect(QPixmap& pixmap) -{ - KIconEffect* effect = KIconLoader::global()->iconEffect(); - pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); -} - -void KFileItemListWidget::applyHiddenEffect(QPixmap& pixmap) +KFileItemListWidgetInformant::KFileItemListWidgetInformant() : + KStandardItemListWidgetInformant() { - KIconEffect::semiTransparent(pixmap); } -KFileItemListWidget::RoleType KFileItemListWidget::roleType(const QByteArray& role) +KFileItemListWidgetInformant::~KFileItemListWidgetInformant() { - static QHash<QByteArray, RoleType> rolesHash; - if (rolesHash.isEmpty()) { - rolesHash.insert("name", Name); - rolesHash.insert("size", Size); - rolesHash.insert("date", Date); - rolesHash.insert("rating", Rating); - } - - return rolesHash.value(role, Generic); } -QString KFileItemListWidget::roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values) +QString KFileItemListWidgetInformant::roleText(const QByteArray& role, + const QHash<QByteArray, QVariant>& values) const { QString text; const QVariant roleValue = values.value(role); - switch (roleType(role)) { - case Size: { + // Implementation note: In case if more roles require a custom handling + // use a hash + switch for a linear runtime. + + if (role == "size") { if (values.value("isDir").toBool()) { // The item represents a directory. Show the number of sub directories // instead of the file size of the directory. @@ -1230,41 +59,33 @@ QString KFileItemListWidget::roleText(const QByteArray& role, const QHash<QByteA const KIO::filesize_t size = roleValue.value<KIO::filesize_t>(); text = KGlobal::locale()->formatByteSize(size); } - break; - } - - case Date: { + } else if (role == "date") { const QDateTime dateTime = roleValue.toDateTime(); text = KGlobal::locale()->formatDateTime(dateTime); - break; + } else { + text = KStandardItemListWidgetInformant::roleText(role, values); } - case Rating: - // Always use an empty text, as the rating is shown by the image m_rating. - break; - - case Name: - case Generic: - text = roleValue.toString(); - break; + return text; +} - default: - Q_ASSERT(false); - break; - } +KFileItemListWidget::KFileItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : + KStandardItemListWidget(informant, parent) +{ +} - return text; +KFileItemListWidget::~KFileItemListWidget() +{ } -QSizeF KFileItemListWidget::preferredRatingSize(const KItemListStyleOption& option) +KItemListWidgetInformant* KFileItemListWidget::createInformant() { - const qreal height = option.fontMetrics.ascent(); - return QSizeF(height * 5, height); + return new KFileItemListWidgetInformant(); } -qreal KFileItemListWidget::columnPadding(const KItemListStyleOption& option) +bool KFileItemListWidget::isRoleRightAligned(const QByteArray& role) const { - return option.padding * 6; + return role == "size"; } #include "kfileitemlistwidget.moc" diff --git a/src/kitemviews/kfileitemlistwidget.h b/src/kitemviews/kfileitemlistwidget.h index 33d348bab..4bd375481 100644 --- a/src/kitemviews/kfileitemlistwidget.h +++ b/src/kitemviews/kfileitemlistwidget.h @@ -22,188 +22,30 @@ #include <libdolphin_export.h> -#include <kitemviews/kitemlistwidget.h> +#include <kitemviews/kstandarditemlistwidget.h> -#include <QPixmap> -#include <QPointF> -#include <QStaticText> +class LIBDOLPHINPRIVATE_EXPORT KFileItemListWidgetInformant : public KStandardItemListWidgetInformant +{ +public: + KFileItemListWidgetInformant(); + virtual ~KFileItemListWidgetInformant(); -class KItemListRoleEditor; -class KItemListStyleOption; -class KItemListView; +protected: + virtual QString roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values) const; +}; -class LIBDOLPHINPRIVATE_EXPORT KFileItemListWidget : public KItemListWidget +class LIBDOLPHINPRIVATE_EXPORT KFileItemListWidget : public KStandardItemListWidget { Q_OBJECT public: - enum Layout - { - IconsLayout, - CompactLayout, - DetailsLayout - }; - - KFileItemListWidget(QGraphicsItem* parent); + KFileItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent); virtual ~KFileItemListWidget(); - void setLayout(Layout layout); - Layout layout() const; - - void setSupportsItemExpanding(bool supportsItemExpanding); - bool supportsItemExpanding() const; - - virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); - - virtual QRectF iconRect() const; - virtual QRectF textRect() const; - virtual QRectF textFocusRect() const; - virtual QRectF expansionToggleRect() const; - virtual QRectF selectionToggleRect() const; - - /** - * Implementation of KItemListWidgetCreatorBase::itemSizeHint() when - * using the KItemListWidgetCreator-template. - * - * @see KItemListView - */ - static QSizeF itemSizeHint(int index, const KItemListView* view); - - /** - * Implementation of KItemListWidgetCreatorBase::preferredRoleColumnWidth() when - * using the KItemListWidgetCreator-template. - * - * @see KItemListView - */ - static qreal preferredRoleColumnWidth(const QByteArray& role, int index, const KItemListView* view); + static KItemListWidgetInformant* createInformant(); protected: - /** - * Invalidates the cache which results in calling KFileItemListWidget::refreshCache() as - * soon as the item need to gets repainted. - */ - void invalidateCache(); - - /** - * Is called if the cache got invalidated by KFileItemListWidget::invalidateCache(). - * The default implementation is empty. - */ - virtual void refreshCache(); - - void setTextColor(const QColor& color); - QColor textColor() const; - - void setOverlay(const QPixmap& overlay); - QPixmap overlay() const; - - virtual void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>()); - virtual void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous); - virtual void columnWidthChanged(const QByteArray& role, qreal current, qreal previous); - virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous); - virtual void hoveredChanged(bool hovered); - virtual void selectedChanged(bool selected); - virtual void siblingsInformationChanged(const QBitArray& current, const QBitArray& previous); - virtual void editedRoleChanged(const QByteArray& current, const QByteArray& previous); - virtual void resizeEvent(QGraphicsSceneResizeEvent* event); - virtual void showEvent(QShowEvent* event); - virtual void hideEvent(QHideEvent* event); - -private slots: - void slotCutItemsChanged(); - void slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value); - void slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value); - -private: - /** - * Typedefinitions for roles that require a special handling - * and must be accessible in a fast way. The mapping of a - * QByteArray role to the type is done by KFileItemListWidget::roleType(). - */ - enum RoleType { - Name, - Size, - Date, - Rating, - Generic // Mandatory last entry - }; - - void triggerCacheRefreshing(); - void updateExpansionArea(); - void updatePixmapCache(); - - void updateTextsCache(); - void updateIconsLayoutTextCache(); - void updateCompactLayoutTextCache(); - void updateDetailsLayoutTextCache(); - - void updateAdditionalInfoTextColor(); - - void drawPixmap(QPainter* painter, const QPixmap& pixmap); - void drawSiblingsInformation(QPainter* painter); - - QRectF roleEditingRect(const QByteArray &role) const; - - static QPixmap pixmapForIcon(const QString& name, int size); - static void applyCutEffect(QPixmap& pixmap); - static void applyHiddenEffect(QPixmap& pixmap); - static RoleType roleType(const QByteArray& role); - - /** - * @return Preferred size of the rating-image based on the given - * style-option. The height of the font is taken as - * reference. - */ - static QSizeF preferredRatingSize(const KItemListStyleOption& option); - - /** - * @return Horizontal padding in pixels that is added to the required width of - * a column to display the content. - */ - static qreal columnPadding(const KItemListStyleOption& option); - - /** - * @return Shown string for the role \p role of the item with the values \p values. - */ - static QString roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values); - -private: - bool m_isCut; - bool m_isHidden; - bool m_isExpandable; - bool m_supportsItemExpanding; - - bool m_dirtyLayout; - bool m_dirtyContent; - QSet<QByteArray> m_dirtyContentRoles; - - Layout m_layout; - QPointF m_pixmapPos; - QPixmap m_pixmap; - QSize m_scaledPixmapSize; - - QRectF m_iconRect; // Cache for KItemListWidget::iconRect() - QPixmap m_hoverPixmap; // Cache for modified m_pixmap when hovering the item - - struct TextInfo - { - QPointF pos; - QStaticText staticText; - }; - QHash<QByteArray, TextInfo*> m_textInfo; - - QRectF m_textRect; - - QList<QByteArray> m_sortedVisibleRoles; - - QRectF m_expansionArea; - - QColor m_customTextColor; - QColor m_additionalInfoTextColor; - - QPixmap m_overlay; - QPixmap m_rating; - - KItemListRoleEditor* m_roleEditor; + virtual bool isRoleRightAligned(const QByteArray& role) const; }; #endif diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 8710f4440..ac8fc3bef 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -35,7 +35,7 @@ // #define KFILEITEMMODEL_DEBUG KFileItemModel::KFileItemModel(QObject* parent) : - KItemModelBase("name", parent), + KItemModelBase("text", parent), m_dirLister(0), m_naturalSorting(KGlobalSettings::naturalSorting()), m_sortDirsFirst(true), @@ -77,7 +77,7 @@ KFileItemModel::KFileItemModel(QObject* parent) : resetRoles(); m_requestRole[NameRole] = true; m_requestRole[IsDirRole] = true; - m_roles.insert("name"); + m_roles.insert("text"); m_roles.insert("isDir"); // For slow KIO-slaves like used for searching it makes sense to show results periodically even @@ -253,12 +253,12 @@ int KFileItemModel::indexForKeyboardSearch(const QString& text, int startFromInd { startFromIndex = qMax(0, startFromIndex); for (int i = startFromIndex; i < count(); ++i) { - if (data(i)["name"].toString().startsWith(text, Qt::CaseInsensitive)) { + if (data(i)["text"].toString().startsWith(text, Qt::CaseInsensitive)) { return i; } } for (int i = 0; i < startFromIndex; ++i) { - if (data(i)["name"].toString().startsWith(text, Qt::CaseInsensitive)) { + if (data(i)["text"].toString().startsWith(text, Qt::CaseInsensitive)) { return i; } } @@ -1165,7 +1165,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item) } if (m_requestRole[NameRole]) { - data.insert("name", item.text()); + data.insert("text", item.text()); } if (m_requestRole[SizeRole]) { @@ -1523,7 +1523,7 @@ QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const continue; } - const QString name = m_itemData.at(i)->values.value("name").toString(); + const QString name = m_itemData.at(i)->values.value("text").toString(); // Use the first character of the name as group indication QChar newFirstChar = name.at(0).toUpper(); @@ -1873,7 +1873,7 @@ const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count) static const RoleInfoMap rolesInfoMap[] = { // | role | roleType | role translation | group translation | requires Nepomuk | requires indexer { 0, NoRole, 0, 0, 0, 0, false, false }, - { "name", NameRole, I18N_NOOP2_NOSTRIP("@label", "Name"), 0, 0, false, false }, + { "text", NameRole, I18N_NOOP2_NOSTRIP("@label", "Name"), 0, 0, false, false }, { "size", SizeRole, I18N_NOOP2_NOSTRIP("@label", "Size"), 0, 0, false, false }, { "date", DateRole, I18N_NOOP2_NOSTRIP("@label", "Date"), 0, 0, false, false }, { "type", TypeRole, I18N_NOOP2_NOSTRIP("@label", "Type"), 0, 0, false, false }, diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index b29c76c92..2683336e1 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -108,7 +108,7 @@ public: virtual bool supportsDropping(int index) const; /** @reimp */ - virtual QString roleDescription(const QByteArray& typeForRole) const; + virtual QString roleDescription(const QByteArray& role) const; /** @reimp */ virtual QList<QPair<int, QVariant> > groups() const; diff --git a/src/kitemviews/kitemlistcontainer.cpp b/src/kitemviews/kitemlistcontainer.cpp index 5a485b62c..5500851c8 100644 --- a/src/kitemviews/kitemlistcontainer.cpp +++ b/src/kitemviews/kitemlistcontainer.cpp @@ -67,8 +67,6 @@ void KItemListContainerViewport::wheelEvent(QWheelEvent* event) event->ignore(); } - - KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* parent) : QAbstractScrollArea(parent), m_controller(controller), @@ -77,20 +75,32 @@ KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* { Q_ASSERT(controller); controller->setParent(this); - initialize(); -} -KItemListContainer::KItemListContainer(QWidget* parent) : - QAbstractScrollArea(parent), - m_controller(0), - m_horizontalSmoothScroller(0), - m_verticalSmoothScroller(0) -{ - initialize(); + QGraphicsView* graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this); + setViewport(graphicsView); + + m_horizontalSmoothScroller = new KItemListSmoothScroller(horizontalScrollBar(), this); + m_verticalSmoothScroller = new KItemListSmoothScroller(verticalScrollBar(), this); + + if (controller->model()) { + slotModelChanged(controller->model(), 0); + } + if (controller->view()) { + slotViewChanged(controller->view(), 0); + } + + connect(controller, SIGNAL(modelChanged(KItemModelBase*,KItemModelBase*)), + this, SLOT(slotModelChanged(KItemModelBase*,KItemModelBase*))); + connect(controller, SIGNAL(viewChanged(KItemListView*,KItemListView*)), + this, SLOT(slotViewChanged(KItemListView*,KItemListView*))); } KItemListContainer::~KItemListContainer() { + // Don't rely on the QObject-order to delete the controller, otherwise + // the QGraphicsScene might get deleted before the view. + delete m_controller; + m_controller = 0; } KItemListController* KItemListContainer::controller() const @@ -98,6 +108,33 @@ KItemListController* KItemListContainer::controller() const return m_controller; } +void KItemListContainer::setEnabledFrame(bool enable) +{ + QGraphicsView* graphicsView = qobject_cast<QGraphicsView*>(viewport()); + if (enable) { + setFrameShape(QFrame::StyledPanel); + graphicsView->setPalette(palette()); + graphicsView->viewport()->setAutoFillBackground(true); + } else { + setFrameShape(QFrame::NoFrame); + // Make the background of the container transparent and apply the window-text color + // to the text color, so that enough contrast is given for all color + // schemes + QPalette p = graphicsView->palette(); + p.setColor(QPalette::Active, QPalette::Text, p.color(QPalette::Active, QPalette::WindowText)); + p.setColor(QPalette::Inactive, QPalette::Text, p.color(QPalette::Inactive, QPalette::WindowText)); + p.setColor(QPalette::Disabled, QPalette::Text, p.color(QPalette::Disabled, QPalette::WindowText)); + graphicsView->setPalette(p); + graphicsView->viewport()->setAutoFillBackground(false); + } +} + +bool KItemListContainer::enabledFrame() const +{ + const QGraphicsView* graphicsView = qobject_cast<QGraphicsView*>(viewport()); + return graphicsView->autoFillBackground(); +} + void KItemListContainer::keyPressEvent(QKeyEvent* event) { // TODO: We should find a better way to handle the key press events in the view. @@ -355,29 +392,4 @@ void KItemListContainer::updateScrollOffsetScrollBarPolicy() } } -void KItemListContainer::initialize() -{ - if (m_controller) { - if (m_controller->model()) { - slotModelChanged(m_controller->model(), 0); - } - if (m_controller->view()) { - slotViewChanged(m_controller->view(), 0); - } - } else { - 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); - - m_horizontalSmoothScroller = new KItemListSmoothScroller(horizontalScrollBar(), this); - m_verticalSmoothScroller = new KItemListSmoothScroller(verticalScrollBar(), this); -} - #include "kitemlistcontainer.moc" diff --git a/src/kitemviews/kitemlistcontainer.h b/src/kitemviews/kitemlistcontainer.h index b41f48a7f..474a9ecc0 100644 --- a/src/kitemviews/kitemlistcontainer.h +++ b/src/kitemviews/kitemlistcontainer.h @@ -37,6 +37,8 @@ class QPropertyAnimation; /** * @brief Provides a QWidget based scrolling view for a KItemListController. * + * The model and view are maintained by the KItemListController. + * * @see KItemListController */ class LIBDOLPHINPRIVATE_EXPORT KItemListContainer : public QAbstractScrollArea @@ -44,12 +46,19 @@ class LIBDOLPHINPRIVATE_EXPORT KItemListContainer : public QAbstractScrollArea Q_OBJECT public: + /** + * @param controller Controller that maintains the model and the view. + * The KItemListContainer takes ownership of the controller + * (the parent will be set to the KItemListContainer). + * @param parent Optional parent widget. + */ explicit KItemListContainer(KItemListController* controller, QWidget* parent = 0); - KItemListContainer(QWidget* parent = 0); virtual ~KItemListContainer(); - KItemListController* controller() const; + void setEnabledFrame(bool enable); + bool enabledFrame() const; + protected: virtual void keyPressEvent(QKeyEvent* event); virtual void showEvent(QShowEvent* event); @@ -66,7 +75,6 @@ private slots: void updateItemOffsetScrollBar(); private: - void initialize(); void updateGeometries(); void updateSmoothScrollers(Qt::Orientation orientation); diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index c0f565b4d..011a3b57d 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -41,7 +41,7 @@ #include <QMimeData> #include <QTimer> -KItemListController::KItemListController(QObject* parent) : +KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) : QObject(parent), m_singleClickActivation(KGlobalSettings::singleClick()), m_selectionTogglePressed(false), @@ -65,10 +65,20 @@ KItemListController::KItemListController(QObject* parent) : m_autoActivationTimer->setSingleShot(true); m_autoActivationTimer->setInterval(-1); connect(m_autoActivationTimer, SIGNAL(timeout()), this, SLOT(slotAutoActivationTimeout())); + + setModel(model); + setView(view); } KItemListController::~KItemListController() { + setView(0); + delete m_view; + m_view = 0; + + setModel(0); + delete m_model; + m_model = 0; } void KItemListController::setModel(KItemModelBase* model) @@ -79,6 +89,9 @@ void KItemListController::setModel(KItemModelBase* model) KItemModelBase* oldModel = m_model; m_model = model; + if (m_model) { + m_model->setParent(this); + } if (m_view) { m_view->setModel(m_model); @@ -116,6 +129,7 @@ void KItemListController::setView(KItemListView* view) m_view->setController(this); m_view->setModel(m_model); connect(m_view, SIGNAL(scrollOffsetChanged(qreal,qreal)), this, SLOT(slotViewScrollOffsetChanged(qreal,qreal))); + updateExtendedSelectionRegion(); } emit viewChanged(m_view, oldView); @@ -129,6 +143,7 @@ KItemListView* KItemListController::view() const void KItemListController::setSelectionBehavior(SelectionBehavior behavior) { m_selectionBehavior = behavior; + updateExtendedSelectionRegion(); } KItemListController::SelectionBehavior KItemListController::selectionBehavior() const @@ -1159,4 +1174,16 @@ qreal KItemListController::keyboardAnchorPos(int index) const return 0; } +void KItemListController::updateExtendedSelectionRegion() +{ + if (m_view) { + const bool extend = (m_selectionBehavior != MultiSelection); + KItemListStyleOption option = m_view->styleOption(); + if (option.extendedSelectionRegion != extend) { + option.extendedSelectionRegion = extend; + m_view->setStyleOption(option); + } + } +} + #include "kitemlistcontroller.moc" diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h index b44fcca3c..db31d50c3 100644 --- a/src/kitemviews/kitemlistcontroller.h +++ b/src/kitemviews/kitemlistcontroller.h @@ -72,7 +72,12 @@ public: MultiSelection }; - KItemListController(QObject* parent = 0); + /** + * @param model Model of the controller. The ownership is passed to the controller. + * @param view View of the controller. The ownership is passed to the controller. + * @param parent Optional parent object. + */ + KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent = 0); virtual ~KItemListController(); void setModel(KItemModelBase* model); @@ -261,6 +266,13 @@ private: */ qreal keyboardAnchorPos(int index) const; + /** + * Dependent on the selection-behavior the extendedSelectionRegion-property + * of the KItemListStyleOption from the view should be adjusted: If no + * rubberband selection is used the property should be enabled. + */ + void updateExtendedSelectionRegion(); + private: bool m_singleClickActivation; bool m_selectionTogglePressed; diff --git a/src/kitemviews/kitemliststyleoption.cpp b/src/kitemviews/kitemliststyleoption.cpp index 36cfeb088..ac2587962 100644 --- a/src/kitemviews/kitemliststyleoption.cpp +++ b/src/kitemviews/kitemliststyleoption.cpp @@ -26,10 +26,10 @@ KItemListStyleOption::KItemListStyleOption() : font(), fontMetrics(QFont()), palette(), - padding(0), - horizontalMargin(0), - verticalMargin(0), - iconSize(KIconLoader::SizeMedium), + padding(-1), + horizontalMargin(-1), + verticalMargin(-1), + iconSize(-1), extendedSelectionRegion(false), maxTextSize() { diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index d53c24589..934edf050 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -22,6 +22,7 @@ #include "kitemlistview.h" +#include <KDebug> #include "kitemlistcontroller.h" #include "kitemlistheader.h" #include "kitemlistselectionmanager.h" @@ -33,8 +34,6 @@ #include "private/kitemlistviewlayouter.h" #include "private/kitemlistviewanimation.h" -#include <KDebug> - #include <QCursor> #include <QGraphicsSceneMouseEvent> #include <QPainter> @@ -113,91 +112,17 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : KItemListView::~KItemListView() { - delete m_sizeHintResolver; - m_sizeHintResolver = 0; -} - -void KItemListView::setScrollOrientation(Qt::Orientation orientation) -{ - const Qt::Orientation previousOrientation = m_layouter->scrollOrientation(); - if (orientation == previousOrientation) { - return; - } - - m_layouter->setScrollOrientation(orientation); - m_animation->setScrollOrientation(orientation); - m_sizeHintResolver->clearCache(); - - if (m_grouped) { - QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups); - while (it.hasNext()) { - it.next(); - it.value()->setScrollOrientation(orientation); - } - updateGroupHeaderHeight(); - - } - - doLayout(NoAnimation); - - onScrollOrientationChanged(orientation, previousOrientation); - emit scrollOrientationChanged(orientation, previousOrientation); -} - -Qt::Orientation KItemListView::scrollOrientation() const -{ - return m_layouter->scrollOrientation(); -} + // The group headers are children of the widgets created by + // widgetCreator(). So it is mandatory to delete the group headers + // first. + delete m_groupHeaderCreator; + m_groupHeaderCreator = 0; -void KItemListView::setItemSize(const QSizeF& size) -{ - const QSizeF previousSize = m_itemSize; - if (size == previousSize) { - return; - } + delete m_widgetCreator; + m_widgetCreator = 0; - // Skip animations when the number of rows or columns - // are changed in the grid layout. Although the animation - // engine can handle this usecase, it looks obtrusive. - const bool animate = !changesItemGridLayout(m_layouter->size(), - size, - m_layouter->itemMargin()); - - const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) && - (( m_itemSize.isEmpty() && !size.isEmpty()) || - (!m_itemSize.isEmpty() && size.isEmpty())); - - m_itemSize = size; - - if (alternateBackgroundsChanged) { - // For an empty item size alternate backgrounds are drawn if more than - // one role is shown. Assure that the backgrounds for visible items are - // updated when changing the size in this context. - updateAlternateBackgrounds(); - } - - if (size.isEmpty()) { - if (m_headerWidget->automaticColumnResizing()) { - updatePreferredColumnWidths(); - } else { - // Only apply the changed height and respect the header widths - // set by the user - const qreal currentWidth = m_layouter->itemSize().width(); - const QSizeF newSize(currentWidth, size.height()); - m_layouter->setItemSize(newSize); - } - } else { - m_layouter->setItemSize(size); - } - - m_sizeHintResolver->clearCache(); - doLayout(animate ? Animation : NoAnimation); - onItemSizeChanged(size, previousSize); -} - -QSizeF KItemListView::itemSize() const -{ - return m_itemSize; + delete m_sizeHintResolver; + m_sizeHintResolver = 0; } void KItemListView::setScrollOffset(qreal offset) @@ -354,62 +279,39 @@ KItemModelBase* KItemListView::model() const void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator) { + if (m_widgetCreator) { + delete m_widgetCreator; + } m_widgetCreator = widgetCreator; } KItemListWidgetCreatorBase* KItemListView::widgetCreator() const { + if (!m_widgetCreator) { + m_widgetCreator = defaultWidgetCreator(); + } return m_widgetCreator; } void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator) { + if (m_groupHeaderCreator) { + delete m_groupHeaderCreator; + } m_groupHeaderCreator = groupHeaderCreator; } KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const { + if (!m_groupHeaderCreator) { + m_groupHeaderCreator = defaultGroupHeaderCreator(); + } return m_groupHeaderCreator; } -void KItemListView::setStyleOption(const KItemListStyleOption& option) +QSizeF KItemListView::itemSize() const { - const KItemListStyleOption previousOption = m_styleOption; - m_styleOption = option; - - bool animate = true; - const QSizeF margin(option.horizontalMargin, option.verticalMargin); - if (margin != m_layouter->itemMargin()) { - // Skip animations when the number of rows or columns - // are changed in the grid layout. Although the animation - // engine can handle this usecase, it looks obtrusive. - animate = !changesItemGridLayout(m_layouter->size(), - m_layouter->itemSize(), - margin); - m_layouter->setItemMargin(margin); - } - - if (m_grouped) { - updateGroupHeaderHeight(); - } - - if (animate && previousOption.maxTextSize != option.maxTextSize) { - // Animating a change of the maximum text size just results in expensive - // temporary eliding and clipping operations and does not look good visually. - animate = false; - } - - QHashIterator<int, KItemListWidget*> it(m_visibleItems); - while (it.hasNext()) { - it.next(); - it.value()->setStyleOption(option); - } - - m_sizeHintResolver->clearCache(); - m_layouter->markAsDirty(); - doLayout(animate ? Animation : NoAnimation); - - onStyleOptionChanged(option, previousOption); + return m_itemSize; } const KItemListStyleOption& KItemListView::styleOption() const @@ -519,7 +421,7 @@ int KItemListView::lastVisibleIndex() const QSizeF KItemListView::itemSizeHint(int index) const { - return m_widgetCreator->itemSizeHint(index, this); + return widgetCreator()->itemSizeHint(index, this); } void KItemListView::setSupportsItemExpanding(bool supportsExpanding) @@ -709,6 +611,134 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt } } +void KItemListView::setItemSize(const QSizeF& size) +{ + const QSizeF previousSize = m_itemSize; + if (size == previousSize) { + return; + } + + // Skip animations when the number of rows or columns + // are changed in the grid layout. Although the animation + // engine can handle this usecase, it looks obtrusive. + const bool animate = !changesItemGridLayout(m_layouter->size(), + size, + m_layouter->itemMargin()); + + const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) && + (( m_itemSize.isEmpty() && !size.isEmpty()) || + (!m_itemSize.isEmpty() && size.isEmpty())); + + m_itemSize = size; + + if (alternateBackgroundsChanged) { + // For an empty item size alternate backgrounds are drawn if more than + // one role is shown. Assure that the backgrounds for visible items are + // updated when changing the size in this context. + updateAlternateBackgrounds(); + } + + if (size.isEmpty()) { + if (m_headerWidget->automaticColumnResizing()) { + updatePreferredColumnWidths(); + } else { + // Only apply the changed height and respect the header widths + // set by the user + const qreal currentWidth = m_layouter->itemSize().width(); + const QSizeF newSize(currentWidth, size.height()); + m_layouter->setItemSize(newSize); + } + } else { + m_layouter->setItemSize(size); + } + + m_sizeHintResolver->clearCache(); + doLayout(animate ? Animation : NoAnimation); + onItemSizeChanged(size, previousSize); +} + +void KItemListView::setStyleOption(const KItemListStyleOption& option) +{ + const KItemListStyleOption previousOption = m_styleOption; + m_styleOption = option; + + bool animate = true; + const QSizeF margin(option.horizontalMargin, option.verticalMargin); + if (margin != m_layouter->itemMargin()) { + // Skip animations when the number of rows or columns + // are changed in the grid layout. Although the animation + // engine can handle this usecase, it looks obtrusive. + animate = !changesItemGridLayout(m_layouter->size(), + m_layouter->itemSize(), + margin); + m_layouter->setItemMargin(margin); + } + + if (m_grouped) { + updateGroupHeaderHeight(); + } + + if (animate && previousOption.maxTextSize != option.maxTextSize) { + // Animating a change of the maximum text size just results in expensive + // temporary eliding and clipping operations and does not look good visually. + animate = false; + } + + QHashIterator<int, KItemListWidget*> it(m_visibleItems); + while (it.hasNext()) { + it.next(); + it.value()->setStyleOption(option); + } + + m_sizeHintResolver->clearCache(); + m_layouter->markAsDirty(); + doLayout(animate ? Animation : NoAnimation); + + onStyleOptionChanged(option, previousOption); +} + +void KItemListView::setScrollOrientation(Qt::Orientation orientation) +{ + const Qt::Orientation previousOrientation = m_layouter->scrollOrientation(); + if (orientation == previousOrientation) { + return; + } + + m_layouter->setScrollOrientation(orientation); + m_animation->setScrollOrientation(orientation); + m_sizeHintResolver->clearCache(); + + if (m_grouped) { + QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups); + while (it.hasNext()) { + it.next(); + it.value()->setScrollOrientation(orientation); + } + updateGroupHeaderHeight(); + + } + + doLayout(NoAnimation); + + onScrollOrientationChanged(orientation, previousOrientation); + emit scrollOrientationChanged(orientation, previousOrientation); +} + +Qt::Orientation KItemListView::scrollOrientation() const +{ + return m_layouter->scrollOrientation(); +} + +KItemListWidgetCreatorBase* KItemListView::defaultWidgetCreator() const +{ + return 0; +} + +KItemListGroupHeaderCreatorBase* KItemListView::defaultGroupHeaderCreator() const +{ + return 0; +} + void KItemListView::initializeItemListWidget(KItemListWidget* item) { Q_UNUSED(item); @@ -728,8 +758,13 @@ void KItemListView::onControllerChanged(KItemListController* current, KItemListC void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous) { - Q_UNUSED(current); Q_UNUSED(previous); + + m_sizeHintResolver->clearCache(); + const int itemCount = current->count(); + if (itemCount > 0) { + m_sizeHintResolver->itemsInserted(0, itemCount); + } } void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) @@ -1205,7 +1240,7 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, // by m_visibleWidgets and must be deleted manually after the animation has // been finished. recycleGroupHeaderForWidget(itemListWidget); - m_widgetCreator->recycle(itemListWidget); + widgetCreator()->recycle(itemListWidget); break; } @@ -1713,7 +1748,7 @@ void KItemListView::emitOffsetChanges() KItemListWidget* KItemListView::createWidget(int index) { - KItemListWidget* widget = m_widgetCreator->create(this); + KItemListWidget* widget = widgetCreator()->create(this); widget->setFlag(QGraphicsItem::ItemStacksBehindParent); m_visibleItems.insert(index, widget); @@ -1733,7 +1768,7 @@ void KItemListView::recycleWidget(KItemListWidget* widget) m_visibleItems.remove(index); m_visibleCells.remove(index); - m_widgetCreator->recycle(widget); + widgetCreator()->recycle(widget); } void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) @@ -1813,7 +1848,7 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); if (!groupHeader) { - groupHeader = m_groupHeaderCreator->create(this); + groupHeader = groupHeaderCreator()->create(this); groupHeader->setParentItem(widget); m_visibleGroups.insert(widget, groupHeader); connect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged())); @@ -1859,7 +1894,7 @@ void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) KItemListGroupHeader* header = m_visibleGroups.value(widget); if (header) { header->setParentItem(0); - m_groupHeaderCreator->recycle(header); + groupHeaderCreator()->recycle(header); m_visibleGroups.remove(widget); disconnect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged())); } @@ -1960,6 +1995,7 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi // Calculate the preferred column withs for each item and ignore values // smaller than the width for showing the headline unclipped. + const KItemListWidgetCreatorBase* creator = widgetCreator(); int calculatedItemCount = 0; bool maxTimeExceeded = false; foreach (const KItemRange& itemRange, itemRanges) { @@ -1969,7 +2005,7 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi for (int i = startIndex; i <= endIndex; ++i) { foreach (const QByteArray& visibleRole, visibleRoles()) { qreal maxWidth = widths.value(visibleRole, 0); - const qreal width = m_widgetCreator->preferredRoleColumnWidth(visibleRole, i, this); + const qreal width = creator->preferredRoleColumnWidth(visibleRole, i, this); maxWidth = qMax(width, maxWidth); widths.insert(visibleRole, maxWidth); } @@ -2071,6 +2107,9 @@ void KItemListView::applyAutomaticColumnWidths() { Q_ASSERT(m_itemSize.isEmpty()); Q_ASSERT(m_headerWidget->automaticColumnResizing()); + if (m_visibleRoles.isEmpty()) { + return; + } // Calculate the maximum size of an item by considering the // visible role sizes and apply them to the layouter. If the diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 13f62f89b..3c47e95c6 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -42,6 +42,7 @@ class KItemListRubberBand; class KItemListViewAnimation; class KItemListViewLayouter; class KItemListWidget; +class KItemListWidgetInformant; class KItemListWidgetCreatorBase; class KItemListViewCreatorBase; class QTimer; @@ -53,11 +54,8 @@ class QTimer; * 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(). + * KItemListController::setView() or with the constructor of + * KItemListController. * * @see KItemListWidget * @see KItemModelBase @@ -74,17 +72,6 @@ public: virtual ~KItemListView(); /** - * If the scroll-orientation is vertical, the items are ordered - * from top to bottom (= default setting). If the scroll-orientation - * is horizontal, the items are ordered from left to right. - */ - void setScrollOrientation(Qt::Orientation orientation); - Qt::Orientation scrollOrientation() const; - - void setItemSize(const QSizeF& size); - QSizeF itemSize() const; - - /** * Offset of the scrollbar that represents the scroll-orientation * (see setScrollOrientation()). */ @@ -145,17 +132,27 @@ public: * <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. + * The ownership of the widget creator is transferred to + * the item-list view. **/ void setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator); KItemListWidgetCreatorBase* widgetCreator() const; + /** + * Sets the creator that creates a group header. Usually it is sufficient + * to implement a custom header widget X derived from KItemListGroupHeader and + * set the creator by: + * <code> + * itemListView->setGroupHeaderCreator(new KItemListGroupHeaderCreator<X>()); + * </code> + * The ownership of the gropup header creator is transferred to + * the item-list view. + **/ void setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator); KItemListGroupHeaderCreatorBase* groupHeaderCreator() const; - void setStyleOption(const KItemListStyleOption& option); + QSizeF itemSize() const; + const KItemListStyleOption& styleOption() const; /** @reimp */ @@ -313,6 +310,33 @@ signals: void roleEditingFinished(int index, const QByteArray& role, const QVariant& value); protected: + void setItemSize(const QSizeF& size); + void setStyleOption(const KItemListStyleOption& option); + + /** + * If the scroll-orientation is vertical, the items are ordered + * from top to bottom (= default setting). If the scroll-orientation + * is horizontal, the items are ordered from left to right. + */ + void setScrollOrientation(Qt::Orientation orientation); + Qt::Orientation scrollOrientation() const; + + /** + * Factory method for creating a default widget-creator. The method will be used + * in case if setWidgetCreator() has not been set by the application. + * @return New instance of the widget-creator that should be used per + * default. + */ + virtual KItemListWidgetCreatorBase* defaultWidgetCreator() const; + + /** + * Factory method for creating a default group-header-creator. The method will be used + * in case if setGroupHeaderCreator() has not been set by the application. + * @return New instance of the group-header-creator that should be used per + * default. + */ + virtual KItemListGroupHeaderCreatorBase* defaultGroupHeaderCreator() const; + /** * Is called when creating a new KItemListWidget instance and allows derived * classes to do a custom initialization. @@ -656,8 +680,8 @@ private: KItemListController* m_controller; KItemModelBase* m_model; QList<QByteArray> m_visibleRoles; - KItemListWidgetCreatorBase* m_widgetCreator; - KItemListGroupHeaderCreatorBase* m_groupHeaderCreator; + mutable KItemListWidgetCreatorBase* m_widgetCreator; + mutable KItemListGroupHeaderCreatorBase* m_groupHeaderCreator; KItemListStyleOption m_styleOption; QHash<int, KItemListWidget*> m_visibleItems; @@ -747,18 +771,12 @@ public: /** * @brief Template class for creating KItemListWidgets. - * - * The template class must provide the following two static methods: - * - QSizeF itemSizeHint(int index, const KItemListView* view) - * - preferredRoleColumnWidth(const QByteArray& role, int index, const KItemListView* view) - * Those static methods are used as implementation for - * KItemListWidgetCreatorBase::itemSizeHint() and - * KItemListWidgetCreatorBase::preferedRoleColumnWidth(). */ template <class T> class KItemListWidgetCreator : public KItemListWidgetCreatorBase { public: + KItemListWidgetCreator(); virtual ~KItemListWidgetCreator(); virtual KItemListWidget* create(KItemListView* view); @@ -768,11 +786,20 @@ public: virtual qreal preferredRoleColumnWidth(const QByteArray& role, int index, const KItemListView* view) const; +private: + KItemListWidgetInformant* m_informant; }; template <class T> +KItemListWidgetCreator<T>::KItemListWidgetCreator() : + m_informant(T::createInformant()) +{ +} + +template <class T> KItemListWidgetCreator<T>::~KItemListWidgetCreator() { + delete m_informant; } template <class T> @@ -780,7 +807,7 @@ KItemListWidget* KItemListWidgetCreator<T>::create(KItemListView* view) { KItemListWidget* widget = static_cast<KItemListWidget*>(popRecycleableWidget()); if (!widget) { - widget = new T(view); + widget = new T(m_informant, view); addCreatedWidget(widget); } return widget; @@ -789,7 +816,7 @@ KItemListWidget* KItemListWidgetCreator<T>::create(KItemListView* view) template<class T> QSizeF KItemListWidgetCreator<T>::itemSizeHint(int index, const KItemListView* view) const { - return T::itemSizeHint(index, view); + return m_informant->itemSizeHint(index, view); } template<class T> @@ -797,7 +824,7 @@ qreal KItemListWidgetCreator<T>::preferredRoleColumnWidth(const QByteArray& role int index, const KItemListView* view) const { - return T::preferredRoleColumnWidth(role, index, view); + return m_informant->preferredRoleColumnWidth(role, index, view); } /** diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp index 542b781f1..f304aa41d 100644 --- a/src/kitemviews/kitemlistwidget.cpp +++ b/src/kitemviews/kitemlistwidget.cpp @@ -35,8 +35,17 @@ #include <QPropertyAnimation> #include <QStyleOption> -KItemListWidget::KItemListWidget(QGraphicsItem* parent) : +KItemListWidgetInformant::KItemListWidgetInformant() +{ +} + +KItemListWidgetInformant::~KItemListWidgetInformant() +{ +} + +KItemListWidget::KItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : QGraphicsWidget(parent, 0), + m_informant(informant), m_index(-1), m_selected(false), m_current(false), diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h index 8a28913b6..fdb07d62e 100644 --- a/src/kitemviews/kitemlistwidget.h +++ b/src/kitemviews/kitemlistwidget.h @@ -32,9 +32,31 @@ #include <QStyle> class KItemListSelectionToggle; +class KItemListView; class QPropertyAnimation; /** + * @brief Provides information for creating an instance of KItemListWidget. + * + * KItemListView only creates KItemListWidget instances for the visible + * area. For calculating the required size of all items the expected + * size for the invisible items must be accessible. KItemListWidgetInformant + * provides this information. + */ +class LIBDOLPHINPRIVATE_EXPORT KItemListWidgetInformant +{ +public: + KItemListWidgetInformant(); + virtual ~KItemListWidgetInformant(); + + virtual QSizeF itemSizeHint(int index, const KItemListView* view) const = 0; + + virtual qreal preferredRoleColumnWidth(const QByteArray& role, + int index, + const KItemListView* view) const = 0; +}; + +/** * @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(). @@ -46,7 +68,7 @@ class LIBDOLPHINPRIVATE_EXPORT KItemListWidget : public QGraphicsWidget Q_OBJECT public: - KItemListWidget(QGraphicsItem* parent); + KItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent); virtual ~KItemListWidget(); void setIndex(int index); @@ -176,6 +198,8 @@ protected: */ qreal hoverOpacity() const; + const KItemListWidgetInformant* informant() const; + private slots: void slotHoverAnimationFinished(); @@ -188,6 +212,7 @@ private: private: Q_PROPERTY(qreal hoverOpacity READ hoverOpacity WRITE setHoverOpacity) + KItemListWidgetInformant* m_informant; int m_index; bool m_selected; bool m_current; @@ -208,6 +233,12 @@ private: QByteArray m_editedRole; }; + +inline const KItemListWidgetInformant* KItemListWidget::informant() const +{ + return m_informant; +} + #endif diff --git a/src/kitemviews/kstandarditem.cpp b/src/kitemviews/kstandarditem.cpp new file mode 100644 index 000000000..090746df1 --- /dev/null +++ b/src/kitemviews/kstandarditem.cpp @@ -0,0 +1,100 @@ +/*************************************************************************** + * Copyright (C) 2012 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 "kstandarditem.h" + +KStandardItem::KStandardItem(KStandardItem* parent) : + m_text(), + m_icon(), + m_group(), + m_parent(parent), + m_children(), + m_model(0) +{ +} + +KStandardItem::KStandardItem(const QString& text, KStandardItem* parent) : + m_text(text), + m_icon(), + m_group(), + m_parent(parent), + m_children(), + m_model(0) +{ +} + +KStandardItem::KStandardItem(const QIcon& icon, const QString& text, KStandardItem* parent) : + m_text(text), + m_icon(icon), + m_group(), + m_parent(parent), + m_children(), + m_model(0) +{ +} + +KStandardItem::~KStandardItem() +{ +} + +void KStandardItem::setText(const QString& text) +{ + m_text = text; +} + +QString KStandardItem::text() const +{ + return m_text; +} + +void KStandardItem::setIcon(const QIcon& icon) +{ + m_icon = icon; +} + +QIcon KStandardItem::icon() const +{ + return m_icon; +} + +void KStandardItem::setGroup(const QString& group) +{ + m_group = group; +} + +QString KStandardItem::group() const +{ + return m_group; +} + +void KStandardItem::setParent(KStandardItem* parent) +{ + // TODO: not implemented yet + m_parent = parent; +} + +KStandardItem* KStandardItem::parent() const +{ + return m_parent; +} + +QList<KStandardItem*> KStandardItem::children() const +{ + return m_children; +} diff --git a/src/kitemviews/kstandarditem.h b/src/kitemviews/kstandarditem.h new file mode 100644 index 000000000..0315f4bf6 --- /dev/null +++ b/src/kitemviews/kstandarditem.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2012 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 KSTANDARDITEM_H +#define KSTANDARDITEM_H + +#include <libdolphin_export.h> + +#include <QIcon> +#include <QList> + +class KStandardItemModel; + +/** + * @brief Represents and item of KStandardItemModel. + * + * Provides setter- and getter-methods for most commonly + * used properties. + */ +class LIBDOLPHINPRIVATE_EXPORT KStandardItem +{ + +public: + explicit KStandardItem(KStandardItem* parent = 0); + explicit KStandardItem(const QString& text, KStandardItem* parent = 0); + KStandardItem(const QIcon& icon, const QString& text, KStandardItem* parent = 0); + virtual ~KStandardItem(); + + void setText(const QString& text); + QString text() const; + + void setIcon(const QIcon& icon); + QIcon icon() const; + + void setGroup(const QString& group); + QString group() const; + + void setParent(KStandardItem* parent); + KStandardItem* parent() const; + + QList<KStandardItem*> children() const; + +private: + QString m_text; + QIcon m_icon; + QString m_group; + KStandardItem* m_parent; + QList<KStandardItem*> m_children; + KStandardItemModel* m_model; + + friend class KStandardItemModel; +}; + +#endif + + diff --git a/src/kitemviews/kstandarditemlistview.cpp b/src/kitemviews/kstandarditemlistview.cpp new file mode 100644 index 000000000..bd5da9eb0 --- /dev/null +++ b/src/kitemviews/kstandarditemlistview.cpp @@ -0,0 +1,191 @@ +/*************************************************************************** + * Copyright (C) 2012 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 "kstandarditemlistview.h" + +#include <KDebug> +#include <KIconLoader> +#include "kstandarditemlistwidget.h" + +KStandardItemListView::KStandardItemListView(QGraphicsWidget* parent) : + KItemListView(parent), + m_itemLayout(DetailsLayout) +{ + setAcceptDrops(true); + setScrollOrientation(Qt::Vertical); + setVisibleRoles(QList<QByteArray>() << "text"); +} + +KStandardItemListView::~KStandardItemListView() +{ +} + +void KStandardItemListView::setItemLayout(ItemLayout layout) +{ + if (m_itemLayout == layout) { + return; + } + + beginTransaction(); + + const ItemLayout previous = m_itemLayout; + m_itemLayout = layout; + + switch (layout) { + case IconsLayout: + setScrollOrientation(Qt::Vertical); + setSupportsItemExpanding(false); + break; + case DetailsLayout: + setScrollOrientation(Qt::Vertical); + setSupportsItemExpanding(true); + break; + case CompactLayout: + setScrollOrientation(Qt::Horizontal); + setSupportsItemExpanding(false); + break; + default: + Q_ASSERT(false); + break; + } + + onItemLayoutChanged(layout, previous); + + endTransaction(); +} + +KStandardItemListView::ItemLayout KStandardItemListView::itemLayout() const +{ + return m_itemLayout; +} + +KItemListWidgetCreatorBase* KStandardItemListView::defaultWidgetCreator() const +{ + return new KItemListWidgetCreator<KStandardItemListWidget>(); +} + +KItemListGroupHeaderCreatorBase* KStandardItemListView::defaultGroupHeaderCreator() const +{ + return 0; // TODO: new KItemListGroupHeaderCreator<KStandardItemListGroupHeader>() +} + +void KStandardItemListView::initializeItemListWidget(KItemListWidget* item) +{ + KStandardItemListWidget* standardItemListWidget = qobject_cast<KStandardItemListWidget*>(item); + Q_ASSERT(standardItemListWidget); + + switch (itemLayout()) { + case IconsLayout: standardItemListWidget->setLayout(KStandardItemListWidget::IconsLayout); break; + case CompactLayout: standardItemListWidget->setLayout(KStandardItemListWidget::CompactLayout); break; + case DetailsLayout: standardItemListWidget->setLayout(KStandardItemListWidget::DetailsLayout); break; + default: Q_ASSERT(false); break; + } + + standardItemListWidget->setSupportsItemExpanding(supportsItemExpanding()); +} + + +bool KStandardItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const +{ + // Even if the icons have a different size they are always aligned within + // the area defined by KItemStyleOption.iconSize and hence result in no + // change of the item-size. + const bool containsIconName = changedRoles.contains("iconName"); + const bool containsIconPixmap = changedRoles.contains("iconPixmap"); + const int count = changedRoles.count(); + + const bool iconChanged = (containsIconName && containsIconPixmap && count == 2) || + (containsIconName && count == 1) || + (containsIconPixmap && count == 1); + return !iconChanged; +} + +void KStandardItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + updateLayoutOfVisibleItems(); +} + +void KStandardItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + updateLayoutOfVisibleItems(); +} + +void KStandardItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) +{ + Q_UNUSED(supportsExpanding); + updateLayoutOfVisibleItems(); +} + + +void KStandardItemListView::polishEvent() +{ + switch (m_itemLayout) { + case IconsLayout: applyDefaultStyleOption(KIconLoader::SizeMedium, 2, 4, 8); break; + case CompactLayout: applyDefaultStyleOption(KIconLoader::SizeSmall, 2, 8, 0); break; + case DetailsLayout: applyDefaultStyleOption(KIconLoader::SizeSmall, 2, 0, 0); break; + default: Q_ASSERT(false); break; + } + + QGraphicsWidget::polishEvent(); +} + +void KStandardItemListView::applyDefaultStyleOption(int iconSize, + int padding, + int horizontalMargin, + int verticalMargin) +{ + KItemListStyleOption option = styleOption(); + + bool changed = false; + if (option.iconSize < 0) { + option.iconSize = iconSize; + changed = true; + } + if (option.padding < 0) { + option.padding = padding; + changed = true; + } + if (option.horizontalMargin < 0) { + option.horizontalMargin = horizontalMargin; + changed = true; + } + if (option.verticalMargin < 0) { + option.verticalMargin = verticalMargin; + changed = true; + } + + if (changed) { + setStyleOption(option); + } +} + +void KStandardItemListView::updateLayoutOfVisibleItems() +{ + if (model()) { + foreach (KItemListWidget* widget, visibleItemListWidgets()) { + initializeItemListWidget(widget); + } + } +} + +#include "kstandarditemlistview.moc" diff --git a/src/kitemviews/kstandarditemlistview.h b/src/kitemviews/kstandarditemlistview.h new file mode 100644 index 000000000..fd4fa861c --- /dev/null +++ b/src/kitemviews/kstandarditemlistview.h @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (C) 2012 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 KSTANDARDITEMLISTVIEW_H +#define KSTANDARDITEMLISTVIEW_H + +#include <libdolphin_export.h> + +#include <kitemviews/kitemlistview.h> + +/** + * @brief Provides layouts for icons-, compact- and details-view. + * + * Together with the KStandardItemModel lists for standard usecases + * can be created in a straight forward way. + * + * Example code: + * <code> + * KStandardItemListView* view = new KStandardItemListView(); + * KStandardItemModel* model = new KStandardItemModel(); + * model->appendItem(new KStandardItem("Item 1")); + * model->appendItem(new KStandardItem("Item 2")); + * KItemListController* controller = new KItemListController(model, view); + * KItemListContainer* container = new KItemListContainer(controller, parentWidget); + * </code> + */ +class LIBDOLPHINPRIVATE_EXPORT KStandardItemListView : public KItemListView +{ + Q_OBJECT + +public: + enum ItemLayout + { + IconsLayout, + CompactLayout, + DetailsLayout + }; + + KStandardItemListView(QGraphicsWidget* parent = 0); + virtual ~KStandardItemListView(); + + void setItemLayout(ItemLayout layout); + ItemLayout itemLayout() const; + +protected: + virtual KItemListWidgetCreatorBase* defaultWidgetCreator() const; + virtual KItemListGroupHeaderCreatorBase* defaultGroupHeaderCreator() const; + virtual void initializeItemListWidget(KItemListWidget* item); + virtual bool itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const; + virtual void onItemLayoutChanged(ItemLayout current, ItemLayout previous); + virtual void onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous); + virtual void onSupportsItemExpandingChanged(bool supportsExpanding); + virtual void polishEvent(); + +private: + void applyDefaultStyleOption(int iconSize, int padding, int horizontalMargin, int verticalMargin); + void updateLayoutOfVisibleItems(); + +private: + ItemLayout m_itemLayout; +}; + +#endif + + diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp new file mode 100644 index 000000000..c9d9b4271 --- /dev/null +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -0,0 +1,1234 @@ +/*************************************************************************** + * Copyright (C) 2012 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 "kstandarditemlistwidget.h" + +#include "kfileitemlistview.h" +#include "kfileitemmodel.h" + +#include <KIcon> +#include <KIconEffect> +#include <KIconLoader> +#include <KLocale> +#include <kratingpainter.h> +#include <KStringHandler> +#include <KDebug> + +#include "private/kfileitemclipboard.h" +#include "private/kitemlistroleeditor.h" +#include "private/kpixmapmodifier.h" + +#include <QFontMetricsF> +#include <QGraphicsScene> +#include <QGraphicsSceneResizeEvent> +#include <QGraphicsView> +#include <QPainter> +#include <QStyleOption> +#include <QTextLayout> +#include <QTextLine> + +// #define KFILEITEMLISTWIDGET_DEBUG + +KStandardItemListWidgetInformant::KStandardItemListWidgetInformant() : + KItemListWidgetInformant() +{ +} + +KStandardItemListWidgetInformant::~KStandardItemListWidgetInformant() +{ +} + +QSizeF KStandardItemListWidgetInformant::itemSizeHint(int index, const KItemListView* view) const +{ + const QHash<QByteArray, QVariant> values = view->model()->data(index); + const KItemListStyleOption& option = view->styleOption(); + const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); + + switch (static_cast<const KStandardItemListView*>(view)->itemLayout()) { + case KStandardItemListWidget::IconsLayout: { + const QString text = KStringHandler::preProcessWrap(values["text"].toString()); + + const qreal itemWidth = view->itemSize().width(); + const qreal maxWidth = itemWidth - 2 * option.padding; + QTextLine line; + + // Calculate the number of lines required for wrapping the name + QTextOption textOption(Qt::AlignHCenter); + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + + qreal textHeight = 0; + QTextLayout layout(text, option.font); + layout.setTextOption(textOption); + layout.beginLayout(); + while ((line = layout.createLine()).isValid()) { + line.setLineWidth(maxWidth); + line.naturalTextWidth(); + textHeight += line.height(); + } + layout.endLayout(); + + // Add one line for each additional information + textHeight += additionalRolesCount * option.fontMetrics.lineSpacing(); + + const qreal maxTextHeight = option.maxTextSize.height(); + if (maxTextHeight > 0 && textHeight > maxTextHeight) { + textHeight = maxTextHeight; + } + + return QSizeF(itemWidth, textHeight + option.iconSize + option.padding * 3); + } + + case KStandardItemListWidget::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; + + foreach (const QByteArray& role, view->visibleRoles()) { + const QString text = roleText(role, values); + const qreal requiredWidth = option.fontMetrics.width(text); + maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth); + } + + qreal width = option.padding * 4 + option.iconSize + maximumRequiredWidth; + const qreal maxWidth = option.maxTextSize.width(); + if (maxWidth > 0 && width > maxWidth) { + width = maxWidth; + } + const qreal height = option.padding * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * option.fontMetrics.lineSpacing()); + return QSizeF(width, height); + } + + case KStandardItemListWidget::DetailsLayout: { + const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height()); + return QSizeF(-1, height); + } + + default: + Q_ASSERT(false); + break; + } + + return QSize(); +} + +qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArray& role, + int index, + const KItemListView* view) const +{ + const QHash<QByteArray, QVariant> values = view->model()->data(index); + const KItemListStyleOption& option = view->styleOption(); + + const QString text = roleText(role, values); + qreal width = KStandardItemListWidget::columnPadding(option); + + if (role == "rating") { + width += KStandardItemListWidget::preferredRatingSize(option).width(); + } else { + width += option.fontMetrics.width(text); + + if (role == "text") { + // Increase the width by the expansion-toggle and the current expansion level + const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); + width += option.padding + (expandedParentsCount + 1) * view->itemSize().height() + KIconLoader::SizeSmall; + + // Increase the width by the required space for the icon + width += option.padding * 2 + option.iconSize; + } + } + + return width; +} + +QString KStandardItemListWidgetInformant::roleText(const QByteArray& role, + const QHash<QByteArray, QVariant>& values) const +{ + if (role == "rating") { + // Always use an empty text, as the rating is shown by the image m_rating. + return QString(); + } + return values.value(role).toString(); +} + +KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : + KItemListWidget(informant, parent), + m_isCut(false), + m_isHidden(false), + m_isExpandable(false), + m_supportsItemExpanding(false), + m_dirtyLayout(true), + m_dirtyContent(true), + m_dirtyContentRoles(), + m_layout(IconsLayout), + m_pixmapPos(), + m_pixmap(), + m_scaledPixmapSize(), + m_iconRect(), + m_hoverPixmap(), + m_textInfo(), + m_textRect(), + m_sortedVisibleRoles(), + m_expansionArea(), + m_customTextColor(), + m_additionalInfoTextColor(), + m_overlay(), + m_rating(), + m_roleEditor(0) +{ +} + +KStandardItemListWidget::~KStandardItemListWidget() +{ + qDeleteAll(m_textInfo); + m_textInfo.clear(); + + delete m_roleEditor; +} + +void KStandardItemListWidget::setLayout(Layout layout) +{ + if (m_layout != layout) { + m_layout = layout; + m_dirtyLayout = true; + updateAdditionalInfoTextColor(); + update(); + } +} + +KStandardItemListWidget::Layout KStandardItemListWidget::layout() const +{ + return m_layout; +} + +void KStandardItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding) +{ + if (m_supportsItemExpanding != supportsItemExpanding) { + m_supportsItemExpanding = supportsItemExpanding; + m_dirtyLayout = true; + update(); + } +} + +bool KStandardItemListWidget::supportsItemExpanding() const +{ + return m_supportsItemExpanding; +} + +void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + const_cast<KStandardItemListWidget*>(this)->triggerCacheRefreshing(); + + KItemListWidget::paint(painter, option, widget); + + if (!m_expansionArea.isEmpty()) { + drawSiblingsInformation(painter); + } + + 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); + painter->setOpacity(opacity); + } else { + drawPixmap(painter, m_pixmap); + } + + painter->setFont(itemListStyleOption.font); + painter->setPen(textColor()); + const TextInfo* textInfo = m_textInfo.value("text"); + painter->drawStaticText(textInfo->pos, textInfo->staticText); + + bool clipAdditionalInfoBounds = false; + if (m_supportsItemExpanding) { + // Prevent a possible overlapping of the additional-information texts + // with the icon. This can happen if the user has minimized the width + // of the name-column to a very small value. + const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding; + if (textInfo->pos.x() + columnWidth("text") > minX) { + clipAdditionalInfoBounds = true; + painter->save(); + painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip); + } + } + + painter->setPen(m_additionalInfoTextColor); + painter->setFont(itemListStyleOption.font); + + for (int i = 1; i < m_sortedVisibleRoles.count(); ++i) { + const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles[i]); + painter->drawStaticText(textInfo->pos, textInfo->staticText); + } + + if (!m_rating.isNull()) { + const TextInfo* ratingTextInfo = m_textInfo.value("rating"); + QPointF pos = ratingTextInfo->pos; + const Qt::Alignment align = ratingTextInfo->staticText.textOption().alignment(); + if (align & Qt::AlignHCenter) { + pos.rx() += (size().width() - m_rating.width()) / 2; + } + painter->drawPixmap(pos, m_rating); + } + + if (clipAdditionalInfoBounds) { + painter->restore(); + } + +#ifdef KFILEITEMLISTWIDGET_DEBUG + painter->setBrush(Qt::NoBrush); + painter->setPen(Qt::green); + painter->drawRect(m_iconRect); + + painter->setPen(Qt::red); + painter->drawText(QPointF(0, itemListStyleOption.fontMetrics.height()), QString::number(index())); + painter->drawRect(rect()); +#endif +} + +QRectF KStandardItemListWidget::iconRect() const +{ + const_cast<KStandardItemListWidget*>(this)->triggerCacheRefreshing(); + return m_iconRect; +} + +QRectF KStandardItemListWidget::textRect() const +{ + const_cast<KStandardItemListWidget*>(this)->triggerCacheRefreshing(); + return m_textRect; +} + +QRectF KStandardItemListWidget::textFocusRect() const +{ + // In the compact- and details-layout a larger textRect() is returned to be aligned + // with the iconRect(). This is useful to have a larger selection/hover-area + // when having a quite large icon size but only one line of text. Still the + // focus rectangle should be shown as narrow as possible around the text. + + const_cast<KStandardItemListWidget*>(this)->triggerCacheRefreshing(); + + switch (m_layout) { + case CompactLayout: { + QRectF rect = m_textRect; + const TextInfo* topText = m_textInfo.value(m_sortedVisibleRoles.first()); + const TextInfo* bottomText = m_textInfo.value(m_sortedVisibleRoles.last()); + rect.setTop(topText->pos.y()); + rect.setBottom(bottomText->pos.y() + bottomText->staticText.size().height()); + return rect; + } + + case DetailsLayout: { + QRectF rect = m_textRect; + const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles.first()); + rect.setTop(textInfo->pos.y()); + rect.setBottom(textInfo->pos.y() + textInfo->staticText.size().height()); + return rect; + } + + default: + break; + } + + return m_textRect; +} + +QRectF KStandardItemListWidget::expansionToggleRect() const +{ + const_cast<KStandardItemListWidget*>(this)->triggerCacheRefreshing(); + return m_isExpandable ? m_expansionArea : QRectF(); +} + +QRectF KStandardItemListWidget::selectionToggleRect() const +{ + const_cast<KStandardItemListWidget*>(this)->triggerCacheRefreshing(); + + const int iconHeight = styleOption().iconSize; + + int toggleSize = KIconLoader::SizeSmall; + if (iconHeight >= KIconLoader::SizeEnormous) { + toggleSize = KIconLoader::SizeMedium; + } else if (iconHeight >= KIconLoader::SizeLarge) { + toggleSize = KIconLoader::SizeSmallMedium; + } + + QPointF pos = iconRect().topLeft(); + + // If the selection toggle has a very small distance to the + // widget borders, the size of the selection toggle will get + // increased to prevent an accidental clicking of the item + // when trying to hit the toggle. + const int widgetHeight = size().height(); + const int widgetWidth = size().width(); + const int minMargin = 2; + + if (toggleSize + minMargin * 2 >= widgetHeight) { + pos.rx() -= (widgetHeight - toggleSize) / 2; + toggleSize = widgetHeight; + pos.setY(0); + } + if (toggleSize + minMargin * 2 >= widgetWidth) { + pos.ry() -= (widgetWidth - toggleSize) / 2; + toggleSize = widgetWidth; + pos.setX(0); + } + + return QRectF(pos, QSizeF(toggleSize, toggleSize)); +} + +KItemListWidgetInformant* KStandardItemListWidget::createInformant() +{ + return new KStandardItemListWidgetInformant(); +} + +void KStandardItemListWidget::invalidateCache() +{ + m_dirtyLayout = true; + m_dirtyContent = true; +} + +void KStandardItemListWidget::refreshCache() +{ +} + +bool KStandardItemListWidget::isRoleRightAligned(const QByteArray& role) const +{ + Q_UNUSED(role); + return false; +} + +void KStandardItemListWidget::setTextColor(const QColor& color) +{ + if (color != m_customTextColor) { + m_customTextColor = color; + updateAdditionalInfoTextColor(); + update(); + } +} + +QColor KStandardItemListWidget::textColor() const +{ + if (m_customTextColor.isValid() && !isSelected()) { + return m_customTextColor; + } + + const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; + const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : QPalette::Text; + return styleOption().palette.brush(group, role).color(); +} + +void KStandardItemListWidget::setOverlay(const QPixmap& overlay) +{ + m_overlay = overlay; + m_dirtyContent = true; + update(); +} + +QPixmap KStandardItemListWidget::overlay() const +{ + return m_overlay; +} + + +QString KStandardItemListWidget::roleText(const QByteArray& role, + const QHash<QByteArray, QVariant>& values) const +{ + return static_cast<const KStandardItemListWidgetInformant*>(informant())->roleText(role, values); +} + +void KStandardItemListWidget::dataChanged(const QHash<QByteArray, QVariant>& current, + const QSet<QByteArray>& roles) +{ + Q_UNUSED(current); + + m_dirtyContent = true; + + QSet<QByteArray> dirtyRoles; + if (roles.isEmpty()) { + dirtyRoles = visibleRoles().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 KStandardItemListWidget::visibleRolesChanged(const QList<QByteArray>& current, + const QList<QByteArray>& previous) +{ + Q_UNUSED(previous); + m_sortedVisibleRoles = current; + m_dirtyLayout = true; +} + +void KStandardItemListWidget::columnWidthChanged(const QByteArray& role, + qreal current, + qreal previous) +{ + Q_UNUSED(role); + Q_UNUSED(current); + Q_UNUSED(previous); + m_dirtyLayout = true; +} + +void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption& current, + const KItemListStyleOption& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + updateAdditionalInfoTextColor(); + m_dirtyLayout = true; +} + +void KStandardItemListWidget::hoveredChanged(bool hovered) +{ + Q_UNUSED(hovered); + m_dirtyLayout = true; +} + +void KStandardItemListWidget::selectedChanged(bool selected) +{ + Q_UNUSED(selected); + updateAdditionalInfoTextColor(); +} + +void KStandardItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + m_dirtyLayout = true; +} + +void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const QByteArray& previous) +{ + Q_UNUSED(previous); + + QGraphicsView* parent = scene()->views()[0]; + if (current.isEmpty() || !parent || current != "text") { + if (m_roleEditor) { + emit roleEditingCanceled(index(), current, data().value(current)); + m_roleEditor->deleteLater(); + m_roleEditor = 0; + } + return; + } + + Q_ASSERT(!m_roleEditor); + + const TextInfo* textInfo = m_textInfo.value("text"); + + m_roleEditor = new KItemListRoleEditor(parent); + m_roleEditor->setIndex(index()); + m_roleEditor->setRole(current); + + const QString text = data().value(current).toString(); + m_roleEditor->setPlainText(text); + + QTextOption textOption = textInfo->staticText.textOption(); + m_roleEditor->document()->setDefaultTextOption(textOption); + + // Select the text without MIME-type extension + int selectionLength = text.length(); + + const QString extension = KMimeType::extractKnownExtension(text); + if (!extension.isEmpty()) { + selectionLength -= extension.length() + 1; + } + + if (selectionLength > 0) { + QTextCursor cursor = m_roleEditor->textCursor(); + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectionLength); + m_roleEditor->setTextCursor(cursor); + } + + connect(m_roleEditor, SIGNAL(roleEditingCanceled(int,QByteArray,QVariant)), + this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant))); + connect(m_roleEditor, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)), + this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant))); + + // Adjust the geometry of the editor + QRectF rect = roleEditingRect(current); + const int frameWidth = m_roleEditor->frameWidth(); + rect.adjust(-frameWidth, -frameWidth, frameWidth, frameWidth); + rect.translate(pos()); + if (rect.right() > parent->width()) { + rect.setWidth(parent->width() - rect.left()); + } + m_roleEditor->setGeometry(rect.toRect()); + m_roleEditor->show(); + m_roleEditor->setFocus(); +} + +void KStandardItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) +{ + if (m_roleEditor) { + setEditedRole(QByteArray()); + Q_ASSERT(!m_roleEditor); + } + + KItemListWidget::resizeEvent(event); + + m_dirtyLayout = true; +} + +void KStandardItemListWidget::showEvent(QShowEvent* event) +{ + KItemListWidget::showEvent(event); + + // Listen to changes of the clipboard to mark the item as cut/uncut + KFileItemClipboard* clipboard = KFileItemClipboard::instance(); + + const KUrl itemUrl = data().value("url").value<KUrl>(); + m_isCut = clipboard->isCut(itemUrl); + + connect(clipboard, SIGNAL(cutItemsChanged()), + this, SLOT(slotCutItemsChanged())); +} + +void KStandardItemListWidget::hideEvent(QHideEvent* event) +{ + disconnect(KFileItemClipboard::instance(), SIGNAL(cutItemsChanged()), + this, SLOT(slotCutItemsChanged())); + + KItemListWidget::hideEvent(event); +} + +void KStandardItemListWidget::slotCutItemsChanged() +{ + const KUrl itemUrl = data().value("url").value<KUrl>(); + const bool isCut = KFileItemClipboard::instance()->isCut(itemUrl); + if (m_isCut != isCut) { + m_isCut = isCut; + m_pixmap = QPixmap(); + m_dirtyContent = true; + update(); + } +} + +void KStandardItemListWidget::slotRoleEditingCanceled(int index, + const QByteArray& role, + const QVariant& value) +{ + m_roleEditor->deleteLater(); + m_roleEditor = 0; + emit roleEditingCanceled(index, role, value); + setEditedRole(QByteArray()); +} + +void KStandardItemListWidget::slotRoleEditingFinished(int index, + const QByteArray& role, + const QVariant& value) +{ + m_roleEditor->deleteLater(); + m_roleEditor = 0; + emit roleEditingFinished(index, role, value); + setEditedRole(QByteArray()); +} + +void KStandardItemListWidget::triggerCacheRefreshing() +{ + if ((!m_dirtyContent && !m_dirtyLayout) || index() < 0) { + return; + } + + refreshCache(); + + const QHash<QByteArray, QVariant> values = data(); + m_isExpandable = m_supportsItemExpanding && values["isExpandable"].toBool(); + m_isHidden = values["text"].toString().startsWith(QLatin1Char('.')); + + updateExpansionArea(); + updateTextsCache(); + updatePixmapCache(); + + m_dirtyLayout = false; + m_dirtyContent = false; + m_dirtyContentRoles.clear(); +} + +void KStandardItemListWidget::updateExpansionArea() +{ + if (m_supportsItemExpanding) { + const QHash<QByteArray, QVariant> values = data(); + Q_ASSERT(values.contains("expandedParentsCount")); + const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); + if (expandedParentsCount >= 0) { + const qreal widgetHeight = size().height(); + const qreal inc = (widgetHeight - KIconLoader::SizeSmall) / 2; + const qreal x = expandedParentsCount * widgetHeight + inc; + const qreal y = inc; + m_expansionArea = QRectF(x, y, KIconLoader::SizeSmall, KIconLoader::SizeSmall); + return; + } + } + + m_expansionArea = QRectF(); +} + +void KStandardItemListWidget::updatePixmapCache() +{ + // Precondition: Requires already updated m_textPos values to calculate + // the remaining height when the alignment is vertical. + + const QSizeF widgetSize = size(); + const bool iconOnTop = (m_layout == IconsLayout); + const KItemListStyleOption& option = styleOption(); + const qreal padding = option.padding; + + const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : option.iconSize; + const int maxIconHeight = option.iconSize; + + const QHash<QByteArray, QVariant> values = data(); + + bool updatePixmap = (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight); + if (!updatePixmap && m_dirtyContent) { + updatePixmap = m_dirtyContentRoles.isEmpty() + || m_dirtyContentRoles.contains("iconPixmap") + || m_dirtyContentRoles.contains("iconName") + || m_dirtyContentRoles.contains("iconOverlays"); + } + + 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, maxIconHeight); + } else if (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight) { + // A custom pixmap has been applied. Assure that the pixmap + // is scaled to the maximum available size. + KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight)); + } + + const QStringList overlays = values["iconOverlays"].toStringList(); + + // Strangely KFileItem::overlays() returns empty string-values, so + // we need to check first whether an overlay must be drawn at all. + // It is more efficient to do it here, as KIconLoader::drawOverlays() + // assumes that an overlay will be drawn and has some additional + // setup time. + foreach (const QString& overlay, overlays) { + if (!overlay.isEmpty()) { + // There is at least one overlay, draw all overlays above m_pixmap + // and cancel the check + KIconLoader::global()->drawOverlays(overlays, m_pixmap, KIconLoader::Desktop); + break; + } + } + + if (m_isCut) { + applyCutEffect(m_pixmap); + } + + if (m_isHidden) { + applyHiddenEffect(m_pixmap); + } + } + + if (!m_overlay.isNull()) { + QPainter painter(&m_pixmap); + painter.drawPixmap(0, m_pixmap.height() - m_overlay.height(), m_overlay); + } + + int scaledIconSize = 0; + if (iconOnTop) { + const TextInfo* textInfo = m_textInfo.value("text"); + scaledIconSize = static_cast<int>(textInfo->pos.y() - 2 * padding); + } else { + const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1; + const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height(); + scaledIconSize = (requiredTextHeight < maxIconHeight) ? + widgetSize.height() - 2 * padding : maxIconHeight; + } + + const int maxScaledIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : scaledIconSize; + const int maxScaledIconHeight = scaledIconSize; + + m_scaledPixmapSize = m_pixmap.size(); + m_scaledPixmapSize.scale(maxScaledIconWidth, maxScaledIconHeight, Qt::KeepAspectRatio); + + if (iconOnTop) { + // Center horizontally and align on bottom within the icon-area + m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2); + m_pixmapPos.setY(padding + scaledIconSize - m_scaledPixmapSize.height()); + } else { + // Center horizontally and vertically within the icon-area + const TextInfo* textInfo = m_textInfo.value("text"); + m_pixmapPos.setX(textInfo->pos.x() - 2 * padding + - (scaledIconSize + m_scaledPixmapSize.width()) / 2); + m_pixmapPos.setY(padding + + (scaledIconSize - m_scaledPixmapSize.height()) / 2); + } + + m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)); + + // Prepare the pixmap that is used when the item gets hovered + if (isHovered()) { + 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 KStandardItemListWidget::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; + } + + qDeleteAll(m_textInfo); + m_textInfo.clear(); + for (int i = 0; i < m_sortedVisibleRoles.count(); ++i) { + TextInfo* textInfo = new TextInfo(); + textInfo->staticText.setTextFormat(Qt::PlainText); + textInfo->staticText.setPerformanceHint(QStaticText::AggressiveCaching); + textInfo->staticText.setTextOption(textOption); + m_textInfo.insert(m_sortedVisibleRoles[i], textInfo); + } + + switch (m_layout) { + case IconsLayout: updateIconsLayoutTextCache(); break; + case CompactLayout: updateCompactLayoutTextCache(); break; + case DetailsLayout: updateDetailsLayoutTextCache(); break; + default: Q_ASSERT(false); break; + } + + const TextInfo* ratingTextInfo = m_textInfo.value("rating"); + if (ratingTextInfo) { + // The text of the rating-role has been set to empty to get + // replaced by a rating-image showing the rating as stars. + const KItemListStyleOption& option = styleOption(); + QSizeF ratingSize = preferredRatingSize(option); + + const qreal availableWidth = (m_layout == DetailsLayout) + ? columnWidth("rating") - columnPadding(option) + : m_textRect.width(); + if (ratingSize.width() > availableWidth) { + ratingSize.rwidth() = availableWidth; + } + m_rating = QPixmap(ratingSize.toSize()); + m_rating.fill(Qt::transparent); + + QPainter painter(&m_rating); + const QRect rect(0, 0, m_rating.width(), m_rating.height()); + const int rating = data().value("rating").toInt(); + KRatingPainter::paintRating(&painter, rect, Qt::AlignJustify | Qt::AlignVCenter, rating); + } else if (!m_rating.isNull()) { + m_rating = QPixmap(); + } +} + +void KStandardItemListWidget::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 padding = option.padding; + const qreal maxWidth = size().width() - 2 * padding; + const qreal widgetHeight = size().height(); + const qreal lineSpacing = option.fontMetrics.lineSpacing(); + + // Initialize properties for the "text" role. It will be used as anchor + // for initializing the position of the other roles. + TextInfo* nameTextInfo = m_textInfo.value("text"); + const QString nameText = KStringHandler::preProcessWrap(values["text"].toString()); + nameTextInfo->staticText.setText(nameText); + + // Calculate the number of lines required for the name and the required width + qreal nameWidth = 0; + qreal nameHeight = 0; + QTextLine line; + + const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0); + const int maxNameLines = (option.maxTextSize.height() / int(lineSpacing)) - additionalRolesCount; + + QTextLayout layout(nameTextInfo->staticText.text(), option.font); + layout.setTextOption(nameTextInfo->staticText.textOption()); + layout.beginLayout(); + int nameLineIndex = 0; + while ((line = layout.createLine()).isValid()) { + line.setLineWidth(maxWidth); + nameWidth = qMax(nameWidth, line.naturalTextWidth()); + nameHeight += line.height(); + + ++nameLineIndex; + if (nameLineIndex == maxNameLines) { + // The maximum number of textlines has been reached. If this is + // the case provide an elided text if necessary. + const int textLength = line.textStart() + line.textLength(); + if (textLength < nameText.length()) { + // Elide the last line of the text + QString lastTextLine = nameText.mid(line.textStart(), line.textLength()); + lastTextLine = option.fontMetrics.elidedText(lastTextLine, + Qt::ElideRight, + line.naturalTextWidth() - 1); + const QString elidedText = nameText.left(line.textStart()) + lastTextLine; + nameTextInfo->staticText.setText(elidedText); + } + break; + } + } + layout.endLayout(); + + // Use one line for each additional information + nameTextInfo->staticText.setTextWidth(maxWidth); + nameTextInfo->pos = QPointF(padding, widgetHeight - + nameHeight - + additionalRolesCount * lineSpacing - + padding); + m_textRect = QRectF(padding + (maxWidth - nameWidth) / 2, + nameTextInfo->pos.y(), + nameWidth, + nameHeight); + + // Calculate the position for each additional information + qreal y = nameTextInfo->pos.y() + nameHeight; + foreach (const QByteArray& role, m_sortedVisibleRoles) { + if (role == "text") { + continue; + } + + const QString text = roleText(role, values); + TextInfo* textInfo = m_textInfo.value(role); + textInfo->staticText.setText(text); + + qreal requiredWidth = 0; + + QTextLayout layout(text, option.font); + QTextOption textOption; + textOption.setWrapMode(QTextOption::NoWrap); + layout.setTextOption(textOption); + + layout.beginLayout(); + QTextLine textLine = layout.createLine(); + if (textLine.isValid()) { + textLine.setLineWidth(maxWidth); + requiredWidth = textLine.naturalTextWidth(); + if (requiredWidth > maxWidth) { + const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth); + textInfo->staticText.setText(elidedText); + requiredWidth = option.fontMetrics.width(elidedText); + } + } + layout.endLayout(); + + textInfo->pos = QPointF(padding, y); + textInfo->staticText.setTextWidth(maxWidth); + + const QRectF textRect(padding + (maxWidth - requiredWidth) / 2, y, requiredWidth, lineSpacing); + m_textRect |= textRect; + + y += lineSpacing; + } + + // Add a padding to the text rectangle + m_textRect.adjust(-padding, -padding, padding, padding); +} + +void KStandardItemListWidget::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 lineSpacing = option.fontMetrics.lineSpacing(); + const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * lineSpacing; + const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.padding : option.iconSize; + + qreal maximumRequiredTextWidth = 0; + const qreal x = option.padding * 3 + scaledIconSize; + qreal y = qRound((widgetHeight - textLinesHeight) / 2); + const qreal maxWidth = size().width() - x - option.padding; + foreach (const QByteArray& role, m_sortedVisibleRoles) { + const QString text = roleText(role, values); + TextInfo* textInfo = m_textInfo.value(role); + textInfo->staticText.setText(text); + + qreal requiredWidth = option.fontMetrics.width(text); + if (requiredWidth > maxWidth) { + requiredWidth = maxWidth; + const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth); + textInfo->staticText.setText(elidedText); + } + + textInfo->pos = QPointF(x, y); + textInfo->staticText.setTextWidth(maxWidth); + + maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth); + + y += lineSpacing; + } + + m_textRect = QRectF(x - option.padding, 0, maximumRequiredTextWidth + 2 * option.padding, widgetHeight); +} + +void KStandardItemListWidget::updateDetailsLayoutTextCache() +{ + // Precondition: Requires already updated m_expansionArea + // to determine the left position. + + // +------+ + // | Icon | Name role Additional role 1 Additional role 2 + // +------+ + m_textRect = QRectF(); + + const KItemListStyleOption& option = styleOption(); + const QHash<QByteArray, QVariant> values = data(); + + const qreal widgetHeight = size().height(); + const int scaledIconSize = widgetHeight - 2 * option.padding; + const int fontHeight = option.fontMetrics.height(); + + const qreal columnWidthInc = columnPadding(option); + qreal firstColumnInc = scaledIconSize; + if (m_supportsItemExpanding) { + firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2; + } else { + firstColumnInc += option.padding; + } + + qreal x = firstColumnInc; + const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2); + + foreach (const QByteArray& role, m_sortedVisibleRoles) { + QString text = roleText(role, values); + + // Elide the text in case it does not fit into the available column-width + qreal requiredWidth = option.fontMetrics.width(text); + const qreal roleWidth = columnWidth(role); + qreal availableTextWidth = roleWidth - columnWidthInc; + + const bool isTextRole = (role == "text"); + if (isTextRole) { + availableTextWidth -= firstColumnInc; + } + + if (requiredWidth > availableTextWidth) { + text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth); + requiredWidth = option.fontMetrics.width(text); + } + + TextInfo* textInfo = m_textInfo.value(role); + textInfo->staticText.setText(text); + textInfo->pos = QPointF(x + columnWidthInc / 2, y); + x += roleWidth; + + if (isTextRole) { + const qreal textWidth = option.extendedSelectionRegion + ? size().width() - textInfo->pos.x() + : requiredWidth + 2 * option.padding; + m_textRect = QRectF(textInfo->pos.x() - option.padding, 0, + textWidth, 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 -= firstColumnInc; + } else if (isRoleRightAligned(role)) { + textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc; + } + } +} + +void KStandardItemListWidget::updateAdditionalInfoTextColor() +{ + QColor c1; + if (m_customTextColor.isValid()) { + c1 = m_customTextColor; + } else if (isSelected() && m_layout != DetailsLayout) { + c1 = styleOption().palette.highlightedText().color(); + } else { + c1 = styleOption().palette.text().color(); + } + + // 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 c1 is slightly mixed with the background color. + const QColor c2 = styleOption().palette.base().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); +} + +void KStandardItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap) +{ + if (m_scaledPixmapSize != pixmap.size()) { + QPixmap scaledPixmap = pixmap; + KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize); + painter->drawPixmap(m_pixmapPos, scaledPixmap); + +#ifdef KFILEITEMLISTWIDGET_DEBUG + painter->setPen(Qt::blue); + painter->drawRect(QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize))); +#endif + } else { + painter->drawPixmap(m_pixmapPos, pixmap); + } +} + +void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter) +{ + const int siblingSize = size().height(); + const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2; + QRect siblingRect(x, 0, siblingSize, siblingSize); + + QStyleOption option; + bool isItemSibling = true; + + const QBitArray siblings = siblingsInformation(); + for (int i = siblings.count() - 1; i >= 0; --i) { + option.rect = siblingRect; + option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None; + + if (isItemSibling) { + option.state |= QStyle::State_Item; + if (m_isExpandable) { + option.state |= QStyle::State_Children; + } + if (data()["isExpanded"].toBool()) { + option.state |= QStyle::State_Open; + } + isItemSibling = false; + } + + style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter); + + siblingRect.translate(-siblingRect.width(), 0); + } +} + +QRectF KStandardItemListWidget::roleEditingRect(const QByteArray& role) const +{ + const TextInfo* textInfo = m_textInfo.value(role); + if (!textInfo) { + return QRectF(); + } + + QRectF rect(textInfo->pos, textInfo->staticText.size()); + if (m_layout == DetailsLayout) { + rect.setWidth(columnWidth(role) - rect.x()); + } + + return rect; +} + +QPixmap KStandardItemListWidget::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; +} + +void KStandardItemListWidget::applyCutEffect(QPixmap& pixmap) +{ + KIconEffect* effect = KIconLoader::global()->iconEffect(); + pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); +} + +void KStandardItemListWidget::applyHiddenEffect(QPixmap& pixmap) +{ + KIconEffect::semiTransparent(pixmap); +} + +QSizeF KStandardItemListWidget::preferredRatingSize(const KItemListStyleOption& option) +{ + const qreal height = option.fontMetrics.ascent(); + return QSizeF(height * 5, height); +} + +qreal KStandardItemListWidget::columnPadding(const KItemListStyleOption& option) +{ + return option.padding * 6; +} + +#include "kstandarditemlistwidget.moc" diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h new file mode 100644 index 000000000..1bd44e2f6 --- /dev/null +++ b/src/kitemviews/kstandarditemlistwidget.h @@ -0,0 +1,218 @@ +/*************************************************************************** + * Copyright (C) 2012 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 KSTANDARDITEMLISTWIDGET_H +#define KSTANDARDITEMLISTWIDGET_H + +#include <libdolphin_export.h> + +#include <kitemviews/kitemlistwidget.h> + +#include <QPixmap> +#include <QPointF> +#include <QStaticText> + +class KItemListRoleEditor; +class KItemListStyleOption; +class KItemListView; + +class LIBDOLPHINPRIVATE_EXPORT KStandardItemListWidgetInformant : public KItemListWidgetInformant +{ +public: + KStandardItemListWidgetInformant(); + virtual ~KStandardItemListWidgetInformant(); + + virtual QSizeF itemSizeHint(int index, const KItemListView* view) const; + + virtual qreal preferredRoleColumnWidth(const QByteArray& role, + int index, + const KItemListView* view) const; +protected: + /** + * @return String representation of the role \a role. The representation of + * a role might depend on other roles, so the values of all roles + * are passed as parameter. + */ + virtual QString roleText(const QByteArray& role, + const QHash<QByteArray, QVariant>& values) const; + + friend class KStandardItemListWidget; // Accesses roleText() +}; + +/** + * @brief Itemlist widget implementation for KStandardItemView and KStandardItemModel. + */ +class LIBDOLPHINPRIVATE_EXPORT KStandardItemListWidget : public KItemListWidget +{ + Q_OBJECT + +public: + enum Layout + { + IconsLayout, + CompactLayout, + DetailsLayout + }; + + KStandardItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent); + virtual ~KStandardItemListWidget(); + + void setLayout(Layout layout); + Layout layout() const; + + void setSupportsItemExpanding(bool supportsItemExpanding); + bool supportsItemExpanding() const; + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); + + virtual QRectF iconRect() const; + virtual QRectF textRect() const; + virtual QRectF textFocusRect() const; + virtual QRectF expansionToggleRect() const; + virtual QRectF selectionToggleRect() const; + + static KItemListWidgetInformant* createInformant(); + +protected: + /** + * Invalidates the cache which results in calling KStandardItemListWidget::refreshCache() as + * soon as the item need to gets repainted. + */ + void invalidateCache(); + + /** + * Is called if the cache got invalidated by KStandardItemListWidget::invalidateCache(). + * The default implementation is empty. + */ + virtual void refreshCache(); + + /** + * @return True if the give role should be right aligned when showing it inside a column. + * Per default false is returned. + */ + virtual bool isRoleRightAligned(const QByteArray& role) const; + + void setTextColor(const QColor& color); + QColor textColor() const; + + void setOverlay(const QPixmap& overlay); + QPixmap overlay() const; + + /** + * @see KStandardItemListWidgetInformant::roleText(). + */ + QString roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values) const; + + virtual void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>()); + virtual void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous); + virtual void columnWidthChanged(const QByteArray& role, qreal current, qreal previous); + virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous); + virtual void hoveredChanged(bool hovered); + virtual void selectedChanged(bool selected); + virtual void siblingsInformationChanged(const QBitArray& current, const QBitArray& previous); + virtual void editedRoleChanged(const QByteArray& current, const QByteArray& previous); + virtual void resizeEvent(QGraphicsSceneResizeEvent* event); + virtual void showEvent(QShowEvent* event); + virtual void hideEvent(QHideEvent* event); + +private slots: + void slotCutItemsChanged(); + void slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value); + void slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value); + +private: + void triggerCacheRefreshing(); + void updateExpansionArea(); + void updatePixmapCache(); + + void updateTextsCache(); + void updateIconsLayoutTextCache(); + void updateCompactLayoutTextCache(); + void updateDetailsLayoutTextCache(); + + void updateAdditionalInfoTextColor(); + + void drawPixmap(QPainter* painter, const QPixmap& pixmap); + void drawSiblingsInformation(QPainter* painter); + + QRectF roleEditingRect(const QByteArray &role) const; + + static QPixmap pixmapForIcon(const QString& name, int size); + static void applyCutEffect(QPixmap& pixmap); + static void applyHiddenEffect(QPixmap& pixmap); + + /** + * @return Preferred size of the rating-image based on the given + * style-option. The height of the font is taken as + * reference. + */ + static QSizeF preferredRatingSize(const KItemListStyleOption& option); + + /** + * @return Horizontal padding in pixels that is added to the required width of + * a column to display the content. + */ + static qreal columnPadding(const KItemListStyleOption& option); + +private: + bool m_isCut; + bool m_isHidden; + bool m_isExpandable; + bool m_supportsItemExpanding; + + bool m_dirtyLayout; + bool m_dirtyContent; + QSet<QByteArray> m_dirtyContentRoles; + + Layout m_layout; + QPointF m_pixmapPos; + QPixmap m_pixmap; + QSize m_scaledPixmapSize; + + QRectF m_iconRect; // Cache for KItemListWidget::iconRect() + QPixmap m_hoverPixmap; // Cache for modified m_pixmap when hovering the item + + struct TextInfo + { + QPointF pos; + QStaticText staticText; + }; + QHash<QByteArray, TextInfo*> m_textInfo; + + QRectF m_textRect; + + QList<QByteArray> m_sortedVisibleRoles; + + QRectF m_expansionArea; + + QColor m_customTextColor; + QColor m_additionalInfoTextColor; + + QPixmap m_overlay; + QPixmap m_rating; + + KItemListRoleEditor* m_roleEditor; + + friend class KStandardItemListWidgetInformant; // Accesses private static methods to be able to + // share a common layout calculation +}; + +#endif + + diff --git a/src/kitemviews/kstandarditemmodel.cpp b/src/kitemviews/kstandarditemmodel.cpp new file mode 100644 index 000000000..76b2ad09a --- /dev/null +++ b/src/kitemviews/kstandarditemmodel.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** + * Copyright (C) 2012 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 "kstandarditemmodel.h" +#include "kstandarditem.h" + +KStandardItemModel::KStandardItemModel(QObject* parent) : + KItemModelBase(parent), + m_items(), + m_indexesForItems() +{ +} + +KStandardItemModel::~KStandardItemModel() +{ +} + +void KStandardItemModel::insertItem(int index, KStandardItem* item) +{ + if (!m_indexesForItems.contains(item) && !item->m_model) { + m_items.insert(index, item); + m_indexesForItems.insert(item, index); + item->m_model = this; + // TODO: no hierarchical items are handled yet + } +} + +void KStandardItemModel::appendItem(KStandardItem *item) +{ + insertItem(m_items.count(), item); +} + +void KStandardItemModel::removeItem(KStandardItem* item) +{ + const int index = m_indexesForItems.value(item, -1); + if (index >= 0) { + m_items.removeAt(index); + m_indexesForItems.remove(item); + delete item; + // TODO: no hierarchical items are handled yet + } +} + +KStandardItem* KStandardItemModel::item(int index) const +{ + if (index < 0 || index >= m_items.count()) { + return 0; + } + return m_items[index]; +} + +int KStandardItemModel::index(const KStandardItem* item) const +{ + return m_indexesForItems.value(item, -1); +} + +int KStandardItemModel::count() const +{ + return m_items.count(); +} + +QHash<QByteArray, QVariant> KStandardItemModel::data(int index) const +{ + // TODO: Ugly hack + QHash<QByteArray, QVariant> values; + const KStandardItem* item = m_items[index]; + values.insert("text", item->text()); + values.insert("iconName", item->icon().name()); + return values; +} + +bool KStandardItemModel::setData(int index, const QHash<QByteArray, QVariant>& values) +{ + Q_UNUSED(values); + if (index < 0 || index >= count()) { + return false; + } + + return true; +} + +QMimeData* KStandardItemModel::createMimeData(const QSet<int>& indexes) const +{ + Q_UNUSED(indexes); + return 0; +} + +int KStandardItemModel::indexForKeyboardSearch(const QString& text, int startFromIndex) const +{ + Q_UNUSED(text); + Q_UNUSED(startFromIndex); + return -1; +} + +bool KStandardItemModel::supportsDropping(int index) const +{ + Q_UNUSED(index); + return false; +} + +QString KStandardItemModel::roleDescription(const QByteArray& role) const +{ + Q_UNUSED(role); + return QString(); +} + +QList<QPair<int, QVariant> > KStandardItemModel::groups() const +{ + return QList<QPair<int, QVariant> >(); +} + +#include "kstandarditemmodel.moc" diff --git a/src/kitemviews/kstandarditemmodel.h b/src/kitemviews/kstandarditemmodel.h new file mode 100644 index 000000000..01d2d4d76 --- /dev/null +++ b/src/kitemviews/kstandarditemmodel.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2012 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 KSTANDARDITEMMODEL_H +#define KSTANDARDITEMMODEL_H + +#include <libdolphin_export.h> +#include <kitemviews/kitemmodelbase.h> +#include <QHash> +#include <QList> + +class KStandardItem; + +/** + * @brief Model counterpart for KStandardItemView. + * + * Allows to add items to the model in an easy way by the + * class KStandardItem. + * + * @see KStandardItem + */ +class LIBDOLPHINPRIVATE_EXPORT KStandardItemModel : public KItemModelBase +{ + Q_OBJECT + +public: + explicit KStandardItemModel(QObject* parent = 0); + virtual ~KStandardItemModel(); + + void insertItem(int index, KStandardItem* item); + void appendItem(KStandardItem* item); + void removeItem(KStandardItem* item); + KStandardItem* item(int index) const; + int index(const KStandardItem* item) const; + + virtual int count() const; + virtual QHash<QByteArray, QVariant> data(int index) const; + virtual bool setData(int index, const QHash<QByteArray, QVariant>& values); + virtual QMimeData* createMimeData(const QSet<int>& indexes) const; + virtual int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const; + virtual bool supportsDropping(int index) const; + virtual QString roleDescription(const QByteArray& role) const; + virtual QList<QPair<int, QVariant> > groups() const; + +private: + QList<KStandardItem*> m_items; + QHash<const KStandardItem*, int> m_indexesForItems; +}; + +#endif + + |
