From: san Date: Sat, 5 Dec 2009 15:30:30 +0000 (+0000) Subject: POSEIDON GUI team: multi-column sorting implementation X-Git-Tag: V_5_1_3~6 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=daa6ba35b152f746215f444cbdc3ad234afcf817;p=modules%2Fgui.git POSEIDON GUI team: multi-column sorting implementation --- diff --git a/src/Qtx/QtxTreeModel.cxx b/src/Qtx/QtxTreeModel.cxx new file mode 100755 index 000000000..8356a6676 --- /dev/null +++ b/src/Qtx/QtxTreeModel.cxx @@ -0,0 +1,250 @@ +// Copyright (C) 2005 OPEN CASCADE, CEA/DEN, EDF R&D, PRINCIPIA R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// File: QtxTreeModel.cxx +// Author: Sergey ANIKIN, Open CASCADE S.A.S. (sergey.anikin@opencascade.com) +// + +#include "QtxTreeModel.h" + + +/*! + \class QtxTreeModel + \brief Base tree model class with custom sorting support. + \sa QtxProxyModel +*/ + +/*! + \brief Constructor. + \param parent parent object +*/ +QtxTreeModel::QtxTreeModel( QObject* parent ) +: QAbstractItemModel( parent ) +{ +} + +/*! + \brief Destructor +*/ +QtxTreeModel::~QtxTreeModel() +{ +} + +/*! + \brief Check if the specified column supports custom sorting. + \param column column index on which data is being sorted + \return \c true if column requires custom sorting + \sa lessThan() +*/ +bool QtxTreeModel::customSorting( const int /*column*/ ) const +{ + return false; +} + +/*! + \brief Compares two model indexes for the sorting purposes. + + This method is called only for those columns for which customSorting() + method returns \c true. The default implementation is useless, derived classes + should re-implement this method in order to use custom sorting rules. + + \param left first index to compare + \param right second index to compare + \return result of the comparison + \sa customSorting() +*/ +bool QtxTreeModel::lessThan( const QModelIndex& left, const QModelIndex& right ) const +{ + return left.row() < right.row(); +} + + +/*! + \class QtxProxyModel + \brief Proxy model which can be used above the QtxTreeMovel class + to enable custom sorting/filtering of the data. +*/ + +/*! + \brief Constructor. + \param parent parent object +*/ +QtxProxyModel::QtxProxyModel( QObject* parent ) +: QSortFilterProxyModel( parent ), + mySortingEnabled( true ) +{ +} + +/*! + \brief Destructor. +*/ +QtxProxyModel::~QtxProxyModel() +{ +} + +/*! + \brief Check if sorting is enabled. + \return \c true if sorting is enabled + \sa setSortingEnabled() +*/ +bool QtxProxyModel::isSortingEnabled() const +{ + return mySortingEnabled; +} + +/*! + \brief Enable/disable sorting. + \param enabled new flag state + \sa isSortingEnabled() +*/ +void QtxProxyModel::setSortingEnabled( bool enabled ) +{ + mySortingEnabled = enabled; + clear(); +} + +/*! + \brief Compares two model indexes for the sorting purposes. + \param left first index to compare + \param right second index to compare + \return True if item for left is less than item for right +*/ +bool QtxProxyModel::lessThan( const QModelIndex& left, const QModelIndex& right ) const +{ + if ( !isSortingEnabled() && left.isValid() && right.isValid() ) { + return left.row() < right.row(); + } + if ( treeModel() && treeModel()->customSorting( left.column() ) ) { + return treeModel()->lessThan( left, right ); + } + return QSortFilterProxyModel::lessThan( left, right ); +} + +/* + \brief Get tree model. + \return tree model +*/ +QtxTreeModel* QtxProxyModel::treeModel() const +{ + return dynamic_cast( sourceModel() ); +} + +/*! + \class QtxMultiSortModel + \brief Proxy model for multi-column sorting. + + This class can be used when it is necessary to sort the source model + by several columns. It provides overloaded sort( columns, order ) method and re-implements + lessThan() method to consider several items from the same row. + Note that multi-column sorting is less efficient with the current Qt version (4.4.3) + than single-column sorting, as it requires significantly more comparisons, so use it with care! +*/ + +/*! + \brief Constructor. + \param parent parent object + */ +QtxMultiSortModel::QtxMultiSortModel( QObject* parent ) +: QtxProxyModel( parent ) +{ +} + +/*! + \brief Destructor. + */ +QtxMultiSortModel::~QtxMultiSortModel() +{ +} + +/*! + \brief Performs multi-column sorting. + + This is a front-end for multi-column sorting. Indices of columns should be ordered with descending priority + (that is, the first element in the array has maximum priority during sorting, + and the last one has the minimum priority). + Comparison is performed by re-implementation of lessThan(). + Note that "multiSort" method name is used instead of "sort" to avoid overloading + problems between derived classes and the bases. + + \param columns array of column indices + \param orders array of individual sort orders for each column in columns + \sa lessThan() + */ +void QtxMultiSortModel::multiSort( const QVector& columns, + const QVector& orders ) +{ + // Non-empty column list means we're already sorting + if ( mySortColumns.size() ) + return; + + // mySortColumns will be used by lessThan() + mySortColumns = columns; + mySortOrders = orders; + // Remember this parameter to use it in lessThan() + myMajorOrder = mySortOrders.empty() ? Qt::AscendingOrder : mySortOrders.first(); + int aDummySortColumn = mySortColumns.empty() ? 0 : mySortColumns.first(); + + // Virtual mechanism still works, so sort() can be re-implemented in derived classes + invalidate(); + sort( aDummySortColumn, myMajorOrder ); + + mySortColumns.clear(); + mySortOrders.clear(); +} + +/*! + \brief Comparison method based on a list of columns + \param idx1 Specifies first row for comparison + \param idx2 Specifies second row for comparison + \return true if items for idx1.row() are less that items for idx2.row() + \sa sort() + */ +bool QtxMultiSortModel::lessThan( const QModelIndex& idx1, const QModelIndex& idx2 ) const +{ + if ( mySortColumns.empty() ) + return QtxProxyModel::lessThan( idx1, idx2 ); + + int aSrcRow1 = idx1.row(); + int aSrcRow2 = idx2.row(); + bool aResult = false; + + size_t anIt = 0, anEnd = mySortColumns.size(); + for ( ; !aResult && anIt != anEnd; anIt++ ){ + // Do we have to swap the rows to reverse the comparison result? + int aRow1 = aSrcRow1, aRow2 = aSrcRow2; + QModelIndex aParent1 = idx1.parent(), aParent2 = idx2.parent(); + if ( mySortOrders[anIt] != myMajorOrder ){ + std::swap( aRow1, aRow2 ); + std::swap( aParent1, aParent2 ); + } + + QModelIndex aSortIdx1 = this->sourceModel()->index( aRow1, mySortColumns[anIt], aParent1 ); + QModelIndex aSortIdx2 = this->sourceModel()->index( aRow2, mySortColumns[anIt], aParent2 ); + + bool localLess = QtxProxyModel::lessThan( aSortIdx1, aSortIdx2 ); + + // aSortIdx2 < aSortIdx1 --> it means we should stop here and return false + if ( !localLess && QtxProxyModel::lessThan( aSortIdx2, aSortIdx1 ) ) + break; + + aResult = aResult || localLess; + } + + return aResult; +} + diff --git a/src/Qtx/QtxTreeModel.h b/src/Qtx/QtxTreeModel.h new file mode 100755 index 000000000..ac602477d --- /dev/null +++ b/src/Qtx/QtxTreeModel.h @@ -0,0 +1,95 @@ +// Copyright (C) 2005 OPEN CASCADE, CEA/DEN, EDF R&D, PRINCIPIA R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// File: QtxTreeModel.h +// Author: Sergey ANIKIN, Open CASCADE S.A.S. (sergey.anikin@opencascade.com) +// + +#ifndef QTXTREEMODEL_H +#define QTXTREEMODEL_H + +#include + +#include +#include + +#ifdef WIN32 +#pragma warning( disable:4251 ) +#endif + +class QModelIndex; + +class QTX_EXPORT QtxTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + QtxTreeModel( QObject* = 0 ); + ~QtxTreeModel(); + + virtual bool customSorting( const int ) const; + virtual bool lessThan( const QModelIndex& left, const QModelIndex& right ) const; +}; + +class QTX_EXPORT QtxProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + QtxProxyModel( QObject* = 0 ); + ~QtxProxyModel(); + + bool isSortingEnabled() const; + +public slots: + void setSortingEnabled( bool ); + +protected: + virtual bool lessThan( const QModelIndex&, const QModelIndex& ) const; + +private: + QtxTreeModel* treeModel() const; + +private: + bool mySortingEnabled; +}; + +class QTX_EXPORT QtxMultiSortModel : public QtxProxyModel +{ + Q_OBJECT + +public: + QtxMultiSortModel( QObject* = 0 ); + ~QtxMultiSortModel(); + + virtual void multiSort( const QVector&, const QVector& ); + +protected: + virtual bool lessThan( const QModelIndex&, const QModelIndex& ) const; + +private: + QVector mySortColumns; + QVector mySortOrders; + Qt::SortOrder myMajorOrder; +}; + +#ifdef WIN32 +#pragma warning( default:4251 ) +#endif + +#endif // QTXTREEMODEL_H diff --git a/src/Qtx/QtxTreeView.cxx b/src/Qtx/QtxTreeView.cxx index c57192b03..ae612c89b 100644 --- a/src/Qtx/QtxTreeView.cxx +++ b/src/Qtx/QtxTreeView.cxx @@ -21,10 +21,14 @@ // #include "QtxTreeView.h" +#include "QtxTreeModel.h" +#include #include #include #include +#include +#include /*! \class QtxTreeView::Header @@ -34,21 +38,38 @@ class QtxTreeView::Header : public QHeaderView { + Q_OBJECT public: Header( const bool, QWidget* = 0 ); ~Header(); - void setSortMenuEnabled( const bool ); - bool sortMenuEnabled() const; + void setSortMenuEnabled( const bool ); + bool sortMenuEnabled() const; - void addMenuAction( QAction* ); + void setSortingEnabled( const bool ); + bool sortingEnabled() const; + void setMultiSortEnabled( const bool ); + bool multiSortEnabled() const; + void setSortIndicators( const QVector&, const QVector& ); + void sortIndicators( QVector&, QVector& ) const; + void clearSortIndicators(); + bool sortIndicatorsShown() const; + + void addMenuAction( QAction* ); protected: - void contextMenuEvent( QContextMenuEvent* ); + virtual void contextMenuEvent( QContextMenuEvent* ); + virtual void paintSection( QPainter* painter, const QRect& rect, int logicalIndex ) const; + +private slots: + void onSectionClicked( int logicalIndex ); private: typedef QMap ActionsMap; - bool myEnableSortMenu; - ActionsMap myActions; + bool myEnableSortMenu; + ActionsMap myActions; + bool myEnableMultiSort; + QVector mySortSections; + QVector mySortOrders; }; /*! @@ -61,6 +82,10 @@ QtxTreeView::Header::Header( const bool enableSortMenu, QWidget* parent ) : QHeaderView( Qt::Horizontal, parent ), myEnableSortMenu( enableSortMenu ) { + setMultiSortEnabled( false ); + // This connection should be created as early as possible, to perform internal processing + // before any external actions + connect( this, SIGNAL( sectionClicked( int ) ), this, SLOT( onSectionClicked( int ) ) ); } /*! @@ -71,7 +96,55 @@ QtxTreeView::Header::~Header() { } -/* +/*! + \brief Enable/disable sorting operation for the header. + \param enable if \c true, makes the header clickable and shows sort indicator(s) + \internal +*/ +void QtxTreeView::Header::setSortingEnabled( const bool enable ) +{ + // Suppress default Qt sort indicator for multi-sort case + setSortIndicatorShown( enable && !multiSortEnabled() ); + + // Clear multi-sort indicators + if ( !enable ) + clearSortIndicators(); + + setClickable( enable ); + + QtxTreeView* view = qobject_cast( parent() ); + if ( view ) { + view->emitSortingEnabled( enable ); + if ( enable ) { + connect( this, SIGNAL( sectionClicked( int ) ), view, SLOT( onHeaderClicked() ) ); + if ( multiSortEnabled() ) + { + QVector columns; + columns.push_back( 0 ); + QVector orders; + orders.push_back( Qt::AscendingOrder ); + setSortIndicators( columns, orders ); + } + view->onHeaderClicked(); + } + else { + disconnect( this, SIGNAL( sectionClicked( int ) ), view, SLOT( onHeaderClicked() ) ); + view->sortByColumn( 0, Qt::AscendingOrder ); + } + } +} + +/*! + \brief Query status of sorting (enabled or disabled). + \return \c true if single or multiple sort indicators are shown + \internal +*/ +bool QtxTreeView::Header::sortingEnabled() const +{ + return sortIndicatorsShown(); +} + +/*! \brief Enable/disable "Sorting" popup menu command for the header. \param enableSortMenu if \c true, enable "Sorting" menu command \internal @@ -81,7 +154,7 @@ void QtxTreeView::Header::setSortMenuEnabled( const bool enableSortMenu ) myEnableSortMenu = enableSortMenu; } -/* +/*! \brief Check if "Sorting" popup menu command for the header is enabled. \return \c true if "Sorting" menu command is enabled \internal @@ -91,7 +164,218 @@ bool QtxTreeView::Header::sortMenuEnabled() const return myEnableSortMenu; } -/* +/*! + \brief Enable/disable multi-column sorting. Sorting itself should be enabled using setSortingEnabled( true ) + \param enable if \c true, enables multi-column sorting + \internal +*/ +void QtxTreeView::Header::setMultiSortEnabled( const bool enable ) +{ + myEnableMultiSort = enable; + // Multi-sort and usual sort are mutually exclusive + setSortIndicatorShown( isSortIndicatorShown() && !enable ); +} + +/*! + \brief Check multi-column sorting status (enabled or disabled). + \return \c true if multi-column sorting is enabled + \internal +*/ +bool QtxTreeView::Header::multiSortEnabled() const +{ + return myEnableMultiSort; +} + +/*! + \brief Set sorting columns and sort orders for multi-sorting operation. + \param logicalIndices Zero-based logical indices of columns to be used for sorting + \param orders orders[i] contains sort order for section logicalIndices[i] + \internal +*/ +void QtxTreeView::Header::setSortIndicators( const QVector& logicalIndices, + const QVector& orders ) +{ + mySortSections = logicalIndices; + mySortOrders = orders; +} + +/*! + \brief Query sorting columns and sort orders for multi-sorting operation. + \param logicalIndices Out parameter for zero-based logical indices of columns currently used for sorting + \param orders Out parameter, orders[i] contains the current sort order for section logicalIndices[i] + \internal +*/ +void QtxTreeView::Header::sortIndicators( QVector& logicalIndices, + QVector& orders ) const +{ + logicalIndices.clear(); + orders.clear(); + + if ( isSortIndicatorShown() ){ + logicalIndices.push_back( sortIndicatorSection() ); + orders.push_back ( sortIndicatorOrder () ); + } + else{ + logicalIndices = mySortSections; + orders = mySortOrders; + } +} + +/*! + \brief Clears sort indicators for multi-column sorting. + \internal +*/ +void QtxTreeView::Header::clearSortIndicators() +{ + mySortSections.clear(); + mySortOrders.clear(); +} + +/*! + \brief Query sort indicators state (shown or not). + \return \c true if usual or multi-column sorting indicator is shown + \internal +*/ +bool QtxTreeView::Header::sortIndicatorsShown() const +{ + return isSortIndicatorShown() || ( multiSortEnabled() && mySortSections.size() ); +} + +Qt::SortOrder operator~( const Qt::SortOrder& arg ) +{ + return arg == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder; +} + +/*! + \brief Paints the tree view header section. + + Re-implementation of paintSection() handles multiple sort indicators. + It lacks selection support (that is not important for the tree view header in most cases) + and some dynamic features such as mouse hover feedback. This can be added if necessary. + + \internal +*/ +void QtxTreeView::Header::paintSection( QPainter* painter, const QRect& rect, int logicalIndex ) const +{ + if (!rect.isValid()) + return; + // get the state of the section + QStyleOptionHeader opt; + initStyleOption(&opt); + + if (isEnabled()) + opt.state |= QStyle::State_Enabled; + if (window()->isActiveWindow()) + opt.state |= QStyle::State_Active; + + QVector sortIndices; + QVector sortOrders; + sortIndicators( sortIndices, sortOrders ); + + int sortIndex = sortIndices.indexOf( logicalIndex ); + + if ( ( isSortIndicatorShown() || multiSortEnabled() ) && sortIndex != -1 ) + opt.sortIndicator = ( sortOrders[sortIndex] == Qt::AscendingOrder ) + ? QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp; + + // setup the style options structure + QVariant textAlignment = model()->headerData( logicalIndex, Qt::Horizontal, Qt::TextAlignmentRole ); + opt.rect = rect; + opt.section = logicalIndex; + opt.textAlignment = Qt::Alignment( textAlignment.isValid() + ? Qt::Alignment( textAlignment.toInt() ) : defaultAlignment() ); + + opt.iconAlignment = Qt::AlignVCenter; + opt.text = model()->headerData( logicalIndex, Qt::Horizontal, Qt::DisplayRole ).toString(); + + QVariant variant = model()->headerData( logicalIndex, Qt::Horizontal, Qt::DecorationRole ); + opt.icon = qvariant_cast(variant); + if (opt.icon.isNull()) + opt.icon = qvariant_cast(variant); + + QVariant foregroundBrush = model()->headerData( logicalIndex, Qt::Horizontal, Qt::ForegroundRole); + if (qVariantCanConvert(foregroundBrush)) + opt.palette.setBrush(QPalette::ButtonText, qvariant_cast(foregroundBrush)); + + QPointF oldBO = painter->brushOrigin(); + QVariant backgroundBrush = model()->headerData( logicalIndex, Qt::Horizontal, Qt::BackgroundRole ); + if ( qVariantCanConvert( backgroundBrush ) ) { + opt.palette.setBrush(QPalette::Button, qvariant_cast(backgroundBrush)); + opt.palette.setBrush(QPalette::Window, qvariant_cast(backgroundBrush)); + painter->setBrushOrigin( opt.rect.topLeft() ); + } + + // the section position + int visual = visualIndex( logicalIndex ); + Q_ASSERT(visual != -1); + if ( count() == 1 ) + opt.position = QStyleOptionHeader::OnlyOneSection; + else if ( visual == 0 ) + opt.position = QStyleOptionHeader::Beginning; + else if ( visual == count() - 1 ) + opt.position = QStyleOptionHeader::End; + else + opt.position = QStyleOptionHeader::Middle; + + opt.orientation = Qt::Horizontal; + + // draw the section (background, label, pixmap and sort indicator) + style()->drawControl( QStyle::CE_Header, &opt, painter, this ); + + // Optionally, draw the sort priority for the current column + if ( sortIndex >=0 && sortIndices.size() > 1 ){ + QRegion oldClip = painter->clipRegion(); + painter->setClipRect( rect ); + + QFont oldFont = painter->font(); + QFont fnt( oldFont ); + fnt.setPointSize( fnt.pointSize() - 2 ); + painter->setFont(fnt); + style()->drawItemText( painter, + //rect, + QRect( rect.left(), rect.top(), rect.width() - 2, rect.height() ), + Qt::AlignRight, + opt.palette, + ( opt.state & QStyle::State_Enabled ), + QString( "%1" ).arg( ++sortIndex ), + QPalette::ButtonText ); + + painter->setFont( oldFont ); + painter->setClipRegion( oldClip ); + } + + painter->setBrushOrigin(oldBO); +} + +/*! + \brief Custom mouse click handler, supports clicking multiple header sections with held. + \param logicalIndex the logical index of section + \internal +*/ +void QtxTreeView::Header::onSectionClicked( int logicalIndex ) +{ + if ( multiSortEnabled() ){ + // Already using section for sorting -> reverse the order + int sortIndex = mySortSections.indexOf( logicalIndex ); + Qt::SortOrder aDefSortOrder = sortIndex != -1 ? ~mySortOrders[sortIndex] : Qt::AscendingOrder; + + // not pressed -> clear all sort columns first + if ( !( QApplication::keyboardModifiers() & Qt::ControlModifier ) ){ + mySortSections.clear(); + mySortOrders.clear(); + } + + if ( sortIndex >=0 && sortIndex < mySortOrders.size() ){ + mySortOrders[sortIndex] = aDefSortOrder; + } + else{ + mySortSections.push_back( logicalIndex ); + mySortOrders.push_back ( mySortOrders.empty() ? aDefSortOrder : mySortOrders.last() ); + } + } +} + +/*! \brief Appends action to header popup menu. \param action the action \internal @@ -143,7 +427,7 @@ void QtxTreeView::Header::contextMenuEvent( QContextMenuEvent* e ) menu.addSeparator(); sortAction = menu.addAction( tr( "Enable sorting" ) ); sortAction->setCheckable( true ); - sortAction->setChecked( isSortIndicatorShown() ); + sortAction->setChecked( sortingEnabled() ); } if ( myActions.size() > 0 ) { menu.addSeparator(); @@ -158,20 +442,7 @@ void QtxTreeView::Header::contextMenuEvent( QContextMenuEvent* e ) setSectionHidden( actionMap[ a ], !isSectionHidden( actionMap[ a ] ) ); } else if ( a && a == sortAction ) { - setSortIndicatorShown( a->isChecked() ); - setClickable( a->isChecked() ); - QtxTreeView* view = qobject_cast( parent() ); - if ( view ) { - view->emitSortingEnabled( a->isChecked() ); - if ( a->isChecked() ) { - connect( this, SIGNAL( sectionClicked( int ) ), view, SLOT( onHeaderClicked( int ) ) ); - view->sortByColumn( sortIndicatorSection(), sortIndicatorOrder() ); - } - else { - disconnect( this, SIGNAL( sectionClicked( int ) ), view, SLOT( onHeaderClicked( int ) ) ); - view->sortByColumn( 0, Qt::AscendingOrder ); - } - } + setSortingEnabled( a->isChecked() ); } } e->accept(); @@ -326,13 +597,78 @@ void QtxTreeView::resizeColumnToEncloseContents( int column ) setColumnWidth( column, sizeHint ); } +/*! + \brief Enable/disable sorting operation for the view. + \param enable if \c true, makes the tree view header clickable and shows sort indicator(s) + \sa setMultiSortEnabled() +*/ +void QtxTreeView::setSortingEnabled( const bool enable ) +{ + Header* h = dynamic_cast( header() ); + if ( h ) + h->setSortingEnabled( enable ); +} + +/*! + \brief Query status of sorting (enabled or disabled). + \return \c true if single or multiple sort indicators are shown + \sa multiSortEnabled() +*/ +bool QtxTreeView::sortingEnabled() const +{ + Header* h = dynamic_cast( header() ); + return h && ( h->sortingEnabled() ); +} + +/*! + \brief Enables multi-column sorting. + As soon as multi-column sorting is enabled, the user can click + several header sections in required order while holding key so as to sort the contents + of the tree view on a basis of these columns' values. The column clicked first + has maximum sort priority, the column clicked last has minimum sort priority. + Each column used for sorting has a sort indicator and sort priority value displayed + in its header section. + Note that sorting in general should be enabled first using setSortingEnabled(). + \param enable true to enable, otherwise false. + \sa setSortingEnabled() +*/ +void QtxTreeView::setMultiSortEnabled( const bool enable ) +{ + Header* h = dynamic_cast( header() ); + if ( h ) + h->setMultiSortEnabled( enable ); +} + +/*! + \brief Returns true if multi-sorting is enabled, otherwise returns false. + \return Multi-sorting status. +*/ +bool QtxTreeView::multiSortEnabled() const +{ + Header* h = dynamic_cast( header() ); + return h ? h->multiSortEnabled() : false; +} + /* \brief Called when the header section is clicked. - \param column header column index */ -void QtxTreeView::onHeaderClicked( int column ) +void QtxTreeView::onHeaderClicked() { - sortByColumn( column, header()->sortIndicatorOrder() ); + Header* h = dynamic_cast( header() ); + QtxMultiSortModel* m = dynamic_cast( model() ); + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + if ( h && m && multiSortEnabled() ){ + QVector columns; + QVector orders; + h->sortIndicators( columns, orders ); + m->multiSort( columns, orders ); + } + else + sortByColumn( header()->sortIndicatorSection(), header()->sortIndicatorOrder() ); + + QApplication::restoreOverrideCursor(); } /*! @@ -418,3 +754,5 @@ void QtxTreeView::emitSortingEnabled( bool enabled ) { emit( sortingEnabled( enabled ) ); } + +#include diff --git a/src/Qtx/QtxTreeView.h b/src/Qtx/QtxTreeView.h index dfa3a5268..412b5ae7a 100644 --- a/src/Qtx/QtxTreeView.h +++ b/src/Qtx/QtxTreeView.h @@ -55,8 +55,14 @@ public: void resizeColumnToEncloseContents( int ); + void setSortingEnabled( const bool ); + bool sortingEnabled() const; + + void setMultiSortEnabled( const bool ); + bool multiSortEnabled() const; + protected slots: - void onHeaderClicked( int ); + void onHeaderClicked(); void rowsAboutToBeRemoved( const QModelIndex&, int, int ); void selectionChanged( const QItemSelection&, const QItemSelection& ); diff --git a/src/SUIT/SUIT_DataObject.cxx b/src/SUIT/SUIT_DataObject.cxx index 26c27a429..8d96d85ab 100755 --- a/src/SUIT/SUIT_DataObject.cxx +++ b/src/SUIT/SUIT_DataObject.cxx @@ -485,6 +485,13 @@ QPixmap SUIT_DataObject::icon( const int /*index*/ ) const The parameter \a index specifies the column number (to display, for example, in the tree view widget). + There is a tricky point around alpha value for role == Background. + Zero alpha is treated as fully transparent, therefore no background + is drawn at all (that is, the base color will appear instead of the custom backround color). + However, maximum alpha (each QColor has alpha == 1.0f by default) might be also inacceptable + since it disables blending effects that might be used by a custom style. + Thus applications should choose color's alpha carefully to avoid visual artefacts. + \param role color role \param index column index \return object color for the specified column diff --git a/src/SUIT/SUIT_TreeModel.cxx b/src/SUIT/SUIT_TreeModel.cxx index 147e87e16..4cfd7e81a 100755 --- a/src/SUIT/SUIT_TreeModel.cxx +++ b/src/SUIT/SUIT_TreeModel.cxx @@ -410,7 +410,7 @@ bool SUIT_TreeModel::TreeSync::needUpdate( const ItemPtr& item ) const \param parent parent object */ SUIT_TreeModel::SUIT_TreeModel( QObject* parent ) -: QAbstractItemModel( parent ), +: QtxTreeModel( parent ), myRoot( 0 ), myRootItem( 0 ), myAutoDeleteTree( false ), @@ -425,7 +425,7 @@ SUIT_TreeModel::SUIT_TreeModel( QObject* parent ) \param parent parent object */ SUIT_TreeModel::SUIT_TreeModel( SUIT_DataObject* root, QObject* parent ) -: QAbstractItemModel( parent ), +: QtxTreeModel( parent ), myRoot( root ), myRootItem( 0 ), myAutoDeleteTree( false ), @@ -535,9 +535,15 @@ QVariant SUIT_TreeModel::data( const QModelIndex& index, int role ) const case BackgroundRole: // data background color for the specified column c = obj->color( SUIT_DataObject::Background, index.column() ); - if ( !c.isValid() ) // default value + // NOTE by san: Zero alpha is treated as fully transparent, therefore no background + // is drawn at all (that is, the base color will appear instead of the custom backround). + // However, maximum alpha (each QColor has alpha == 1.0f by default) might be also unacceptable + // since it disables blending effects that might be used by a custom style. + // Thus applications should choose color's alpha themselves to get required visual result. + if ( !c.isValid() ){ // default value, should be fully transparent c = QApplication::palette().color( QPalette::Base ); - c.setAlpha( 0 ); + c.setAlpha( 0 ); + } val = c; break; case ForegroundRole: @@ -621,7 +627,7 @@ bool SUIT_TreeModel::setData( const QModelIndex& index, } } } - return QAbstractItemModel::setData( index, value, role ); + return QtxTreeModel::setData( index, value, role ); } /*! @@ -1119,9 +1125,9 @@ void SUIT_TreeModel::onRemoved( SUIT_DataObject* /*object*/, SUIT_DataObject* pa \param parent parent object */ SUIT_ProxyModel::SUIT_ProxyModel( QObject* parent ) -: QSortFilterProxyModel( parent ), - mySortingEnabled( true ) +: QtxMultiSortModel( parent ) { + setSortingEnabled( true ); SUIT_TreeModel* model = new SUIT_TreeModel( this ); connect( model, SIGNAL( modelUpdated() ), this, SIGNAL( modelUpdated() ) ); setSourceModel( model ); @@ -1133,9 +1139,9 @@ SUIT_ProxyModel::SUIT_ProxyModel( QObject* parent ) \param parent parent object */ SUIT_ProxyModel::SUIT_ProxyModel( SUIT_DataObject* root, QObject* parent ) -: QSortFilterProxyModel( parent ), - mySortingEnabled( true ) +: QtxMultiSortModel( parent ) { + setSortingEnabled( true ); SUIT_TreeModel* model = new SUIT_TreeModel( root, this ); connect( model, SIGNAL( modelUpdated() ), this, SIGNAL( modelUpdated() ) ); setSourceModel( model ); @@ -1147,9 +1153,9 @@ SUIT_ProxyModel::SUIT_ProxyModel( SUIT_DataObject* root, QObject* parent ) \param parent parent object */ SUIT_ProxyModel::SUIT_ProxyModel( SUIT_TreeModel* model, QObject* parent ) -: QSortFilterProxyModel( parent ), - mySortingEnabled( true ) +: QtxMultiSortModel( parent ) { + setSortingEnabled( true ); connect( model, SIGNAL( modelUpdated() ), this, SIGNAL( modelUpdated() ) ); setSourceModel( model ); } @@ -1255,16 +1261,6 @@ void SUIT_ProxyModel::setAutoUpdate( const bool on, const bool updateImmediately treeModel()->setAutoUpdate( on, updateImmediately ); } -/*! - \brief Check if sorting is enabled. - \return \c true if sorting is enabled - \sa setSortingEnabled() -*/ -bool SUIT_ProxyModel::isSortingEnabled() const -{ - return mySortingEnabled; -} - /*! \brief Get item delegate for the model. \return new item delegate @@ -1306,34 +1302,6 @@ void SUIT_ProxyModel::updateTree( SUIT_DataObject* obj ) treeModel()->updateTree( obj ); } -/*! - \brief Compares two model indexes for the sorting purposes. - \param left first index to compare - \param right second index to compare - \return result of the comparison -*/ -bool SUIT_ProxyModel::lessThan( const QModelIndex& left, const QModelIndex& right ) const -{ - if ( !isSortingEnabled() && left.isValid() && right.isValid() ) { - return left.row() < right.row(); - } - if ( treeModel() && treeModel()->customSorting( left.column() ) ) { - return treeModel()->lessThan( left, right ); - } - return QSortFilterProxyModel::lessThan( left, right ); -} - -/*! - \brief Enable/disable sorting. - \param enabled new flag state - \sa isSortingEnabled() -*/ -void SUIT_ProxyModel::setSortingEnabled( bool enabled ) -{ - mySortingEnabled = enabled; - clear(); -} - /* \brief Get tree model. \return tree model @@ -1380,11 +1348,8 @@ void SUIT_ItemDelegate::paint( QPainter* painter, // Note: we check into account only custom roles; other roles are process // correctly by the QItemDelegate class QVariant val = index.data( SUIT_TreeModel::BaseColorRole ); - if ( val.isValid() && val.value().isValid() ) { - QColor aBase = val.value(); - aBase.setAlpha( 0 ); + if ( val.isValid() && val.value().isValid() ) opt.palette.setBrush( QPalette::Base, val.value() ); - } val = index.data( SUIT_TreeModel::TextColorRole ); if ( val.isValid() && val.value().isValid() ) opt.palette.setBrush( QPalette::Text, val.value() ); diff --git a/src/SUIT/SUIT_TreeModel.h b/src/SUIT/SUIT_TreeModel.h index 06c028383..32eec7923 100755 --- a/src/SUIT/SUIT_TreeModel.h +++ b/src/SUIT/SUIT_TreeModel.h @@ -25,7 +25,7 @@ #include "SUIT.h" -#include +#include #include #include @@ -40,7 +40,7 @@ class SUIT_DataObject; class SUIT_TreeModel; -class SUIT_EXPORT SUIT_TreeModel : public QAbstractItemModel +class SUIT_EXPORT SUIT_TreeModel : public QtxTreeModel { Q_OBJECT @@ -141,7 +141,7 @@ private: friend class SUIT_TreeModel::TreeSync; }; -class SUIT_EXPORT SUIT_ProxyModel : public QSortFilterProxyModel +class SUIT_EXPORT SUIT_ProxyModel : public QtxMultiSortModel { Q_OBJECT @@ -163,26 +163,17 @@ public: bool autoUpdate() const; void setAutoUpdate( const bool, const bool = true ); - bool isSortingEnabled() const; - QAbstractItemDelegate* delegate() const; public slots: virtual void updateTree( const QModelIndex& ); virtual void updateTree( SUIT_DataObject* = 0 ); - void setSortingEnabled( bool ); signals: void modelUpdated(); -protected: - virtual bool lessThan( const QModelIndex&, const QModelIndex& ) const; - private: SUIT_TreeModel* treeModel() const; - -private: - bool mySortingEnabled; }; class SUIT_EXPORT SUIT_ItemDelegate : public QItemDelegate