From 935ad6b02c47a74d94dc5afd5ca2795cbd9f7c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 4 Aug 2013 12:21:51 +0200 Subject: Fix KDE includes problem --- gui/instancemodel.h | 2 +- gui/mainwindow.cpp | 17 +- libgroupview/CMakeLists.txt | 16 +- .../include/categorizedsortfilterproxymodel.h | 175 ++ libgroupview/include/categorizedview.h | 332 ++++ libgroupview/include/categorydrawer.h | 179 ++ .../include/kcategorizedsortfilterproxymodel.h | 176 -- libgroupview/include/kcategorizedview.h | 333 ---- libgroupview/include/kcategorydrawer.h | 179 -- .../src/categorizedsortfilterproxymodel.cpp | 168 ++ .../src/categorizedsortfilterproxymodel_p.h | 48 + libgroupview/src/categorizedview.cpp | 1713 ++++++++++++++++++++ libgroupview/src/categorizedview_p.h | 159 ++ libgroupview/src/categorydrawer.cpp | 231 +++ .../src/kcategorizedsortfilterproxymodel.cpp | 168 -- .../src/kcategorizedsortfilterproxymodel_p.h | 48 - libgroupview/src/kcategorizedview.cpp | 1713 -------------------- libgroupview/src/kcategorizedview_p.h | 159 -- libgroupview/src/kcategorydrawer.cpp | 231 --- 19 files changed, 3017 insertions(+), 3030 deletions(-) create mode 100644 libgroupview/include/categorizedsortfilterproxymodel.h create mode 100644 libgroupview/include/categorizedview.h create mode 100644 libgroupview/include/categorydrawer.h delete mode 100644 libgroupview/include/kcategorizedsortfilterproxymodel.h delete mode 100644 libgroupview/include/kcategorizedview.h delete mode 100644 libgroupview/include/kcategorydrawer.h create mode 100644 libgroupview/src/categorizedsortfilterproxymodel.cpp create mode 100644 libgroupview/src/categorizedsortfilterproxymodel_p.h create mode 100644 libgroupview/src/categorizedview.cpp create mode 100644 libgroupview/src/categorizedview_p.h create mode 100644 libgroupview/src/categorydrawer.cpp delete mode 100644 libgroupview/src/kcategorizedsortfilterproxymodel.cpp delete mode 100644 libgroupview/src/kcategorizedsortfilterproxymodel_p.h delete mode 100644 libgroupview/src/kcategorizedview.cpp delete mode 100644 libgroupview/src/kcategorizedview_p.h delete mode 100644 libgroupview/src/kcategorydrawer.cpp diff --git a/gui/instancemodel.h b/gui/instancemodel.h index 3938da45..fe05744f 100644 --- a/gui/instancemodel.h +++ b/gui/instancemodel.h @@ -1,7 +1,7 @@ #pragma once #include -#include "kcategorizedsortfilterproxymodel.h" +#include "categorizedsortfilterproxymodel.h" #include "lists/InstanceList.h" #include diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index c4e8dc03..d3dee254 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -45,8 +45,8 @@ #include "gui/legacymodeditdialog.h" #include "gui/instancesettings.h" -#include "kcategorizedview.h" -#include "kcategorydrawer.h" +#include "categorizedview.h" +#include "categorydrawer.h" #include "lists/InstanceList.h" #include "AppSettings.h" @@ -84,12 +84,6 @@ MainWindow::MainWindow ( QWidget *parent ) : // Create the widget view = new KCategorizedView ( ui->centralWidget ); drawer = new KCategoryDrawer ( view ); - /* - QPalette pal = view->palette(); - pal.setBrush(QPalette::Base, QBrush(QPixmap(QString::fromUtf8(":/backgrounds/kitteh")))); - view->setPalette(pal); - */ - view->setStyleSheet( "QListView\ {\ @@ -137,13 +131,8 @@ MainWindow::MainWindow ( QWidget *parent ) : connect(view, SIGNAL(clicked(const QModelIndex &)), this, SLOT(instanceChanged(const QModelIndex &))); - // Load the instances. + // Load the instances. FIXME: this is not the place I'd say. instList.loadList(); - // just a test - /* - instList.at(0)->setGroup("TEST GROUP"); - instList.at(0)->setName("TEST ITEM"); - */ //FIXME: WTF if (!MinecraftVersionList::getMainList().isLoaded()) diff --git a/libgroupview/CMakeLists.txt b/libgroupview/CMakeLists.txt index c53988a0..d60d914a 100644 --- a/libgroupview/CMakeLists.txt +++ b/libgroupview/CMakeLists.txt @@ -13,19 +13,19 @@ SET(LIBGROUPVIEW_HEADERS include/libgroupview_config.h # Public headers -include/kcategorizedsortfilterproxymodel.h -include/kcategorizedview.h -include/kcategorydrawer.h +include/categorizedsortfilterproxymodel.h +include/categorizedview.h +include/categorydrawer.h # Private headers -src/kcategorizedsortfilterproxymodel_p.h -src/kcategorizedview_p.h +src/categorizedsortfilterproxymodel_p.h +src/categorizedview_p.h ) SET(LIBGROUPVIEW_SOURCES -src/kcategorizedsortfilterproxymodel.cpp -src/kcategorizedview.cpp -src/kcategorydrawer.cpp +src/categorizedsortfilterproxymodel.cpp +src/categorizedview.cpp +src/categorydrawer.cpp ) # Set the include dir path. diff --git a/libgroupview/include/categorizedsortfilterproxymodel.h b/libgroupview/include/categorizedsortfilterproxymodel.h new file mode 100644 index 00000000..1ef39fc2 --- /dev/null +++ b/libgroupview/include/categorizedsortfilterproxymodel.h @@ -0,0 +1,175 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2007 Rafael Fernández López + * Copyright (C) 2007 John Tapsell + * + * 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 KCATEGORIZEDSORTFILTERPROXYMODEL_H +#define KCATEGORIZEDSORTFILTERPROXYMODEL_H + +#include + +#include + +class QItemSelection; + + +/** + * This class lets you categorize a view. It is meant to be used along with + * KCategorizedView class. + * + * In general terms all you need to do is to reimplement subSortLessThan() and + * compareCategories() methods. In order to make categorization work, you need + * to also call setCategorizedModel() class to enable it, since the categorization + * is disabled by default. + * + * @see KCategorizedView + * + * @author Rafael Fernández López + */ +class LIBGROUPVIEW_EXPORT KCategorizedSortFilterProxyModel + : public QSortFilterProxyModel +{ +public: + enum AdditionalRoles + { + // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM)) + // to define additional roles. + CategoryDisplayRole = 0x17CE990A, ///< This role is used for asking the category to a given index + + CategorySortRole = 0x27857E60 ///< This role is used for sorting categories. You can return a + ///< string or a long long value. Strings will be sorted alphabetically + ///< while long long will be sorted by their value. Please note that this + ///< value won't be shown on the view, is only for sorting purposes. What will + ///< be shown as "Category" on the view will be asked with the role + ///< CategoryDisplayRole. + }; + + KCategorizedSortFilterProxyModel ( QObject *parent = 0 ); + virtual ~KCategorizedSortFilterProxyModel(); + + /** + * Overridden from QSortFilterProxyModel. Sorts the source model using + * @p column for the given @p order. + */ + virtual void sort ( int column, Qt::SortOrder order = Qt::AscendingOrder ); + + /** + * @return whether the model is categorized or not. Disabled by default. + */ + bool isCategorizedModel() const; + + /** + * Enables or disables the categorization feature. + * + * @param categorizedModel whether to enable or disable the categorization feature. + */ + void setCategorizedModel ( bool categorizedModel ); + + /** + * @return the column being used for sorting. + */ + int sortColumn() const; + + /** + * @return the sort order being used for sorting. + */ + Qt::SortOrder sortOrder() const; + + /** + * Set if the sorting using CategorySortRole will use a natural comparison + * in the case that strings were returned. If enabled, QString::localeAwareCompare + * will be used for sorting. + * + * @param sortCategoriesByNaturalComparison whether to sort using a natural comparison or not. + */ + void setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison ); + + /** + * @return whether it is being used a natural comparison for sorting. Enabled by default. + */ + bool sortCategoriesByNaturalComparison() const; + +protected: + /** + * Overridden from QSortFilterProxyModel. If you are subclassing + * KCategorizedSortFilterProxyModel, you will probably not need to reimplement this + * method. + * + * It calls compareCategories() to sort by category. If the both items are in the + * same category (i.e. compareCategories returns 0), then subSortLessThan is called. + * + * @return Returns true if the item @p left is less than the item @p right when sorting. + * + * @warning You usually won't need to reimplement this method when subclassing + * from KCategorizedSortFilterProxyModel. + */ + virtual bool lessThan ( const QModelIndex &left, const QModelIndex &right ) const; + + /** + * This method has a similar purpose as lessThan() has on QSortFilterProxyModel. + * It is used for sorting items that are in the same category. + * + * @return Returns true if the item @p left is less than the item @p right when sorting. + */ + virtual bool subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const; + + /** + * This method compares the category of the @p left index with the category + * of the @p right index. + * + * Internally and if not reimplemented, this method will ask for @p left and + * @p right models for role CategorySortRole. In order to correctly sort + * categories, the data() metod of the model should return a qlonglong (or numeric) value, or + * a QString object. QString objects will be sorted with QString::localeAwareCompare if + * sortCategoriesByNaturalComparison() is true. + * + * @note Please have present that: + * QString(QChar(QChar::ObjectReplacementCharacter)) > + * QString(QChar(QChar::ReplacementCharacter)) > + * [ all possible strings ] > + * QString(); + * + * This means that QString() will be sorted the first one, while + * QString(QChar(QChar::ObjectReplacementCharacter)) and + * QString(QChar(QChar::ReplacementCharacter)) will be sorted in last + * position. + * + * @warning Please note that data() method of the model should return always + * information of the same type. If you return a QString for an index, + * you should return always QStrings for all indexes for role CategorySortRole + * in order to correctly sort categories. You can't mix by returning + * a QString for one index, and a qlonglong for other. + * + * @note If you need a more complex layout, you will have to reimplement this + * method. + * + * @return A negative value if the category of @p left should be placed before the + * category of @p right. 0 if @p left and @p right are on the same category, and + * a positive value if the category of @p left should be placed after the + * category of @p right. + */ + virtual int compareCategories ( const QModelIndex &left, const QModelIndex &right ) const; + +private: + class Private; + Private *const d; +}; + + +#endif // KCATEGORIZEDSORTFILTERPROXYMODEL_H diff --git a/libgroupview/include/categorizedview.h b/libgroupview/include/categorizedview.h new file mode 100644 index 00000000..243ccf15 --- /dev/null +++ b/libgroupview/include/categorizedview.h @@ -0,0 +1,332 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007, 2009 Rafael Fernández López + * + * 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 KCATEGORIZEDVIEW_H +#define KCATEGORIZEDVIEW_H + +#include + +#include + +class KCategoryDrawer; + +/** + * @short Item view for listing items in a categorized fashion optionally + * + * KCategorizedView basically has the same functionality as QListView, only that it also lets you + * layout items in a way that they are categorized visually. + * + * For it to work you will need to set a KCategorizedSortFilterProxyModel and a KCategoryDrawer + * with methods setModel() and setCategoryDrawer() respectively. Also, the model will need to be + * flagged as categorized with KCategorizedSortFilterProxyModel::setCategorizedModel(true). + * + * The way it works (if categorization enabled): + * + * - When sorting, it does more things than QListView does. It will ask the model for the + * special role CategorySortRole (@see KCategorizedSortFilterProxyModel). This can return + * a QString or an int in order to tell the view the order of categories. In this sense, for + * instance, if we are sorting by name ascending, "A" would be before than "B". If we are + * sorting by size ascending, 512 bytes would be before 1024 bytes. This way categories are + * also sorted. + * + * - When the view has to paint, it will ask the model with the role CategoryDisplayRole + * (@see KCategorizedSortFilterProxyModel). It will for instance return "F" for "foo.pdf" if + * we are sorting by name ascending, or "Small" if a certain item has 100 bytes, for example. + * + * For drawing categories, KCategoryDrawer will be used. You can inherit this class to do your own + * drawing. + * + * @note All examples cited before talk about filesystems and such, but have present that this + * is a completely generic class, and it can be used for whatever your purpose is. For + * instance when talking about animals, you can separate them by "Mammal" and "Oviparous". In + * this very case, for example, the CategorySortRole and the CategoryDisplayRole could be the + * same ("Mammal" and "Oviparous"). + * + * @note There is a really performance boost if CategorySortRole returns an int instead of a QString. + * Have present that this role is asked (n * log n) times when sorting and compared. Comparing + * ints is always faster than comparing strings, whithout mattering how fast the string + * comparison is. Consider thinking of a way of returning ints instead of QStrings if your + * model can contain a high number of items. + * + * @warning Note that for really drawing items in blocks you will need some things to be done: + * - The model set to this view has to be (or inherit if you want to do special stuff + * in it) KCategorizedSortFilterProxyModel. + * - This model needs to be set setCategorizedModel to true. + * - Set a category drawer by calling setCategoryDrawer. + * + * @see KCategorizedSortFilterProxyModel, KCategoryDrawer + * + * @author Rafael Fernández López + */ +class LIBGROUPVIEW_EXPORT KCategorizedView + : public QListView +{ + Q_OBJECT + Q_PROPERTY ( int categorySpacing READ categorySpacing WRITE setCategorySpacing ) + Q_PROPERTY ( bool alternatingBlockColors READ alternatingBlockColors WRITE setAlternatingBlockColors ) + Q_PROPERTY ( bool collapsibleBlocks READ collapsibleBlocks WRITE setCollapsibleBlocks ) + +public: + KCategorizedView ( QWidget *parent = 0 ); + + ~KCategorizedView(); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void setModel ( QAbstractItemModel *model ); + + /** + * Calls to setGridSizeOwn(). + */ + void setGridSize ( const QSize &size ); + + /** + * @warning note that setGridSize is not virtual in the base class (QListView), so if you are + * calling to this method, make sure you have a KCategorizedView pointer around. This + * means that something like: + * @code + * QListView *lv = new KCategorizedView(); + * lv->setGridSize(mySize); + * @endcode + * + * will not call to the expected setGridSize method. Instead do something like this: + * + * @code + * QListView *lv; + * ... + * KCategorizedView *cv = qobject_cast(lv); + * if (cv) { + * cv->setGridSizeOwn(mySize); + * } else { + * lv->setGridSize(mySize); + * } + * @endcode + * + * @note this method will call to QListView::setGridSize among other operations. + * + * @since 4.4 + */ + void setGridSizeOwn ( const QSize &size ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual QRect visualRect ( const QModelIndex &index ) const; + + /** + * Returns the current category drawer. + */ + KCategoryDrawer *categoryDrawer() const; + + /** + * The category drawer that will be used for drawing categories. + */ + void setCategoryDrawer ( KCategoryDrawer *categoryDrawer ); + + /** + * @return Category spacing. The spacing between categories. + * + * @since 4.4 + */ + int categorySpacing() const; + + /** + * Stablishes the category spacing. This is the spacing between categories. + * + * @since 4.4 + */ + void setCategorySpacing ( int categorySpacing ); + + /** + * @return Whether blocks should be drawn with alternating colors. + * + * @since 4.4 + */ + bool alternatingBlockColors() const; + + /** + * Sets whether blocks should be drawn with alternating colors. + * + * @since 4.4 + */ + void setAlternatingBlockColors ( bool enable ); + + /** + * @return Whether blocks can be collapsed or not. + * + * @since 4.4 + */ + bool collapsibleBlocks() const; + + /** + * Sets whether blocks can be collapsed or not. + * + * @since 4.4 + */ + void setCollapsibleBlocks ( bool enable ); + + /** + * @return Block of indexes that are into @p category. + * + * @since 4.5 + */ + QModelIndexList block ( const QString &category ); + + /** + * @return Block of indexes that are represented by @p representative. + * + * @since 4.5 + */ + QModelIndexList block ( const QModelIndex &representative ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual QModelIndex indexAt ( const QPoint &point ) const; + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void reset(); + + /** + * Signify that all item delegates size hints return the same fixed size + */ + void setUniformItemWidths(bool enable); + + /** + * Do all item delegate size hints return the same fixed size? + */ + bool uniformItemWidths() const; + +protected: + /** + * Reimplemented from QWidget. + */ + virtual void paintEvent ( QPaintEvent *event ); + + /** + * Reimplemented from QWidget. + */ + virtual void resizeEvent ( QResizeEvent *event ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void setSelection ( const QRect &rect, + QItemSelectionModel::SelectionFlags flags ); + + /** + * Reimplemented from QWidget. + */ + virtual void mouseMoveEvent ( QMouseEvent *event ); + + /** + * Reimplemented from QWidget. + */ + virtual void mousePressEvent ( QMouseEvent *event ); + + /** + * Reimplemented from QWidget. + */ + virtual void mouseReleaseEvent ( QMouseEvent *event ); + + /** + * Reimplemented from QWidget. + */ + virtual void leaveEvent ( QEvent *event ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void startDrag ( Qt::DropActions supportedActions ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void dragMoveEvent ( QDragMoveEvent *event ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void dragEnterEvent ( QDragEnterEvent *event ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void dragLeaveEvent ( QDragLeaveEvent *event ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void dropEvent ( QDropEvent *event ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual QModelIndex moveCursor ( CursorAction cursorAction, + Qt::KeyboardModifiers modifiers ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void rowsAboutToBeRemoved ( const QModelIndex &parent, + int start, + int end ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void updateGeometries(); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void currentChanged ( const QModelIndex ¤t, + const QModelIndex &previous ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void dataChanged ( const QModelIndex &topLeft, + const QModelIndex &bottomRight ); + + /** + * Reimplemented from QAbstractItemView. + */ + virtual void rowsInserted ( const QModelIndex &parent, + int start, + int end ); + +protected Q_SLOTS: + /** + * @internal + * Reposition items as needed. + */ + virtual void slotLayoutChanged(); + virtual void slotCollapseOrExpandClicked ( QModelIndex ); + +private: + class Private; + Private *const d; +}; + +#endif // KCATEGORIZEDVIEW_H diff --git a/libgroupview/include/categorydrawer.h b/libgroupview/include/categorydrawer.h new file mode 100644 index 00000000..aa561f0d --- /dev/null +++ b/libgroupview/include/categorydrawer.h @@ -0,0 +1,179 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007, 2009 Rafael Fernández López + * + * 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 KCATEGORYDRAWER_H +#define KCATEGORYDRAWER_H + +#include + +#include +#include + +class QPainter; +class QModelIndex; +class QStyleOption; +class KCategorizedView; + + +/** + * @since 4.5 + */ +class LIBGROUPVIEW_EXPORT KCategoryDrawer + : public QObject +{ + friend class KCategorizedView; + Q_OBJECT + + +public: + KCategoryDrawer ( KCategorizedView *view ); + virtual ~KCategoryDrawer(); + + /** + * @return The view this category drawer is associated with. + */ + KCategorizedView *view() const; + + /** + * This method purpose is to draw a category represented by the given + * @param index with the given @param sortRole sorting role + * + * @note This method will be called one time per category, always with the + * first element in that category + */ + virtual void drawCategory ( const QModelIndex &index, + int sortRole, + const QStyleOption &option, + QPainter *painter ) const; + + /** + * @return The category height for the category representated by index @p index with + * style options @p option. + */ + virtual int categoryHeight ( const QModelIndex &index, const QStyleOption &option ) const; + + //TODO KDE5: make virtual as leftMargin + /** + * @note 0 by default + * + * @since 4.4 + */ + int leftMargin() const; + + /** + * @note call to this method on the KCategoryDrawer constructor to set the left margin + * + * @since 4.4 + */ + void setLeftMargin ( int leftMargin ); + + //TODO KDE5: make virtual as rightMargin + /** + * @note 0 by default + * + * @since 4.4 + */ + int rightMargin() const; + + /** + * @note call to this method on the KCategoryDrawer constructor to set the right margin + * + * @since 4.4 + */ + void setRightMargin ( int rightMargin ); + + KCategoryDrawer &operator= ( const KCategoryDrawer &cd ); +protected: + /** + * Method called when the mouse button has been pressed. + * + * @param index The representative index of the block of items. + * @param blockRect The rect occupied by the block of items. + * @param event The mouse event. + * + * @warning You explicitly have to determine whether the event has been accepted or not. You + * have to call event->accept() or event->ignore() at all possible case branches in + * your code. + */ + virtual void mouseButtonPressed ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); + + /** + * Method called when the mouse button has been released. + * + * @param index The representative index of the block of items. + * @param blockRect The rect occupied by the block of items. + * @param event The mouse event. + * + * @warning You explicitly have to determine whether the event has been accepted or not. You + * have to call event->accept() or event->ignore() at all possible case branches in + * your code. + */ + virtual void mouseButtonReleased ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); + + /** + * Method called when the mouse has been moved. + * + * @param index The representative index of the block of items. + * @param blockRect The rect occupied by the block of items. + * @param event The mouse event. + */ + virtual void mouseMoved ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); + + /** + * Method called when the mouse button has been double clicked. + * + * @param index The representative index of the block of items. + * @param blockRect The rect occupied by the block of items. + * @param event The mouse event. + * + * @warning You explicitly have to determine whether the event has been accepted or not. You + * have to call event->accept() or event->ignore() at all possible case branches in + * your code. + */ + virtual void mouseButtonDoubleClicked ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); + + /** + * Method called when the mouse button has left this block. + * + * @param index The representative index of the block of items. + * @param blockRect The rect occupied by the block of items. + */ + virtual void mouseLeft ( const QModelIndex &index, const QRect &blockRect ); + +private: + class Private; + Private *const d; +Q_SIGNALS: + /** + * This signal becomes emitted when collapse or expand has been clicked. + */ + void collapseOrExpandClicked ( const QModelIndex &index ); + + /** + * Emit this signal on your subclass implementation to notify that something happened. Usually + * this will be triggered when you have received an event, and its position matched some "hot spot". + * + * You give this action the integer you want, and having connected this signal to your code, + * the connected slot can perform the needed changes (view, model, selection model, delegate...) + */ + void actionRequested ( int action, const QModelIndex &index ); +}; + +#endif // KCATEGORYDRAWER_H diff --git a/libgroupview/include/kcategorizedsortfilterproxymodel.h b/libgroupview/include/kcategorizedsortfilterproxymodel.h deleted file mode 100644 index df1be736..00000000 --- a/libgroupview/include/kcategorizedsortfilterproxymodel.h +++ /dev/null @@ -1,176 +0,0 @@ -/* - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * 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 KCATEGORIZEDSORTFILTERPROXYMODEL_H -#define KCATEGORIZEDSORTFILTERPROXYMODEL_H - -#include - -//#include -#include - -class QItemSelection; - - -/** - * This class lets you categorize a view. It is meant to be used along with - * KCategorizedView class. - * - * In general terms all you need to do is to reimplement subSortLessThan() and - * compareCategories() methods. In order to make categorization work, you need - * to also call setCategorizedModel() class to enable it, since the categorization - * is disabled by default. - * - * @see KCategorizedView - * - * @author Rafael Fernández López - */ -class LIBGROUPVIEW_EXPORT KCategorizedSortFilterProxyModel - : public QSortFilterProxyModel -{ -public: - enum AdditionalRoles - { - // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM)) - // to define additional roles. - CategoryDisplayRole = 0x17CE990A, ///< This role is used for asking the category to a given index - - CategorySortRole = 0x27857E60 ///< This role is used for sorting categories. You can return a - ///< string or a long long value. Strings will be sorted alphabetically - ///< while long long will be sorted by their value. Please note that this - ///< value won't be shown on the view, is only for sorting purposes. What will - ///< be shown as "Category" on the view will be asked with the role - ///< CategoryDisplayRole. - }; - - KCategorizedSortFilterProxyModel ( QObject *parent = 0 ); - virtual ~KCategorizedSortFilterProxyModel(); - - /** - * Overridden from QSortFilterProxyModel. Sorts the source model using - * @p column for the given @p order. - */ - virtual void sort ( int column, Qt::SortOrder order = Qt::AscendingOrder ); - - /** - * @return whether the model is categorized or not. Disabled by default. - */ - bool isCategorizedModel() const; - - /** - * Enables or disables the categorization feature. - * - * @param categorizedModel whether to enable or disable the categorization feature. - */ - void setCategorizedModel ( bool categorizedModel ); - - /** - * @return the column being used for sorting. - */ - int sortColumn() const; - - /** - * @return the sort order being used for sorting. - */ - Qt::SortOrder sortOrder() const; - - /** - * Set if the sorting using CategorySortRole will use a natural comparison - * in the case that strings were returned. If enabled, QString::localeAwareCompare - * will be used for sorting. - * - * @param sortCategoriesByNaturalComparison whether to sort using a natural comparison or not. - */ - void setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison ); - - /** - * @return whether it is being used a natural comparison for sorting. Enabled by default. - */ - bool sortCategoriesByNaturalComparison() const; - -protected: - /** - * Overridden from QSortFilterProxyModel. If you are subclassing - * KCategorizedSortFilterProxyModel, you will probably not need to reimplement this - * method. - * - * It calls compareCategories() to sort by category. If the both items are in the - * same category (i.e. compareCategories returns 0), then subSortLessThan is called. - * - * @return Returns true if the item @p left is less than the item @p right when sorting. - * - * @warning You usually won't need to reimplement this method when subclassing - * from KCategorizedSortFilterProxyModel. - */ - virtual bool lessThan ( const QModelIndex &left, const QModelIndex &right ) const; - - /** - * This method has a similar purpose as lessThan() has on QSortFilterProxyModel. - * It is used for sorting items that are in the same category. - * - * @return Returns true if the item @p left is less than the item @p right when sorting. - */ - virtual bool subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const; - - /** - * This method compares the category of the @p left index with the category - * of the @p right index. - * - * Internally and if not reimplemented, this method will ask for @p left and - * @p right models for role CategorySortRole. In order to correctly sort - * categories, the data() metod of the model should return a qlonglong (or numeric) value, or - * a QString object. QString objects will be sorted with QString::localeAwareCompare if - * sortCategoriesByNaturalComparison() is true. - * - * @note Please have present that: - * QString(QChar(QChar::ObjectReplacementCharacter)) > - * QString(QChar(QChar::ReplacementCharacter)) > - * [ all possible strings ] > - * QString(); - * - * This means that QString() will be sorted the first one, while - * QString(QChar(QChar::ObjectReplacementCharacter)) and - * QString(QChar(QChar::ReplacementCharacter)) will be sorted in last - * position. - * - * @warning Please note that data() method of the model should return always - * information of the same type. If you return a QString for an index, - * you should return always QStrings for all indexes for role CategorySortRole - * in order to correctly sort categories. You can't mix by returning - * a QString for one index, and a qlonglong for other. - * - * @note If you need a more complex layout, you will have to reimplement this - * method. - * - * @return A negative value if the category of @p left should be placed before the - * category of @p right. 0 if @p left and @p right are on the same category, and - * a positive value if the category of @p left should be placed after the - * category of @p right. - */ - virtual int compareCategories ( const QModelIndex &left, const QModelIndex &right ) const; - -private: - class Private; - Private *const d; -}; - - -#endif // KCATEGORIZEDSORTFILTERPROXYMODEL_H diff --git a/libgroupview/include/kcategorizedview.h b/libgroupview/include/kcategorizedview.h deleted file mode 100644 index e0ac31fd..00000000 --- a/libgroupview/include/kcategorizedview.h +++ /dev/null @@ -1,333 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * 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 KCATEGORIZEDVIEW_H -#define KCATEGORIZEDVIEW_H - -#include - -//#include -#include - -class KCategoryDrawer; - -/** - * @short Item view for listing items in a categorized fashion optionally - * - * KCategorizedView basically has the same functionality as QListView, only that it also lets you - * layout items in a way that they are categorized visually. - * - * For it to work you will need to set a KCategorizedSortFilterProxyModel and a KCategoryDrawer - * with methods setModel() and setCategoryDrawer() respectively. Also, the model will need to be - * flagged as categorized with KCategorizedSortFilterProxyModel::setCategorizedModel(true). - * - * The way it works (if categorization enabled): - * - * - When sorting, it does more things than QListView does. It will ask the model for the - * special role CategorySortRole (@see KCategorizedSortFilterProxyModel). This can return - * a QString or an int in order to tell the view the order of categories. In this sense, for - * instance, if we are sorting by name ascending, "A" would be before than "B". If we are - * sorting by size ascending, 512 bytes would be before 1024 bytes. This way categories are - * also sorted. - * - * - When the view has to paint, it will ask the model with the role CategoryDisplayRole - * (@see KCategorizedSortFilterProxyModel). It will for instance return "F" for "foo.pdf" if - * we are sorting by name ascending, or "Small" if a certain item has 100 bytes, for example. - * - * For drawing categories, KCategoryDrawer will be used. You can inherit this class to do your own - * drawing. - * - * @note All examples cited before talk about filesystems and such, but have present that this - * is a completely generic class, and it can be used for whatever your purpose is. For - * instance when talking about animals, you can separate them by "Mammal" and "Oviparous". In - * this very case, for example, the CategorySortRole and the CategoryDisplayRole could be the - * same ("Mammal" and "Oviparous"). - * - * @note There is a really performance boost if CategorySortRole returns an int instead of a QString. - * Have present that this role is asked (n * log n) times when sorting and compared. Comparing - * ints is always faster than comparing strings, whithout mattering how fast the string - * comparison is. Consider thinking of a way of returning ints instead of QStrings if your - * model can contain a high number of items. - * - * @warning Note that for really drawing items in blocks you will need some things to be done: - * - The model set to this view has to be (or inherit if you want to do special stuff - * in it) KCategorizedSortFilterProxyModel. - * - This model needs to be set setCategorizedModel to true. - * - Set a category drawer by calling setCategoryDrawer. - * - * @see KCategorizedSortFilterProxyModel, KCategoryDrawer - * - * @author Rafael Fernández López - */ -class LIBGROUPVIEW_EXPORT KCategorizedView - : public QListView -{ - Q_OBJECT - Q_PROPERTY ( int categorySpacing READ categorySpacing WRITE setCategorySpacing ) - Q_PROPERTY ( bool alternatingBlockColors READ alternatingBlockColors WRITE setAlternatingBlockColors ) - Q_PROPERTY ( bool collapsibleBlocks READ collapsibleBlocks WRITE setCollapsibleBlocks ) - -public: - KCategorizedView ( QWidget *parent = 0 ); - - ~KCategorizedView(); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void setModel ( QAbstractItemModel *model ); - - /** - * Calls to setGridSizeOwn(). - */ - void setGridSize ( const QSize &size ); - - /** - * @warning note that setGridSize is not virtual in the base class (QListView), so if you are - * calling to this method, make sure you have a KCategorizedView pointer around. This - * means that something like: - * @code - * QListView *lv = new KCategorizedView(); - * lv->setGridSize(mySize); - * @endcode - * - * will not call to the expected setGridSize method. Instead do something like this: - * - * @code - * QListView *lv; - * ... - * KCategorizedView *cv = qobject_cast(lv); - * if (cv) { - * cv->setGridSizeOwn(mySize); - * } else { - * lv->setGridSize(mySize); - * } - * @endcode - * - * @note this method will call to QListView::setGridSize among other operations. - * - * @since 4.4 - */ - void setGridSizeOwn ( const QSize &size ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QRect visualRect ( const QModelIndex &index ) const; - - /** - * Returns the current category drawer. - */ - KCategoryDrawer *categoryDrawer() const; - - /** - * The category drawer that will be used for drawing categories. - */ - void setCategoryDrawer ( KCategoryDrawer *categoryDrawer ); - - /** - * @return Category spacing. The spacing between categories. - * - * @since 4.4 - */ - int categorySpacing() const; - - /** - * Stablishes the category spacing. This is the spacing between categories. - * - * @since 4.4 - */ - void setCategorySpacing ( int categorySpacing ); - - /** - * @return Whether blocks should be drawn with alternating colors. - * - * @since 4.4 - */ - bool alternatingBlockColors() const; - - /** - * Sets whether blocks should be drawn with alternating colors. - * - * @since 4.4 - */ - void setAlternatingBlockColors ( bool enable ); - - /** - * @return Whether blocks can be collapsed or not. - * - * @since 4.4 - */ - bool collapsibleBlocks() const; - - /** - * Sets whether blocks can be collapsed or not. - * - * @since 4.4 - */ - void setCollapsibleBlocks ( bool enable ); - - /** - * @return Block of indexes that are into @p category. - * - * @since 4.5 - */ - QModelIndexList block ( const QString &category ); - - /** - * @return Block of indexes that are represented by @p representative. - * - * @since 4.5 - */ - QModelIndexList block ( const QModelIndex &representative ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QModelIndex indexAt ( const QPoint &point ) const; - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void reset(); - - /** - * Signify that all item delegates size hints return the same fixed size - */ - void setUniformItemWidths(bool enable); - - /** - * Do all item delegate size hints return the same fixed size? - */ - bool uniformItemWidths() const; - -protected: - /** - * Reimplemented from QWidget. - */ - virtual void paintEvent ( QPaintEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void resizeEvent ( QResizeEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void setSelection ( const QRect &rect, - QItemSelectionModel::SelectionFlags flags ); - - /** - * Reimplemented from QWidget. - */ - virtual void mouseMoveEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void mousePressEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void mouseReleaseEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void leaveEvent ( QEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void startDrag ( Qt::DropActions supportedActions ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragMoveEvent ( QDragMoveEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragEnterEvent ( QDragEnterEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragLeaveEvent ( QDragLeaveEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dropEvent ( QDropEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QModelIndex moveCursor ( CursorAction cursorAction, - Qt::KeyboardModifiers modifiers ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void rowsAboutToBeRemoved ( const QModelIndex &parent, - int start, - int end ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void updateGeometries(); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void currentChanged ( const QModelIndex ¤t, - const QModelIndex &previous ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dataChanged ( const QModelIndex &topLeft, - const QModelIndex &bottomRight ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void rowsInserted ( const QModelIndex &parent, - int start, - int end ); - -protected Q_SLOTS: - /** - * @internal - * Reposition items as needed. - */ - virtual void slotLayoutChanged(); - virtual void slotCollapseOrExpandClicked ( QModelIndex ); - -private: - class Private; - Private *const d; -}; - -#endif // KCATEGORIZEDVIEW_H diff --git a/libgroupview/include/kcategorydrawer.h b/libgroupview/include/kcategorydrawer.h deleted file mode 100644 index aa561f0d..00000000 --- a/libgroupview/include/kcategorydrawer.h +++ /dev/null @@ -1,179 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * 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 KCATEGORYDRAWER_H -#define KCATEGORYDRAWER_H - -#include - -#include -#include - -class QPainter; -class QModelIndex; -class QStyleOption; -class KCategorizedView; - - -/** - * @since 4.5 - */ -class LIBGROUPVIEW_EXPORT KCategoryDrawer - : public QObject -{ - friend class KCategorizedView; - Q_OBJECT - - -public: - KCategoryDrawer ( KCategorizedView *view ); - virtual ~KCategoryDrawer(); - - /** - * @return The view this category drawer is associated with. - */ - KCategorizedView *view() const; - - /** - * This method purpose is to draw a category represented by the given - * @param index with the given @param sortRole sorting role - * - * @note This method will be called one time per category, always with the - * first element in that category - */ - virtual void drawCategory ( const QModelIndex &index, - int sortRole, - const QStyleOption &option, - QPainter *painter ) const; - - /** - * @return The category height for the category representated by index @p index with - * style options @p option. - */ - virtual int categoryHeight ( const QModelIndex &index, const QStyleOption &option ) const; - - //TODO KDE5: make virtual as leftMargin - /** - * @note 0 by default - * - * @since 4.4 - */ - int leftMargin() const; - - /** - * @note call to this method on the KCategoryDrawer constructor to set the left margin - * - * @since 4.4 - */ - void setLeftMargin ( int leftMargin ); - - //TODO KDE5: make virtual as rightMargin - /** - * @note 0 by default - * - * @since 4.4 - */ - int rightMargin() const; - - /** - * @note call to this method on the KCategoryDrawer constructor to set the right margin - * - * @since 4.4 - */ - void setRightMargin ( int rightMargin ); - - KCategoryDrawer &operator= ( const KCategoryDrawer &cd ); -protected: - /** - * Method called when the mouse button has been pressed. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonPressed ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has been released. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonReleased ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse has been moved. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - */ - virtual void mouseMoved ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has been double clicked. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonDoubleClicked ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has left this block. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - */ - virtual void mouseLeft ( const QModelIndex &index, const QRect &blockRect ); - -private: - class Private; - Private *const d; -Q_SIGNALS: - /** - * This signal becomes emitted when collapse or expand has been clicked. - */ - void collapseOrExpandClicked ( const QModelIndex &index ); - - /** - * Emit this signal on your subclass implementation to notify that something happened. Usually - * this will be triggered when you have received an event, and its position matched some "hot spot". - * - * You give this action the integer you want, and having connected this signal to your code, - * the connected slot can perform the needed changes (view, model, selection model, delegate...) - */ - void actionRequested ( int action, const QModelIndex &index ); -}; - -#endif // KCATEGORYDRAWER_H diff --git a/libgroupview/src/categorizedsortfilterproxymodel.cpp b/libgroupview/src/categorizedsortfilterproxymodel.cpp new file mode 100644 index 00000000..09da9dd3 --- /dev/null +++ b/libgroupview/src/categorizedsortfilterproxymodel.cpp @@ -0,0 +1,168 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007 Rafael Fernández López + * Copyright (C) 2007 John Tapsell + * + * 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. + */ + +#include "categorizedsortfilterproxymodel.h" +#include "categorizedsortfilterproxymodel_p.h" + +#include + +#include +#include +#include + +KCategorizedSortFilterProxyModel::KCategorizedSortFilterProxyModel ( QObject *parent ) + : QSortFilterProxyModel ( parent ) + , d ( new Private() ) +{ +} + +KCategorizedSortFilterProxyModel::~KCategorizedSortFilterProxyModel() +{ + delete d; +} + +void KCategorizedSortFilterProxyModel::sort ( int column, Qt::SortOrder order ) +{ + d->sortColumn = column; + d->sortOrder = order; + + QSortFilterProxyModel::sort ( column, order ); +} + +bool KCategorizedSortFilterProxyModel::isCategorizedModel() const +{ + return d->categorizedModel; +} + +void KCategorizedSortFilterProxyModel::setCategorizedModel ( bool categorizedModel ) +{ + if ( categorizedModel == d->categorizedModel ) + { + return; + } + + d->categorizedModel = categorizedModel; + + invalidate(); +} + +int KCategorizedSortFilterProxyModel::sortColumn() const +{ + return d->sortColumn; +} + +Qt::SortOrder KCategorizedSortFilterProxyModel::sortOrder() const +{ + return d->sortOrder; +} + +void KCategorizedSortFilterProxyModel::setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison ) +{ + if ( sortCategoriesByNaturalComparison == d->sortCategoriesByNaturalComparison ) + { + return; + } + + d->sortCategoriesByNaturalComparison = sortCategoriesByNaturalComparison; + + invalidate(); +} + +bool KCategorizedSortFilterProxyModel::sortCategoriesByNaturalComparison() const +{ + return d->sortCategoriesByNaturalComparison; +} + +bool KCategorizedSortFilterProxyModel::lessThan ( const QModelIndex &left, const QModelIndex &right ) const +{ + if ( d->categorizedModel ) + { + int compare = compareCategories ( left, right ); + + if ( compare > 0 ) // left is greater than right + { + return false; + } + else if ( compare < 0 ) // left is less than right + { + return true; + } + } + + return subSortLessThan ( left, right ); +} + +bool KCategorizedSortFilterProxyModel::subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const +{ + return QSortFilterProxyModel::lessThan ( left, right ); +} + +int KCategorizedSortFilterProxyModel::compareCategories ( const QModelIndex &left, const QModelIndex &right ) const +{ + QVariant l = ( left.model() ? left.model()->data ( left, CategorySortRole ) : QVariant() ); + QVariant r = ( right.model() ? right.model()->data ( right, CategorySortRole ) : QVariant() ); + + Q_ASSERT ( l.isValid() ); + Q_ASSERT ( r.isValid() ); + Q_ASSERT ( l.type() == r.type() ); + + if ( l.type() == QVariant::String ) + { + QString lstr = l.toString(); + QString rstr = r.toString(); + + /* + if ( d->sortCategoriesByNaturalComparison ) + { + return KStringHandler::naturalCompare ( lstr, rstr ); + } + else + { + */ + if ( lstr < rstr ) + { + return -1; + } + + if ( lstr > rstr ) + { + return 1; + } + + return 0; + //} + } + + qlonglong lint = l.toLongLong(); + qlonglong rint = r.toLongLong(); + + if ( lint < rint ) + { + return -1; + } + + if ( lint > rint ) + { + return 1; + } + + return 0; +} diff --git a/libgroupview/src/categorizedsortfilterproxymodel_p.h b/libgroupview/src/categorizedsortfilterproxymodel_p.h new file mode 100644 index 00000000..d7e7c9a0 --- /dev/null +++ b/libgroupview/src/categorizedsortfilterproxymodel_p.h @@ -0,0 +1,48 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007 Rafael Fernández López + * Copyright (C) 2007 John Tapsell + * + * 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 KCATEGORIZEDSORTFILTERPROXYMODEL_P_H +#define KCATEGORIZEDSORTFILTERPROXYMODEL_P_H + +class KCategorizedSortFilterProxyModel; + +class KCategorizedSortFilterProxyModel::Private +{ +public: + Private() + : sortColumn ( 0 ) + , sortOrder ( Qt::AscendingOrder ) + , categorizedModel ( false ) + , sortCategoriesByNaturalComparison ( true ) + { + } + + ~Private() + { + } + + int sortColumn; + Qt::SortOrder sortOrder; + bool categorizedModel; + bool sortCategoriesByNaturalComparison; +}; + +#endif diff --git a/libgroupview/src/categorizedview.cpp b/libgroupview/src/categorizedview.cpp new file mode 100644 index 00000000..f4449949 --- /dev/null +++ b/libgroupview/src/categorizedview.cpp @@ -0,0 +1,1713 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007, 2009 Rafael Fernández López + * + * 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. + */ + +/** + * IMPLEMENTATION NOTES: + * + * QListView::setRowHidden() and QListView::isRowHidden() are not taken into + * account. This methods should actually not exist. This effect should be handled + * by an hypothetical QSortFilterProxyModel which filters out the desired rows. + * + * In case this needs to be implemented, contact me, but I consider this a faulty + * design. + */ + +#include "categorizedview.h" +#include "categorizedview_p.h" + +#include // trunc on C99 compliant systems +//#include // trunc for not C99 compliant systems + +#include +#include +#include + +#include "categorydrawer.h" +#include "categorizedsortfilterproxymodel.h" + +//BEGIN: Private part + +struct KCategorizedView::Private::Item +{ + Item() + : topLeft ( QPoint() ) + , size ( QSize() ) + { + } + + QPoint topLeft; + QSize size; +}; + +struct KCategorizedView::Private::Block +{ + Block() + : topLeft ( QPoint() ) + , height ( -1 ) + , firstIndex ( QModelIndex() ) + , quarantineStart ( QModelIndex() ) + , items ( QList() ) + , outOfQuarantine ( false ) + , alternate ( false ) + , collapsed ( false ) + { + } + + bool operator!= ( const Block &rhs ) const + { + return firstIndex != rhs.firstIndex; + } + + static bool lessThan ( const Block &left, const Block &right ) + { + Q_ASSERT ( left.firstIndex.isValid() ); + Q_ASSERT ( right.firstIndex.isValid() ); + return left.firstIndex.row() < right.firstIndex.row(); + } + + QPoint topLeft; + int height; + QPersistentModelIndex firstIndex; + // if we have n elements on this block, and we inserted an element at position i. The quarantine + // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the + // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine + // will only affect the current block, since the rest of blocks can be affected only in the way + // that the whole block will have different offset, but items will keep the same relative position + // in terms of their parent blocks. + QPersistentModelIndex quarantineStart; + QList items; + + // this affects the whole block, not items separately. items contain the topLeft point relative + // to the block. Because of insertions or removals a whole block can be moved, so the whole block + // will enter in quarantine, what is faster than moving all items in absolute terms. + bool outOfQuarantine; + + // should we alternate its color ? is just a hint, could not be used + bool alternate; + bool collapsed; +}; + +KCategorizedView::Private::Private ( KCategorizedView *q ) + : q ( q ) + , proxyModel ( 0 ) + , categoryDrawer ( 0 ) + , categorySpacing ( 5 ) + , alternatingBlockColors ( false ) + , collapsibleBlocks ( false ) + , hoveredBlock ( new Block() ) + , hoveredIndex ( QModelIndex() ) + , pressedPosition ( QPoint() ) + , rubberBandRect ( QRect() ) + , constantItemWidth( 0 ) +{ +} + +KCategorizedView::Private::~Private() +{ + delete hoveredBlock; +} + +bool KCategorizedView::Private::isCategorized() const +{ + return proxyModel && categoryDrawer && proxyModel->isCategorizedModel(); +} + +QStyleOptionViewItemV4 KCategorizedView::Private::blockRect ( const QModelIndex &representative ) +{ + QStyleOptionViewItemV4 option ( q->viewOptions() ); + const int height = categoryDrawer->categoryHeight ( representative, option ); + const QString categoryDisplay = representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); + QPoint pos = blockPosition ( categoryDisplay ); + pos.ry() -= height; + option.rect.setTopLeft ( pos ); + option.rect.setWidth ( viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin() ); + option.rect.setHeight ( height + blockHeight ( categoryDisplay ) ); + option.rect = mapToViewport ( option.rect ); + + return option; +} + +QPair KCategorizedView::Private::intersectingIndexesWithRect ( const QRect &_rect ) const +{ + const int rowCount = proxyModel->rowCount(); + + const QRect rect = _rect.normalized(); + + // binary search to find out the top border + int bottom = 0; + int top = rowCount - 1; + while ( bottom <= top ) + { + const int middle = ( bottom + top ) / 2; + const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() ); + QRect itemRect = q->visualRect ( index ); + const int verticalOff = q->verticalOffset(); + const int horizontalOff = q->horizontalOffset(); + itemRect.topLeft().ry() += verticalOff; + itemRect.topLeft().rx() += horizontalOff; + itemRect.bottomRight().ry() += verticalOff; + itemRect.bottomRight().rx() += horizontalOff; + if ( itemRect.bottomRight().y() <= rect.topLeft().y() ) + { + bottom = middle + 1; + } + else + { + top = middle - 1; + } + } + + const QModelIndex bottomIndex = proxyModel->index ( bottom, q->modelColumn(), q->rootIndex() ); + + // binary search to find out the bottom border + bottom = 0; + top = rowCount - 1; + while ( bottom <= top ) + { + const int middle = ( bottom + top ) / 2; + const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() ); + QRect itemRect = q->visualRect ( index ); + const int verticalOff = q->verticalOffset(); + const int horizontalOff = q->horizontalOffset(); + itemRect.topLeft().ry() += verticalOff; + itemRect.topLeft().rx() += horizontalOff; + itemRect.bottomRight().ry() += verticalOff; + itemRect.bottomRight().rx() += horizontalOff; + if ( itemRect.topLeft().y() <= rect.bottomRight().y() ) + { + bottom = middle + 1; + } + else + { + top = middle - 1; + } + } + + const QModelIndex topIndex = proxyModel->index ( top, q->modelColumn(), q->rootIndex() ); + + return qMakePair ( bottomIndex, topIndex ); +} + +QPoint KCategorizedView::Private::blockPosition ( const QString &category ) +{ + Block &block = blocks[category]; + + if ( block.outOfQuarantine && !block.topLeft.isNull() ) + { + return block.topLeft; + } + + QPoint res ( categorySpacing, 0 ); + + const QModelIndex index = block.firstIndex; + + for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) + { + Block &block = *it; + const QModelIndex categoryIndex = block.firstIndex; + if ( index.row() < categoryIndex.row() ) + { + continue; + } + res.ry() += categoryDrawer->categoryHeight ( categoryIndex, q->viewOptions() ) + categorySpacing; + if ( index.row() == categoryIndex.row() ) + { + continue; + } + res.ry() += blockHeight ( it.key() ); + } + + block.outOfQuarantine = true; + block.topLeft = res; + + return res; +} + +int KCategorizedView::Private::blockHeight ( const QString &category ) +{ + Block &block = blocks[category]; + + if ( block.collapsed ) + { + return 0; + } + + if ( block.height > -1 ) + { + return block.height; + } + + const QModelIndex firstIndex = block.firstIndex; + const QModelIndex lastIndex = proxyModel->index ( firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() ); + const QRect topLeft = q->visualRect ( firstIndex ); + QRect bottomRight = q->visualRect ( lastIndex ); + + if ( hasGrid() ) + { + bottomRight.setHeight ( qMax ( bottomRight.height(), q->gridSize().height() ) ); + } + else + { + if ( !q->uniformItemSizes() ) + { + bottomRight.setHeight ( highestElementInLastRow ( block ) + q->spacing() * 2 ); + } + } + + const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1; + block.height = height; + + return height; +} + +int KCategorizedView::Private::viewportWidth() const +{ + return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin(); +} + +void KCategorizedView::Private::regenerateAllElements() +{ + for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) + { + Block &block = *it; + block.outOfQuarantine = false; + block.quarantineStart = block.firstIndex; + block.height = -1; + } +} + +void KCategorizedView::Private::rowsInserted ( const QModelIndex &parent, int start, int end ) +{ + if ( !isCategorized() ) + { + return; + } + + for ( int i = start; i <= end; ++i ) + { + const QModelIndex index = proxyModel->index ( i, q->modelColumn(), parent ); + + Q_ASSERT ( index.isValid() ); + + const QString category = categoryForIndex ( index ); + + Block &block = blocks[category]; + + //BEGIN: update firstIndex + // save as firstIndex in block if + // - it forced the category creation (first element on this category) + // - it is before the first row on that category + const QModelIndex firstIndex = block.firstIndex; + if ( !firstIndex.isValid() || index.row() < firstIndex.row() ) + { + block.firstIndex = index; + } + //END: update firstIndex + + Q_ASSERT ( block.firstIndex.isValid() ); + + const int firstIndexRow = block.firstIndex.row(); + + block.items.insert ( index.row() - firstIndexRow, Private::Item() ); + block.height = -1; + + q->visualRect ( index ); + q->viewport()->update(); + } + + //BEGIN: update the items that are in quarantine in affected categories + { + const QModelIndex lastIndex = proxyModel->index ( end, q->modelColumn(), parent ); + const QString category = categoryForIndex ( lastIndex ); + Private::Block &block = blocks[category]; + block.quarantineStart = block.firstIndex; + } + //END: update the items that are in quarantine in affected categories + + //BEGIN: mark as in quarantine those categories that are under the affected ones + { + const QModelIndex firstIndex = proxyModel->index ( start, q->modelColumn(), parent ); + const QString category = categoryForIndex ( firstIndex ); + const QModelIndex firstAffectedCategory = blocks[category].firstIndex; + //BEGIN: order for marking as alternate those blocks that are alternate + QList blockList = blocks.values(); + qSort ( blockList.begin(), blockList.end(), Block::lessThan ); + QList firstIndexesRows; + foreach ( const Block &block, blockList ) + { + firstIndexesRows << block.firstIndex.row(); + } + //END: order for marking as alternate those blocks that are alternate + for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) + { + Private::Block &block = *it; + if ( block.firstIndex.row() > firstAffectedCategory.row() ) + { + block.outOfQuarantine = false; + block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; + } + else if ( block.firstIndex.row() == firstAffectedCategory.row() ) + { + block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; + } + } + } + //END: mark as in quarantine those categories that are under the affected ones +} + +QRect KCategorizedView::Private::mapToViewport ( const QRect &rect ) const +{ + const int dx = -q->horizontalOffset(); + const int dy = -q->verticalOffset(); + return rect.adjusted ( dx, dy, dx, dy ); +} + +QRect KCategorizedView::Private::mapFromViewport ( const QRect &rect ) const +{ + const int dx = q->horizontalOffset(); + const int dy = q->verticalOffset(); + return rect.adjusted ( dx, dy, dx, dy ); +} + +int KCategorizedView::Private::highestElementInLastRow ( const Block &block ) const +{ + //Find the highest element in the last row + const QModelIndex lastIndex = proxyModel->index ( block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() ); + const QRect prevRect = q->visualRect ( lastIndex ); + int res = prevRect.height(); + QModelIndex prevIndex = proxyModel->index ( lastIndex.row() - 1, q->modelColumn(), q->rootIndex() ); + if ( !prevIndex.isValid() ) + { + return res; + } + Q_FOREVER + { + const QRect tempRect = q->visualRect ( prevIndex ); + if ( tempRect.topLeft().y() < prevRect.topLeft().y() ) + { + break; + } + res = qMax ( res, tempRect.height() ); + if ( prevIndex == block.firstIndex ) + { + break; + } + prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() ); + } + + return res; +} + +bool KCategorizedView::Private::hasGrid() const +{ + const QSize gridSize = q->gridSize(); + return gridSize.isValid() && !gridSize.isNull(); +} + +QString KCategorizedView::Private::categoryForIndex ( const QModelIndex &index ) const +{ + const QModelIndex categoryIndex = index.model()->index ( index.row(), proxyModel->sortColumn(), index.parent() ); + return categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); +} + +void KCategorizedView::Private::leftToRightVisualRect ( const QModelIndex &index, Item &item, + const Block &block, const QPoint &blockPos ) const +{ + const int firstIndexRow = block.firstIndex.row(); + + if ( hasGrid() ) + { + const int relativeRow = index.row() - firstIndexRow; + const int maxItemsPerRow = qMax ( viewportWidth() / q->gridSize().width(), 1 ); + if ( q->layoutDirection() == Qt::LeftToRight ) + { + item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin(); + } + else + { + item.topLeft.rx() = viewportWidth() - ( ( relativeRow % maxItemsPerRow ) + 1 ) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing; + } + item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * q->gridSize().height(); + } + else + { + if ( q->uniformItemSizes() /*|| q->uniformItemWidths()*/ ) + { + const int relativeRow = index.row() - firstIndexRow; + const QSize itemSize = q->sizeHintForIndex ( index ); + //HACK: Why is the -2 needed? + const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() - 2 ) / ( itemSize.width() + q->spacing() ), 1 ); + if ( q->layoutDirection() == Qt::LeftToRight ) + { + item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin(); + } + else + { + item.topLeft.rx() = viewportWidth() - ( relativeRow % maxItemsPerRow ) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing; + } + item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * itemSize.height(); + } + else + { + const QSize currSize = q->sizeHintForIndex ( index ); + if ( index != block.firstIndex ) + { + const int viewportW = viewportWidth() - q->spacing(); + QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() ); + QRect prevRect = q->visualRect ( prevIndex ); + prevRect = mapFromViewport ( prevRect ); + if ( ( prevRect.bottomRight().x() + 1 ) + currSize.width() - blockPos.x() + q->spacing() > viewportW ) + { + // we have to check the whole previous row, and see which one was the + // highest. + Q_FOREVER + { + prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() ); + QRect tempRect = q->visualRect ( prevIndex ); + tempRect = mapFromViewport ( tempRect ); + if ( tempRect.topLeft().y() < prevRect.topLeft().y() ) + { + break; + } + if ( tempRect.bottomRight().y() > prevRect.bottomRight().y() ) + { + prevRect = tempRect; + } + if ( prevIndex == block.firstIndex ) + { + break; + } + } + if ( q->layoutDirection() == Qt::LeftToRight ) + { + item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing(); + } + else + { + item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; + } + item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y(); + } + else + { + if ( q->layoutDirection() == Qt::LeftToRight ) + { + item.topLeft.rx() = ( prevRect.bottomRight().x() + 1 ) + q->spacing(); + } + else + { + item.topLeft.rx() = ( prevRect.bottomLeft().x() - 1 ) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing; + } + item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y(); + } + } + else + { + if ( q->layoutDirection() == Qt::LeftToRight ) + { + item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); + } + else + { + item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; + } + item.topLeft.ry() = q->spacing(); + } + } + } + item.size = q->sizeHintForIndex ( index ); +} + +void KCategorizedView::Private::topToBottomVisualRect ( const QModelIndex &index, Item &item, + const Block &block, const QPoint &blockPos ) const +{ + const int firstIndexRow = block.firstIndex.row(); + + if ( hasGrid() ) + { + const int relativeRow = index.row() - firstIndexRow; + item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); + item.topLeft.ry() = relativeRow * q->gridSize().height(); + } + else + { + if ( q->uniformItemSizes() ) + { + const int relativeRow = index.row() - firstIndexRow; + const QSize itemSize = q->sizeHintForIndex ( index ); + item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); + item.topLeft.ry() = relativeRow * itemSize.height(); + } + else + { + if ( index != block.firstIndex ) + { + QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() ); + QRect prevRect = q->visualRect ( prevIndex ); + prevRect = mapFromViewport ( prevRect ); + item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); + item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y(); + } + else + { + item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); + item.topLeft.ry() = q->spacing(); + } + } + } + item.size = q->sizeHintForIndex ( index ); + item.size.setWidth ( viewportWidth() ); +} + +void KCategorizedView::Private::_k_slotCollapseOrExpandClicked ( QModelIndex ) +{ +} + +//END: Private part + +//BEGIN: Public part + +KCategorizedView::KCategorizedView ( QWidget *parent ) + : QListView ( parent ) + , d ( new Private ( this ) ) +{ +} + +KCategorizedView::~KCategorizedView() +{ + delete d; +} + +void KCategorizedView::setModel ( QAbstractItemModel *model ) +{ + if ( d->proxyModel == model ) + { + return; + } + + d->blocks.clear(); + + if ( d->proxyModel ) + { + disconnect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) ); + } + + d->proxyModel = dynamic_cast ( model ); + + if ( d->proxyModel ) + { + connect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) ); + } + + QListView::setModel ( model ); + + // if the model already had information inserted, update our data structures to it + if ( model->rowCount() ) + { + slotLayoutChanged(); + } +} + + +void KCategorizedView::setUniformItemWidths(bool enable) +{ + d->constantItemWidth = enable; +} + + +bool KCategorizedView::uniformItemWidths() const +{ + return d->constantItemWidth; +} + +void KCategorizedView::setGridSize ( const QSize &size ) +{ + setGridSizeOwn ( size ); +} + +void KCategorizedView::setGridSizeOwn ( const QSize &size ) +{ + d->regenerateAllElements(); + QListView::setGridSize ( size ); +} + +QRect KCategorizedView::visualRect ( const QModelIndex &index ) const +{ + if ( !d->isCategorized() ) + { + return QListView::visualRect ( index ); + } + + if ( !index.isValid() ) + { + return QRect(); + } + + const QString category = d->categoryForIndex ( index ); + + if ( !d->blocks.contains ( category ) ) + { + return QRect(); + } + + Private::Block &block = d->blocks[category]; + const int firstIndexRow = block.firstIndex.row(); + + Q_ASSERT ( block.firstIndex.isValid() ); + + if ( index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count() ) + { + return QRect(); + } + + const QPoint blockPos = d->blockPosition ( category ); + + Private::Item &ritem = block.items[index.row() - firstIndexRow]; + + if ( ritem.topLeft.isNull() || ( block.quarantineStart.isValid() && + index.row() >= block.quarantineStart.row() ) ) + { + if ( flow() == LeftToRight ) + { + d->leftToRightVisualRect ( index, ritem, block, blockPos ); + } + else + { + d->topToBottomVisualRect ( index, ritem, block, blockPos ); + } + + //BEGIN: update the quarantine start + const bool wasLastIndex = ( index.row() == ( block.firstIndex.row() + block.items.count() - 1 ) ); + if ( index.row() == block.quarantineStart.row() ) + { + if ( wasLastIndex ) + { + block.quarantineStart = QModelIndex(); + } + else + { + const QModelIndex nextIndex = d->proxyModel->index ( index.row() + 1, modelColumn(), rootIndex() ); + block.quarantineStart = nextIndex; + } + } + //END: update the quarantine start + } + + // we get now the absolute position through the relative position of the parent block. do not + // save this on ritem, since this would override the item relative position in block terms. + Private::Item item ( ritem ); + item.topLeft.ry() += blockPos.y(); + + const QSize sizeHint = item.size; + + if ( d->hasGrid() ) + { + const QSize sizeGrid = gridSize(); + const QSize resultingSize = sizeHint.boundedTo ( sizeGrid ); + QRect res ( item.topLeft.x() + ( ( sizeGrid.width() - resultingSize.width() ) / 2 ), + item.topLeft.y(), resultingSize.width(), resultingSize.height() ); + if ( block.collapsed ) + { + // we can still do binary search, while we "hide" items. We move those items in collapsed + // blocks to the left and set a 0 height. + res.setLeft ( -resultingSize.width() ); + res.setHeight ( 0 ); + } + return d->mapToViewport ( res ); + } + + QRect res ( item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height() ); + if ( block.collapsed ) + { + // we can still do binary search, while we "hide" items. We move those items in collapsed + // blocks to the left and set a 0 height. + res.setLeft ( -sizeHint.width() ); + res.setHeight ( 0 ); + } + return d->mapToViewport ( res ); +} + +KCategoryDrawer *KCategorizedView::categoryDrawer() const +{ + return d->categoryDrawer; +} + +void KCategorizedView::setCategoryDrawer ( KCategoryDrawer *categoryDrawer ) +{ + disconnect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ), + this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) ); + d->categoryDrawer = categoryDrawer; + + connect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ), + this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) ); +} + +int KCategorizedView::categorySpacing() const +{ + return d->categorySpacing; +} + +void KCategorizedView::setCategorySpacing ( int categorySpacing ) +{ + if ( d->categorySpacing == categorySpacing ) + { + return; + } + + d->categorySpacing = categorySpacing; + + for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) + { + Private::Block &block = *it; + block.outOfQuarantine = false; + } +} + +bool KCategorizedView::alternatingBlockColors() const +{ + return d->alternatingBlockColors; +} + +void KCategorizedView::setAlternatingBlockColors ( bool enable ) +{ + d->alternatingBlockColors = enable; +} + +bool KCategorizedView::collapsibleBlocks() const +{ + return d->collapsibleBlocks; +} + +void KCategorizedView::setCollapsibleBlocks ( bool enable ) +{ + d->collapsibleBlocks = enable; +} + +QModelIndexList KCategorizedView::block ( const QString &category ) +{ + QModelIndexList res; + const Private::Block &block = d->blocks[category]; + if ( block.height == -1 ) + { + return res; + } + QModelIndex current = block.firstIndex; + const int first = current.row(); + for ( int i = 1; i <= block.items.count(); ++i ) + { + if ( current.isValid() ) + { + res << current; + } + current = d->proxyModel->index ( first + i, modelColumn(), rootIndex() ); + } + return res; +} + +QModelIndexList KCategorizedView::block ( const QModelIndex &representative ) +{ + return block ( representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString() ); +} + +QModelIndex KCategorizedView::indexAt ( const QPoint &point ) const +{ + if ( !d->isCategorized() ) + { + return QListView::indexAt ( point ); + } + + const int rowCount = d->proxyModel->rowCount(); + if ( !rowCount ) + { + return QModelIndex(); + } + + // Binary search that will try to spot if there is an index under point + int bottom = 0; + int top = rowCount - 1; + while ( bottom <= top ) + { + const int middle = ( bottom + top ) / 2; + const QModelIndex index = d->proxyModel->index ( middle, modelColumn(), rootIndex() ); + QRect rect = visualRect ( index ); + const int verticalOff = verticalOffset(); + int horizontalOff = horizontalOffset(); + if ( layoutDirection() == Qt::RightToLeft ) + { + horizontalOff *= -1; + } + rect.topLeft().ry() += verticalOff; + rect.topLeft().rx() += horizontalOff; + rect.bottomRight().ry() += verticalOff; + rect.bottomRight().rx() += horizontalOff; + if ( rect.contains ( point ) ) + { + if ( index.model()->flags ( index ) & Qt::ItemIsEnabled ) + { + return index; + } + return QModelIndex(); + } + bool directionCondition; + if ( layoutDirection() == Qt::LeftToRight ) + { + directionCondition = point.x() > rect.bottomRight().x(); + } + else + { + directionCondition = point.x() < rect.bottomLeft().x(); + } + if ( point.y() > rect.bottomRight().y() || + ( point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition ) ) + { + bottom = middle + 1; + } + else + { + top = middle - 1; + } + } + return QModelIndex(); +} + +void KCategorizedView::reset() +{ + d->blocks.clear(); + QListView::reset(); +} + +void KCategorizedView::paintEvent ( QPaintEvent *event ) +{ + if ( !d->isCategorized() ) + { + QListView::paintEvent ( event ); + return; + } + + const QPair intersecting = d->intersectingIndexesWithRect ( viewport()->rect().intersected ( event->rect() ) ); + + QPainter p ( viewport() ); + p.save(); + + Q_ASSERT ( selectionModel()->model() == d->proxyModel ); + + //BEGIN: draw categories + QHash::ConstIterator it ( d->blocks.constBegin() ); + while ( it != d->blocks.constEnd() ) + { + const Private::Block &block = *it; + const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); + QStyleOptionViewItemV4 option ( viewOptions() ); + option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate + : QStyleOptionViewItemV4::None; + option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open + : QStyle::State_None; + const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); + QPoint pos = d->blockPosition ( it.key() ); + pos.ry() -= height; + option.rect.setTopLeft ( pos ); + option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); + option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); + option.rect = d->mapToViewport ( option.rect ); + if ( !option.rect.intersects ( viewport()->rect() ) ) + { + ++it; + continue; + } + d->categoryDrawer->drawCategory ( categoryIndex, d->proxyModel->sortRole(), option, &p ); + ++it; + } + //END: draw categories + + if ( intersecting.first.isValid() && intersecting.second.isValid() ) + { + //BEGIN: draw items + int i = intersecting.first.row(); + int indexToCheckIfBlockCollapsed = i; + QModelIndex categoryIndex; + QString category; + Private::Block *block = 0; + while ( i <= intersecting.second.row() ) + { + //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting + if ( i == indexToCheckIfBlockCollapsed ) + { + categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); + category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); + block = &d->blocks[category]; + indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count(); + if ( block->collapsed ) + { + i = indexToCheckIfBlockCollapsed; + continue; + } + } + //END: first check if the block is collapsed. if so, we have to skip the item painting + + Q_ASSERT ( block ); + + const bool alternateItem = ( i - block->firstIndex.row() ) % 2; + + const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); + const Qt::ItemFlags flags = d->proxyModel->flags ( index ); + QStyleOptionViewItemV4 option ( viewOptions() ); + option.rect = visualRect ( index ); + option.widget = this; + option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText + : QStyleOptionViewItemV2::None; + option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate + : QStyleOptionViewItemV4::None; + if ( flags & Qt::ItemIsSelectable ) + { + option.state |= selectionModel()->isSelected ( index ) ? QStyle::State_Selected + : QStyle::State_None; + } + else + { + option.state &= ~QStyle::State_Selected; + } + option.state |= ( index == currentIndex() ) ? QStyle::State_HasFocus + : QStyle::State_None; + if ( ! ( flags & Qt::ItemIsEnabled ) ) + { + option.state &= ~QStyle::State_Enabled; + } + else + { + option.state |= ( index == d->hoveredIndex ) ? QStyle::State_MouseOver + : QStyle::State_None; + } + + itemDelegate ( index )->paint ( &p, option, index ); + ++i; + } + //END: draw items + } + + //BEGIN: draw selection rect + if ( isSelectionRectVisible() && d->rubberBandRect.isValid() ) + { + QStyleOptionRubberBand opt; + opt.initFrom ( this ); + opt.shape = QRubberBand::Rectangle; + opt.opaque = false; + opt.rect = d->mapToViewport ( d->rubberBandRect ).intersected ( viewport()->rect().adjusted ( -16, -16, 16, 16 ) ); + p.save(); + style()->drawControl ( QStyle::CE_RubberBand, &opt, &p ); + p.restore(); + } + //END: draw selection rect + + p.restore(); +} + +void KCategorizedView::resizeEvent ( QResizeEvent *event ) +{ + d->regenerateAllElements(); + QListView::resizeEvent ( event ); +} + +void KCategorizedView::setSelection ( const QRect &rect, + QItemSelectionModel::SelectionFlags flags ) +{ + if ( !d->isCategorized() ) + { + QListView::setSelection ( rect, flags ); + return; + } + + if ( rect.topLeft() == rect.bottomRight() ) + { + const QModelIndex index = indexAt ( rect.topLeft() ); + selectionModel()->select ( index, flags ); + return; + } + + const QPair intersecting = d->intersectingIndexesWithRect ( rect ); + + QItemSelection selection; + + //TODO: think of a faster implementation + QModelIndex firstIndex; + QModelIndex lastIndex; + for ( int i = intersecting.first.row(); i <= intersecting.second.row(); ++i ) + { + const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); + const bool visualRectIntersects = visualRect ( index ).intersects ( rect ); + if ( firstIndex.isValid() ) + { + if ( visualRectIntersects ) + { + lastIndex = index; + } + else + { + selection << QItemSelectionRange ( firstIndex, lastIndex ); + firstIndex = QModelIndex(); + } + } + else if ( visualRectIntersects ) + { + firstIndex = index; + lastIndex = index; + } + } + + if ( firstIndex.isValid() ) + { + selection << QItemSelectionRange ( firstIndex, lastIndex ); + } + + selectionModel()->select ( selection, flags ); +} + +void KCategorizedView::mouseMoveEvent ( QMouseEvent *event ) +{ + QListView::mouseMoveEvent ( event ); + d->hoveredIndex = indexAt ( event->pos() ); + const SelectionMode itemViewSelectionMode = selectionMode(); + if ( state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection + && itemViewSelectionMode != NoSelection ) + { + QRect rect ( d->pressedPosition, event->pos() + QPoint ( horizontalOffset(), verticalOffset() ) ); + rect = rect.normalized(); + update ( rect.united ( d->rubberBandRect ) ); + d->rubberBandRect = rect; + } + QHash::ConstIterator it ( d->blocks.constBegin() ); + while ( it != d->blocks.constEnd() ) + { + const Private::Block &block = *it; + const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); + QStyleOptionViewItemV4 option ( viewOptions() ); + const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); + QPoint pos = d->blockPosition ( it.key() ); + pos.ry() -= height; + option.rect.setTopLeft ( pos ); + option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); + option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); + option.rect = d->mapToViewport ( option.rect ); + const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); + if ( option.rect.contains ( mousePos ) ) + { + if ( d->categoryDrawer && d->hoveredBlock->height != -1 && *d->hoveredBlock != block ) + { + const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); + const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); + d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); + *d->hoveredBlock = block; + d->hoveredCategory = it.key(); + viewport()->update ( option.rect ); + } + else if ( d->hoveredBlock->height == -1 ) + { + *d->hoveredBlock = block; + d->hoveredCategory = it.key(); + } + else if ( d->categoryDrawer ) + { + d->categoryDrawer->mouseMoved ( categoryIndex, option.rect, event ); + } + viewport()->update ( option.rect ); + return; + } + ++it; + } + if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) + { + const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); + const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); + d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); + *d->hoveredBlock = Private::Block(); + d->hoveredCategory = QString(); + viewport()->update ( option.rect ); + } +} + +void KCategorizedView::mousePressEvent ( QMouseEvent *event ) +{ + if ( event->button() == Qt::LeftButton ) + { + d->pressedPosition = event->pos(); + d->pressedPosition.rx() += horizontalOffset(); + d->pressedPosition.ry() += verticalOffset(); + } + if ( !d->categoryDrawer ) + { + QListView::mousePressEvent ( event ); + return; + } + QHash::ConstIterator it ( d->blocks.constBegin() ); + while ( it != d->blocks.constEnd() ) + { + const Private::Block &block = *it; + const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); + const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); + const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); + if ( option.rect.contains ( mousePos ) ) + { + if ( d->categoryDrawer ) + { + d->categoryDrawer->mouseButtonPressed ( categoryIndex, option.rect, event ); + } + viewport()->update ( option.rect ); + if ( !event->isAccepted() ) + { + QListView::mousePressEvent ( event ); + } + return; + } + ++it; + } + QListView::mousePressEvent ( event ); +} + +void KCategorizedView::mouseReleaseEvent ( QMouseEvent *event ) +{ + d->pressedPosition = QPoint(); + d->rubberBandRect = QRect(); + QHash::ConstIterator it ( d->blocks.constBegin() ); + while ( it != d->blocks.constEnd() ) + { + const Private::Block &block = *it; + const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); + const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); + const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); + if ( option.rect.contains ( mousePos ) ) + { + if ( d->categoryDrawer ) + { + d->categoryDrawer->mouseButtonReleased ( categoryIndex, option.rect, event ); + } + viewport()->update ( option.rect ); + if ( !event->isAccepted() ) + { + QListView::mouseReleaseEvent ( event ); + } + return; + } + ++it; + } + QListView::mouseReleaseEvent ( event ); +} + +void KCategorizedView::leaveEvent ( QEvent *event ) +{ + QListView::leaveEvent ( event ); + if ( d->hoveredIndex.isValid() ) + { + viewport()->update ( visualRect ( d->hoveredIndex ) ); + d->hoveredIndex = QModelIndex(); + } + if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) + { + const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); + const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); + d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); + *d->hoveredBlock = Private::Block(); + d->hoveredCategory = QString(); + viewport()->update ( option.rect ); + } +} + +void KCategorizedView::startDrag ( Qt::DropActions supportedActions ) +{ + QListView::startDrag ( supportedActions ); +} + +void KCategorizedView::dragMoveEvent ( QDragMoveEvent *event ) +{ + QListView::dragMoveEvent ( event ); + d->hoveredIndex = indexAt ( event->pos() ); +} + +void KCategorizedView::dragEnterEvent ( QDragEnterEvent *event ) +{ + QListView::dragEnterEvent ( event ); +} + +void KCategorizedView::dragLeaveEvent ( QDragLeaveEvent *event ) +{ + QListView::dragLeaveEvent ( event ); +} + +void KCategorizedView::dropEvent ( QDropEvent *event ) +{ + QListView::dropEvent ( event ); +} + +//TODO: improve se we take into account collapsed blocks +//TODO: take into account when there is no grid and no uniformItemSizes +QModelIndex KCategorizedView::moveCursor ( CursorAction cursorAction, + Qt::KeyboardModifiers modifiers ) +{ + if ( !d->isCategorized() ) + { + return QListView::moveCursor ( cursorAction, modifiers ); + } + + const QModelIndex current = currentIndex(); + const QRect currentRect = visualRect ( current ); + if ( !current.isValid() ) + { + const int rowCount = d->proxyModel->rowCount ( rootIndex() ); + if ( !rowCount ) + { + return QModelIndex(); + } + return d->proxyModel->index ( 0, modelColumn(), rootIndex() ); + } + + switch ( cursorAction ) + { + case MoveLeft: + { + if ( !current.row() ) + { + return QModelIndex(); + } + const QModelIndex previous = d->proxyModel->index ( current.row() - 1, modelColumn(), rootIndex() ); + const QRect previousRect = visualRect ( previous ); + if ( previousRect.top() == currentRect.top() ) + { + return previous; + } + + return QModelIndex(); + } + case MoveRight: + { + if ( current.row() == d->proxyModel->rowCount() - 1 ) + { + return QModelIndex(); + } + const QModelIndex next = d->proxyModel->index ( current.row() + 1, modelColumn(), rootIndex() ); + const QRect nextRect = visualRect ( next ); + if ( nextRect.top() == currentRect.top() ) + { + return next; + } + + return QModelIndex(); + } + case MoveDown: + { + if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) + { + const QModelIndex current = currentIndex(); + const QSize itemSize = d->hasGrid() ? gridSize() + : sizeHintForIndex ( current ); + const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; + //HACK: Why is the -2 needed? + const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); + const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() + + block.items.count(); + + if ( canMove ) + { + return d->proxyModel->index ( current.row() + maxItemsPerRow, modelColumn(), rootIndex() ); + } + + const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; + const QModelIndex nextIndex = d->proxyModel->index ( block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex() ); + + if ( !nextIndex.isValid() ) + { + return QModelIndex(); + } + + const Private::Block &nextBlock = d->blocks[d->categoryForIndex ( nextIndex )]; + + if ( nextBlock.items.count() <= currentRelativePos ) + { + return QModelIndex(); + } + + if ( currentRelativePos < ( block.items.count() % maxItemsPerRow ) ) + { + return d->proxyModel->index ( nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex() ); + } + + return QModelIndex(); + } + } + case MoveUp: + { + if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) + { + const QModelIndex current = currentIndex(); + const QSize itemSize = d->hasGrid() ? gridSize() + : sizeHintForIndex ( current ); + const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; + //HACK: Why is the -2 needed? + const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); + const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row(); + + if ( canMove ) + { + return d->proxyModel->index ( current.row() - maxItemsPerRow, modelColumn(), rootIndex() ); + } + + const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; + const QModelIndex prevIndex = d->proxyModel->index ( block.firstIndex.row() - 1, modelColumn(), rootIndex() ); + + if ( !prevIndex.isValid() ) + { + return QModelIndex(); + } + + const Private::Block &prevBlock = d->blocks[d->categoryForIndex ( prevIndex )]; + + if ( prevBlock.items.count() <= currentRelativePos ) + { + return QModelIndex(); + } + + const int remainder = prevBlock.items.count() % maxItemsPerRow; + if ( currentRelativePos < remainder ) + { + return d->proxyModel->index ( prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex() ); + } + + return QModelIndex(); + } + } + default: + break; + } + + return QModelIndex(); +} + +void KCategorizedView::rowsAboutToBeRemoved ( const QModelIndex &parent, + int start, + int end ) +{ + if ( !d->isCategorized() ) + { + QListView::rowsAboutToBeRemoved ( parent, start, end ); + return; + } + + *d->hoveredBlock = Private::Block(); + d->hoveredCategory = QString(); + + if ( end - start + 1 == d->proxyModel->rowCount() ) + { + d->blocks.clear(); + QListView::rowsAboutToBeRemoved ( parent, start, end ); + return; + } + + // Removal feels a bit more complicated than insertion. Basically we can consider there are + // 3 different cases when going to remove items. (*) represents an item, Items between ([) and + // (]) are the ones which are marked for removal. + // + // - 1st case: + // ... * * * * * * [ * * * ... + // + // The items marked for removal are the last part of this category. No need to mark any item + // of this category as in quarantine, because no special offset will be pushed to items at + // the right because of any changes (since the removed items are those on the right most part + // of the category). + // + // - 2nd case: + // ... * * * * * * ] * * * ... + // + // The items marked for removal are the first part of this category. We have to mark as in + // quarantine all items in this category. Absolutely all. All items will have to be moved to + // the left (or moving up, because rows got a different offset). + // + // - 3rd case: + // ... * * [ * * * * ] * * ... + // + // The items marked for removal are in between of this category. We have to mark as in + // quarantine only those items that are at the right of the end of the removal interval, + // (starting on "]"). + // + // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are + // located under the top most affected category as in quarantine (the block itself, as a whole), + // because such a change can force it to have a different offset (note that items themselves + // contain relative positions to the block, so marking the block as in quarantine is enough). + // + // Also note that removal implicitly means that we have to update correctly firstIndex of each + // block, and in general keep updated the internal information of elements. + + QStringList listOfCategoriesMarkedForRemoval; + + QString lastCategory; + int alreadyRemoved = 0; + for ( int i = start; i <= end; ++i ) + { + const QModelIndex index = d->proxyModel->index ( i, modelColumn(), parent ); + + Q_ASSERT ( index.isValid() ); + + const QString category = d->categoryForIndex ( index ); + + if ( lastCategory != category ) + { + lastCategory = category; + alreadyRemoved = 0; + } + + Private::Block &block = d->blocks[category]; + block.items.removeAt ( i - block.firstIndex.row() - alreadyRemoved ); + ++alreadyRemoved; + + if ( !block.items.count() ) + { + listOfCategoriesMarkedForRemoval << category; + } + + block.height = -1; + + viewport()->update(); + } + + //BEGIN: update the items that are in quarantine in affected categories + { + const QModelIndex lastIndex = d->proxyModel->index ( end, modelColumn(), parent ); + const QString category = d->categoryForIndex ( lastIndex ); + Private::Block &block = d->blocks[category]; + if ( block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row() ) + { + block.firstIndex = d->proxyModel->index ( end + 1, modelColumn(), parent ); + } + block.quarantineStart = block.firstIndex; + } + //END: update the items that are in quarantine in affected categories + + Q_FOREACH ( const QString &category, listOfCategoriesMarkedForRemoval ) + { + d->blocks.remove ( category ); + } + + //BEGIN: mark as in quarantine those categories that are under the affected ones + { + //BEGIN: order for marking as alternate those blocks that are alternate + QList blockList = d->blocks.values(); + qSort ( blockList.begin(), blockList.end(), Private::Block::lessThan ); + QList firstIndexesRows; + foreach ( const Private::Block &block, blockList ) + { + firstIndexesRows << block.firstIndex.row(); + } + //END: order for marking as alternate those blocks that are alternate + for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) + { + Private::Block &block = *it; + if ( block.firstIndex.row() > start ) + { + block.outOfQuarantine = false; + block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; + } + else if ( block.firstIndex.row() == start ) + { + block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; + } + } + } + //END: mark as in quarantine those categories that are under the affected ones + + QListView::rowsAboutToBeRemoved ( parent, start, end ); +} + +void KCategorizedView::updateGeometries() +{ + const int oldVerticalOffset = verticalOffset(); + const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy(); + + //BEGIN bugs 213068, 287847 ------------------------------------------------------------ + /* + * QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not + * and triggers a (sometimes additionally timered) resize through ::layoutChildren() + * http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499 + * (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy) + * + * As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI + * So we prevent QListView from having an own opinion on the scrollbar visibility by + * fixing it before calling the baseclass QListView::updateGeometries() + * + * Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded + * we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself. + */ + if ( d->isCategorized() ) // important! - otherwise we'd pollute the setting if the view is initially not categorized + { + setVerticalScrollBarPolicy ( ( verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo ( this ) ) ? + Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); + setHorizontalScrollBarPolicy ( ( horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo ( this ) ) ? + Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); + } + //END bugs 213068, 287847 -------------------------------------------------------------- + + QListView::updateGeometries(); + + if ( !d->isCategorized() ) + { + return; + } + + const int rowCount = d->proxyModel->rowCount(); + if ( !rowCount ) + { + verticalScrollBar()->setRange ( 0, 0 ); + // unconditional, see function end todo + horizontalScrollBar()->setRange ( 0, 0 ); + return; + } + + const QModelIndex lastIndex = d->proxyModel->index ( rowCount - 1, modelColumn(), rootIndex() ); + Q_ASSERT ( lastIndex.isValid() ); + QRect lastItemRect = visualRect ( lastIndex ); + + if ( d->hasGrid() ) + { + lastItemRect.setSize ( lastItemRect.size().expandedTo ( gridSize() ) ); + } + else + { + if ( uniformItemSizes() ) + { + QSize itemSize = sizeHintForIndex ( lastIndex ); + itemSize.setHeight ( itemSize.height() + spacing() ); + lastItemRect.setSize ( itemSize ); + } + else + { + QSize itemSize = sizeHintForIndex ( lastIndex ); + const QString category = d->categoryForIndex ( lastIndex ); + itemSize.setHeight ( d->highestElementInLastRow ( d->blocks[category] ) + spacing() ); + lastItemRect.setSize ( itemSize ); + } + } + + const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height(); + + if ( verticalScrollMode() == ScrollPerItem ) + { + verticalScrollBar()->setSingleStep ( lastItemRect.height() ); + const int rowsPerPage = qMax ( viewport()->height() / lastItemRect.height(), 1 ); + verticalScrollBar()->setPageStep ( rowsPerPage * lastItemRect.height() ); + } + + verticalScrollBar()->setRange ( 0, bottomRange ); + verticalScrollBar()->setValue ( oldVerticalOffset ); + + //TODO: also consider working with the horizontal scroll bar. since at this level I am not still + // supporting "top to bottom" flow, there is no real problem. If I support that someday + // (think how to draw categories), we would have to take care of the horizontal scroll bar too. + // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar. + horizontalScrollBar()->setRange ( 0, 0 ); + + //BEGIN bugs 213068, 287847 ------------------------------------------------------------ + // restoring values from above ... + setVerticalScrollBarPolicy ( verticalP ); + setHorizontalScrollBarPolicy ( horizontalP ); + // ... and correct the visibility + bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum(); + if ( verticalP == Qt::ScrollBarAsNeeded && ( verticalScrollBar()->isVisibleTo ( this ) != validRange ) ) + verticalScrollBar()->setVisible ( validRange ); + validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum(); + if ( horizontalP == Qt::ScrollBarAsNeeded && ( horizontalScrollBar()->isVisibleTo ( this ) != validRange ) ) + horizontalScrollBar()->setVisible ( validRange ); + //END bugs 213068, 287847 -------------------------------------------------------------- +} + +void KCategorizedView::currentChanged ( const QModelIndex ¤t, + const QModelIndex &previous ) +{ + QListView::currentChanged ( current, previous ); +} + +void KCategorizedView::dataChanged ( const QModelIndex &topLeft, + const QModelIndex &bottomRight ) +{ + QListView::dataChanged ( topLeft, bottomRight ); + if ( !d->isCategorized() ) + { + return; + } + + *d->hoveredBlock = Private::Block(); + d->hoveredCategory = QString(); + + //BEGIN: since the model changed data, we need to reconsider item sizes + int i = topLeft.row(); + int indexToCheck = i; + QModelIndex categoryIndex; + QString category; + Private::Block *block; + while ( i <= bottomRight.row() ) + { + const QModelIndex currIndex = d->proxyModel->index ( i, modelColumn(), rootIndex() ); + if ( i == indexToCheck ) + { + categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); + category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); + block = &d->blocks[category]; + block->quarantineStart = currIndex; + indexToCheck = block->firstIndex.row() + block->items.count(); + } + visualRect ( currIndex ); + ++i; + } + //END: since the model changed data, we need to reconsider item sizes +} + +void KCategorizedView::rowsInserted ( const QModelIndex &parent, + int start, + int end ) +{ + QListView::rowsInserted ( parent, start, end ); + if ( !d->isCategorized() ) + { + return; + } + + *d->hoveredBlock = Private::Block(); + d->hoveredCategory = QString(); + d->rowsInserted ( parent, start, end ); +} + +void KCategorizedView::slotLayoutChanged() +{ + if ( !d->isCategorized() ) + { + return; + } + + d->blocks.clear(); + *d->hoveredBlock = Private::Block(); + d->hoveredCategory = QString(); + if ( d->proxyModel->rowCount() ) + { + d->rowsInserted ( rootIndex(), 0, d->proxyModel->rowCount() - 1 ); + } +} +//END: Public part + +void KCategorizedView::slotCollapseOrExpandClicked ( QModelIndex idx ) +{ + d->_k_slotCollapseOrExpandClicked ( idx ); +} + + +#include "categorizedview.moc" diff --git a/libgroupview/src/categorizedview_p.h b/libgroupview/src/categorizedview_p.h new file mode 100644 index 00000000..13809312 --- /dev/null +++ b/libgroupview/src/categorizedview_p.h @@ -0,0 +1,159 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007, 2009 Rafael Fernández López + * + * 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 KCATEGORIZEDVIEW_P_H +#define KCATEGORIZEDVIEW_P_H + +class KCategorizedSortFilterProxyModel; +class KCategoryDrawer; +class KCategoryDrawerV2; +class KCategoryDrawerV3; + +/** + * @internal + */ +class KCategorizedView::Private +{ +public: + struct Block; + struct Item; + + Private(KCategorizedView *q); + ~Private(); + + /** + * @return whether this view has all required elements to be categorized. + */ + bool isCategorized() const; + + /** + * @return the block rect for the representative @p representative. + */ + QStyleOptionViewItemV4 blockRect(const QModelIndex &representative); + + /** + * Returns the first and last element that intersects with rect. + * + * @note see that here we cannot take out items between first and last (as we could + * do with the rubberband). + * + * Complexity: O(log(n)) where n is model()->rowCount(). + */ + QPair intersectingIndexesWithRect(const QRect &rect) const; + + /** + * Returns the position of the block of @p category. + * + * Complexity: O(n) where n is the number of different categories when the block has been + * marked as in quarantine. O(1) the rest of the times (the vast majority). + */ + QPoint blockPosition(const QString &category); + + /** + * Returns the height of the block determined by @p category. + */ + int blockHeight(const QString &category); + + /** + * Returns the actual viewport width. + */ + int viewportWidth() const; + + /** + * Marks all elements as in quarantine. + * + * Complexity: O(n) where n is model()->rowCount(). + * + * @warning this is an expensive operation + */ + void regenerateAllElements(); + + /** + * Update internal information, and keep sync with the real information that the model contains. + */ + void rowsInserted(const QModelIndex &parent, int start, int end); + + /** + * Returns @p rect in viewport terms, taking in count horizontal and vertical offsets. + */ + QRect mapToViewport(const QRect &rect) const; + + /** + * Returns @p rect in absolute terms, converted from viewport position. + */ + QRect mapFromViewport(const QRect &rect) const; + + /** + * Returns the height of the highest element in last row. This is only applicable if there is + * no grid set and uniformItemSizes is false. + * + * @param block in which block are we searching. Necessary to stop the search if we hit the + * first item in this block. + */ + int highestElementInLastRow(const Block &block) const; + + /** + * Returns whether the view has a valid grid size. + */ + bool hasGrid() const; + + /** + * Returns the category for the given index. + */ + QString categoryForIndex(const QModelIndex &index) const; + + /** + * Updates the visual rect for item when flow is LeftToRight. + */ + void leftToRightVisualRect(const QModelIndex &index, Item &item, + const Block &block, const QPoint &blockPos) const; + + /** + * Updates the visual rect for item when flow is TopToBottom. + * @note we only support viewMode == ListMode in this case. + */ + void topToBottomVisualRect(const QModelIndex &index, Item &item, + const Block &block, const QPoint &blockPos) const; + + /** + * Called when expand or collapse has been clicked on the category drawer. + */ + void _k_slotCollapseOrExpandClicked(QModelIndex); + + KCategorizedView *q; + KCategorizedSortFilterProxyModel *proxyModel; + KCategoryDrawer *categoryDrawer; + int categorySpacing; + bool alternatingBlockColors; + bool collapsibleBlocks; + bool constantItemWidth; + + Block *hoveredBlock; + QString hoveredCategory; + QModelIndex hoveredIndex; + + QPoint pressedPosition; + QRect rubberBandRect; + + QHash blocks; +}; + +#endif // KCATEGORIZEDVIEW_P_H + diff --git a/libgroupview/src/categorydrawer.cpp b/libgroupview/src/categorydrawer.cpp new file mode 100644 index 00000000..04903206 --- /dev/null +++ b/libgroupview/src/categorydrawer.cpp @@ -0,0 +1,231 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007, 2009 Rafael Fernández López + * + * 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. + */ + +#include "categorydrawer.h" + +#include +#include +#include + +#include +#include + +#define HORIZONTAL_HINT 3 + +class KCategoryDrawer::Private +{ +public: + Private(KCategorizedView *view) + : view(view) + , leftMargin(0) + , rightMargin(0) + { + } + + ~Private() + { + } + int leftMargin; + int rightMargin; + KCategorizedView *view; +}; + +KCategoryDrawer::KCategoryDrawer(KCategorizedView *view) + : QObject(view) + , d(new Private(view)) +{ + setLeftMargin(2); + setRightMargin(2); +} + +KCategoryDrawer::~KCategoryDrawer() +{ + delete d; +} + + +void KCategoryDrawer::drawCategory(const QModelIndex &index, + int /*sortRole*/, + const QStyleOption &option, + QPainter *painter) const +{ + painter->setRenderHint(QPainter::Antialiasing); + + const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); + const QRect optRect = option.rect; + QFont font(QApplication::font()); + font.setBold(true); + const QFontMetrics fontMetrics = QFontMetrics(font); + + QColor outlineColor = option.palette.text().color(); + outlineColor.setAlphaF(0.35); + + //BEGIN: top left corner + { + painter->save(); + painter->setPen(outlineColor); + const QPointF topLeft(optRect.topLeft()); + QRectF arc(topLeft, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 1440, 1440); + painter->restore(); + } + //END: top left corner + + //BEGIN: left vertical line + { + QPoint start(optRect.topLeft()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topLeft()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: left vertical line + + //BEGIN: horizontal line + { + QPoint start(optRect.topLeft()); + start.rx() += 3; + QPoint horizontalGradTop(optRect.topLeft()); + horizontalGradTop.rx() += optRect.width() - 6; + painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); + } + //END: horizontal line + + //BEGIN: top right corner + { + painter->save(); + painter->setPen(outlineColor); + QPointF topRight(optRect.topRight()); + topRight.rx() -= 4; + QRectF arc(topRight, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 0, 1440); + painter->restore(); + } + //END: top right corner + + //BEGIN: right vertical line + { + QPoint start(optRect.topRight()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topRight()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: right vertical line + + //BEGIN: text + { + QRect textRect(option.rect); + textRect.setTop(textRect.top() + 7); + textRect.setLeft(textRect.left() + 7); + textRect.setHeight(fontMetrics.height()); + textRect.setRight(textRect.right() - 7); + + painter->save(); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, category); + painter->restore(); + } + //END: text +} + +int KCategoryDrawer::categoryHeight(const QModelIndex &index, const QStyleOption &option) const +{ + Q_UNUSED(index); + Q_UNUSED(option) + + QFont font(QApplication::font()); + font.setBold(true); + QFontMetrics fontMetrics(font); + + const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ + + 11 /* top and bottom separation */; + return height; +} + +int KCategoryDrawer::leftMargin() const +{ + return d->leftMargin; +} + +void KCategoryDrawer::setLeftMargin(int leftMargin) +{ + d->leftMargin = leftMargin; +} + +int KCategoryDrawer::rightMargin() const +{ + return d->rightMargin; +} + +void KCategoryDrawer::setRightMargin(int rightMargin) +{ + d->rightMargin = rightMargin; +} + +KCategoryDrawer &KCategoryDrawer::operator=(const KCategoryDrawer &cd) +{ + d->leftMargin = cd.d->leftMargin; + d->rightMargin = cd.d->rightMargin; + d->view = cd.d->view; + return *this; +} + +KCategorizedView *KCategoryDrawer::view() const +{ + return d->view; +} + +void KCategoryDrawer::mouseButtonPressed(const QModelIndex&, const QRect&, QMouseEvent *event) +{ + event->ignore(); +} + +void KCategoryDrawer::mouseButtonReleased(const QModelIndex&, const QRect&, QMouseEvent *event) +{ + event->ignore(); +} + +void KCategoryDrawer::mouseMoved(const QModelIndex&, const QRect&, QMouseEvent *event) +{ + event->ignore(); +} + +void KCategoryDrawer::mouseButtonDoubleClicked(const QModelIndex&, const QRect&, QMouseEvent *event) +{ + event->ignore(); +} + +void KCategoryDrawer::mouseLeft(const QModelIndex&, const QRect&) +{ +} + +#include "categorydrawer.moc" diff --git a/libgroupview/src/kcategorizedsortfilterproxymodel.cpp b/libgroupview/src/kcategorizedsortfilterproxymodel.cpp deleted file mode 100644 index 46a845e0..00000000 --- a/libgroupview/src/kcategorizedsortfilterproxymodel.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * 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. - */ - -#include "kcategorizedsortfilterproxymodel.h" -#include "kcategorizedsortfilterproxymodel_p.h" - -#include - -#include -#include -#include - -KCategorizedSortFilterProxyModel::KCategorizedSortFilterProxyModel ( QObject *parent ) - : QSortFilterProxyModel ( parent ) - , d ( new Private() ) -{ -} - -KCategorizedSortFilterProxyModel::~KCategorizedSortFilterProxyModel() -{ - delete d; -} - -void KCategorizedSortFilterProxyModel::sort ( int column, Qt::SortOrder order ) -{ - d->sortColumn = column; - d->sortOrder = order; - - QSortFilterProxyModel::sort ( column, order ); -} - -bool KCategorizedSortFilterProxyModel::isCategorizedModel() const -{ - return d->categorizedModel; -} - -void KCategorizedSortFilterProxyModel::setCategorizedModel ( bool categorizedModel ) -{ - if ( categorizedModel == d->categorizedModel ) - { - return; - } - - d->categorizedModel = categorizedModel; - - invalidate(); -} - -int KCategorizedSortFilterProxyModel::sortColumn() const -{ - return d->sortColumn; -} - -Qt::SortOrder KCategorizedSortFilterProxyModel::sortOrder() const -{ - return d->sortOrder; -} - -void KCategorizedSortFilterProxyModel::setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison ) -{ - if ( sortCategoriesByNaturalComparison == d->sortCategoriesByNaturalComparison ) - { - return; - } - - d->sortCategoriesByNaturalComparison = sortCategoriesByNaturalComparison; - - invalidate(); -} - -bool KCategorizedSortFilterProxyModel::sortCategoriesByNaturalComparison() const -{ - return d->sortCategoriesByNaturalComparison; -} - -bool KCategorizedSortFilterProxyModel::lessThan ( const QModelIndex &left, const QModelIndex &right ) const -{ - if ( d->categorizedModel ) - { - int compare = compareCategories ( left, right ); - - if ( compare > 0 ) // left is greater than right - { - return false; - } - else if ( compare < 0 ) // left is less than right - { - return true; - } - } - - return subSortLessThan ( left, right ); -} - -bool KCategorizedSortFilterProxyModel::subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const -{ - return QSortFilterProxyModel::lessThan ( left, right ); -} - -int KCategorizedSortFilterProxyModel::compareCategories ( const QModelIndex &left, const QModelIndex &right ) const -{ - QVariant l = ( left.model() ? left.model()->data ( left, CategorySortRole ) : QVariant() ); - QVariant r = ( right.model() ? right.model()->data ( right, CategorySortRole ) : QVariant() ); - - Q_ASSERT ( l.isValid() ); - Q_ASSERT ( r.isValid() ); - Q_ASSERT ( l.type() == r.type() ); - - if ( l.type() == QVariant::String ) - { - QString lstr = l.toString(); - QString rstr = r.toString(); - - /* - if ( d->sortCategoriesByNaturalComparison ) - { - return KStringHandler::naturalCompare ( lstr, rstr ); - } - else - { - */ - if ( lstr < rstr ) - { - return -1; - } - - if ( lstr > rstr ) - { - return 1; - } - - return 0; - //} - } - - qlonglong lint = l.toLongLong(); - qlonglong rint = r.toLongLong(); - - if ( lint < rint ) - { - return -1; - } - - if ( lint > rint ) - { - return 1; - } - - return 0; -} diff --git a/libgroupview/src/kcategorizedsortfilterproxymodel_p.h b/libgroupview/src/kcategorizedsortfilterproxymodel_p.h deleted file mode 100644 index d7e7c9a0..00000000 --- a/libgroupview/src/kcategorizedsortfilterproxymodel_p.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * 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 KCATEGORIZEDSORTFILTERPROXYMODEL_P_H -#define KCATEGORIZEDSORTFILTERPROXYMODEL_P_H - -class KCategorizedSortFilterProxyModel; - -class KCategorizedSortFilterProxyModel::Private -{ -public: - Private() - : sortColumn ( 0 ) - , sortOrder ( Qt::AscendingOrder ) - , categorizedModel ( false ) - , sortCategoriesByNaturalComparison ( true ) - { - } - - ~Private() - { - } - - int sortColumn; - Qt::SortOrder sortOrder; - bool categorizedModel; - bool sortCategoriesByNaturalComparison; -}; - -#endif diff --git a/libgroupview/src/kcategorizedview.cpp b/libgroupview/src/kcategorizedview.cpp deleted file mode 100644 index 4da4271f..00000000 --- a/libgroupview/src/kcategorizedview.cpp +++ /dev/null @@ -1,1713 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * 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. - */ - -/** - * IMPLEMENTATION NOTES: - * - * QListView::setRowHidden() and QListView::isRowHidden() are not taken into - * account. This methods should actually not exist. This effect should be handled - * by an hypothetical QSortFilterProxyModel which filters out the desired rows. - * - * In case this needs to be implemented, contact me, but I consider this a faulty - * design. - */ - -#include "kcategorizedview.h" -#include "kcategorizedview_p.h" - -#include // trunc on C99 compliant systems -//#include // trunc for not C99 compliant systems - -#include -#include -#include - -#include "kcategorydrawer.h" -#include "kcategorizedsortfilterproxymodel.h" - -//BEGIN: Private part - -struct KCategorizedView::Private::Item -{ - Item() - : topLeft ( QPoint() ) - , size ( QSize() ) - { - } - - QPoint topLeft; - QSize size; -}; - -struct KCategorizedView::Private::Block -{ - Block() - : topLeft ( QPoint() ) - , height ( -1 ) - , firstIndex ( QModelIndex() ) - , quarantineStart ( QModelIndex() ) - , items ( QList() ) - , outOfQuarantine ( false ) - , alternate ( false ) - , collapsed ( false ) - { - } - - bool operator!= ( const Block &rhs ) const - { - return firstIndex != rhs.firstIndex; - } - - static bool lessThan ( const Block &left, const Block &right ) - { - Q_ASSERT ( left.firstIndex.isValid() ); - Q_ASSERT ( right.firstIndex.isValid() ); - return left.firstIndex.row() < right.firstIndex.row(); - } - - QPoint topLeft; - int height; - QPersistentModelIndex firstIndex; - // if we have n elements on this block, and we inserted an element at position i. The quarantine - // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the - // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine - // will only affect the current block, since the rest of blocks can be affected only in the way - // that the whole block will have different offset, but items will keep the same relative position - // in terms of their parent blocks. - QPersistentModelIndex quarantineStart; - QList items; - - // this affects the whole block, not items separately. items contain the topLeft point relative - // to the block. Because of insertions or removals a whole block can be moved, so the whole block - // will enter in quarantine, what is faster than moving all items in absolute terms. - bool outOfQuarantine; - - // should we alternate its color ? is just a hint, could not be used - bool alternate; - bool collapsed; -}; - -KCategorizedView::Private::Private ( KCategorizedView *q ) - : q ( q ) - , proxyModel ( 0 ) - , categoryDrawer ( 0 ) - , categorySpacing ( 5 ) - , alternatingBlockColors ( false ) - , collapsibleBlocks ( false ) - , hoveredBlock ( new Block() ) - , hoveredIndex ( QModelIndex() ) - , pressedPosition ( QPoint() ) - , rubberBandRect ( QRect() ) - , constantItemWidth( 0 ) -{ -} - -KCategorizedView::Private::~Private() -{ - delete hoveredBlock; -} - -bool KCategorizedView::Private::isCategorized() const -{ - return proxyModel && categoryDrawer && proxyModel->isCategorizedModel(); -} - -QStyleOptionViewItemV4 KCategorizedView::Private::blockRect ( const QModelIndex &representative ) -{ - QStyleOptionViewItemV4 option ( q->viewOptions() ); - const int height = categoryDrawer->categoryHeight ( representative, option ); - const QString categoryDisplay = representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - QPoint pos = blockPosition ( categoryDisplay ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + blockHeight ( categoryDisplay ) ); - option.rect = mapToViewport ( option.rect ); - - return option; -} - -QPair KCategorizedView::Private::intersectingIndexesWithRect ( const QRect &_rect ) const -{ - const int rowCount = proxyModel->rowCount(); - - const QRect rect = _rect.normalized(); - - // binary search to find out the top border - int bottom = 0; - int top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() ); - QRect itemRect = q->visualRect ( index ); - const int verticalOff = q->verticalOffset(); - const int horizontalOff = q->horizontalOffset(); - itemRect.topLeft().ry() += verticalOff; - itemRect.topLeft().rx() += horizontalOff; - itemRect.bottomRight().ry() += verticalOff; - itemRect.bottomRight().rx() += horizontalOff; - if ( itemRect.bottomRight().y() <= rect.topLeft().y() ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - - const QModelIndex bottomIndex = proxyModel->index ( bottom, q->modelColumn(), q->rootIndex() ); - - // binary search to find out the bottom border - bottom = 0; - top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() ); - QRect itemRect = q->visualRect ( index ); - const int verticalOff = q->verticalOffset(); - const int horizontalOff = q->horizontalOffset(); - itemRect.topLeft().ry() += verticalOff; - itemRect.topLeft().rx() += horizontalOff; - itemRect.bottomRight().ry() += verticalOff; - itemRect.bottomRight().rx() += horizontalOff; - if ( itemRect.topLeft().y() <= rect.bottomRight().y() ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - - const QModelIndex topIndex = proxyModel->index ( top, q->modelColumn(), q->rootIndex() ); - - return qMakePair ( bottomIndex, topIndex ); -} - -QPoint KCategorizedView::Private::blockPosition ( const QString &category ) -{ - Block &block = blocks[category]; - - if ( block.outOfQuarantine && !block.topLeft.isNull() ) - { - return block.topLeft; - } - - QPoint res ( categorySpacing, 0 ); - - const QModelIndex index = block.firstIndex; - - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Block &block = *it; - const QModelIndex categoryIndex = block.firstIndex; - if ( index.row() < categoryIndex.row() ) - { - continue; - } - res.ry() += categoryDrawer->categoryHeight ( categoryIndex, q->viewOptions() ) + categorySpacing; - if ( index.row() == categoryIndex.row() ) - { - continue; - } - res.ry() += blockHeight ( it.key() ); - } - - block.outOfQuarantine = true; - block.topLeft = res; - - return res; -} - -int KCategorizedView::Private::blockHeight ( const QString &category ) -{ - Block &block = blocks[category]; - - if ( block.collapsed ) - { - return 0; - } - - if ( block.height > -1 ) - { - return block.height; - } - - const QModelIndex firstIndex = block.firstIndex; - const QModelIndex lastIndex = proxyModel->index ( firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() ); - const QRect topLeft = q->visualRect ( firstIndex ); - QRect bottomRight = q->visualRect ( lastIndex ); - - if ( hasGrid() ) - { - bottomRight.setHeight ( qMax ( bottomRight.height(), q->gridSize().height() ) ); - } - else - { - if ( !q->uniformItemSizes() ) - { - bottomRight.setHeight ( highestElementInLastRow ( block ) + q->spacing() * 2 ); - } - } - - const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1; - block.height = height; - - return height; -} - -int KCategorizedView::Private::viewportWidth() const -{ - return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin(); -} - -void KCategorizedView::Private::regenerateAllElements() -{ - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Block &block = *it; - block.outOfQuarantine = false; - block.quarantineStart = block.firstIndex; - block.height = -1; - } -} - -void KCategorizedView::Private::rowsInserted ( const QModelIndex &parent, int start, int end ) -{ - if ( !isCategorized() ) - { - return; - } - - for ( int i = start; i <= end; ++i ) - { - const QModelIndex index = proxyModel->index ( i, q->modelColumn(), parent ); - - Q_ASSERT ( index.isValid() ); - - const QString category = categoryForIndex ( index ); - - Block &block = blocks[category]; - - //BEGIN: update firstIndex - // save as firstIndex in block if - // - it forced the category creation (first element on this category) - // - it is before the first row on that category - const QModelIndex firstIndex = block.firstIndex; - if ( !firstIndex.isValid() || index.row() < firstIndex.row() ) - { - block.firstIndex = index; - } - //END: update firstIndex - - Q_ASSERT ( block.firstIndex.isValid() ); - - const int firstIndexRow = block.firstIndex.row(); - - block.items.insert ( index.row() - firstIndexRow, Private::Item() ); - block.height = -1; - - q->visualRect ( index ); - q->viewport()->update(); - } - - //BEGIN: update the items that are in quarantine in affected categories - { - const QModelIndex lastIndex = proxyModel->index ( end, q->modelColumn(), parent ); - const QString category = categoryForIndex ( lastIndex ); - Private::Block &block = blocks[category]; - block.quarantineStart = block.firstIndex; - } - //END: update the items that are in quarantine in affected categories - - //BEGIN: mark as in quarantine those categories that are under the affected ones - { - const QModelIndex firstIndex = proxyModel->index ( start, q->modelColumn(), parent ); - const QString category = categoryForIndex ( firstIndex ); - const QModelIndex firstAffectedCategory = blocks[category].firstIndex; - //BEGIN: order for marking as alternate those blocks that are alternate - QList blockList = blocks.values(); - qSort ( blockList.begin(), blockList.end(), Block::lessThan ); - QList firstIndexesRows; - foreach ( const Block &block, blockList ) - { - firstIndexesRows << block.firstIndex.row(); - } - //END: order for marking as alternate those blocks that are alternate - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Private::Block &block = *it; - if ( block.firstIndex.row() > firstAffectedCategory.row() ) - { - block.outOfQuarantine = false; - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - else if ( block.firstIndex.row() == firstAffectedCategory.row() ) - { - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - } - } - //END: mark as in quarantine those categories that are under the affected ones -} - -QRect KCategorizedView::Private::mapToViewport ( const QRect &rect ) const -{ - const int dx = -q->horizontalOffset(); - const int dy = -q->verticalOffset(); - return rect.adjusted ( dx, dy, dx, dy ); -} - -QRect KCategorizedView::Private::mapFromViewport ( const QRect &rect ) const -{ - const int dx = q->horizontalOffset(); - const int dy = q->verticalOffset(); - return rect.adjusted ( dx, dy, dx, dy ); -} - -int KCategorizedView::Private::highestElementInLastRow ( const Block &block ) const -{ - //Find the highest element in the last row - const QModelIndex lastIndex = proxyModel->index ( block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() ); - const QRect prevRect = q->visualRect ( lastIndex ); - int res = prevRect.height(); - QModelIndex prevIndex = proxyModel->index ( lastIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - if ( !prevIndex.isValid() ) - { - return res; - } - Q_FOREVER - { - const QRect tempRect = q->visualRect ( prevIndex ); - if ( tempRect.topLeft().y() < prevRect.topLeft().y() ) - { - break; - } - res = qMax ( res, tempRect.height() ); - if ( prevIndex == block.firstIndex ) - { - break; - } - prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - } - - return res; -} - -bool KCategorizedView::Private::hasGrid() const -{ - const QSize gridSize = q->gridSize(); - return gridSize.isValid() && !gridSize.isNull(); -} - -QString KCategorizedView::Private::categoryForIndex ( const QModelIndex &index ) const -{ - const QModelIndex categoryIndex = index.model()->index ( index.row(), proxyModel->sortColumn(), index.parent() ); - return categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); -} - -void KCategorizedView::Private::leftToRightVisualRect ( const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos ) const -{ - const int firstIndexRow = block.firstIndex.row(); - - if ( hasGrid() ) - { - const int relativeRow = index.row() - firstIndexRow; - const int maxItemsPerRow = qMax ( viewportWidth() / q->gridSize().width(), 1 ); - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin(); - } - else - { - item.topLeft.rx() = viewportWidth() - ( ( relativeRow % maxItemsPerRow ) + 1 ) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * q->gridSize().height(); - } - else - { - if ( q->uniformItemSizes() /*|| q->uniformItemWidths()*/ ) - { - const int relativeRow = index.row() - firstIndexRow; - const QSize itemSize = q->sizeHintForIndex ( index ); - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() - 2 ) / ( itemSize.width() + q->spacing() ), 1 ); - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin(); - } - else - { - item.topLeft.rx() = viewportWidth() - ( relativeRow % maxItemsPerRow ) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * itemSize.height(); - } - else - { - const QSize currSize = q->sizeHintForIndex ( index ); - if ( index != block.firstIndex ) - { - const int viewportW = viewportWidth() - q->spacing(); - QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect prevRect = q->visualRect ( prevIndex ); - prevRect = mapFromViewport ( prevRect ); - if ( ( prevRect.bottomRight().x() + 1 ) + currSize.width() - blockPos.x() + q->spacing() > viewportW ) - { - // we have to check the whole previous row, and see which one was the - // highest. - Q_FOREVER - { - prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect tempRect = q->visualRect ( prevIndex ); - tempRect = mapFromViewport ( tempRect ); - if ( tempRect.topLeft().y() < prevRect.topLeft().y() ) - { - break; - } - if ( tempRect.bottomRight().y() > prevRect.bottomRight().y() ) - { - prevRect = tempRect; - } - if ( prevIndex == block.firstIndex ) - { - break; - } - } - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing(); - } - else - { - item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y(); - } - else - { - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( prevRect.bottomRight().x() + 1 ) + q->spacing(); - } - else - { - item.topLeft.rx() = ( prevRect.bottomLeft().x() - 1 ) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y(); - } - } - else - { - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - } - else - { - item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = q->spacing(); - } - } - } - item.size = q->sizeHintForIndex ( index ); -} - -void KCategorizedView::Private::topToBottomVisualRect ( const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos ) const -{ - const int firstIndexRow = block.firstIndex.row(); - - if ( hasGrid() ) - { - const int relativeRow = index.row() - firstIndexRow; - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); - item.topLeft.ry() = relativeRow * q->gridSize().height(); - } - else - { - if ( q->uniformItemSizes() ) - { - const int relativeRow = index.row() - firstIndexRow; - const QSize itemSize = q->sizeHintForIndex ( index ); - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); - item.topLeft.ry() = relativeRow * itemSize.height(); - } - else - { - if ( index != block.firstIndex ) - { - QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect prevRect = q->visualRect ( prevIndex ); - prevRect = mapFromViewport ( prevRect ); - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y(); - } - else - { - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - item.topLeft.ry() = q->spacing(); - } - } - } - item.size = q->sizeHintForIndex ( index ); - item.size.setWidth ( viewportWidth() ); -} - -void KCategorizedView::Private::_k_slotCollapseOrExpandClicked ( QModelIndex ) -{ -} - -//END: Private part - -//BEGIN: Public part - -KCategorizedView::KCategorizedView ( QWidget *parent ) - : QListView ( parent ) - , d ( new Private ( this ) ) -{ -} - -KCategorizedView::~KCategorizedView() -{ - delete d; -} - -void KCategorizedView::setModel ( QAbstractItemModel *model ) -{ - if ( d->proxyModel == model ) - { - return; - } - - d->blocks.clear(); - - if ( d->proxyModel ) - { - disconnect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) ); - } - - d->proxyModel = dynamic_cast ( model ); - - if ( d->proxyModel ) - { - connect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) ); - } - - QListView::setModel ( model ); - - // if the model already had information inserted, update our data structures to it - if ( model->rowCount() ) - { - slotLayoutChanged(); - } -} - - -void KCategorizedView::setUniformItemWidths(bool enable) -{ - d->constantItemWidth = enable; -} - - -bool KCategorizedView::uniformItemWidths() const -{ - return d->constantItemWidth; -} - -void KCategorizedView::setGridSize ( const QSize &size ) -{ - setGridSizeOwn ( size ); -} - -void KCategorizedView::setGridSizeOwn ( const QSize &size ) -{ - d->regenerateAllElements(); - QListView::setGridSize ( size ); -} - -QRect KCategorizedView::visualRect ( const QModelIndex &index ) const -{ - if ( !d->isCategorized() ) - { - return QListView::visualRect ( index ); - } - - if ( !index.isValid() ) - { - return QRect(); - } - - const QString category = d->categoryForIndex ( index ); - - if ( !d->blocks.contains ( category ) ) - { - return QRect(); - } - - Private::Block &block = d->blocks[category]; - const int firstIndexRow = block.firstIndex.row(); - - Q_ASSERT ( block.firstIndex.isValid() ); - - if ( index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count() ) - { - return QRect(); - } - - const QPoint blockPos = d->blockPosition ( category ); - - Private::Item &ritem = block.items[index.row() - firstIndexRow]; - - if ( ritem.topLeft.isNull() || ( block.quarantineStart.isValid() && - index.row() >= block.quarantineStart.row() ) ) - { - if ( flow() == LeftToRight ) - { - d->leftToRightVisualRect ( index, ritem, block, blockPos ); - } - else - { - d->topToBottomVisualRect ( index, ritem, block, blockPos ); - } - - //BEGIN: update the quarantine start - const bool wasLastIndex = ( index.row() == ( block.firstIndex.row() + block.items.count() - 1 ) ); - if ( index.row() == block.quarantineStart.row() ) - { - if ( wasLastIndex ) - { - block.quarantineStart = QModelIndex(); - } - else - { - const QModelIndex nextIndex = d->proxyModel->index ( index.row() + 1, modelColumn(), rootIndex() ); - block.quarantineStart = nextIndex; - } - } - //END: update the quarantine start - } - - // we get now the absolute position through the relative position of the parent block. do not - // save this on ritem, since this would override the item relative position in block terms. - Private::Item item ( ritem ); - item.topLeft.ry() += blockPos.y(); - - const QSize sizeHint = item.size; - - if ( d->hasGrid() ) - { - const QSize sizeGrid = gridSize(); - const QSize resultingSize = sizeHint.boundedTo ( sizeGrid ); - QRect res ( item.topLeft.x() + ( ( sizeGrid.width() - resultingSize.width() ) / 2 ), - item.topLeft.y(), resultingSize.width(), resultingSize.height() ); - if ( block.collapsed ) - { - // we can still do binary search, while we "hide" items. We move those items in collapsed - // blocks to the left and set a 0 height. - res.setLeft ( -resultingSize.width() ); - res.setHeight ( 0 ); - } - return d->mapToViewport ( res ); - } - - QRect res ( item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height() ); - if ( block.collapsed ) - { - // we can still do binary search, while we "hide" items. We move those items in collapsed - // blocks to the left and set a 0 height. - res.setLeft ( -sizeHint.width() ); - res.setHeight ( 0 ); - } - return d->mapToViewport ( res ); -} - -KCategoryDrawer *KCategorizedView::categoryDrawer() const -{ - return d->categoryDrawer; -} - -void KCategorizedView::setCategoryDrawer ( KCategoryDrawer *categoryDrawer ) -{ - disconnect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ), - this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) ); - d->categoryDrawer = categoryDrawer; - - connect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ), - this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) ); -} - -int KCategorizedView::categorySpacing() const -{ - return d->categorySpacing; -} - -void KCategorizedView::setCategorySpacing ( int categorySpacing ) -{ - if ( d->categorySpacing == categorySpacing ) - { - return; - } - - d->categorySpacing = categorySpacing; - - for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) - { - Private::Block &block = *it; - block.outOfQuarantine = false; - } -} - -bool KCategorizedView::alternatingBlockColors() const -{ - return d->alternatingBlockColors; -} - -void KCategorizedView::setAlternatingBlockColors ( bool enable ) -{ - d->alternatingBlockColors = enable; -} - -bool KCategorizedView::collapsibleBlocks() const -{ - return d->collapsibleBlocks; -} - -void KCategorizedView::setCollapsibleBlocks ( bool enable ) -{ - d->collapsibleBlocks = enable; -} - -QModelIndexList KCategorizedView::block ( const QString &category ) -{ - QModelIndexList res; - const Private::Block &block = d->blocks[category]; - if ( block.height == -1 ) - { - return res; - } - QModelIndex current = block.firstIndex; - const int first = current.row(); - for ( int i = 1; i <= block.items.count(); ++i ) - { - if ( current.isValid() ) - { - res << current; - } - current = d->proxyModel->index ( first + i, modelColumn(), rootIndex() ); - } - return res; -} - -QModelIndexList KCategorizedView::block ( const QModelIndex &representative ) -{ - return block ( representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString() ); -} - -QModelIndex KCategorizedView::indexAt ( const QPoint &point ) const -{ - if ( !d->isCategorized() ) - { - return QListView::indexAt ( point ); - } - - const int rowCount = d->proxyModel->rowCount(); - if ( !rowCount ) - { - return QModelIndex(); - } - - // Binary search that will try to spot if there is an index under point - int bottom = 0; - int top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = d->proxyModel->index ( middle, modelColumn(), rootIndex() ); - QRect rect = visualRect ( index ); - const int verticalOff = verticalOffset(); - int horizontalOff = horizontalOffset(); - if ( layoutDirection() == Qt::RightToLeft ) - { - horizontalOff *= -1; - } - rect.topLeft().ry() += verticalOff; - rect.topLeft().rx() += horizontalOff; - rect.bottomRight().ry() += verticalOff; - rect.bottomRight().rx() += horizontalOff; - if ( rect.contains ( point ) ) - { - if ( index.model()->flags ( index ) & Qt::ItemIsEnabled ) - { - return index; - } - return QModelIndex(); - } - bool directionCondition; - if ( layoutDirection() == Qt::LeftToRight ) - { - directionCondition = point.x() > rect.bottomRight().x(); - } - else - { - directionCondition = point.x() < rect.bottomLeft().x(); - } - if ( point.y() > rect.bottomRight().y() || - ( point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition ) ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - return QModelIndex(); -} - -void KCategorizedView::reset() -{ - d->blocks.clear(); - QListView::reset(); -} - -void KCategorizedView::paintEvent ( QPaintEvent *event ) -{ - if ( !d->isCategorized() ) - { - QListView::paintEvent ( event ); - return; - } - - const QPair intersecting = d->intersectingIndexesWithRect ( viewport()->rect().intersected ( event->rect() ) ); - - QPainter p ( viewport() ); - p.save(); - - Q_ASSERT ( selectionModel()->model() == d->proxyModel ); - - //BEGIN: draw categories - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - QStyleOptionViewItemV4 option ( viewOptions() ); - option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate - : QStyleOptionViewItemV4::None; - option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open - : QStyle::State_None; - const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); - QPoint pos = d->blockPosition ( it.key() ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); - option.rect = d->mapToViewport ( option.rect ); - if ( !option.rect.intersects ( viewport()->rect() ) ) - { - ++it; - continue; - } - d->categoryDrawer->drawCategory ( categoryIndex, d->proxyModel->sortRole(), option, &p ); - ++it; - } - //END: draw categories - - if ( intersecting.first.isValid() && intersecting.second.isValid() ) - { - //BEGIN: draw items - int i = intersecting.first.row(); - int indexToCheckIfBlockCollapsed = i; - QModelIndex categoryIndex; - QString category; - Private::Block *block = 0; - while ( i <= intersecting.second.row() ) - { - //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting - if ( i == indexToCheckIfBlockCollapsed ) - { - categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); - category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - block = &d->blocks[category]; - indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count(); - if ( block->collapsed ) - { - i = indexToCheckIfBlockCollapsed; - continue; - } - } - //END: first check if the block is collapsed. if so, we have to skip the item painting - - Q_ASSERT ( block ); - - const bool alternateItem = ( i - block->firstIndex.row() ) % 2; - - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - const Qt::ItemFlags flags = d->proxyModel->flags ( index ); - QStyleOptionViewItemV4 option ( viewOptions() ); - option.rect = visualRect ( index ); - option.widget = this; - option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText - : QStyleOptionViewItemV2::None; - option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate - : QStyleOptionViewItemV4::None; - if ( flags & Qt::ItemIsSelectable ) - { - option.state |= selectionModel()->isSelected ( index ) ? QStyle::State_Selected - : QStyle::State_None; - } - else - { - option.state &= ~QStyle::State_Selected; - } - option.state |= ( index == currentIndex() ) ? QStyle::State_HasFocus - : QStyle::State_None; - if ( ! ( flags & Qt::ItemIsEnabled ) ) - { - option.state &= ~QStyle::State_Enabled; - } - else - { - option.state |= ( index == d->hoveredIndex ) ? QStyle::State_MouseOver - : QStyle::State_None; - } - - itemDelegate ( index )->paint ( &p, option, index ); - ++i; - } - //END: draw items - } - - //BEGIN: draw selection rect - if ( isSelectionRectVisible() && d->rubberBandRect.isValid() ) - { - QStyleOptionRubberBand opt; - opt.initFrom ( this ); - opt.shape = QRubberBand::Rectangle; - opt.opaque = false; - opt.rect = d->mapToViewport ( d->rubberBandRect ).intersected ( viewport()->rect().adjusted ( -16, -16, 16, 16 ) ); - p.save(); - style()->drawControl ( QStyle::CE_RubberBand, &opt, &p ); - p.restore(); - } - //END: draw selection rect - - p.restore(); -} - -void KCategorizedView::resizeEvent ( QResizeEvent *event ) -{ - d->regenerateAllElements(); - QListView::resizeEvent ( event ); -} - -void KCategorizedView::setSelection ( const QRect &rect, - QItemSelectionModel::SelectionFlags flags ) -{ - if ( !d->isCategorized() ) - { - QListView::setSelection ( rect, flags ); - return; - } - - if ( rect.topLeft() == rect.bottomRight() ) - { - const QModelIndex index = indexAt ( rect.topLeft() ); - selectionModel()->select ( index, flags ); - return; - } - - const QPair intersecting = d->intersectingIndexesWithRect ( rect ); - - QItemSelection selection; - - //TODO: think of a faster implementation - QModelIndex firstIndex; - QModelIndex lastIndex; - for ( int i = intersecting.first.row(); i <= intersecting.second.row(); ++i ) - { - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - const bool visualRectIntersects = visualRect ( index ).intersects ( rect ); - if ( firstIndex.isValid() ) - { - if ( visualRectIntersects ) - { - lastIndex = index; - } - else - { - selection << QItemSelectionRange ( firstIndex, lastIndex ); - firstIndex = QModelIndex(); - } - } - else if ( visualRectIntersects ) - { - firstIndex = index; - lastIndex = index; - } - } - - if ( firstIndex.isValid() ) - { - selection << QItemSelectionRange ( firstIndex, lastIndex ); - } - - selectionModel()->select ( selection, flags ); -} - -void KCategorizedView::mouseMoveEvent ( QMouseEvent *event ) -{ - QListView::mouseMoveEvent ( event ); - d->hoveredIndex = indexAt ( event->pos() ); - const SelectionMode itemViewSelectionMode = selectionMode(); - if ( state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection - && itemViewSelectionMode != NoSelection ) - { - QRect rect ( d->pressedPosition, event->pos() + QPoint ( horizontalOffset(), verticalOffset() ) ); - rect = rect.normalized(); - update ( rect.united ( d->rubberBandRect ) ); - d->rubberBandRect = rect; - } - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - QStyleOptionViewItemV4 option ( viewOptions() ); - const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); - QPoint pos = d->blockPosition ( it.key() ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); - option.rect = d->mapToViewport ( option.rect ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer && d->hoveredBlock->height != -1 && *d->hoveredBlock != block ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = block; - d->hoveredCategory = it.key(); - viewport()->update ( option.rect ); - } - else if ( d->hoveredBlock->height == -1 ) - { - *d->hoveredBlock = block; - d->hoveredCategory = it.key(); - } - else if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseMoved ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - return; - } - ++it; - } - if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - viewport()->update ( option.rect ); - } -} - -void KCategorizedView::mousePressEvent ( QMouseEvent *event ) -{ - if ( event->button() == Qt::LeftButton ) - { - d->pressedPosition = event->pos(); - d->pressedPosition.rx() += horizontalOffset(); - d->pressedPosition.ry() += verticalOffset(); - } - if ( !d->categoryDrawer ) - { - QListView::mousePressEvent ( event ); - return; - } - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseButtonPressed ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - if ( !event->isAccepted() ) - { - QListView::mousePressEvent ( event ); - } - return; - } - ++it; - } - QListView::mousePressEvent ( event ); -} - -void KCategorizedView::mouseReleaseEvent ( QMouseEvent *event ) -{ - d->pressedPosition = QPoint(); - d->rubberBandRect = QRect(); - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseButtonReleased ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - if ( !event->isAccepted() ) - { - QListView::mouseReleaseEvent ( event ); - } - return; - } - ++it; - } - QListView::mouseReleaseEvent ( event ); -} - -void KCategorizedView::leaveEvent ( QEvent *event ) -{ - QListView::leaveEvent ( event ); - if ( d->hoveredIndex.isValid() ) - { - viewport()->update ( visualRect ( d->hoveredIndex ) ); - d->hoveredIndex = QModelIndex(); - } - if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - viewport()->update ( option.rect ); - } -} - -void KCategorizedView::startDrag ( Qt::DropActions supportedActions ) -{ - QListView::startDrag ( supportedActions ); -} - -void KCategorizedView::dragMoveEvent ( QDragMoveEvent *event ) -{ - QListView::dragMoveEvent ( event ); - d->hoveredIndex = indexAt ( event->pos() ); -} - -void KCategorizedView::dragEnterEvent ( QDragEnterEvent *event ) -{ - QListView::dragEnterEvent ( event ); -} - -void KCategorizedView::dragLeaveEvent ( QDragLeaveEvent *event ) -{ - QListView::dragLeaveEvent ( event ); -} - -void KCategorizedView::dropEvent ( QDropEvent *event ) -{ - QListView::dropEvent ( event ); -} - -//TODO: improve se we take into account collapsed blocks -//TODO: take into account when there is no grid and no uniformItemSizes -QModelIndex KCategorizedView::moveCursor ( CursorAction cursorAction, - Qt::KeyboardModifiers modifiers ) -{ - if ( !d->isCategorized() ) - { - return QListView::moveCursor ( cursorAction, modifiers ); - } - - const QModelIndex current = currentIndex(); - const QRect currentRect = visualRect ( current ); - if ( !current.isValid() ) - { - const int rowCount = d->proxyModel->rowCount ( rootIndex() ); - if ( !rowCount ) - { - return QModelIndex(); - } - return d->proxyModel->index ( 0, modelColumn(), rootIndex() ); - } - - switch ( cursorAction ) - { - case MoveLeft: - { - if ( !current.row() ) - { - return QModelIndex(); - } - const QModelIndex previous = d->proxyModel->index ( current.row() - 1, modelColumn(), rootIndex() ); - const QRect previousRect = visualRect ( previous ); - if ( previousRect.top() == currentRect.top() ) - { - return previous; - } - - return QModelIndex(); - } - case MoveRight: - { - if ( current.row() == d->proxyModel->rowCount() - 1 ) - { - return QModelIndex(); - } - const QModelIndex next = d->proxyModel->index ( current.row() + 1, modelColumn(), rootIndex() ); - const QRect nextRect = visualRect ( next ); - if ( nextRect.top() == currentRect.top() ) - { - return next; - } - - return QModelIndex(); - } - case MoveDown: - { - if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) - { - const QModelIndex current = currentIndex(); - const QSize itemSize = d->hasGrid() ? gridSize() - : sizeHintForIndex ( current ); - const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); - const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() + - block.items.count(); - - if ( canMove ) - { - return d->proxyModel->index ( current.row() + maxItemsPerRow, modelColumn(), rootIndex() ); - } - - const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; - const QModelIndex nextIndex = d->proxyModel->index ( block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex() ); - - if ( !nextIndex.isValid() ) - { - return QModelIndex(); - } - - const Private::Block &nextBlock = d->blocks[d->categoryForIndex ( nextIndex )]; - - if ( nextBlock.items.count() <= currentRelativePos ) - { - return QModelIndex(); - } - - if ( currentRelativePos < ( block.items.count() % maxItemsPerRow ) ) - { - return d->proxyModel->index ( nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex() ); - } - - return QModelIndex(); - } - } - case MoveUp: - { - if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) - { - const QModelIndex current = currentIndex(); - const QSize itemSize = d->hasGrid() ? gridSize() - : sizeHintForIndex ( current ); - const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); - const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row(); - - if ( canMove ) - { - return d->proxyModel->index ( current.row() - maxItemsPerRow, modelColumn(), rootIndex() ); - } - - const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; - const QModelIndex prevIndex = d->proxyModel->index ( block.firstIndex.row() - 1, modelColumn(), rootIndex() ); - - if ( !prevIndex.isValid() ) - { - return QModelIndex(); - } - - const Private::Block &prevBlock = d->blocks[d->categoryForIndex ( prevIndex )]; - - if ( prevBlock.items.count() <= currentRelativePos ) - { - return QModelIndex(); - } - - const int remainder = prevBlock.items.count() % maxItemsPerRow; - if ( currentRelativePos < remainder ) - { - return d->proxyModel->index ( prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex() ); - } - - return QModelIndex(); - } - } - default: - break; - } - - return QModelIndex(); -} - -void KCategorizedView::rowsAboutToBeRemoved ( const QModelIndex &parent, - int start, - int end ) -{ - if ( !d->isCategorized() ) - { - QListView::rowsAboutToBeRemoved ( parent, start, end ); - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - - if ( end - start + 1 == d->proxyModel->rowCount() ) - { - d->blocks.clear(); - QListView::rowsAboutToBeRemoved ( parent, start, end ); - return; - } - - // Removal feels a bit more complicated than insertion. Basically we can consider there are - // 3 different cases when going to remove items. (*) represents an item, Items between ([) and - // (]) are the ones which are marked for removal. - // - // - 1st case: - // ... * * * * * * [ * * * ... - // - // The items marked for removal are the last part of this category. No need to mark any item - // of this category as in quarantine, because no special offset will be pushed to items at - // the right because of any changes (since the removed items are those on the right most part - // of the category). - // - // - 2nd case: - // ... * * * * * * ] * * * ... - // - // The items marked for removal are the first part of this category. We have to mark as in - // quarantine all items in this category. Absolutely all. All items will have to be moved to - // the left (or moving up, because rows got a different offset). - // - // - 3rd case: - // ... * * [ * * * * ] * * ... - // - // The items marked for removal are in between of this category. We have to mark as in - // quarantine only those items that are at the right of the end of the removal interval, - // (starting on "]"). - // - // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are - // located under the top most affected category as in quarantine (the block itself, as a whole), - // because such a change can force it to have a different offset (note that items themselves - // contain relative positions to the block, so marking the block as in quarantine is enough). - // - // Also note that removal implicitly means that we have to update correctly firstIndex of each - // block, and in general keep updated the internal information of elements. - - QStringList listOfCategoriesMarkedForRemoval; - - QString lastCategory; - int alreadyRemoved = 0; - for ( int i = start; i <= end; ++i ) - { - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), parent ); - - Q_ASSERT ( index.isValid() ); - - const QString category = d->categoryForIndex ( index ); - - if ( lastCategory != category ) - { - lastCategory = category; - alreadyRemoved = 0; - } - - Private::Block &block = d->blocks[category]; - block.items.removeAt ( i - block.firstIndex.row() - alreadyRemoved ); - ++alreadyRemoved; - - if ( !block.items.count() ) - { - listOfCategoriesMarkedForRemoval << category; - } - - block.height = -1; - - viewport()->update(); - } - - //BEGIN: update the items that are in quarantine in affected categories - { - const QModelIndex lastIndex = d->proxyModel->index ( end, modelColumn(), parent ); - const QString category = d->categoryForIndex ( lastIndex ); - Private::Block &block = d->blocks[category]; - if ( block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row() ) - { - block.firstIndex = d->proxyModel->index ( end + 1, modelColumn(), parent ); - } - block.quarantineStart = block.firstIndex; - } - //END: update the items that are in quarantine in affected categories - - Q_FOREACH ( const QString &category, listOfCategoriesMarkedForRemoval ) - { - d->blocks.remove ( category ); - } - - //BEGIN: mark as in quarantine those categories that are under the affected ones - { - //BEGIN: order for marking as alternate those blocks that are alternate - QList blockList = d->blocks.values(); - qSort ( blockList.begin(), blockList.end(), Private::Block::lessThan ); - QList firstIndexesRows; - foreach ( const Private::Block &block, blockList ) - { - firstIndexesRows << block.firstIndex.row(); - } - //END: order for marking as alternate those blocks that are alternate - for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) - { - Private::Block &block = *it; - if ( block.firstIndex.row() > start ) - { - block.outOfQuarantine = false; - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - else if ( block.firstIndex.row() == start ) - { - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - } - } - //END: mark as in quarantine those categories that are under the affected ones - - QListView::rowsAboutToBeRemoved ( parent, start, end ); -} - -void KCategorizedView::updateGeometries() -{ - const int oldVerticalOffset = verticalOffset(); - const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy(); - - //BEGIN bugs 213068, 287847 ------------------------------------------------------------ - /* - * QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not - * and triggers a (sometimes additionally timered) resize through ::layoutChildren() - * http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499 - * (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy) - * - * As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI - * So we prevent QListView from having an own opinion on the scrollbar visibility by - * fixing it before calling the baseclass QListView::updateGeometries() - * - * Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded - * we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself. - */ - if ( d->isCategorized() ) // important! - otherwise we'd pollute the setting if the view is initially not categorized - { - setVerticalScrollBarPolicy ( ( verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo ( this ) ) ? - Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); - setHorizontalScrollBarPolicy ( ( horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo ( this ) ) ? - Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); - } - //END bugs 213068, 287847 -------------------------------------------------------------- - - QListView::updateGeometries(); - - if ( !d->isCategorized() ) - { - return; - } - - const int rowCount = d->proxyModel->rowCount(); - if ( !rowCount ) - { - verticalScrollBar()->setRange ( 0, 0 ); - // unconditional, see function end todo - horizontalScrollBar()->setRange ( 0, 0 ); - return; - } - - const QModelIndex lastIndex = d->proxyModel->index ( rowCount - 1, modelColumn(), rootIndex() ); - Q_ASSERT ( lastIndex.isValid() ); - QRect lastItemRect = visualRect ( lastIndex ); - - if ( d->hasGrid() ) - { - lastItemRect.setSize ( lastItemRect.size().expandedTo ( gridSize() ) ); - } - else - { - if ( uniformItemSizes() ) - { - QSize itemSize = sizeHintForIndex ( lastIndex ); - itemSize.setHeight ( itemSize.height() + spacing() ); - lastItemRect.setSize ( itemSize ); - } - else - { - QSize itemSize = sizeHintForIndex ( lastIndex ); - const QString category = d->categoryForIndex ( lastIndex ); - itemSize.setHeight ( d->highestElementInLastRow ( d->blocks[category] ) + spacing() ); - lastItemRect.setSize ( itemSize ); - } - } - - const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height(); - - if ( verticalScrollMode() == ScrollPerItem ) - { - verticalScrollBar()->setSingleStep ( lastItemRect.height() ); - const int rowsPerPage = qMax ( viewport()->height() / lastItemRect.height(), 1 ); - verticalScrollBar()->setPageStep ( rowsPerPage * lastItemRect.height() ); - } - - verticalScrollBar()->setRange ( 0, bottomRange ); - verticalScrollBar()->setValue ( oldVerticalOffset ); - - //TODO: also consider working with the horizontal scroll bar. since at this level I am not still - // supporting "top to bottom" flow, there is no real problem. If I support that someday - // (think how to draw categories), we would have to take care of the horizontal scroll bar too. - // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar. - horizontalScrollBar()->setRange ( 0, 0 ); - - //BEGIN bugs 213068, 287847 ------------------------------------------------------------ - // restoring values from above ... - setVerticalScrollBarPolicy ( verticalP ); - setHorizontalScrollBarPolicy ( horizontalP ); - // ... and correct the visibility - bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum(); - if ( verticalP == Qt::ScrollBarAsNeeded && ( verticalScrollBar()->isVisibleTo ( this ) != validRange ) ) - verticalScrollBar()->setVisible ( validRange ); - validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum(); - if ( horizontalP == Qt::ScrollBarAsNeeded && ( horizontalScrollBar()->isVisibleTo ( this ) != validRange ) ) - horizontalScrollBar()->setVisible ( validRange ); - //END bugs 213068, 287847 -------------------------------------------------------------- -} - -void KCategorizedView::currentChanged ( const QModelIndex ¤t, - const QModelIndex &previous ) -{ - QListView::currentChanged ( current, previous ); -} - -void KCategorizedView::dataChanged ( const QModelIndex &topLeft, - const QModelIndex &bottomRight ) -{ - QListView::dataChanged ( topLeft, bottomRight ); - if ( !d->isCategorized() ) - { - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - - //BEGIN: since the model changed data, we need to reconsider item sizes - int i = topLeft.row(); - int indexToCheck = i; - QModelIndex categoryIndex; - QString category; - Private::Block *block; - while ( i <= bottomRight.row() ) - { - const QModelIndex currIndex = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - if ( i == indexToCheck ) - { - categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); - category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - block = &d->blocks[category]; - block->quarantineStart = currIndex; - indexToCheck = block->firstIndex.row() + block->items.count(); - } - visualRect ( currIndex ); - ++i; - } - //END: since the model changed data, we need to reconsider item sizes -} - -void KCategorizedView::rowsInserted ( const QModelIndex &parent, - int start, - int end ) -{ - QListView::rowsInserted ( parent, start, end ); - if ( !d->isCategorized() ) - { - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - d->rowsInserted ( parent, start, end ); -} - -void KCategorizedView::slotLayoutChanged() -{ - if ( !d->isCategorized() ) - { - return; - } - - d->blocks.clear(); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - if ( d->proxyModel->rowCount() ) - { - d->rowsInserted ( rootIndex(), 0, d->proxyModel->rowCount() - 1 ); - } -} -//END: Public part - -void KCategorizedView::slotCollapseOrExpandClicked ( QModelIndex idx ) -{ - d->_k_slotCollapseOrExpandClicked ( idx ); -} - - -#include "kcategorizedview.moc" diff --git a/libgroupview/src/kcategorizedview_p.h b/libgroupview/src/kcategorizedview_p.h deleted file mode 100644 index 13809312..00000000 --- a/libgroupview/src/kcategorizedview_p.h +++ /dev/null @@ -1,159 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * 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 KCATEGORIZEDVIEW_P_H -#define KCATEGORIZEDVIEW_P_H - -class KCategorizedSortFilterProxyModel; -class KCategoryDrawer; -class KCategoryDrawerV2; -class KCategoryDrawerV3; - -/** - * @internal - */ -class KCategorizedView::Private -{ -public: - struct Block; - struct Item; - - Private(KCategorizedView *q); - ~Private(); - - /** - * @return whether this view has all required elements to be categorized. - */ - bool isCategorized() const; - - /** - * @return the block rect for the representative @p representative. - */ - QStyleOptionViewItemV4 blockRect(const QModelIndex &representative); - - /** - * Returns the first and last element that intersects with rect. - * - * @note see that here we cannot take out items between first and last (as we could - * do with the rubberband). - * - * Complexity: O(log(n)) where n is model()->rowCount(). - */ - QPair intersectingIndexesWithRect(const QRect &rect) const; - - /** - * Returns the position of the block of @p category. - * - * Complexity: O(n) where n is the number of different categories when the block has been - * marked as in quarantine. O(1) the rest of the times (the vast majority). - */ - QPoint blockPosition(const QString &category); - - /** - * Returns the height of the block determined by @p category. - */ - int blockHeight(const QString &category); - - /** - * Returns the actual viewport width. - */ - int viewportWidth() const; - - /** - * Marks all elements as in quarantine. - * - * Complexity: O(n) where n is model()->rowCount(). - * - * @warning this is an expensive operation - */ - void regenerateAllElements(); - - /** - * Update internal information, and keep sync with the real information that the model contains. - */ - void rowsInserted(const QModelIndex &parent, int start, int end); - - /** - * Returns @p rect in viewport terms, taking in count horizontal and vertical offsets. - */ - QRect mapToViewport(const QRect &rect) const; - - /** - * Returns @p rect in absolute terms, converted from viewport position. - */ - QRect mapFromViewport(const QRect &rect) const; - - /** - * Returns the height of the highest element in last row. This is only applicable if there is - * no grid set and uniformItemSizes is false. - * - * @param block in which block are we searching. Necessary to stop the search if we hit the - * first item in this block. - */ - int highestElementInLastRow(const Block &block) const; - - /** - * Returns whether the view has a valid grid size. - */ - bool hasGrid() const; - - /** - * Returns the category for the given index. - */ - QString categoryForIndex(const QModelIndex &index) const; - - /** - * Updates the visual rect for item when flow is LeftToRight. - */ - void leftToRightVisualRect(const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos) const; - - /** - * Updates the visual rect for item when flow is TopToBottom. - * @note we only support viewMode == ListMode in this case. - */ - void topToBottomVisualRect(const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos) const; - - /** - * Called when expand or collapse has been clicked on the category drawer. - */ - void _k_slotCollapseOrExpandClicked(QModelIndex); - - KCategorizedView *q; - KCategorizedSortFilterProxyModel *proxyModel; - KCategoryDrawer *categoryDrawer; - int categorySpacing; - bool alternatingBlockColors; - bool collapsibleBlocks; - bool constantItemWidth; - - Block *hoveredBlock; - QString hoveredCategory; - QModelIndex hoveredIndex; - - QPoint pressedPosition; - QRect rubberBandRect; - - QHash blocks; -}; - -#endif // KCATEGORIZEDVIEW_P_H - diff --git a/libgroupview/src/kcategorydrawer.cpp b/libgroupview/src/kcategorydrawer.cpp deleted file mode 100644 index 951c1eb2..00000000 --- a/libgroupview/src/kcategorydrawer.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * 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. - */ - -#include "kcategorydrawer.h" - -#include -#include -#include - -#include -#include - -#define HORIZONTAL_HINT 3 - -class KCategoryDrawer::Private -{ -public: - Private(KCategorizedView *view) - : view(view) - , leftMargin(0) - , rightMargin(0) - { - } - - ~Private() - { - } - int leftMargin; - int rightMargin; - KCategorizedView *view; -}; - -KCategoryDrawer::KCategoryDrawer(KCategorizedView *view) - : QObject(view) - , d(new Private(view)) -{ - setLeftMargin(2); - setRightMargin(2); -} - -KCategoryDrawer::~KCategoryDrawer() -{ - delete d; -} - - -void KCategoryDrawer::drawCategory(const QModelIndex &index, - int /*sortRole*/, - const QStyleOption &option, - QPainter *painter) const -{ - painter->setRenderHint(QPainter::Antialiasing); - - const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); - const QRect optRect = option.rect; - QFont font(QApplication::font()); - font.setBold(true); - const QFontMetrics fontMetrics = QFontMetrics(font); - - QColor outlineColor = option.palette.text().color(); - outlineColor.setAlphaF(0.35); - - //BEGIN: top left corner - { - painter->save(); - painter->setPen(outlineColor); - const QPointF topLeft(optRect.topLeft()); - QRectF arc(topLeft, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 1440, 1440); - painter->restore(); - } - //END: top left corner - - //BEGIN: left vertical line - { - QPoint start(optRect.topLeft()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topLeft()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: left vertical line - - //BEGIN: horizontal line - { - QPoint start(optRect.topLeft()); - start.rx() += 3; - QPoint horizontalGradTop(optRect.topLeft()); - horizontalGradTop.rx() += optRect.width() - 6; - painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); - } - //END: horizontal line - - //BEGIN: top right corner - { - painter->save(); - painter->setPen(outlineColor); - QPointF topRight(optRect.topRight()); - topRight.rx() -= 4; - QRectF arc(topRight, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 0, 1440); - painter->restore(); - } - //END: top right corner - - //BEGIN: right vertical line - { - QPoint start(optRect.topRight()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topRight()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: right vertical line - - //BEGIN: text - { - QRect textRect(option.rect); - textRect.setTop(textRect.top() + 7); - textRect.setLeft(textRect.left() + 7); - textRect.setHeight(fontMetrics.height()); - textRect.setRight(textRect.right() - 7); - - painter->save(); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, category); - painter->restore(); - } - //END: text -} - -int KCategoryDrawer::categoryHeight(const QModelIndex &index, const QStyleOption &option) const -{ - Q_UNUSED(index); - Q_UNUSED(option) - - QFont font(QApplication::font()); - font.setBold(true); - QFontMetrics fontMetrics(font); - - const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ - + 11 /* top and bottom separation */; - return height; -} - -int KCategoryDrawer::leftMargin() const -{ - return d->leftMargin; -} - -void KCategoryDrawer::setLeftMargin(int leftMargin) -{ - d->leftMargin = leftMargin; -} - -int KCategoryDrawer::rightMargin() const -{ - return d->rightMargin; -} - -void KCategoryDrawer::setRightMargin(int rightMargin) -{ - d->rightMargin = rightMargin; -} - -KCategoryDrawer &KCategoryDrawer::operator=(const KCategoryDrawer &cd) -{ - d->leftMargin = cd.d->leftMargin; - d->rightMargin = cd.d->rightMargin; - d->view = cd.d->view; - return *this; -} - -KCategorizedView *KCategoryDrawer::view() const -{ - return d->view; -} - -void KCategoryDrawer::mouseButtonPressed(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseButtonReleased(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseMoved(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseButtonDoubleClicked(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseLeft(const QModelIndex&, const QRect&) -{ -} - -#include "kcategorydrawer.moc" -- cgit v1.2.3