]> SALOME platform Git repositories - modules/gui.git/commitdiff
Salome HOME
POSEIDON GUI team: multi-column sorting implementation
authorsan <san@opencascade.com>
Sat, 5 Dec 2009 15:30:30 +0000 (15:30 +0000)
committersan <san@opencascade.com>
Sat, 5 Dec 2009 15:30:30 +0000 (15:30 +0000)
src/Qtx/QtxTreeModel.cxx [new file with mode: 0755]
src/Qtx/QtxTreeModel.h [new file with mode: 0755]
src/Qtx/QtxTreeView.cxx
src/Qtx/QtxTreeView.h
src/SUIT/SUIT_DataObject.cxx
src/SUIT/SUIT_TreeModel.cxx
src/SUIT/SUIT_TreeModel.h

diff --git a/src/Qtx/QtxTreeModel.cxx b/src/Qtx/QtxTreeModel.cxx
new file mode 100755 (executable)
index 0000000..8356a66
--- /dev/null
@@ -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<QtxTreeModel*>( 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<int>& columns, 
+                                   const QVector<Qt::SortOrder>& 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 (executable)
index 0000000..ac60247
--- /dev/null
@@ -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 <Qtx.h>
+
+#include <QAbstractItemModel>
+#include <QSortFilterProxyModel>
+
+#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<int>&, const QVector<Qt::SortOrder>& );
+
+protected:
+  virtual bool           lessThan( const QModelIndex&, const QModelIndex& ) const;
+
+private:
+  QVector<int>             mySortColumns;
+  QVector<Qt::SortOrder>   mySortOrders;
+  Qt::SortOrder            myMajorOrder;
+};
+
+#ifdef WIN32
+#pragma warning( default:4251 )
+#endif
+
+#endif // QTXTREEMODEL_H
index c57192b03162783f34ceeed1ce11bc745d17152d..ae612c89bd552b8cc0868b0fbac73a3b6a70ee8c 100644 (file)
 //
 
 #include "QtxTreeView.h"
+#include "QtxTreeModel.h"
 
+#include <QApplication>
 #include <QHeaderView>
 #include <QMenu>
 #include <QMouseEvent>
+#include <QPainter>
+#include <QPalette>
 
 /*!
   \class QtxTreeView::Header
 
 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<int>&, const QVector<Qt::SortOrder>& );
+  void         sortIndicators( QVector<int>&, QVector<Qt::SortOrder>& ) 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<int, QAction*> ActionsMap;
-  bool     myEnableSortMenu;
-  ActionsMap myActions;
+  bool                   myEnableSortMenu;
+  ActionsMap             myActions;
+  bool                   myEnableMultiSort;
+  QVector<int>           mySortSections;
+  QVector<Qt::SortOrder> 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<QtxTreeView*>( parent() );
+  if ( view ) {
+    view->emitSortingEnabled( enable );
+    if ( enable ) {
+      connect( this, SIGNAL( sectionClicked( int ) ), view, SLOT( onHeaderClicked() ) );
+      if ( multiSortEnabled() )
+      {
+        QVector<int> columns;
+        columns.push_back( 0 );
+        QVector<Qt::SortOrder> 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<int>& logicalIndices, 
+                                             const QVector<Qt::SortOrder>& 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<int>& logicalIndices, 
+                                          QVector<Qt::SortOrder>& 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<int>           sortIndices;
+  QVector<Qt::SortOrder> 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<QIcon>(variant);
+  if (opt.icon.isNull())
+    opt.icon = qvariant_cast<QPixmap>(variant);
+
+  QVariant foregroundBrush = model()->headerData( logicalIndex, Qt::Horizontal, Qt::ForegroundRole);
+  if (qVariantCanConvert<QBrush>(foregroundBrush))
+    opt.palette.setBrush(QPalette::ButtonText, qvariant_cast<QBrush>(foregroundBrush));
+
+  QPointF oldBO = painter->brushOrigin();
+  QVariant backgroundBrush = model()->headerData( logicalIndex, Qt::Horizontal, Qt::BackgroundRole );
+  if ( qVariantCanConvert<QBrush>( backgroundBrush ) ) {
+    opt.palette.setBrush(QPalette::Button, qvariant_cast<QBrush>(backgroundBrush));
+    opt.palette.setBrush(QPalette::Window, qvariant_cast<QBrush>(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 <Ctrl> 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;
+
+    // <Ctrl> 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<QtxTreeView*>( 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*>( 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*>( 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 <Ctrl> 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*>( 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*>( 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*>( header() );
+  QtxMultiSortModel* m = dynamic_cast<QtxMultiSortModel*>( model() );
+
+  QApplication::setOverrideCursor( Qt::WaitCursor );
+
+  if ( h && m && multiSortEnabled() ){
+    QVector<int>           columns;
+    QVector<Qt::SortOrder> 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 <QtxTreeView.moc>
index dfa3a52686cbdba808417c3fbb769a10d89fedf1..412b5ae7a2b0d42e8d9115e77dc58d8a890c4ecd 100644 (file)
@@ -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& );
   
index 26c27a42931cd584d31fb0a5173311283687d0f6..8d96d85abe53561dead990ba701232f8169dd7b1 100755 (executable)
@@ -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
index 147e87e16a786a4ba178339754fc21b59e6b17db..4cfd7e81acbe1af9748e64dcae7dedb0c5b924b3 100755 (executable)
@@ -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<QColor>().isValid() ) {
-      QColor aBase = val.value<QColor>();
-      aBase.setAlpha( 0 );
+    if ( val.isValid() && val.value<QColor>().isValid() )
       opt.palette.setBrush( QPalette::Base, val.value<QColor>() );
-    }
     val = index.data( SUIT_TreeModel::TextColorRole );
     if ( val.isValid() && val.value<QColor>().isValid() )
       opt.palette.setBrush( QPalette::Text, val.value<QColor>() );
index 06c028383a06080cefd17a4c5d2eedf8b0d7a041..32eec7923d5d7b6bfcd3f29f72edb1f3c34cd011 100755 (executable)
@@ -25,7 +25,7 @@
 
 #include "SUIT.h"
 
-#include <Qtx.h>
+#include <QtxTreeModel.h>
 
 #include <QAbstractItemModel>
 #include <QSortFilterProxyModel>
@@ -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