diff options
| author | Peter Penz <[email protected]> | 2012-04-11 16:06:18 +0200 |
|---|---|---|
| committer | Peter Penz <[email protected]> | 2012-04-11 16:08:32 +0200 |
| commit | 6c3d9acbc22ea9463ba40ef84c9e8c8419dfacf3 (patch) | |
| tree | e7ffd63acd5e28eb71a077f816a23534b06fcae2 /src/kitemviews/private | |
| parent | d9dbd3398a258d04ec4517fd13e795b437c869d6 (diff) | |
KItemViews: Internal directory restructuration
- Move all private headers from the kitemviews-directory into
the 'private' subdirectory.
- Get rid of DolphinDirLister and just use a directory-lister
internally in KFileItemModel.
- Minor interface-cleanups for signals
Diffstat (limited to 'src/kitemviews/private')
28 files changed, 4310 insertions, 0 deletions
diff --git a/src/kitemviews/private/kfileitemclipboard.cpp b/src/kitemviews/private/kfileitemclipboard.cpp new file mode 100644 index 000000000..6d6085641 --- /dev/null +++ b/src/kitemviews/private/kfileitemclipboard.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kfileitemclipboard.h" + +#include <KGlobal> +#include <QApplication> +#include <QClipboard> +#include <QMimeData> + +class KFileItemClipboardSingleton +{ +public: + KFileItemClipboard instance; +}; +K_GLOBAL_STATIC(KFileItemClipboardSingleton, s_KFileItemClipboard) + + + +KFileItemClipboard* KFileItemClipboard::instance() +{ + return &s_KFileItemClipboard->instance; +} + +bool KFileItemClipboard::isCut(const KUrl& url) const +{ + return m_cutItems.contains(url); +} + +QList<KUrl> KFileItemClipboard::cutItems() const +{ + return m_cutItems.toList(); +} + +KFileItemClipboard::~KFileItemClipboard() +{ +} + +void KFileItemClipboard::updateCutItems() +{ + const QMimeData* mimeData = QApplication::clipboard()->mimeData(); + const QByteArray data = mimeData->data("application/x-kde-cutselection"); + const bool isCutSelection = (!data.isEmpty() && data.at(0) == QLatin1Char('1')); + if (isCutSelection) { + m_cutItems = KUrl::List::fromMimeData(mimeData).toSet(); + emit cutItemsChanged(); + } +} + +KFileItemClipboard::KFileItemClipboard() : + QObject(0), + m_cutItems() +{ + updateCutItems(); + + connect(QApplication::clipboard(), SIGNAL(dataChanged()), + this, SLOT(updateCutItems())); +} + +#include "kfileitemclipboard.moc" diff --git a/src/kitemviews/private/kfileitemclipboard.h b/src/kitemviews/private/kfileitemclipboard.h new file mode 100644 index 000000000..86eb8e9fc --- /dev/null +++ b/src/kitemviews/private/kfileitemclipboard.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KFILEITEMCLIPBOARD_H +#define KFILEITEMCLIPBOARD_H + +#include <KUrl> +#include <QList> +#include <QSet> +#include <QObject> + +#include "libdolphin_export.h" + +/** + * @brief Wrapper for QClipboard to provide fast access for checking + * whether a KFileItem has been clipped. + */ +class LIBDOLPHINPRIVATE_EXPORT KFileItemClipboard : public QObject +{ + Q_OBJECT + +public: + static KFileItemClipboard* instance(); + + bool isCut(const KUrl& url) const; + + QList<KUrl> cutItems() const; + +signals: + void cutItemsChanged(); + +protected: + virtual ~KFileItemClipboard(); + +private slots: + void updateCutItems(); + +private: + KFileItemClipboard(); + + QSet<KUrl> m_cutItems; + + friend class KFileItemClipboardSingleton; +}; + +#endif diff --git a/src/kitemviews/private/kfileitemmodeldirlister.cpp b/src/kitemviews/private/kfileitemmodeldirlister.cpp new file mode 100644 index 000000000..be0f9f77b --- /dev/null +++ b/src/kitemviews/private/kfileitemmodeldirlister.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2006-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 "kfileitemmodeldirlister.h" +#include <KLocale> +#include <KIO/JobClasses> + +KFileItemModelDirLister::KFileItemModelDirLister(QObject* parent) : + KDirLister(parent) +{ + setAutoErrorHandlingEnabled(false, 0); +} + +KFileItemModelDirLister::~KFileItemModelDirLister() +{ +} + +void KFileItemModelDirLister::handleError(KIO::Job* job) +{ + const QString errorString = job->errorString(); + if (errorString.isEmpty()) { + emit errorMessage(i18nc("@info:status", "Unknown error.")); + } else { + emit errorMessage(errorString); + } +} + +#include "kfileitemmodeldirlister.moc" diff --git a/src/kitemviews/private/kfileitemmodeldirlister.h b/src/kitemviews/private/kfileitemmodeldirlister.h new file mode 100644 index 000000000..1d58347f4 --- /dev/null +++ b/src/kitemviews/private/kfileitemmodeldirlister.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2006-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 KFILEITEMMODELDIRLISTER_H +#define KFILEITEMMODELDIRLISTER_H + +#include <libdolphin_export.h> +#include <KDirLister> + +/** + * @brief Extends the class KDirLister by emitting a signal when an + * error occurred instead of showing an error dialog. + * KDirLister::autoErrorHandlingEnabled() is set to false. + */ +class LIBDOLPHINPRIVATE_EXPORT KFileItemModelDirLister : public KDirLister +{ + Q_OBJECT + +public: + KFileItemModelDirLister(QObject* parent = 0); + virtual ~KFileItemModelDirLister(); + +signals: + /** Is emitted whenever an error has occurred. */ + void errorMessage(const QString& msg); + +protected: + virtual void handleError(KIO::Job* job); +}; + +#endif diff --git a/src/kitemviews/private/kfileitemmodelfilter.cpp b/src/kitemviews/private/kfileitemmodelfilter.cpp new file mode 100644 index 000000000..816d35634 --- /dev/null +++ b/src/kitemviews/private/kfileitemmodelfilter.cpp @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2011 by Janardhan Reddy * + * <[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 "kfileitemmodelfilter.h" + +#include <KFileItem> +#include <QRegExp> + +KFileItemModelFilter::KFileItemModelFilter() : + m_useRegExp(false), + m_regExp(0), + m_lowerCasePattern(), + m_pattern() +{ +} + +KFileItemModelFilter::~KFileItemModelFilter() +{ + delete m_regExp; + m_regExp = 0; +} + +void KFileItemModelFilter::setPattern(const QString& filter) +{ + m_pattern = filter; + m_lowerCasePattern = filter.toLower(); + + m_useRegExp = filter.contains('*') || + filter.contains('?') || + filter.contains('['); + if (m_useRegExp) { + if (!m_regExp) { + m_regExp = new QRegExp(); + m_regExp->setCaseSensitivity(Qt::CaseInsensitive); + m_regExp->setMinimal(false); + m_regExp->setPatternSyntax(QRegExp::WildcardUnix); + } + m_regExp->setPattern(filter); + } +} + +QString KFileItemModelFilter::pattern() const +{ + return m_pattern; +} + +bool KFileItemModelFilter::matches(const KFileItem& item) const +{ + if (m_useRegExp) { + return m_regExp->exactMatch(item.text()); + } else { + return item.text().toLower().contains(m_lowerCasePattern); + } +} diff --git a/src/kitemviews/private/kfileitemmodelfilter.h b/src/kitemviews/private/kfileitemmodelfilter.h new file mode 100644 index 000000000..9bdf1fd95 --- /dev/null +++ b/src/kitemviews/private/kfileitemmodelfilter.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2011 by Janardhan Reddy * + * <[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 KFILEITEMMODELFILTER_H +#define KFILEITEMMODELFILTER_H + +#include <libdolphin_export.h> +#include <QString> + +class KFileItem; +class QRegExp; + +/** + * @brief Allows to check whether an item of the KFileItemModel + * matches with a set filter-string. + * + * Currently the filter is only checked for the KFileItem::text() + * property of the KFileItem, but this might get extended in + * future. + */ +class LIBDOLPHINPRIVATE_EXPORT KFileItemModelFilter +{ + +public: + KFileItemModelFilter(); + virtual ~KFileItemModelFilter(); + + /** + * Sets the pattern that is used for a comparison with the item + * in KFileItemModelFilter::matches(). Per default the pattern + * defines a sub-string. As soon as the pattern contains at least + * a '*', '?' or '[' the pattern represents a regular expression. + */ + void setPattern(const QString& pattern); + QString pattern() const; + + /** + * @return True if the item matches with the pattern defined by + * KFileItemModelFilter::setPattern(). + */ + bool matches(const KFileItem& item) const; + +private: + bool m_useRegExp; // If true, m_regExp is used for filtering, + // otherwise m_lowerCaseFilter is used. + QRegExp* m_regExp; + QString m_lowerCasePattern; // Lowercase version of m_filter for + // faster comparison in matches(). + QString m_pattern; // Property set by setFilter(). +}; +#endif + + diff --git a/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp b/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp new file mode 100644 index 000000000..e0aac13de --- /dev/null +++ b/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp @@ -0,0 +1,148 @@ +/*************************************************************************** + * 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 "kfileitemmodelsortalgorithm.h" + +void KFileItemModelSortAlgorithm::sort(KFileItemModel* model, + QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end) +{ + // The implementation is based on qStableSortHelper() from qalgorithms.h + // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + + const int span = end - begin; + if (span < 2) { + return; + } + + const QList<KFileItemModel::ItemData*>::iterator middle = begin + span / 2; + sort(model, begin, middle); + sort(model, middle, end); + merge(model, begin, middle, end); +} + +void KFileItemModelSortAlgorithm::merge(KFileItemModel* model, + QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator pivot, + QList<KFileItemModel::ItemData*>::iterator end) +{ + // The implementation is based on qMerge() from qalgorithms.h + // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + + const int len1 = pivot - begin; + const int len2 = end - pivot; + + if (len1 == 0 || len2 == 0) { + return; + } + + if (len1 + len2 == 2) { + if (model->lessThan(*(begin + 1), *(begin))) { + qSwap(*begin, *(begin + 1)); + } + return; + } + + QList<KFileItemModel::ItemData*>::iterator firstCut; + QList<KFileItemModel::ItemData*>::iterator secondCut; + int len2Half; + if (len1 > len2) { + const int len1Half = len1 / 2; + firstCut = begin + len1Half; + secondCut = lowerBound(model, pivot, end, *firstCut); + len2Half = secondCut - pivot; + } else { + len2Half = len2 / 2; + secondCut = pivot + len2Half; + firstCut = upperBound(model, begin, pivot, *secondCut); + } + + reverse(firstCut, pivot); + reverse(pivot, secondCut); + reverse(firstCut, secondCut); + + const QList<KFileItemModel::ItemData*>::iterator newPivot = firstCut + len2Half; + merge(model, begin, firstCut, newPivot); + merge(model, newPivot, secondCut, end); +} + + +QList<KFileItemModel::ItemData*>::iterator +KFileItemModelSortAlgorithm::lowerBound(KFileItemModel* model, + QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end, + const KFileItemModel::ItemData* value) +{ + // The implementation is based on qLowerBound() from qalgorithms.h + // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + + QList<KFileItemModel::ItemData*>::iterator middle; + int n = int(end - begin); + int half; + + while (n > 0) { + half = n >> 1; + middle = begin + half; + if (model->lessThan(*middle, value)) { + begin = middle + 1; + n -= half + 1; + } else { + n = half; + } + } + return begin; +} + +QList<KFileItemModel::ItemData*>::iterator +KFileItemModelSortAlgorithm::upperBound(KFileItemModel* model, + QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end, + const KFileItemModel::ItemData* value) +{ + // The implementation is based on qUpperBound() from qalgorithms.h + // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + + QList<KFileItemModel::ItemData*>::iterator middle; + int n = end - begin; + int half; + + while (n > 0) { + half = n >> 1; + middle = begin + half; + if (model->lessThan(value, *middle)) { + n = half; + } else { + begin = middle + 1; + n -= half + 1; + } + } + return begin; +} + +void KFileItemModelSortAlgorithm::reverse(QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end) +{ + // The implementation is based on qReverse() from qalgorithms.h + // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + + --end; + while (begin < end) { + qSwap(*begin++, *end--); + } +} diff --git a/src/kitemviews/private/kfileitemmodelsortalgorithm.h b/src/kitemviews/private/kfileitemmodelsortalgorithm.h new file mode 100644 index 000000000..3a596dff5 --- /dev/null +++ b/src/kitemviews/private/kfileitemmodelsortalgorithm.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * 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 KFILEITEMMODELSORTALGORITHM_H +#define KFILEITEMMODELSORTALGORITHM_H + +#include <libdolphin_export.h> + +#include <kitemviews/kfileitemmodel.h> + +/** + * @brief Sort algorithm for sorting items of KFileItemModel. + * + * Sorts the items by using KFileItemModel::lessThan() as comparison criteria. + * The merge sort algorithm is used to assure a worst-case + * of O(n * log(n)) and to keep the number of comparisons low. + * + * The implementation is based on qStableSortHelper() from qalgorithms.h + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + * The sorting implementations of qAlgorithms could not be used as they + * don't support having a member-function as comparison criteria. + */ +class LIBDOLPHINPRIVATE_EXPORT KFileItemModelSortAlgorithm +{ +public: + static void sort(KFileItemModel* model, + QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end); + +private: + static void merge(KFileItemModel* model, + QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator pivot, + QList<KFileItemModel::ItemData*>::iterator end); + + static QList<KFileItemModel::ItemData*>::iterator + lowerBound(KFileItemModel* model, + QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end, + const KFileItemModel::ItemData* value); + + static QList<KFileItemModel::ItemData*>::iterator + upperBound(KFileItemModel* model, + QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end, + const KFileItemModel::ItemData* value); + + static void reverse(QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end); +}; + +#endif + + diff --git a/src/kitemviews/private/kitemlistheaderwidget.cpp b/src/kitemviews/private/kitemlistheaderwidget.cpp new file mode 100644 index 000000000..576516f25 --- /dev/null +++ b/src/kitemviews/private/kitemlistheaderwidget.cpp @@ -0,0 +1,535 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistheaderwidget.h" + +#include <KAction> +#include <KMenu> +#include <kitemviews/kitemmodelbase.h> + +#include <QApplication> +#include <QGraphicsSceneHoverEvent> +#include <QPainter> +#include <QStyleOptionHeader> + +#include <KDebug> + +KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget* parent) : + QGraphicsWidget(parent), + m_automaticColumnResizing(true), + m_model(0), + m_offset(0), + m_columns(), + m_columnWidths(), + m_preferredColumnWidths(), + m_hoveredRoleIndex(-1), + m_pressedRoleIndex(-1), + m_roleOperation(NoRoleOperation), + m_pressedMousePos(), + m_movingRole() +{ + m_movingRole.x = 0; + m_movingRole.xDec = 0; + m_movingRole.index = -1; + + setAcceptHoverEvents(true); +} + +KItemListHeaderWidget::~KItemListHeaderWidget() +{ +} + +void KItemListHeaderWidget::setModel(KItemModelBase* model) +{ + if (m_model == model) { + return; + } + + if (m_model) { + disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); + disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + } + + m_model = model; + + if (m_model) { + connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); + connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + } +} + +KItemModelBase* KItemListHeaderWidget::model() const +{ + return m_model; +} + +void KItemListHeaderWidget::setAutomaticColumnResizing(bool automatic) +{ + m_automaticColumnResizing = automatic; +} + +bool KItemListHeaderWidget::automaticColumnResizing() const +{ + return m_automaticColumnResizing; +} + +void KItemListHeaderWidget::setColumns(const QList<QByteArray>& roles) +{ + foreach (const QByteArray& role, roles) { + if (!m_columnWidths.contains(role)) { + m_columnWidths.remove(role); + m_preferredColumnWidths.remove(role); + } + } + + m_columns = roles; + update(); +} + +QList<QByteArray> KItemListHeaderWidget::columns() const +{ + return m_columns; +} + +void KItemListHeaderWidget::setColumnWidth(const QByteArray& role, qreal width) +{ + const qreal minWidth = minimumColumnWidth(); + if (width < minWidth) { + width = minWidth; + } + + if (m_columnWidths.value(role) != width) { + m_columnWidths.insert(role, width); + update(); + } +} + +qreal KItemListHeaderWidget::columnWidth(const QByteArray& role) const +{ + return m_columnWidths.value(role); +} + +void KItemListHeaderWidget::setPreferredColumnWidth(const QByteArray& role, qreal width) +{ + m_preferredColumnWidths.insert(role, width); +} + +qreal KItemListHeaderWidget::preferredColumnWidth(const QByteArray& role) const +{ + return m_preferredColumnWidths.value(role); +} + +void KItemListHeaderWidget::setOffset(qreal offset) +{ + if (m_offset != offset) { + m_offset = offset; + update(); + } +} + +qreal KItemListHeaderWidget::offset() const +{ + return m_offset; +} + +qreal KItemListHeaderWidget::minimumColumnWidth() const +{ + QFontMetricsF fontMetrics(font()); + return fontMetrics.height() * 4; +} + +void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + if (!m_model) { + return; + } + + // Draw roles + painter->setFont(font()); + painter->setPen(palette().text().color()); + + qreal x = -m_offset; + int orderIndex = 0; + foreach (const QByteArray& role, m_columns) { + const qreal roleWidth = m_columnWidths.value(role); + const QRectF rect(x, 0, roleWidth, size().height()); + paintRole(painter, role, rect, orderIndex, widget); + x += roleWidth; + ++orderIndex; + } + + // Draw background without roles + QStyleOption opt; + opt.init(widget); + opt.rect = QRect(x, 0, size().width() - x, size().height()); + opt.state |= QStyle::State_Horizontal; + style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, painter); + + if (!m_movingRole.pixmap.isNull()) { + Q_ASSERT(m_roleOperation == MoveRoleOperation); + painter->drawPixmap(m_movingRole.x, 0, m_movingRole.pixmap); + } +} + +void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + if (event->button() & Qt::LeftButton) { + updatePressedRoleIndex(event->pos()); + m_pressedMousePos = event->pos(); + m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ? + ResizeRoleOperation : NoRoleOperation; + event->accept(); + } else { + event->ignore(); + } +} + +void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsWidget::mouseReleaseEvent(event); + + if (m_pressedRoleIndex == -1) { + return; + } + + switch (m_roleOperation) { + case NoRoleOperation: { + // Only a click has been done and no moving or resizing has been started + const QByteArray sortRole = m_model->sortRole(); + const int sortRoleIndex = m_columns.indexOf(sortRole); + if (m_pressedRoleIndex == sortRoleIndex) { + // Toggle the sort order + const Qt::SortOrder previous = m_model->sortOrder(); + const Qt::SortOrder current = (m_model->sortOrder() == Qt::AscendingOrder) ? + Qt::DescendingOrder : Qt::AscendingOrder; + m_model->setSortOrder(current); + emit sortOrderChanged(current, previous); + } else { + // Change the sort role + const QByteArray previous = m_model->sortRole(); + const QByteArray current = m_columns[m_pressedRoleIndex]; + m_model->setSortRole(current); + emit sortRoleChanged(current, previous); + } + break; + } + + case MoveRoleOperation: + m_movingRole.pixmap = QPixmap(); + m_movingRole.x = 0; + m_movingRole.xDec = 0; + m_movingRole.index = -1; + break; + + default: + break; + } + + m_pressedRoleIndex = -1; + m_roleOperation = NoRoleOperation; + update(); + + QApplication::restoreOverrideCursor(); +} + +void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsWidget::mouseMoveEvent(event); + + switch (m_roleOperation) { + case NoRoleOperation: + if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) { + // A role gets dragged by the user. Create a pixmap of the role that will get + // synchronized on each furter mouse-move-event with the mouse-position. + m_roleOperation = MoveRoleOperation; + const int roleIndex = roleIndexAt(m_pressedMousePos); + m_movingRole.index = roleIndex; + if (roleIndex == 0) { + // TODO: It should be configurable whether moving the first role is allowed. + // In the context of Dolphin this is not required, however this should be + // changed if KItemViews are used in a more generic way. + QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor)); + } else { + m_movingRole.pixmap = createRolePixmap(roleIndex); + + qreal roleX = -m_offset; + for (int i = 0; i < roleIndex; ++i) { + const QByteArray role = m_columns[i]; + roleX += m_columnWidths.value(role); + } + + m_movingRole.xDec = event->pos().x() - roleX; + m_movingRole.x = roleX; + update(); + } + } + break; + + case ResizeRoleOperation: { + const QByteArray pressedRole = m_columns[m_pressedRoleIndex]; + + qreal previousWidth = m_columnWidths.value(pressedRole); + qreal currentWidth = previousWidth; + currentWidth += event->pos().x() - event->lastPos().x(); + currentWidth = qMax(minimumColumnWidth(), currentWidth); + + m_columnWidths.insert(pressedRole, currentWidth); + update(); + + emit columnWidthChanged(pressedRole, currentWidth, previousWidth); + break; + } + + case MoveRoleOperation: { + // TODO: It should be configurable whether moving the first role is allowed. + // In the context of Dolphin this is not required, however this should be + // changed if KItemViews are used in a more generic way. + if (m_movingRole.index > 0) { + m_movingRole.x = event->pos().x() - m_movingRole.xDec; + update(); + + const int targetIndex = targetOfMovingRole(); + if (targetIndex > 0 && targetIndex != m_movingRole.index) { + const QByteArray role = m_columns[m_movingRole.index]; + const int previousIndex = m_movingRole.index; + m_movingRole.index = targetIndex; + emit columnMoved(role, targetIndex, previousIndex); + + m_movingRole.xDec = event->pos().x() - roleXPosition(role); + } + } + break; + } + + default: + break; + } +} + +void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsWidget::hoverEnterEvent(event); + updateHoveredRoleIndex(event->pos()); +} + +void KItemListHeaderWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsWidget::hoverLeaveEvent(event); + if (m_hoveredRoleIndex != -1) { + m_hoveredRoleIndex = -1; + update(); + } +} + +void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsWidget::hoverMoveEvent(event); + + const QPointF& pos = event->pos(); + updateHoveredRoleIndex(pos); + if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) { + setCursor(Qt::SplitHCursor); + } else { + unsetCursor(); + } +} + +void KItemListHeaderWidget::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + update(); +} + +void KItemListHeaderWidget::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + update(); +} + +void KItemListHeaderWidget::paintRole(QPainter* painter, + const QByteArray& role, + const QRectF& rect, + int orderIndex, + QWidget* widget) const +{ + // The following code is based on the code from QHeaderView::paintSection(). + // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + QStyleOptionHeader option; + option.section = orderIndex; + option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal; + if (isEnabled()) { + option.state |= QStyle::State_Enabled; + } + if (window() && window()->isActiveWindow()) { + option.state |= QStyle::State_Active; + } + if (m_hoveredRoleIndex == orderIndex) { + option.state |= QStyle::State_MouseOver; + } + if (m_pressedRoleIndex == orderIndex) { + option.state |= QStyle::State_Sunken; + } + if (m_model->sortRole() == role) { + option.sortIndicator = (m_model->sortOrder() == Qt::AscendingOrder) ? + QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp; + } + option.rect = rect.toRect(); + + if (m_columns.count() == 1) { + option.position = QStyleOptionHeader::OnlyOneSection; + } else if (orderIndex == 0) { + option.position = QStyleOptionHeader::Beginning; + } else if (orderIndex == m_columns.count() - 1) { + option.position = QStyleOptionHeader::End; + } else { + option.position = QStyleOptionHeader::Middle; + } + + option.orientation = Qt::Horizontal; + option.selectedPosition = QStyleOptionHeader::NotAdjacent; + option.text = m_model->roleDescription(role); + + style()->drawControl(QStyle::CE_Header, &option, painter, widget); +} + +void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF& pos) +{ + const int pressedIndex = roleIndexAt(pos); + if (m_pressedRoleIndex != pressedIndex) { + m_pressedRoleIndex = pressedIndex; + update(); + } +} + +void KItemListHeaderWidget::updateHoveredRoleIndex(const QPointF& pos) +{ + const int hoverIndex = roleIndexAt(pos); + if (m_hoveredRoleIndex != hoverIndex) { + m_hoveredRoleIndex = hoverIndex; + update(); + } +} + +int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const +{ + int index = -1; + + qreal x = -m_offset; + foreach (const QByteArray& role, m_columns) { + ++index; + x += m_columnWidths.value(role); + if (pos.x() <= x) { + break; + } + } + + return index; +} + +bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) const +{ + qreal x = -m_offset; + for (int i = 0; i <= roleIndex; ++i) { + const QByteArray role = m_columns[i]; + x += m_columnWidths.value(role); + } + + const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin); + return pos.x() >= (x - grip) && pos.x() <= x; +} + +QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const +{ + const QByteArray role = m_columns[roleIndex]; + const qreal roleWidth = m_columnWidths.value(role); + const QRect rect(0, 0, roleWidth, size().height()); + + QImage image(rect.size(), QImage::Format_ARGB32_Premultiplied); + + QPainter painter(&image); + paintRole(&painter, role, rect, roleIndex); + + // Apply a highlighting-color + const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; + QColor highlightColor = palette().color(group, QPalette::Highlight); + highlightColor.setAlpha(64); + painter.fillRect(rect, highlightColor); + + // Make the image transparent + painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); + painter.fillRect(0, 0, image.width(), image.height(), QColor(0, 0, 0, 192)); + + return QPixmap::fromImage(image); +} + +int KItemListHeaderWidget::targetOfMovingRole() const +{ + const int movingWidth = m_movingRole.pixmap.width(); + const int movingLeft = m_movingRole.x; + const int movingRight = movingLeft + movingWidth - 1; + + int targetIndex = 0; + qreal targetLeft = -m_offset; + while (targetIndex < m_columns.count()) { + const QByteArray role = m_columns[targetIndex]; + const qreal targetWidth = m_columnWidths.value(role); + const qreal targetRight = targetLeft + targetWidth - 1; + + const bool isInTarget = (targetWidth >= movingWidth && + movingLeft >= targetLeft && + movingRight <= targetRight) || + (targetWidth < movingWidth && + movingLeft <= targetLeft && + movingRight >= targetRight); + + if (isInTarget) { + return targetIndex; + } + + targetLeft += targetWidth; + ++targetIndex; + } + + return m_movingRole.index; +} + +qreal KItemListHeaderWidget::roleXPosition(const QByteArray& role) const +{ + qreal x = -m_offset; + foreach (const QByteArray& visibleRole, m_columns) { + if (visibleRole == role) { + return x; + } + + x += m_columnWidths.value(visibleRole); + } + + return -1; +} + +#include "kitemlistheaderwidget.moc" diff --git a/src/kitemviews/private/kitemlistheaderwidget.h b/src/kitemviews/private/kitemlistheaderwidget.h new file mode 100644 index 000000000..f8bba977b --- /dev/null +++ b/src/kitemviews/private/kitemlistheaderwidget.h @@ -0,0 +1,171 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KITEMLISTHEADERWIDGET_H +#define KITEMLISTHEADERWIDGET_H + +#include <libdolphin_export.h> +#include <QGraphicsWidget> +#include <QHash> +#include <QList> + +class KItemModelBase; + +/** + * @brief Widget the implements the header for KItemListView showing the currently used roles. + * + * The widget is an internal API, the user of KItemListView may only access the + * class KItemListHeader. + */ +class LIBDOLPHINPRIVATE_EXPORT KItemListHeaderWidget : public QGraphicsWidget +{ + Q_OBJECT + +public: + KItemListHeaderWidget(QGraphicsWidget* parent = 0); + virtual ~KItemListHeaderWidget(); + + void setModel(KItemModelBase* model); + KItemModelBase* model() const; + + void setAutomaticColumnResizing(bool automatic); + bool automaticColumnResizing() const; + + void setColumns(const QList<QByteArray>& roles); + QList<QByteArray> columns() const; + + void setColumnWidth(const QByteArray& role, qreal width); + qreal columnWidth(const QByteArray& role) const; + + /** + * Sets the column-width that is required to show the role unclipped. + */ + void setPreferredColumnWidth(const QByteArray& role, qreal width); + qreal preferredColumnWidth(const QByteArray& role) const; + + void setOffset(qreal offset); + qreal offset() const; + + qreal minimumColumnWidth() const; + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); + +signals: + /** + * Is emitted if the width of a visible role has been adjusted by the user with the mouse + * (no signal is emitted if KItemListHeader::setVisibleRoleWidth() is invoked). + */ + void columnWidthChanged(const QByteArray& role, + qreal currentWidth, + qreal previousWidth); + + /** + * Is emitted if the position of the column has been changed. + */ + void columnMoved(const QByteArray& role, int currentIndex, int previousIndex); + + /** + * Is emitted if the user has changed the sort order by clicking on a + * header item. The sort order of the model has already been adjusted to + * the current sort order. Note that no signal will be emitted if the + * sort order of the model has been changed without user interaction. + */ + void sortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); + + /** + * Is emitted if the user has changed the sort role by clicking on a + * header item. The sort role of the model has already been adjusted to + * the current sort role. Note that no signal will be emitted if the + * sort role of the model has been changed without user interaction. + */ + void sortRoleChanged(const QByteArray& current, const QByteArray& previous); + +protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event); + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event); + +private slots: + void slotSortRoleChanged(const QByteArray& current, const QByteArray& previous); + void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); + +private: + void paintRole(QPainter* painter, + const QByteArray& role, + const QRectF& rect, + int orderIndex, + QWidget* widget = 0) const; + + void updatePressedRoleIndex(const QPointF& pos); + void updateHoveredRoleIndex(const QPointF& pos); + int roleIndexAt(const QPointF& pos) const; + bool isAboveRoleGrip(const QPointF& pos, int roleIndex) const; + + /** + * Creates a pixmap of the role with the index \a roleIndex that is shown + * during moving a role. + */ + QPixmap createRolePixmap(int roleIndex) const; + + /** + * @return Target index of the currently moving visible role based on the current + * state of m_movingRole. + */ + int targetOfMovingRole() const; + + /** + * @return x-position of the left border of the role \a role. + */ + qreal roleXPosition(const QByteArray& role) const; + +private: + enum RoleOperation + { + NoRoleOperation, + ResizeRoleOperation, + MoveRoleOperation + }; + + bool m_automaticColumnResizing; + KItemModelBase* m_model; + qreal m_offset; + QList<QByteArray> m_columns; + QHash<QByteArray, qreal> m_columnWidths; + QHash<QByteArray, qreal> m_preferredColumnWidths; + + int m_hoveredRoleIndex; + int m_pressedRoleIndex; + RoleOperation m_roleOperation; + QPointF m_pressedMousePos; + + struct MovingRole + { + QPixmap pixmap; + int x; + int xDec; + int index; + } m_movingRole; +}; + +#endif + + diff --git a/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp new file mode 100644 index 000000000..2f4e93b1d --- /dev/null +++ b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2011 by Tirtha Chatterjee <[email protected]> * + * * + * Based on the Itemviews NG project from Trolltech Labs: * + * http://qt.gitorious.org/qt-labs/itemviews-ng * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistkeyboardsearchmanager.h" + +#include <QApplication> +#include <QElapsedTimer> + +#include <KDebug> + +KItemListKeyboardSearchManager::KItemListKeyboardSearchManager(QObject* parent) : + QObject(parent), + m_timeout(5000) +{ + m_keyboardInputTime.invalidate(); +} + +KItemListKeyboardSearchManager::~KItemListKeyboardSearchManager() +{ +} + +void KItemListKeyboardSearchManager::addKeys(const QString& keys) +{ + const bool keyboardTimeWasValid = m_keyboardInputTime.isValid(); + const qint64 keyboardInputTimeElapsed = m_keyboardInputTime.restart(); + if (keyboardInputTimeElapsed > m_timeout || !keyboardTimeWasValid || keys.isEmpty()) { + m_searchedString.clear(); + } + + const bool newSearch = m_searchedString.isEmpty(); + + if (!keys.isEmpty()) { + m_searchedString.append(keys); + + // Special case: + // If the same key is pressed repeatedly, the next item matching that key should be highlighted + const QChar firstKey = m_searchedString.length() > 0 ? m_searchedString.at(0) : QChar(); + const bool sameKey = m_searchedString.length() > 1 && m_searchedString.count(firstKey) == m_searchedString.length(); + + // Searching for a matching item should start from the next item if either + // 1. a new search is started, or + // 2. a 'repeated key' search is done. + const bool searchFromNextItem = newSearch || sameKey; + + emit changeCurrentItem(sameKey ? firstKey : m_searchedString, searchFromNextItem); + } + m_keyboardInputTime.start(); +} + +void KItemListKeyboardSearchManager::setTimeout(qint64 milliseconds) +{ + m_timeout = milliseconds; +} + +qint64 KItemListKeyboardSearchManager::timeout() const +{ + return m_timeout; +} + diff --git a/src/kitemviews/private/kitemlistrubberband.cpp b/src/kitemviews/private/kitemlistrubberband.cpp new file mode 100644 index 000000000..ae023d2aa --- /dev/null +++ b/src/kitemviews/private/kitemlistrubberband.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistrubberband.h" + +KItemListRubberBand::KItemListRubberBand(QObject* parent) : + QObject(parent), + m_active(false), + m_startPos(), + m_endPos() +{ +} + +KItemListRubberBand::~KItemListRubberBand() +{ +} + +void KItemListRubberBand::setStartPosition(const QPointF& pos) +{ + if (m_startPos != pos) { + const QPointF previous = m_startPos; + m_startPos = pos; + emit startPositionChanged(m_startPos, previous); + } +} + +QPointF KItemListRubberBand::startPosition() const +{ + return m_startPos; +} + +void KItemListRubberBand::setEndPosition(const QPointF& pos) +{ + if (m_endPos != pos) { + const QPointF previous = m_endPos; + m_endPos = pos; + emit endPositionChanged(m_endPos, previous); + } +} + +QPointF KItemListRubberBand::endPosition() const +{ + return m_endPos; +} + +void KItemListRubberBand::setActive(bool active) +{ + if (m_active != active) { + m_active = active; + emit activationChanged(active); + } +} + +bool KItemListRubberBand::isActive() const +{ + return m_active; +} + +#include "kitemlistrubberband.moc" diff --git a/src/kitemviews/private/kitemlistselectiontoggle.cpp b/src/kitemviews/private/kitemlistselectiontoggle.cpp new file mode 100644 index 000000000..66da6a727 --- /dev/null +++ b/src/kitemviews/private/kitemlistselectiontoggle.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistselectiontoggle.h" + +#include <KIconEffect> +#include <KIconLoader> +#include <QPainter> + +#include <KDebug> + +KItemListSelectionToggle::KItemListSelectionToggle(QGraphicsItem* parent) : + QGraphicsWidget(parent, 0), + m_checked(false), + m_hovered(false) +{ + setAcceptHoverEvents(true); +} + +KItemListSelectionToggle::~KItemListSelectionToggle() +{ +} + +void KItemListSelectionToggle::setChecked(bool checked) +{ + if (m_checked != checked) { + m_checked = checked; + m_pixmap = QPixmap(); + update(); + } +} + +bool KItemListSelectionToggle::isChecked() const +{ + return m_checked; +} + +void KItemListSelectionToggle::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + if (m_pixmap.isNull()) { + updatePixmap(); + } + + const qreal x = (size().width() - qreal(m_pixmap.width())) / 2; + const qreal y = (size().height() - qreal(m_pixmap.height())) / 2; + painter->drawPixmap(x, y, m_pixmap); +} + +void KItemListSelectionToggle::hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsWidget::hoverEnterEvent(event); + m_hovered = true; + m_pixmap = QPixmap(); +} + +void KItemListSelectionToggle::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsWidget::hoverLeaveEvent(event); + m_hovered = false; + m_pixmap = QPixmap(); +} + +void KItemListSelectionToggle::updatePixmap() +{ + const char* icon = m_checked ? "list-remove" : "list-add"; + + int iconSize = qMin(size().width(), size().height()); + if (iconSize < KIconLoader::SizeSmallMedium) { + iconSize = KIconLoader::SizeSmall; + } else if (iconSize < KIconLoader::SizeMedium) { + iconSize = KIconLoader::SizeSmallMedium; + } else if (iconSize < KIconLoader::SizeLarge) { + iconSize = KIconLoader::SizeMedium; + } else if (iconSize < KIconLoader::SizeHuge) { + iconSize = KIconLoader::SizeLarge; + } else if (iconSize < KIconLoader::SizeEnormous) { + iconSize = KIconLoader::SizeHuge; + } + + m_pixmap = KIconLoader::global()->loadIcon(QLatin1String(icon), KIconLoader::NoGroup, iconSize); + + if (m_hovered) { + KIconLoader::global()->iconEffect()->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState); + } +} + +#include "kitemlistselectiontoggle.moc" diff --git a/src/kitemviews/private/kitemlistselectiontoggle.h b/src/kitemviews/private/kitemlistselectiontoggle.h new file mode 100644 index 000000000..a8050d811 --- /dev/null +++ b/src/kitemviews/private/kitemlistselectiontoggle.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KITEMLISTSELECTIONTOGGLE_H +#define KITEMLISTSELECTIONTOGGLE_H + +#include <libdolphin_export.h> + +#include <QGraphicsWidget> +#include <QPixmap> + +class QPropertyAnimation; + +/** + * @brief Allows to toggle between the selected and unselected state of an item. + */ +class LIBDOLPHINPRIVATE_EXPORT KItemListSelectionToggle : public QGraphicsWidget +{ + Q_OBJECT + +public: + KItemListSelectionToggle(QGraphicsItem* parent); + virtual ~KItemListSelectionToggle(); + + void setChecked(bool checked); + bool isChecked() const; + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); + +protected: + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); + +private: + void updatePixmap(); + +private: + bool m_checked; + bool m_hovered; + QPixmap m_pixmap; +}; + +#endif + + diff --git a/src/kitemviews/private/kitemlistsizehintresolver.cpp b/src/kitemviews/private/kitemlistsizehintresolver.cpp new file mode 100644 index 000000000..c76ff0f55 --- /dev/null +++ b/src/kitemviews/private/kitemlistsizehintresolver.cpp @@ -0,0 +1,88 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistsizehintresolver.h" + +#include <kitemviews/kitemlistview.h> +#include <KDebug> + +KItemListSizeHintResolver::KItemListSizeHintResolver(const KItemListView* itemListView) : + m_itemListView(itemListView), + m_sizeHintCache() +{ +} + +KItemListSizeHintResolver::~KItemListSizeHintResolver() +{ +} + +QSizeF KItemListSizeHintResolver::sizeHint(int index) const +{ + QSizeF size = m_sizeHintCache.at(index); + if (size.isEmpty()) { + size = m_itemListView->itemSizeHint(index); + m_sizeHintCache[index] = size; + } + return size; +} + +void KItemListSizeHintResolver::itemsInserted(int index, int count) +{ + const int currentCount = m_sizeHintCache.count(); + m_sizeHintCache.reserve(currentCount + count); + while (count > 0) { + m_sizeHintCache.insert(index, QSizeF()); + ++index; + --count; + } +} + +void KItemListSizeHintResolver::itemsRemoved(int index, int count) +{ + const QList<QSizeF>::iterator begin = m_sizeHintCache.begin() + index; + const QList<QSizeF>::iterator end = begin + count; + m_sizeHintCache.erase(begin, end); +} + +void KItemListSizeHintResolver::itemsMoved(int index, int count) +{ + while (count) { + m_sizeHintCache[index] = QSizeF(); + ++index; + --count; + } +} + +void KItemListSizeHintResolver::itemsChanged(int index, int count, const QSet<QByteArray>& roles) +{ + Q_UNUSED(roles); + while (count) { + m_sizeHintCache[index] = QSizeF(); + ++index; + --count; + } +} + +void KItemListSizeHintResolver::clearCache() +{ + const int count = m_sizeHintCache.count(); + for (int i = 0; i < count; ++i) { + m_sizeHintCache[i] = QSizeF(); + } +} diff --git a/src/kitemviews/private/kitemlistsizehintresolver.h b/src/kitemviews/private/kitemlistsizehintresolver.h new file mode 100644 index 000000000..1345e0321 --- /dev/null +++ b/src/kitemviews/private/kitemlistsizehintresolver.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KITEMLISTSIZEHINTRESOLVER_H +#define KITEMLISTSIZEHINTRESOLVER_H + +#include <libdolphin_export.h> + +#include <QByteArray> +#include <QList> +#include <QSizeF> + +class KItemListView; + +/** + * @brief Calculates and caches the sizehints of items in KItemListView. + */ +class LIBDOLPHINPRIVATE_EXPORT KItemListSizeHintResolver +{ +public: + KItemListSizeHintResolver(const KItemListView* itemListView); + virtual ~KItemListSizeHintResolver(); + QSizeF sizeHint(int index) const; + + void itemsInserted(int index, int count); + void itemsRemoved(int index, int count); + void itemsMoved(int index, int count); + void itemsChanged(int index, int count, const QSet<QByteArray>& roles); + + void clearCache(); + +private: + const KItemListView* m_itemListView; + mutable QList<QSizeF> m_sizeHintCache; +}; + +#endif diff --git a/src/kitemviews/private/kitemlistsmoothscroller.cpp b/src/kitemviews/private/kitemlistsmoothscroller.cpp new file mode 100644 index 000000000..6987e1ce1 --- /dev/null +++ b/src/kitemviews/private/kitemlistsmoothscroller.cpp @@ -0,0 +1,207 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistsmoothscroller.h" + +#include <KGlobalSettings> +#include <QEvent> +#include <QPropertyAnimation> +#include <QScrollBar> +#include <QWheelEvent> + +#include <KDebug> + +KItemListSmoothScroller::KItemListSmoothScroller(QScrollBar* scrollBar, + QObject* parent) : + QObject(parent), + m_scrollBarPressed(false), + m_smoothScrolling(true), + m_scrollBar(scrollBar), + m_animation(0) +{ + m_animation = new QPropertyAnimation(this); + const int duration = (KGlobalSettings::graphicEffectsLevel() == KGlobalSettings::NoEffects) ? 1 : 100; + m_animation->setDuration(duration); + connect(m_animation, SIGNAL(stateChanged(QAbstractAnimation::State,QAbstractAnimation::State)), + this, SLOT(slotAnimationStateChanged(QAbstractAnimation::State,QAbstractAnimation::State))); + + m_scrollBar->installEventFilter(this); +} + +KItemListSmoothScroller::~KItemListSmoothScroller() +{ +} + +void KItemListSmoothScroller::setScrollBar(QScrollBar *scrollBar) +{ + m_scrollBar = scrollBar; +} + +QScrollBar* KItemListSmoothScroller::scrollBar() const +{ + return m_scrollBar; +} + +void KItemListSmoothScroller::setTargetObject(QObject* target) +{ + m_animation->setTargetObject(target); +} + +QObject* KItemListSmoothScroller::targetObject() const +{ + return m_animation->targetObject(); +} + +void KItemListSmoothScroller::setPropertyName(const QByteArray& propertyName) +{ + m_animation->setPropertyName(propertyName); +} + +QByteArray KItemListSmoothScroller::propertyName() const +{ + return m_animation->propertyName(); +} + +void KItemListSmoothScroller::scrollContentsBy(qreal distance) +{ + QObject* target = targetObject(); + if (!target) { + return; + } + + const QByteArray name = propertyName(); + const qreal currentOffset = target->property(name).toReal(); + if (static_cast<int>(currentOffset) == m_scrollBar->value()) { + // The current offset is already synchronous to the scrollbar + return; + } + + const bool animRunning = (m_animation->state() == QAbstractAnimation::Running); + if (animRunning) { + // Stopping a running animation means skipping the range from the current offset + // until the target offset. To prevent skipping of the range the difference + // is added to the new target offset. + const qreal oldEndOffset = m_animation->endValue().toReal(); + distance += (currentOffset - oldEndOffset); + } + + const qreal endOffset = currentOffset - distance; + if (m_smoothScrolling || animRunning) { + qreal startOffset = currentOffset; + if (animRunning) { + // If the animation was running and has been interrupted by assigning a new end-offset + // one frame must be added to the start-offset to keep the animation smooth. This also + // assures that animation proceeds even in cases where new end-offset are triggered + // within a very short timeslots. + startOffset += (endOffset - currentOffset) * 1000 / (m_animation->duration() * 60); + if (currentOffset < endOffset) { + startOffset = qMin(startOffset, endOffset); + } else { + startOffset = qMax(startOffset, endOffset); + } + } + + m_animation->stop(); + m_animation->setStartValue(startOffset); + m_animation->setEndValue(endOffset); + m_animation->setEasingCurve(animRunning ? QEasingCurve::OutQuad : QEasingCurve::InOutQuad); + m_animation->start(); + target->setProperty(name, startOffset); + } else { + target->setProperty(name, endOffset); + } +} + +void KItemListSmoothScroller::scrollTo(qreal position) +{ + m_smoothScrolling = true; + m_scrollBar->setValue(position); +} + +bool KItemListSmoothScroller::requestScrollBarUpdate(int newMaximum) +{ + if (m_animation->state() == QAbstractAnimation::Running) { + if (newMaximum == m_scrollBar->maximum()) { + // The value has been changed by the animation, no update + // of the scrollbars is required as their target state will be + // reached with the end of the animation. + return false; + } + + // The maximum has been changed which indicates that the content + // of the view has been changed. Stop the animation in any case and + // update the scrollbars immediately. + m_animation->stop(); + } + return true; +} + +bool KItemListSmoothScroller::eventFilter(QObject* obj, QEvent* event) +{ + Q_ASSERT(obj == m_scrollBar); + + switch (event->type()) { + case QEvent::MouseButtonPress: + m_scrollBarPressed = true; + m_smoothScrolling = true; + break; + + case QEvent::MouseButtonRelease: + m_scrollBarPressed = false; + m_smoothScrolling = false; + break; + + case QEvent::Wheel: + handleWheelEvent(static_cast<QWheelEvent*>(event)); + break; + + default: + break; + } + + return QObject::eventFilter(obj, event); +} + +void KItemListSmoothScroller::slotAnimationStateChanged(QAbstractAnimation::State newState, + QAbstractAnimation::State oldState) +{ + Q_UNUSED(oldState); + if (newState == QAbstractAnimation::Stopped && m_smoothScrolling && !m_scrollBarPressed) { + m_smoothScrolling = false; + } +} + +void KItemListSmoothScroller::handleWheelEvent(QWheelEvent* event) +{ + const int numDegrees = event->delta() / 8; + const int numSteps = numDegrees / 15; + + const bool previous = m_smoothScrolling; + + m_smoothScrolling = true; + const int value = m_scrollBar->value(); + const int pageStep = m_scrollBar->pageStep(); + m_scrollBar->setValue(value - numSteps * pageStep); + + m_smoothScrolling = previous; + + event->accept(); +} + +#include "kitemlistsmoothscroller.moc" diff --git a/src/kitemviews/private/kitemlistsmoothscroller.h b/src/kitemviews/private/kitemlistsmoothscroller.h new file mode 100644 index 000000000..252c966c7 --- /dev/null +++ b/src/kitemviews/private/kitemlistsmoothscroller.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KITEMLISTSMOOTHSCROLLER_H +#define KITEMLISTSMOOTHSCROLLER_H + +#include <libdolphin_export.h> + +#include <QAbstractAnimation> +#include <QObject> + +class QPropertyAnimation; +class QScrollBar; +class QWheelEvent; + +/** + * @brief Helper class for KItemListContainer to have a smooth + * scrolling when adjusting the scrollbars. + */ +class LIBDOLPHINPRIVATE_EXPORT KItemListSmoothScroller : public QObject +{ + Q_OBJECT + +public: + KItemListSmoothScroller(QScrollBar* scrollBar, + QObject* parent = 0); + virtual ~KItemListSmoothScroller(); + + void setScrollBar(QScrollBar* scrollBar); + QScrollBar* scrollBar() const; + + void setTargetObject(QObject* target); + QObject* targetObject() const; + + void setPropertyName(const QByteArray& propertyName); + QByteArray propertyName() const; + + /** + * Adjusts the position of the target by \p distance + * pixels. Is invoked in the context of QAbstractScrollArea::scrollContentsBy() + * where the scrollbars already have the new position but the content + * has not been scrolled yet. + */ + void scrollContentsBy(qreal distance); + + /** + * Does a smooth-scrolling to the position \p position + * on the target and also adjusts the corresponding scrollbar + * to the new position. + */ + void scrollTo(qreal position); + + /** + * Must be invoked before the scrollbar should get updated to have a new + * maximum. True is returned if the new maximum can be applied. If false + * is returned the maximum has already been reached and the value will + * be reached at the end of the animation. + */ + // TODO: This interface is tricky to understand. Try to make this more + // generic/readable if the corresponding code in KItemListContainer got + // stable. + bool requestScrollBarUpdate(int newMaximum); + +protected: + virtual bool eventFilter(QObject* obj, QEvent* event); + +private slots: + void slotAnimationStateChanged(QAbstractAnimation::State newState, + QAbstractAnimation::State oldState); + +private: + /** + * Results into a smooth-scrolling of the target dependent on the direction + * of the wheel event. + */ + void handleWheelEvent(QWheelEvent* event); + +private: + bool m_scrollBarPressed; + bool m_smoothScrolling; + QScrollBar* m_scrollBar; + QPropertyAnimation* m_animation; +}; + +#endif + + diff --git a/src/kitemviews/private/kitemlistviewanimation.cpp b/src/kitemviews/private/kitemlistviewanimation.cpp new file mode 100644 index 000000000..e347c5bb1 --- /dev/null +++ b/src/kitemviews/private/kitemlistviewanimation.cpp @@ -0,0 +1,245 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistviewanimation.h" + +#include <kitemviews/kitemlistview.h> + +#include <KDebug> +#include <KGlobalSettings> + +#include <QGraphicsWidget> +#include <QPropertyAnimation> + +KItemListViewAnimation::KItemListViewAnimation(QObject* parent) : + QObject(parent), + m_animationDuration(200), + m_scrollOrientation(Qt::Vertical), + m_scrollOffset(0), + m_animation() +{ + if (KGlobalSettings::graphicEffectsLevel() == KGlobalSettings::NoEffects) { + m_animationDuration = 1; + } +} + +KItemListViewAnimation::~KItemListViewAnimation() +{ + for (int type = 0; type < AnimationTypeCount; ++type) { + qDeleteAll(m_animation[type]); + } +} + +void KItemListViewAnimation::setScrollOrientation(Qt::Orientation orientation) +{ + m_scrollOrientation = orientation; +} + +Qt::Orientation KItemListViewAnimation::scrollOrientation() const +{ + return m_scrollOrientation; +} + +void KItemListViewAnimation::setScrollOffset(qreal offset) +{ + const qreal diff = m_scrollOffset - offset; + m_scrollOffset = offset; + + // The change of the offset requires that the position of all + // animated QGraphicsWidgets get adjusted. An exception is made + // for the delete animation that should just fade away on the + // existing position. + for (int type = 0; type < AnimationTypeCount; ++type) { + if (type == DeleteAnimation) { + continue; + } + + QHashIterator<QGraphicsWidget*, QPropertyAnimation*> it(m_animation[type]); + while (it.hasNext()) { + it.next(); + + QGraphicsWidget* widget = it.key(); + QPropertyAnimation* propertyAnim = it.value(); + + QPointF currentPos = widget->pos(); + if (m_scrollOrientation == Qt::Vertical) { + currentPos.ry() += diff; + } else { + currentPos.rx() += diff; + } + + if (type == MovingAnimation) { + // Stop the animation, calculate the moved start- and end-value + // and restart the animation for the remaining duration. + const int remainingDuration = propertyAnim->duration() + - propertyAnim->currentTime(); + + const bool block = propertyAnim->signalsBlocked(); + propertyAnim->blockSignals(true); + propertyAnim->stop(); + + QPointF endPos = propertyAnim->endValue().toPointF(); + if (m_scrollOrientation == Qt::Vertical) { + endPos.ry() += diff; + } else { + endPos.rx() += diff; + } + + propertyAnim->setDuration(remainingDuration); + propertyAnim->setStartValue(currentPos); + propertyAnim->setEndValue(endPos); + propertyAnim->start(); + propertyAnim->blockSignals(block); + } else { + widget->setPos(currentPos); + } + } + } +} + +qreal KItemListViewAnimation::scrollOffset() const +{ + return m_scrollOffset; +} + +void KItemListViewAnimation::start(QGraphicsWidget* widget, AnimationType type, const QVariant& endValue) +{ + stop(widget, type); + + QPropertyAnimation* propertyAnim = 0; + + switch (type) { + case MovingAnimation: { + const QPointF newPos = endValue.toPointF(); + if (newPos == widget->pos()) { + return; + } + + propertyAnim = new QPropertyAnimation(widget, "pos"); + propertyAnim->setDuration(m_animationDuration); + propertyAnim->setEndValue(newPos); + break; + } + + case CreateAnimation: { + propertyAnim = new QPropertyAnimation(widget, "opacity"); + propertyAnim->setEasingCurve(QEasingCurve::InQuart); + propertyAnim->setDuration(m_animationDuration); + propertyAnim->setStartValue(0.0); + propertyAnim->setEndValue(1.0); + break; + } + + case DeleteAnimation: { + propertyAnim = new QPropertyAnimation(widget, "opacity"); + propertyAnim->setEasingCurve(QEasingCurve::OutQuart); + propertyAnim->setDuration(m_animationDuration); + propertyAnim->setStartValue(1.0); + propertyAnim->setEndValue(0.0); + break; + } + + case ResizeAnimation: { + const QSizeF newSize = endValue.toSizeF(); + if (newSize == widget->size()) { + return; + } + + propertyAnim = new QPropertyAnimation(widget, "size"); + propertyAnim->setDuration(m_animationDuration); + propertyAnim->setEndValue(newSize); + break; + } + + default: + break; + } + + Q_ASSERT(propertyAnim); + connect(propertyAnim, SIGNAL(finished()), this, SLOT(slotFinished())); + m_animation[type].insert(widget, propertyAnim); + + propertyAnim->start(); +} + +void KItemListViewAnimation::stop(QGraphicsWidget* widget, AnimationType type) +{ + QPropertyAnimation* propertyAnim = m_animation[type].value(widget); + if (propertyAnim) { + propertyAnim->stop(); + + switch (type) { + case MovingAnimation: break; + case CreateAnimation: widget->setOpacity(1.0); break; + case DeleteAnimation: widget->setOpacity(0.0); break; + case ResizeAnimation: break; + default: break; + } + + m_animation[type].remove(widget); + delete propertyAnim; + + emit finished(widget, type); + } +} + +void KItemListViewAnimation::stop(QGraphicsWidget* widget) +{ + for (int type = 0; type < AnimationTypeCount; ++type) { + stop(widget, static_cast<AnimationType>(type)); + } +} + +bool KItemListViewAnimation::isStarted(QGraphicsWidget *widget, AnimationType type) const +{ + return m_animation[type].value(widget); +} + +bool KItemListViewAnimation::isStarted(QGraphicsWidget* widget) const +{ + for (int type = 0; type < AnimationTypeCount; ++type) { + if (isStarted(widget, static_cast<AnimationType>(type))) { + return true; + } + } + return false; +} + +void KItemListViewAnimation::slotFinished() +{ + QPropertyAnimation* finishedAnim = qobject_cast<QPropertyAnimation*>(sender()); + for (int type = 0; type < AnimationTypeCount; ++type) { + QHashIterator<QGraphicsWidget*, QPropertyAnimation*> it(m_animation[type]); + while (it.hasNext()) { + it.next(); + QPropertyAnimation* propertyAnim = it.value(); + if (propertyAnim == finishedAnim) { + QGraphicsWidget* widget = it.key(); + m_animation[type].remove(widget); + finishedAnim->deleteLater(); + + emit finished(widget, static_cast<AnimationType>(type)); + return; + } + } + } + Q_ASSERT(false); +} + +#include "kitemlistviewanimation.moc" diff --git a/src/kitemviews/private/kitemlistviewanimation.h b/src/kitemviews/private/kitemlistviewanimation.h new file mode 100644 index 000000000..a3aceb0f5 --- /dev/null +++ b/src/kitemviews/private/kitemlistviewanimation.h @@ -0,0 +1,106 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KITEMLISTVIEWANIMATION_H +#define KITEMLISTVIEWANIMATION_H + +#include <libdolphin_export.h> + +#include <QHash> +#include <QObject> +#include <QVariant> + +class KItemListView; +class QGraphicsWidget; +class QPointF; +class QPropertyAnimation; + +/** + * @brief Internal helper class for KItemListView to animate the items. + * + * Supports item animations for moving, creating, deleting and resizing + * an item. Several applications can be applied to one item in parallel. + */ +class LIBDOLPHINPRIVATE_EXPORT KItemListViewAnimation : public QObject +{ + Q_OBJECT + +public: + enum AnimationType { + MovingAnimation, + CreateAnimation, + DeleteAnimation, + ResizeAnimation + }; + + KItemListViewAnimation(QObject* parent = 0); + virtual ~KItemListViewAnimation(); + + void setScrollOrientation(Qt::Orientation orientation); + Qt::Orientation scrollOrientation() const; + + void setScrollOffset(qreal scrollOffset); + qreal scrollOffset() const; + + /** + * Starts the animation of the type \a type for the widget \a widget. If an animation + * of the type is already running, this animation will be stopped before starting + * the new animation. + */ + void start(QGraphicsWidget* widget, AnimationType type, const QVariant& endValue = QVariant()); + + /** + * Stops the animation of the type \a type for the widget \a widget. + */ + void stop(QGraphicsWidget* widget, AnimationType type); + + /** + * Stops all animations that have been applied to the widget \a widget. + */ + void stop(QGraphicsWidget* widget); + + /** + * @return True if the animation of the type \a type has been started + * for the widget \a widget.. + */ + bool isStarted(QGraphicsWidget *widget, AnimationType type) const; + + /** + * @return True if any animation has been started for the widget. + */ + bool isStarted(QGraphicsWidget* widget) const; + +signals: + void finished(QGraphicsWidget* widget, KItemListViewAnimation::AnimationType type); + +private slots: + void slotFinished(); + +private: + enum { AnimationTypeCount = 4 }; + + int m_animationDuration; + Qt::Orientation m_scrollOrientation; + qreal m_scrollOffset; + QHash<QGraphicsWidget*, QPropertyAnimation*> m_animation[AnimationTypeCount]; +}; + +#endif + + diff --git a/src/kitemviews/private/kitemlistviewlayouter.cpp b/src/kitemviews/private/kitemlistviewlayouter.cpp new file mode 100644 index 000000000..c15b44e13 --- /dev/null +++ b/src/kitemviews/private/kitemlistviewlayouter.cpp @@ -0,0 +1,630 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistviewlayouter.h" + +#include <kitemviews/kitemmodelbase.h> +#include "kitemlistsizehintresolver.h" + +#include <KDebug> + +// #define KITEMLISTVIEWLAYOUTER_DEBUG + +KItemListViewLayouter::KItemListViewLayouter(QObject* parent) : + QObject(parent), + m_dirty(true), + m_visibleIndexesDirty(true), + m_scrollOrientation(Qt::Vertical), + m_size(), + m_itemSize(128, 128), + m_itemMargin(), + m_headerHeight(0), + m_model(0), + m_sizeHintResolver(0), + m_scrollOffset(0), + m_maximumScrollOffset(0), + m_itemOffset(0), + m_maximumItemOffset(0), + m_firstVisibleIndex(-1), + m_lastVisibleIndex(-1), + m_columnWidth(0), + m_xPosInc(0), + m_columnCount(0), + m_groupItemIndexes(), + m_groupHeaderHeight(0), + m_groupHeaderMargin(0), + m_itemInfos() +{ +} + +KItemListViewLayouter::~KItemListViewLayouter() +{ +} + +void KItemListViewLayouter::setScrollOrientation(Qt::Orientation orientation) +{ + if (m_scrollOrientation != orientation) { + m_scrollOrientation = orientation; + m_dirty = true; + } +} + +Qt::Orientation KItemListViewLayouter::scrollOrientation() const +{ + return m_scrollOrientation; +} + +void KItemListViewLayouter::setSize(const QSizeF& size) +{ + if (m_size != size) { + m_size = size; + m_dirty = true; + } +} + +QSizeF KItemListViewLayouter::size() const +{ + return m_size; +} + +void KItemListViewLayouter::setItemSize(const QSizeF& size) +{ + if (m_itemSize != size) { + m_itemSize = size; + m_dirty = true; + } +} + +QSizeF KItemListViewLayouter::itemSize() const +{ + return m_itemSize; +} + +void KItemListViewLayouter::setItemMargin(const QSizeF& margin) +{ + if (m_itemMargin != margin) { + m_itemMargin = margin; + m_dirty = true; + } +} + +QSizeF KItemListViewLayouter::itemMargin() const +{ + return m_itemMargin; +} + +void KItemListViewLayouter::setHeaderHeight(qreal height) +{ + if (m_headerHeight != height) { + m_headerHeight = height; + m_dirty = true; + } +} + +qreal KItemListViewLayouter::headerHeight() const +{ + return m_headerHeight; +} + +void KItemListViewLayouter::setGroupHeaderHeight(qreal height) +{ + if (m_groupHeaderHeight != height) { + m_groupHeaderHeight = height; + m_dirty = true; + } +} + +qreal KItemListViewLayouter::groupHeaderHeight() const +{ + return m_groupHeaderHeight; +} + +void KItemListViewLayouter::setGroupHeaderMargin(qreal margin) +{ + if (m_groupHeaderMargin != margin) { + m_groupHeaderMargin = margin; + m_dirty = true; + } +} + +qreal KItemListViewLayouter::groupHeaderMargin() const +{ + return m_groupHeaderMargin; +} + +void KItemListViewLayouter::setScrollOffset(qreal offset) +{ + if (m_scrollOffset != offset) { + m_scrollOffset = offset; + m_visibleIndexesDirty = true; + } +} + +qreal KItemListViewLayouter::scrollOffset() const +{ + return m_scrollOffset; +} + +qreal KItemListViewLayouter::maximumScrollOffset() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_maximumScrollOffset; +} + +void KItemListViewLayouter::setItemOffset(qreal offset) +{ + if (m_itemOffset != offset) { + m_itemOffset = offset; + m_visibleIndexesDirty = true; + } +} + +qreal KItemListViewLayouter::itemOffset() const +{ + return m_itemOffset; +} + +qreal KItemListViewLayouter::maximumItemOffset() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_maximumItemOffset; +} + +void KItemListViewLayouter::setModel(const KItemModelBase* model) +{ + if (m_model != model) { + m_model = model; + m_dirty = true; + } +} + +const KItemModelBase* KItemListViewLayouter::model() const +{ + return m_model; +} + +void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver* sizeHintResolver) +{ + if (m_sizeHintResolver != sizeHintResolver) { + m_sizeHintResolver = sizeHintResolver; + m_dirty = true; + } +} + +const KItemListSizeHintResolver* KItemListViewLayouter::sizeHintResolver() const +{ + return m_sizeHintResolver; +} + +int KItemListViewLayouter::firstVisibleIndex() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_firstVisibleIndex; +} + +int KItemListViewLayouter::lastVisibleIndex() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_lastVisibleIndex; +} + +QRectF KItemListViewLayouter::itemRect(int index) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + if (index < 0 || index >= m_itemInfos.count()) { + return QRectF(); + } + + if (m_scrollOrientation == Qt::Horizontal) { + // Rotate the logical direction which is always vertical by 90° + // to get the physical horizontal direction + const QRectF& b = m_itemInfos[index].rect; + QRectF bounds(b.y(), b.x(), b.height(), b.width()); + QPointF pos = bounds.topLeft(); + pos.rx() -= m_scrollOffset; + bounds.moveTo(pos); + return bounds; + } + + QRectF bounds = m_itemInfos[index].rect; + bounds.moveTo(bounds.topLeft() - QPointF(m_itemOffset, m_scrollOffset)); + return bounds; +} + +QRectF KItemListViewLayouter::groupHeaderRect(int index) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + + const QRectF firstItemRect = itemRect(index); + QPointF pos = firstItemRect.topLeft(); + if (pos.isNull()) { + return QRectF(); + } + + QSizeF size; + if (m_scrollOrientation == Qt::Vertical) { + pos.rx() = 0; + pos.ry() -= m_groupHeaderHeight; + size = QSizeF(m_size.width(), m_groupHeaderHeight); + } else { + pos.rx() -= m_itemMargin.width(); + pos.ry() = 0; + + // Determine the maximum width used in the + // current column. As the scroll-direction is + // Qt::Horizontal and m_itemRects is accessed directly, + // the logical height represents the visual width. + qreal width = minimumGroupHeaderWidth(); + const qreal y = m_itemInfos[index].rect.y(); + const int maxIndex = m_itemInfos.count() - 1; + while (index <= maxIndex) { + QRectF bounds = m_itemInfos[index].rect; + if (bounds.y() != y) { + break; + } + + if (bounds.height() > width) { + width = bounds.height(); + } + + ++index; + } + + size = QSizeF(width, m_size.height()); + } + return QRectF(pos, size); +} + +int KItemListViewLayouter::itemColumn(int index) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + if (index < 0 || index >= m_itemInfos.count()) { + return -1; + } + + return (m_scrollOrientation == Qt::Vertical) + ? m_itemInfos[index].column + : m_itemInfos[index].row; +} + +int KItemListViewLayouter::itemRow(int index) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + if (index < 0 || index >= m_itemInfos.count()) { + return -1; + } + + return (m_scrollOrientation == Qt::Vertical) + ? m_itemInfos[index].row + : m_itemInfos[index].column; +} + +int KItemListViewLayouter::maximumVisibleItems() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + + const int height = static_cast<int>(m_size.height()); + const int rowHeight = static_cast<int>(m_itemSize.height()); + int rows = height / rowHeight; + if (height % rowHeight != 0) { + ++rows; + } + + return rows * m_columnCount; +} + +bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_groupItemIndexes.contains(itemIndex); +} + +void KItemListViewLayouter::markAsDirty() +{ + m_dirty = true; +} + + +#ifndef QT_NO_DEBUG + bool KItemListViewLayouter::isDirty() + { + return m_dirty; + } +#endif + +void KItemListViewLayouter::doLayout() +{ + if (m_dirty) { +#ifdef KITEMLISTVIEWLAYOUTER_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + m_visibleIndexesDirty = true; + + QSizeF itemSize = m_itemSize; + QSizeF itemMargin = m_itemMargin; + QSizeF size = m_size; + + const bool grouped = createGroupHeaders(); + + const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal); + if (horizontalScrolling) { + // Flip everything so that the layout logically can work like having + // a vertical scrolling + itemSize.setWidth(m_itemSize.height()); + itemSize.setHeight(m_itemSize.width()); + itemMargin.setWidth(m_itemMargin.height()); + itemMargin.setHeight(m_itemMargin.width()); + size.setWidth(m_size.height()); + size.setHeight(m_size.width()); + + if (grouped) { + // In the horizontal scrolling case all groups are aligned + // at the top, which decreases the available height. For the + // flipped data this means that the width must be decreased. + size.rwidth() -= m_groupHeaderHeight; + } + } + + m_columnWidth = itemSize.width() + itemMargin.width(); + const qreal widthForColumns = size.width() - itemMargin.width(); + m_columnCount = qMax(1, int(widthForColumns / m_columnWidth)); + m_xPosInc = itemMargin.width(); + + const int itemCount = m_model->count(); + if (itemCount > m_columnCount && m_columnWidth >= 32) { + // Apply the unused width equally to each column + const qreal unusedWidth = widthForColumns - m_columnCount * m_columnWidth; + if (unusedWidth > 0) { + const qreal columnInc = unusedWidth / (m_columnCount + 1); + m_columnWidth += columnInc; + m_xPosInc += columnInc; + } + } + + int rowCount = itemCount / m_columnCount; + if (itemCount % m_columnCount != 0) { + ++rowCount; + } + + m_itemInfos.reserve(itemCount); + + qreal y = m_headerHeight + itemMargin.height(); + int row = 0; + + int index = 0; + while (index < itemCount) { + qreal x = m_xPosInc; + qreal maxItemHeight = itemSize.height(); + + if (grouped) { + if (horizontalScrolling) { + // All group headers will always be aligned on the top and not + // flipped like the other properties + x += m_groupHeaderHeight; + } + + if (m_groupItemIndexes.contains(index)) { + // The item is the first item of a group. + // Increase the y-position to provide space + // for the group header. + if (index > 0) { + // Only add a margin if there has been added another + // group already before + y += m_groupHeaderMargin; + } else if (!horizontalScrolling) { + // The first group header should be aligned on top + y -= itemMargin.height(); + } + + if (!horizontalScrolling) { + y += m_groupHeaderHeight; + } + } + } + + int column = 0; + while (index < itemCount && column < m_columnCount) { + qreal requiredItemHeight = itemSize.height(); + if (m_sizeHintResolver) { + const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index); + const qreal sizeHintHeight = horizontalScrolling ? sizeHint.width() : sizeHint.height(); + if (sizeHintHeight > requiredItemHeight) { + requiredItemHeight = sizeHintHeight; + } + } + + const QRectF bounds(x, y, itemSize.width(), requiredItemHeight); + if (index < m_itemInfos.count()) { + m_itemInfos[index].rect = bounds; + m_itemInfos[index].column = column; + m_itemInfos[index].row = row; + } else { + ItemInfo itemInfo; + itemInfo.rect = bounds; + itemInfo.column = column; + itemInfo.row = row; + m_itemInfos.append(itemInfo); + } + + if (grouped && horizontalScrolling) { + // When grouping is enabled in the horizontal mode, the header alignment + // looks like this: + // Header-1 Header-2 Header-3 + // Item 1 Item 4 Item 7 + // Item 2 Item 5 Item 8 + // Item 3 Item 6 Item 9 + // In this case 'requiredItemHeight' represents the column-width. We don't + // check the content of the header in the layouter to determine the required + // width, hence assure that at least a minimal width of 15 characters is given + // (in average a character requires the halve width of the font height). + // + // TODO: Let the group headers provide a minimum width and respect this width here + const qreal headerWidth = minimumGroupHeaderWidth(); + if (requiredItemHeight < headerWidth) { + requiredItemHeight = headerWidth; + } + } + + maxItemHeight = qMax(maxItemHeight, requiredItemHeight); + x += m_columnWidth; + ++index; + ++column; + + if (grouped && m_groupItemIndexes.contains(index)) { + // The item represents the first index of a group + // and must aligned in the first column + break; + } + } + + y += maxItemHeight + itemMargin.height(); + ++row; + } + if (m_itemInfos.count() > itemCount) { + m_itemInfos.erase(m_itemInfos.begin() + itemCount, + m_itemInfos.end()); + } + + if (itemCount > 0) { + // Calculate the maximum y-range of the last row for m_maximumScrollOffset + m_maximumScrollOffset = m_itemInfos.last().rect.bottom(); + const qreal rowY = m_itemInfos.last().rect.y(); + + int index = m_itemInfos.count() - 2; + while (index >= 0 && m_itemInfos[index].rect.bottom() >= rowY) { + m_maximumScrollOffset = qMax(m_maximumScrollOffset, m_itemInfos[index].rect.bottom()); + --index; + } + + m_maximumScrollOffset += itemMargin.height(); + + m_maximumItemOffset = m_columnCount * m_columnWidth; + } else { + m_maximumScrollOffset = 0; + m_maximumItemOffset = 0; + } + +#ifdef KITEMLISTVIEWLAYOUTER_DEBUG + kDebug() << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed(); +#endif + m_dirty = false; + } + + updateVisibleIndexes(); +} + +void KItemListViewLayouter::updateVisibleIndexes() +{ + if (!m_visibleIndexesDirty) { + return; + } + + Q_ASSERT(!m_dirty); + + if (m_model->count() <= 0) { + m_firstVisibleIndex = -1; + m_lastVisibleIndex = -1; + m_visibleIndexesDirty = false; + return; + } + + const int maxIndex = m_model->count() - 1; + + // Calculate the first visible index that is fully visible + int min = 0; + int max = maxIndex; + int mid = 0; + do { + mid = (min + max) / 2; + if (m_itemInfos[mid].rect.top() < m_scrollOffset) { + min = mid + 1; + } else { + max = mid - 1; + } + } while (min <= max); + + if (mid > 0) { + // Include the row before the first fully visible index, as it might + // be partly visible + if (m_itemInfos[mid].rect.top() >= m_scrollOffset) { + --mid; + Q_ASSERT(m_itemInfos[mid].rect.top() < m_scrollOffset); + } + + const qreal rowTop = m_itemInfos[mid].rect.top(); + while (mid > 0 && m_itemInfos[mid - 1].rect.top() == rowTop) { + --mid; + } + } + m_firstVisibleIndex = mid; + + // Calculate the last visible index that is (at least partly) visible + const int visibleHeight = (m_scrollOrientation == Qt::Horizontal) ? m_size.width() : m_size.height(); + qreal bottom = m_scrollOffset + visibleHeight; + if (m_model->groupedSorting()) { + bottom += m_groupHeaderHeight; + } + + min = m_firstVisibleIndex; + max = maxIndex; + do { + mid = (min + max) / 2; + if (m_itemInfos[mid].rect.y() <= bottom) { + min = mid + 1; + } else { + max = mid - 1; + } + } while (min <= max); + + while (mid > 0 && m_itemInfos[mid].rect.y() > bottom) { + --mid; + } + m_lastVisibleIndex = mid; + + m_visibleIndexesDirty = false; +} + +bool KItemListViewLayouter::createGroupHeaders() +{ + if (!m_model->groupedSorting()) { + return false; + } + + m_groupItemIndexes.clear(); + + const QList<QPair<int, QVariant> > groups = m_model->groups(); + if (groups.isEmpty()) { + return false; + } + + for (int i = 0; i < groups.count(); ++i) { + const int firstItemIndex = groups.at(i).first; + m_groupItemIndexes.insert(firstItemIndex); + } + + return true; +} + +qreal KItemListViewLayouter::minimumGroupHeaderWidth() const +{ + return 100; +} + +#include "kitemlistviewlayouter.moc" diff --git a/src/kitemviews/private/kitemlistviewlayouter.h b/src/kitemviews/private/kitemlistviewlayouter.h new file mode 100644 index 000000000..da5bd1d7d --- /dev/null +++ b/src/kitemviews/private/kitemlistviewlayouter.h @@ -0,0 +1,235 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KITEMLISTVIEWLAYOUTER_H +#define KITEMLISTVIEWLAYOUTER_H + +#include <libdolphin_export.h> + +#include <QObject> +#include <QRectF> +#include <QSet> +#include <QSizeF> + +class KItemModelBase; +class KItemListSizeHintResolver; + +/** + * @brief Internal helper class for KItemListView to layout the items. + * + * The layouter is capable to align the items within a grid. If the + * scroll-direction is horizontal the column-width of the grid can be + * variable. If the scroll-direction is vertical the row-height of + * the grid can be variable. + * + * The layouter is implemented in a way that it postpones the expensive + * layout operation until a property is read the first time after + * marking the layouter as dirty (see markAsDirty()). This means that + * changing properties of the layouter is not expensive, only the + * first read of a property can get expensive. + */ +class LIBDOLPHINPRIVATE_EXPORT KItemListViewLayouter : public QObject +{ + Q_OBJECT + +public: + KItemListViewLayouter(QObject* parent = 0); + virtual ~KItemListViewLayouter(); + + void setScrollOrientation(Qt::Orientation orientation); + Qt::Orientation scrollOrientation() const; + + void setSize(const QSizeF& size); + QSizeF size() const; + + void setItemSize(const QSizeF& size); + QSizeF itemSize() const; + + /** + * Margin between the rows and columns of items. + */ + void setItemMargin(const QSizeF& margin); + QSizeF itemMargin() const; + + /** + * Sets the height of the header that is always aligned + * at the top. A height of <= 0.0 means that no header is + * used. + */ + void setHeaderHeight(qreal height); + qreal headerHeight() const; + + /** + * Sets the height of the group header that is used + * to indicate a new item group. + */ + void setGroupHeaderHeight(qreal height); + qreal groupHeaderHeight() const; + + /** + * Sets the margin between the last items of the group n and + * the group header for the group n + 1. + */ + void setGroupHeaderMargin(qreal margin); + qreal groupHeaderMargin() const; + + void setScrollOffset(qreal scrollOffset); + qreal scrollOffset() const; + + qreal maximumScrollOffset() const; + + void setItemOffset(qreal scrollOffset); + qreal itemOffset() const; + + qreal maximumItemOffset() const; + + void setModel(const KItemModelBase* model); + const KItemModelBase* model() const; + + void setSizeHintResolver(const KItemListSizeHintResolver* sizeHintResolver); + const KItemListSizeHintResolver* sizeHintResolver() const; + + /** + * @return The first (at least partly) visible index. -1 is returned + * if the item count is 0. + */ + int firstVisibleIndex() const; + + /** + * @return The last (at least partly) visible index. -1 is returned + * if the item count is 0. + */ + int lastVisibleIndex() const; + + /** + * @return Rectangle of the item with the index \a index. + * The top/left of the bounding rectangle is related to + * the top/left of the KItemListView. An empty rectangle + * is returned if an invalid index is given. + */ + QRectF itemRect(int index) const; + + /** + * @return Rectangle of the group header for the item with the + * index \a index. Note that the layouter does not check + * whether the item really has a header: Usually only + * the first item of a group gets a header (see + * isFirstGroupItem()). + */ + QRectF groupHeaderRect(int index) const; + + /** + * @return Column of the item with the index \a index. + * -1 is returned if an invalid index is given. + */ + int itemColumn(int index) const; + + /** + * @return Row of the item with the index \a index. + * -1 is returned if an invalid index is given. + */ + int itemRow(int index) const; + + /** + * @return Maximum number of (at least partly) visible items for + * the given size. + */ + int maximumVisibleItems() const; + + /** + * @return True if the item with the index \p itemIndex + * is the first item within a group. + */ + bool isFirstGroupItem(int itemIndex) const; + + /** + * Marks the layouter as dirty. This means as soon as a property of + * the layouter gets read, an expensive relayout will be done. + */ + void markAsDirty(); + +#ifndef QT_NO_DEBUG + /** + * @return True if the layouter has been marked as dirty and hence has + * not called yet doLayout(). Is enabled only in the debugging + * mode, as it is not useful to check the dirty state otherwise. + */ + bool isDirty(); +#endif + +private: + void doLayout(); + void updateVisibleIndexes(); + bool createGroupHeaders(); + + /** + * @return Minimum width of group headers when grouping is enabled in the horizontal + * alignment mode. The header alignment is done like this: + * Header-1 Header-2 Header-3 + * Item 1 Item 4 Item 7 + * Item 2 Item 5 Item 8 + * Item 3 Item 6 Item 9 + */ + qreal minimumGroupHeaderWidth() const; + +private: + bool m_dirty; + bool m_visibleIndexesDirty; + + Qt::Orientation m_scrollOrientation; + QSizeF m_size; + + QSizeF m_itemSize; + QSizeF m_itemMargin; + qreal m_headerHeight; + const KItemModelBase* m_model; + const KItemListSizeHintResolver* m_sizeHintResolver; + + qreal m_scrollOffset; + qreal m_maximumScrollOffset; + + qreal m_itemOffset; + qreal m_maximumItemOffset; + + int m_firstVisibleIndex; + int m_lastVisibleIndex; + + qreal m_columnWidth; + qreal m_xPosInc; + int m_columnCount; + + // Stores all item indexes that are the first item of a group. + // Assures fast access for KItemListViewLayouter::isFirstGroupItem(). + QSet<int> m_groupItemIndexes; + qreal m_groupHeaderHeight; + qreal m_groupHeaderMargin; + + struct ItemInfo { + QRectF rect; + int column; + int row; + }; + QList<ItemInfo> m_itemInfos; + + friend class KItemListControllerTest; +}; + +#endif + + diff --git a/src/kitemviews/private/knepomukdatamanagement_export.h b/src/kitemviews/private/knepomukdatamanagement_export.h new file mode 100644 index 000000000..929a737c9 --- /dev/null +++ b/src/kitemviews/private/knepomukdatamanagement_export.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef NEPOMUKDATAMANAGEMENT_EXPORT_H +#define NEPOMUKDATAMANAGEMENT_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include <kdemacros.h> + +#ifndef NEPOMUK_DATA_MANAGEMENT_EXPORT +# if defined(MAKE_NEPOMUKDATAMANAGEMENT_LIB) + /* We are building this library */ +# define NEPOMUK_DATA_MANAGEMENT_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define NEPOMUK_DATA_MANAGEMENT_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef NEPOMUK_DATA_MANAGEMENT_EXPORT_DEPRECATED +# define NEPOMUK_DATA_MANAGEMENT_EXPORT_DEPRECATED KDE_DEPRECATED NEPOMUK_DATA_MANAGEMENT_EXPORT +# endif + +#endif diff --git a/src/kitemviews/private/knepomukresourcewatcher.h b/src/kitemviews/private/knepomukresourcewatcher.h new file mode 100644 index 000000000..3f6643fc8 --- /dev/null +++ b/src/kitemviews/private/knepomukresourcewatcher.h @@ -0,0 +1,288 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <[email protected]> + Copyright (C) 2011 Sebastian Trueg <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef RESOURCEWATCHER_H +#define RESOURCEWATCHER_H + +#include <Nepomuk/Types/Class> +#include <Nepomuk/Types/Property> +#include <Nepomuk/Resource> + +#include <QtDBus/QDBusVariant> +#include <QtCore/QVariant> + +#include "knepomukdatamanagement_export.h" + +namespace Nepomuk { + + /** + * \class ResourceWatcher resourcewatcher.h + * + * \brief Selectively monitor the nepomuk repository for changes. + * + * Resources may be monitored on the basis of types, properties, and uris. + * + * Changes may be monitored in one of the following ways: + * -# By resources - + * Specify the exact resources that should be watched. Any changes made to the specified resources + * (Excluding \ref nepomuk_dms_metadata) will be notified through the propertyAdded() and propertyRemoved() + * signals. Notifications will also be sent if any of the watched resources is deleted. + * -# By resources and properties - + * Specify the exact resources and their properties. Any changes made to the specified resources + * which touch one of the specified properties will be notified through the propertyAdded() and propertyRemoved() + * signals. + * -# By types - + * Specific types may be specified via add/setType. If types are set, then notifications will be + * sent for all new resources of that type. This includes property changes and resource creation and removal. + * TODO: add flags that allow to only watch for resource creation and removal. + * -# By types and properties - + * Both the types and properties may be specified. Notifications will be sent for property changes + * in resource with the specified types. + * + * \section nepomuk_rw_examples Resource Watcher Usage Example + * + * The following code creates a new ResourceWatcher, configures it to listen to changes on the \c nmm:performer + * property on one specific resource \c res. + * + * \code + * Nepomuk::ResourceWatcher* watcher = new Nepomuk::ResourceWatcher(this); + * watcher->addResource(res); + * watcher->addProperty(NMM:performer()); + * connect(watcher, SIGNAL(propertyAdded(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)), + * this, SLOT(slotPropertyChanged())); + * connect(watcher, SIGNAL(propertyRemoved(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)), + * this, SLOT(slotPropertyChanged())); + * rwatcher->start(); + * \endcode + * + * \author Vishesh Handa <[email protected]>, Sebastian Trueg <[email protected]> + * + * \ingroup nepomuk_datamanagement + */ + class NEPOMUK_DATA_MANAGEMENT_EXPORT ResourceWatcher : public QObject + { + Q_OBJECT + + public: + /** + * \brief Create a new %ResourceWatcher instance. + * + * This instance will not emit any signals before it has been configured + * and started. + */ + ResourceWatcher( QObject* parent = 0 ); + + /** + * \brief Destructor. + */ + virtual ~ResourceWatcher(); + + /** + * \brief Add a type to be watched. + * + * Every resource of this type will be watched for changes. + * + * \sa setTypes() + */ + void addType( const Types::Class & type ); + + /** + * \brief Add a resource to be watched. + * + * Every change to this resource will be + * signalled, depending on the configured properties(). + * + * \sa setResources() + */ + void addResource( const Nepomuk::Resource & res ); + + /** + * \brief Add a property to be watched. + * + * Every change to a value of this property + * will be signalled, depending on the configured resources() or types(). + * + * \sa setProperties() + */ + void addProperty( const Types::Property & property ); + + /** + * \brief Set the types to be watched. + * + * Every resource having one of these types will be watched for changes. + * + * \sa addType() + */ + void setTypes( const QList<Types::Class> & types_ ); + + /** + * \brief Set the resources to be watched. + * + * Every change to one of these resources will be + * signalled, depending on the configured properties(). + * + * \sa addResource() + */ + void setResources( const QList<Nepomuk::Resource> & resources_ ); + + /** + * \brief Set the properties to be watched. + * + * Every change to a value of any of these properties + * will be signalled, depending on the configured resources() or types(). + * + * \sa addProperty() + */ + void setProperties( const QList<Types::Property> & properties_ ); + + /** + * \brief The types that have been configured via addType() and setTypes(). + * + * Every resource having one of these types will be watched + * for changes. + */ + QList<Types::Class> types() const; + + /** + * \brief The resources that have been configured via addResource() and setResources(). + * + * Every change to one of these resources will be + * signalled, depending on the configured properties(). + */ + QList<Nepomuk::Resource> resources() const; + + /** + * \brief The properties that have been configured via addProperty() and setProperties(). + * + * Every change to a value of any of these properties + * will be signalled, depending on the configured resources() or types(). + */ + QList<Types::Property> properties() const; + + public Q_SLOTS: + /** + * \brief Start the signalling of changes. + * + * Before calling this method no signal will be emitted. In + * combination with stop() this allows to suspend the watching. + * Calling start() multiple times has no effect. + */ + bool start(); + + /** + * \brief Stop the signalling of changes. + * + * Allows to stop the watcher which has been started + * via start(). Calling stop() multiple times has no effect. + */ + void stop(); + + Q_SIGNALS: + /** + * \brief This signal is emitted when a new resource is created. + * \param resource The newly created resource. + * \param types The types the new resource has. If types() have been configured this list will always + * contain one of the configured types. + */ + void resourceCreated( const Nepomuk::Resource & resource, const QList<QUrl>& types ); //FIXME: Use either Resource or uri, not a mix + + /** + * \brief This signal is emitted when a resource is deleted. + * \param uri The resource URI of the removed resource. + * \param types The types the removed resource had. If types() have been configured this list will always + * contain one of the configured types. + */ + void resourceRemoved( const QUrl & uri, const QList<QUrl>& types ); + + /** + * \brief This signal is emitted when a type has been added to a resource. This does not include creation which + * is signalled via resourceCreated(). It only applies to changes in a resource's types. + * \param res The changed resource. + * \param type The newly added type. If types() have been configured it will be one of them. + */ + void resourceTypeAdded( const Nepomuk::Resource & res, const Types::Class & type ); + + /** + * \brief This signal is emitted when a type has been removed from a resource. + * + * This does not include removal of entire resources which is signalled via resourceRemoved(). + * It only applies to changes in a resource's types. + * \param res The changed resource. + * \param type The removed type. If types() have been configured it will be one of them. + */ + void resourceTypeRemoved( const Nepomuk::Resource & res, const Types::Class & type ); + + /** + * \brief This signal is emitted when a property value is added. + * \param resource The changed resource. + * \param property The property which has a new value. + * \param value The newly added property value. + */ + void propertyAdded( const Nepomuk::Resource & resource, + const Nepomuk::Types::Property & property, + const QVariant & value ); + + /** + * \brief This signal is emitted when a property value is removed. + * \param resource The changed resource. + * \param property The property which was changed. + * \param value The removed property value. + */ + void propertyRemoved( const Nepomuk::Resource & resource, + const Nepomuk::Types::Property & property, + const QVariant & value ); + + /** + * \brief This signal is emitted when a property value is changed. + * + * This signal cannot be emitted for all changes. It doesn't work if a property is first + * removed and then set, cause the Data Mangement Service does not maintain an internal + * cache for the purpose of emitting the propertyChanged signal. + * + * Specially, since one could theoretically take forever between the removal and the + * setting of the property. + * + * \param resource The changed resource. + * \param property The property which was changed. + * \param oldValue The removed property value. + */ + void propertyChanged( const Nepomuk::Resource & resource, + const Nepomuk::Types::Property & property, + const QVariantList & oldValue, + const QVariantList & newValue ); + + private Q_SLOTS: + void slotResourceCreated(const QString& res, const QStringList& types); + void slotResourceRemoved(const QString& res, const QStringList& types); + void slotResourceTypeAdded(const QString& res, const QString& type); + void slotResourceTypeRemoved(const QString& res, const QString& type); + void slotPropertyAdded(const QString& res, const QString& prop, const QDBusVariant& object); + void slotPropertyRemoved(const QString& res, const QString& prop, const QDBusVariant& object); + void slotPropertyChanged(const QString& res, const QString& prop, + const QVariantList & oldObjs, + const QVariantList & newObjs); + private: + class Private; + Private * d; + }; +} + +#endif // RESOURCEWATCHER_H diff --git a/src/kitemviews/private/knepomukrolesprovider.cpp b/src/kitemviews/private/knepomukrolesprovider.cpp new file mode 100644 index 000000000..7af887cbf --- /dev/null +++ b/src/kitemviews/private/knepomukrolesprovider.cpp @@ -0,0 +1,181 @@ +/*************************************************************************** + * 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 "knepomukrolesprovider.h" + +#include <KDebug> +#include <KGlobal> +#include <KLocale> + +#include <Nepomuk/Resource> +#include <Nepomuk/Tag> +#include <Nepomuk/Types/Property> +#include <Nepomuk/Variant> + +struct KNepomukRolesProviderSingleton +{ + KNepomukRolesProvider instance; +}; +K_GLOBAL_STATIC(KNepomukRolesProviderSingleton, s_nepomukRolesProvider) + + +KNepomukRolesProvider& KNepomukRolesProvider::instance() +{ + return s_nepomukRolesProvider->instance; +} + +KNepomukRolesProvider::~KNepomukRolesProvider() +{ +} + +QSet<QByteArray> KNepomukRolesProvider::roles() const +{ + return m_roles; +} + +QHash<QByteArray, QVariant> KNepomukRolesProvider::roleValues(const Nepomuk::Resource& resource, + const QSet<QByteArray>& roles) const +{ + if (!resource.isValid()) { + return QHash<QByteArray, QVariant>(); + } + + QHash<QByteArray, QVariant> values; + + int width = -1; + int height = -1; + + QHashIterator<QUrl, Nepomuk::Variant> it(resource.properties()); + while (it.hasNext()) { + it.next(); + + const Nepomuk::Types::Property property = it.key(); + const QByteArray role = m_roleForUri.value(property.uri()); + if (role.isEmpty() || !roles.contains(role)) { + continue; + } + + const Nepomuk::Variant value = it.value(); + + if (role == "imageSize") { + // Merge the two Nepomuk properties for width and height + // as one string into the "imageSize" role + const QString uri = property.uri().toString(); + if (uri.endsWith("#width")) { + width = value.toInt(); + } else if (uri.endsWith("#height")) { + height = value.toInt(); + } + + if (width >= 0 && height >= 0) { + const QString widthAndHeight = QString::number(width) + + QLatin1String(" x ") + + QString::number(height); + values.insert(role, widthAndHeight); + } + } else if (role == "tags") { + const QString tags = tagsFromValues(value.toStringList()); + values.insert(role, tags); + } else if (role == "orientation") { + const QString orientation = orientationFromValue(value.toInt()); + values.insert(role, orientation); + } else { + values.insert(role, value.toString()); + } + } + + // Assure that empty values get replaced by "-" + foreach (const QByteArray& role, roles) { + if (m_roles.contains(role) && values.value(role).toString().isEmpty()) { + values.insert(role, QLatin1String("-")); + } + } + + return values; +} + +KNepomukRolesProvider::KNepomukRolesProvider() : + m_roles(), + m_roleForUri() +{ + struct UriInfo + { + const char* const uri; + const char* const role; + }; + + // Mapping from the URIs to the KFileItemModel roles. Note that this must not be + // a 1:1 mapping: One role may contain several URI-values (e.g. the URIs for height and + // width of an image are mapped to the role "imageSize") + static const UriInfo uriInfoList[] = { + { "http://www.semanticdesktop.org/ontologies/2007/08/15/nao#numericRating", "rating" }, + { "http://www.semanticdesktop.org/ontologies/2007/08/15/nao#hasTag", "tags" }, + { "http://www.semanticdesktop.org/ontologies/2007/08/15/nao#description", "comment" }, + { "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#wordCount", "wordCount" }, + { "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#lineCount", "lineCount" }, + { "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#width", "imageSize" }, + { "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#height", "imageSize" }, + { "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#orientation", "orientation", }, + { "http://www.semanticdesktop.org/ontologies/2009/02/19/nmm#performer", "artist" }, + { "http://www.semanticdesktop.org/ontologies/2009/02/19/nmm#musicAlbum", "album" }, + { "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#duration", "duration" }, + { "http://www.semanticdesktop.org/ontologies/2009/02/19/nmm#trackNumber", "track" }, + { "http://www.semanticdesktop.org/ontologies/2010/04/30/ndo#copiedFrom", "copiedFrom" } + }; + + for (unsigned int i = 0; i < sizeof(uriInfoList) / sizeof(UriInfo); ++i) { + m_roleForUri.insert(QUrl(uriInfoList[i].uri), uriInfoList[i].role); + m_roles.insert(uriInfoList[i].role); + } +} + +QString KNepomukRolesProvider::tagsFromValues(const QStringList& values) const +{ + QString tags; + + for (int i = 0; i < values.count(); ++i) { + if (i > 0) { + tags.append(QLatin1String(", ")); + } + + const Nepomuk::Tag tag(values[i]); + tags += tag.genericLabel(); + } + + return tags; +} + +QString KNepomukRolesProvider::orientationFromValue(int value) const +{ + QString string; + switch (value) { + case 1: string = i18nc("@item:intable Image orientation", "Unchanged"); break; + case 2: string = i18nc("@item:intable Image orientation", "Horizontally flipped"); break; + case 3: string = i18nc("@item:intable image orientation", "180° rotated"); break; + case 4: string = i18nc("@item:intable image orientation", "Vertically flipped"); break; + case 5: string = i18nc("@item:intable image orientation", "Transposed"); break; + case 6: string = i18nc("@item:intable image orientation", "90° rotated"); break; + case 7: string = i18nc("@item:intable image orientation", "Transversed"); break; + case 8: string = i18nc("@item:intable image orientation", "270° rotated"); break; + default: + break; + } + return string; +} + diff --git a/src/kitemviews/private/knepomukrolesprovider.h b/src/kitemviews/private/knepomukrolesprovider.h new file mode 100644 index 000000000..46a78d4ee --- /dev/null +++ b/src/kitemviews/private/knepomukrolesprovider.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * 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 KNEPOMUKROLESPROVIDER_H +#define KNEPOMUKROLESPROVIDER_H + +#include <libdolphin_export.h> + +#include <QHash> +#include <QSet> +#include <QUrl> + +namespace Nepomuk +{ + class Resource; +} + +/** + * @brief Allows accessing metadata of a file by providing KFileItemModel roles. + * + * Is a helper class for KFileItemModelRolesUpdater to retrieve roles that + * are only accessible with Nepomuk. + */ +class LIBDOLPHINPRIVATE_EXPORT KNepomukRolesProvider +{ +public: + static KNepomukRolesProvider& instance(); + virtual ~KNepomukRolesProvider(); + + /** + * @return Roles that can be provided by KNepomukRolesProvider. + */ + QSet<QByteArray> roles() const; + + /** + * @return Values for the roles \a roles that can be determined from the file + * with the URL \a url. + */ + QHash<QByteArray, QVariant> roleValues(const Nepomuk::Resource& resource, + const QSet<QByteArray>& roles) const; + +protected: + KNepomukRolesProvider(); + +private: + /** + * @return User visible string for the given tag-values. + */ + QString tagsFromValues(const QStringList& values) const; + + /** + * @return User visible string for the EXIF-orientation property + * which can have the values 0 to 8. + * (see http://sylvana.net/jpegcrop/exif_orientation.html) + */ + QString orientationFromValue(int value) const; + +private: + QSet<QByteArray> m_roles; + QHash<QUrl, QByteArray> m_roleForUri; + + friend class KNepomukRolesProviderSingleton; +}; + +#endif + diff --git a/src/kitemviews/private/kpixmapmodifier.cpp b/src/kitemviews/private/kpixmapmodifier.cpp new file mode 100644 index 000000000..29aceb66b --- /dev/null +++ b/src/kitemviews/private/kpixmapmodifier.cpp @@ -0,0 +1,400 @@ +// krazy:excludeall=copyright (email of Maxim is missing) +/* + This file is a part of the KDE project + + Copyright © 2006 Zack Rusin <[email protected]> + Copyright © 2006-2007, 2008 Fredrik Höglund <[email protected]> + + The stack blur algorithm was invented by Mario Klingemann <[email protected]> + + This implementation is based on the version in Anti-Grain Geometry Version 2.4, + Copyright © 2002-2005 Maxim Shemanarev (http://www.antigrain.com) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "kpixmapmodifier.h" + +#include <QImage> +#include <QPainter> +#include <QPixmap> +#include <QSize> + +#include <KDebug> + +#include <config-X11.h> // for HAVE_XRENDER +#if defined(Q_WS_X11) && defined(HAVE_XRENDER) +# include <QX11Info> +# include <X11/Xlib.h> +# include <X11/extensions/Xrender.h> +#endif + +static const quint32 stackBlur8Mul[255] = +{ + 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, + 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, + 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, + 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, + 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, + 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, + 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, + 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, + 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, + 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, + 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, + 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, + 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, + 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, + 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, + 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259 +}; + +static const quint32 stackBlur8Shr[255] = +{ + 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, + 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 +}; + +static void blurHorizontal(QImage& image, unsigned int* stack, int div, int radius) +{ + int stackindex; + int stackstart; + + quint32 * const pixels = reinterpret_cast<quint32 *>(image.bits()); + quint32 pixel; + + int w = image.width(); + int h = image.height(); + int wm = w - 1; + + unsigned int mulSum = stackBlur8Mul[radius]; + unsigned int shrSum = stackBlur8Shr[radius]; + + unsigned int sum, sumIn, sumOut; + + for (int y = 0; y < h; y++) { + sum = 0; + sumIn = 0; + sumOut = 0; + + const int yw = y * w; + pixel = pixels[yw]; + for (int i = 0; i <= radius; i++) { + stack[i] = qAlpha(pixel); + + sum += stack[i] * (i + 1); + sumOut += stack[i]; + } + + for (int i = 1; i <= radius; i++) { + pixel = pixels[yw + qMin(i, wm)]; + + unsigned int* stackpix = &stack[i + radius]; + *stackpix = qAlpha(pixel); + + sum += *stackpix * (radius + 1 - i); + sumIn += *stackpix; + } + + stackindex = radius; + for (int x = 0, i = yw; x < w; x++) { + pixels[i++] = (((sum * mulSum) >> shrSum) << 24) & 0xff000000; + + sum -= sumOut; + + stackstart = stackindex + div - radius; + if (stackstart >= div) { + stackstart -= div; + } + + unsigned int* stackpix = &stack[stackstart]; + + sumOut -= *stackpix; + + pixel = pixels[yw + qMin(x + radius + 1, wm)]; + + *stackpix = qAlpha(pixel); + + sumIn += *stackpix; + sum += sumIn; + + if (++stackindex >= div) { + stackindex = 0; + } + + stackpix = &stack[stackindex]; + + sumOut += *stackpix; + sumIn -= *stackpix; + } + } +} + +static void blurVertical(QImage& image, unsigned int* stack, int div, int radius) +{ + int stackindex; + int stackstart; + + quint32 * const pixels = reinterpret_cast<quint32 *>(image.bits()); + quint32 pixel; + + int w = image.width(); + int h = image.height(); + int hm = h - 1; + + int mul_sum = stackBlur8Mul[radius]; + int shr_sum = stackBlur8Shr[radius]; + + unsigned int sum, sumIn, sumOut; + + for (int x = 0; x < w; x++) { + sum = 0; + sumIn = 0; + sumOut = 0; + + pixel = pixels[x]; + for (int i = 0; i <= radius; i++) { + stack[i] = qAlpha(pixel); + + sum += stack[i] * (i + 1); + sumOut += stack[i]; + } + + for (int i = 1; i <= radius; i++) { + pixel = pixels[qMin(i, hm) * w + x]; + + unsigned int* stackpix = &stack[i + radius]; + *stackpix = qAlpha(pixel); + + sum += *stackpix * (radius + 1 - i); + sumIn += *stackpix; + } + + stackindex = radius; + for (int y = 0, i = x; y < h; y++, i += w) { + pixels[i] = (((sum * mul_sum) >> shr_sum) << 24) & 0xff000000; + + sum -= sumOut; + + stackstart = stackindex + div - radius; + if (stackstart >= div) + stackstart -= div; + + unsigned int* stackpix = &stack[stackstart]; + + sumOut -= *stackpix; + + pixel = pixels[qMin(y + radius + 1, hm) * w + x]; + + *stackpix = qAlpha(pixel); + + sumIn += *stackpix; + sum += sumIn; + + if (++stackindex >= div) { + stackindex = 0; + } + + stackpix = &stack[stackindex]; + + sumOut += *stackpix; + sumIn -= *stackpix; + } + } +} + +static void stackBlur(QImage& image, float radius) +{ + radius = qRound(radius); + + int div = int(radius * 2) + 1; + unsigned int* stack = new unsigned int[div]; + + blurHorizontal(image, stack, div, radius); + blurVertical(image, stack, div, radius); + + delete [] stack; +} + +static void shadowBlur(QImage& image, float radius, const QColor& color) +{ + if (radius < 0) { + return; + } + + if (radius > 0) { + stackBlur(image, radius); + } + + // Correct the color and opacity of the shadow + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(image.rect(), color); +} + +namespace { + /** Helper class for drawing frames for KPixmapModifier::applyFrame(). */ + class TileSet + { + public: + enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 }; + + enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, + RightSide, BottomLeftCorner, BottomSide, BottomRightCorner, + NumTiles }; + + TileSet() + { + QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied); + + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(image.rect(), Qt::transparent); + p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black); + p.end(); + + shadowBlur(image, 3, Qt::black); + + QPixmap pixmap = QPixmap::fromImage(image); + m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8); + m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8); + m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8); + m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8); + m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8); + m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8); + m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8); + m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8); + } + + void paint(QPainter* p, const QRect& r) + { + p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]); + if (r.width() - 16 > 0) { + p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]); + } + p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]); + if (r.height() - 16 > 0) { + p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]); + p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]); + } + p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]); + if (r.width() - 16 > 0) { + p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]); + } + p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]); + + const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, + -(RightMargin + 1), -(BottomMargin + 1)); + p->fillRect(contentRect, Qt::transparent); + } + + QPixmap m_tiles[NumTiles]; + }; +} + +void KPixmapModifier::scale(QPixmap& pixmap, const QSize& scaledSize) +{ + if (scaledSize.isEmpty()) { + pixmap = QPixmap(); + return; + } + +#if defined(Q_WS_X11) && defined(HAVE_XRENDER) + // Assume that the texture size limit is 2048x2048 + if ((pixmap.width() <= 2048) && (pixmap.height() <= 2048) && pixmap.x11PictureHandle()) { + const QPixmap unscaledPixmap = pixmap.copy(); // Make a deep copy for XRender + QSize scaledPixmapSize = pixmap.size(); + scaledPixmapSize.scale(scaledSize, Qt::KeepAspectRatio); + + const qreal factor = scaledPixmapSize.width() / qreal(unscaledPixmap.width()); + + XTransform xform = {{ + { XDoubleToFixed(1 / factor), 0, 0 }, + { 0, XDoubleToFixed(1 / factor), 0 }, + { 0, 0, XDoubleToFixed(1) } + }}; + + QPixmap scaledPixmap(scaledPixmapSize); + scaledPixmap.fill(Qt::transparent); + + Display* dpy = QX11Info::display(); + + XRenderPictureAttributes attr; + attr.repeat = RepeatPad; + XRenderChangePicture(dpy, unscaledPixmap.x11PictureHandle(), CPRepeat, &attr); + + XRenderSetPictureFilter(dpy, unscaledPixmap.x11PictureHandle(), FilterBilinear, 0, 0); + XRenderSetPictureTransform(dpy, unscaledPixmap.x11PictureHandle(), &xform); + XRenderComposite(dpy, PictOpOver, unscaledPixmap.x11PictureHandle(), None, scaledPixmap.x11PictureHandle(), + 0, 0, 0, 0, 0, 0, scaledPixmap.width(), scaledPixmap.height()); + pixmap = scaledPixmap; + } else { + pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } +#else + pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); +#endif +} + +void KPixmapModifier::applyFrame(QPixmap& icon, const QSize& scaledSize) +{ + static TileSet tileSet; + + // Resize the icon to the maximum size minus the space required for the frame + const QSize size(scaledSize.width() - TileSet::LeftMargin - TileSet::RightMargin, + scaledSize.height() - TileSet::TopMargin - TileSet::BottomMargin); + scale(icon, size); + + QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin, + icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin); + framedIcon.fill(Qt::transparent); + + QPainter painter; + painter.begin(&framedIcon); + painter.setCompositionMode(QPainter::CompositionMode_Source); + tileSet.paint(&painter, framedIcon.rect()); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon); + + icon = framedIcon; +} + +QSize KPixmapModifier::sizeInsideFrame(const QSize& frameSize) +{ + return QSize(frameSize.width() - TileSet::LeftMargin - TileSet::RightMargin, + frameSize.height() - TileSet::TopMargin - TileSet::BottomMargin); +} + diff --git a/src/kitemviews/private/kpixmapmodifier.h b/src/kitemviews/private/kpixmapmodifier.h new file mode 100644 index 000000000..4f863c349 --- /dev/null +++ b/src/kitemviews/private/kpixmapmodifier.h @@ -0,0 +1,38 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KPIXMAPMODIFIER_H +#define KPIXMAPMODIFIER_H + +#include <libdolphin_export.h> + +class QPixmap; +class QSize; + +class LIBDOLPHINPRIVATE_EXPORT KPixmapModifier +{ +public: + static void scale(QPixmap& pixmap, const QSize& scaledSize); + static void applyFrame(QPixmap& icon, const QSize& scaledSize); + static QSize sizeInsideFrame(const QSize& frameSize); +}; + +#endif + + |
