From 4c28dceabb45ad6f5792389ede41dfb71669602e Mon Sep 17 00:00:00 2001 From: Trigve Date: Wed, 9 Nov 2016 21:14:20 +0100 Subject: [PATCH] Add support for multiple column sort for WStandardItemModel nad WSortFilterProxyModel --- src/Wt/WSortFilterProxyModel | 25 +++++- src/Wt/WSortFilterProxyModel.C | 104 +++++++++++++++++------ src/Wt/WStandardItem | 5 ++ src/Wt/WStandardItem.C | 188 +++++++++++++++++++++++++++++++++++++++++ src/Wt/WStandardItemModel | 2 + src/Wt/WStandardItemModel.C | 5 ++ 6 files changed, 298 insertions(+), 31 deletions(-) diff --git a/src/Wt/WSortFilterProxyModel b/src/Wt/WSortFilterProxyModel index 386f48a..483540e 100644 --- a/src/Wt/WSortFilterProxyModel +++ b/src/Wt/WSortFilterProxyModel @@ -195,13 +195,27 @@ public: * * \sa sort() */ - int sortColumn() const { return sortKeyColumn_; } + int sortColumn() const { return sortKeyColumns_.empty() ? -1 : sortKeyColumns_.front(); } + + /*! \brief Returns the all sort columns. + * + * When sort() has not been called, the model is unsorted, and this method returns empty container. + * + * \sa sort() + */ + std::vector const &sortColumnsAll() const { return sortKeyColumns_; } /*! \brief Returns the current sort order. * * \sa sort() */ - SortOrder sortOrder() const { return sortOrder_; } + SortOrder sortOrder() const { return sortOrders_.empty() ? SortOrder::AscendingOrder : sortOrders_.front(); } + + /*! \brief Returns the sort order for all sorted columns. + * + * \sa sort() + */ + std::vector const & sortOrdersAll() const { return sortOrders_; } /*! \brief Configure the proxy to dynamically track changes in the * source model. @@ -275,6 +289,8 @@ public: virtual void sort(int column, SortOrder order = AscendingOrder); + virtual void sort(std::vector const &columns, std::vector const &orders); + protected: /*! \brief Returns whether a source row is accepted by the filter. * @@ -347,8 +363,9 @@ private: WRegExp *regex_; int filterKeyColumn_, filterRole_; - int sortKeyColumn_, sortRole_; - SortOrder sortOrder_; + int sortRole_; + std::vector sortKeyColumns_; + std::vector sortOrders_; bool dynamic_, inserting_; std::vector modelConnections_; diff --git a/src/Wt/WSortFilterProxyModel.C b/src/Wt/WSortFilterProxyModel.C index abd2cd0..115b927 100644 --- a/src/Wt/WSortFilterProxyModel.C +++ b/src/Wt/WSortFilterProxyModel.C @@ -16,47 +16,81 @@ namespace Wt { bool WSortFilterProxyModel::Compare::operator()(int sourceRow1, int sourceRow2) const { - if (model->sortOrder_ == AscendingOrder) - return lessThan(sourceRow1, sourceRow2); - else - return lessThan(sourceRow2, sourceRow1); + return lessThan(sourceRow1, sourceRow2); } bool WSortFilterProxyModel::Compare::lessThan(int sourceRow1, int sourceRow2) const { - if (model->sortKeyColumn_ == -1) - return sourceRow1 < sourceRow2; + bool result; - WModelIndex lhs - = model->sourceModel()->index(sourceRow1, model->sortKeyColumn_, + if (model->sortKeyColumns_.empty()) + result = sourceRow1 < sourceRow2; + else + { + // Process columns by one + for(auto i = 0U; i < model->sortKeyColumns_.size(); ++i) + { + auto const column = model->sortKeyColumns_[i]; + auto const order = model->sortOrders_[i]; + + WModelIndex lhs + = model->sourceModel()->index(sourceRow1, column, item->sourceIndex_); - WModelIndex rhs - = model->sourceModel()->index(sourceRow2, model->sortKeyColumn_, + WModelIndex rhs + = model->sourceModel()->index(sourceRow2, column, item->sourceIndex_); - return model->lessThan(lhs, rhs); + + // Save result, if we're at the last column + result = (order == SortOrder::AscendingOrder ? model->lessThan(lhs, rhs) : model->lessThan(rhs, lhs)); + // Are the same, go to the next column comparison + if(!result && !(order == SortOrder::AscendingOrder ? model->lessThan(rhs, lhs) : model->lessThan(lhs, rhs))) + continue; + // Otherwise, we can stop here + else + break; + } + } + + return result; } #endif // WT_TARGET_JAVA int WSortFilterProxyModel::Compare::compare(int sourceRow1, int sourceRow2) const { - int factor = (model->sortOrder_ == AscendingOrder) ? 1 : -1; + int result; - if (model->sortKeyColumn_ == -1) - return factor * (sourceRow1 - sourceRow2); + if(model->sortKeyColumns_.empty()) + result = (sourceRow1 - sourceRow2); + else + { + // Process columns by one + for(auto i = 0U; i < model->sortKeyColumns_.size(); ++i) + { + auto const column = model->sortKeyColumns_[i]; + auto const order = model->sortOrders_[i]; + + int factor = (order == AscendingOrder) ? 1 : -1; - WModelIndex lhs - = model->sourceModel()->index(sourceRow1, model->sortKeyColumn_, + WModelIndex lhs + = model->sourceModel()->index(sourceRow1, column, item->sourceIndex_); - WModelIndex rhs - = model->sourceModel()->index(sourceRow2, model->sortKeyColumn_, + WModelIndex rhs + = model->sourceModel()->index(sourceRow2, column, item->sourceIndex_); - return factor * model->compare(lhs, rhs); + result = factor * model->compare(lhs, rhs); + // Aren't same, we're done + if(result != 0) + break; + } + } + + return result; } #endif // DOXYGEN_ONLY @@ -69,9 +103,7 @@ WSortFilterProxyModel::WSortFilterProxyModel(WObject *parent) regex_(0), filterKeyColumn_(0), filterRole_(DisplayRole), - sortKeyColumn_(-1), sortRole_(DisplayRole), - sortOrder_(AscendingOrder), dynamic_(false), inserting_(false), mappedRootItem_(0) @@ -179,12 +211,21 @@ WFlags WSortFilterProxyModel::filterFlags() const void WSortFilterProxyModel::sort(int column, SortOrder order) { - sortKeyColumn_ = column; - sortOrder_ = order; + sortKeyColumns_.clear(); + sortOrders_.clear(); + + sortKeyColumns_.push_back(column); + sortOrders_.push_back(order); invalidate(); } +void WSortFilterProxyModel::sort(std::vector const &columns, std::vector const &orders) +{ + sortKeyColumns_ = columns; + sortOrders_ = orders; +} + void WSortFilterProxyModel::invalidate() { if (sourceModel()) { @@ -322,7 +363,7 @@ void WSortFilterProxyModel::updateItem(Item *item) const /* * Sort... */ - if (sortKeyColumn_ != -1) { + if (!sortKeyColumns_.empty()) { Utils::stable_sort(item->proxyRowMap_, Compare(this, item)); rebuildSourceRowMap(item); @@ -565,9 +606,18 @@ void WSortFilterProxyModel::sourceDataChanged(const WModelIndex& topLeft, = dynamic_ && (filterKeyColumn_ >= topLeft.column() && filterKeyColumn_ <= bottomRight.column()); - bool resort - = dynamic_ && (sortKeyColumn_ >= topLeft.column() - && sortKeyColumn_ <= bottomRight.column()); + bool resort = dynamic_; + if(dynamic_) + { + for(auto const &column : sortKeyColumns_) + { + resort = (column >= topLeft.column() + && column <= bottomRight.column()); + // At least 1 column in range, no need to continue + if(resort) + break; + } + } WModelIndex parent = mapFromSource(topLeft.parent()); // distinguish between invalid parent being root item or being filtered out diff --git a/src/Wt/WStandardItem b/src/Wt/WStandardItem index 3f5c003..fd76f59 100644 --- a/src/Wt/WStandardItem +++ b/src/Wt/WStandardItem @@ -651,6 +651,10 @@ public: */ virtual void sortChildren(int column, SortOrder order); + /*! \brief Sorts the children according to a given column and sort order. + */ + virtual void sortChildren(std::vector const &columns, std::vector const &orders); + protected: /*! \brief Set the model for this WStandardItem and its children. * @@ -692,6 +696,7 @@ private: void adoptChild(int row, int column, WStandardItem *item); void orphanChild(WStandardItem *item); void recursiveSortChildren(int column, SortOrder order); + void recursiveSortChildren(std::vector const &columns, std::vector const &orders); void renumberColumns(int column); void renumberRows(int row); diff --git a/src/Wt/WStandardItem.C b/src/Wt/WStandardItem.C index 11a9a5d..1e3af06 100644 --- a/src/Wt/WStandardItem.C +++ b/src/Wt/WStandardItem.C @@ -78,6 +78,130 @@ namespace { SortOrder order; }; + // Item comparer for multiple columns + struct WStandardItemCompareMulti W_JAVA_COMPARATOR(int) + { + WStandardItemCompareMulti(WStandardItem *anItem, std::vector const &aColumns, std::vector const &anOrders) + : item(anItem), + columns(aColumns), + orders(anOrders) + { } + +#ifndef WT_TARGET_JAVA + bool operator()(int r1, int r2) const { + return compare(r1, r2); + } + + bool compare(int r1, int r2) const { + // Always should have valid value at return + bool result; + // Process columns by one + for(auto i = 0U; i < columns.size(); ++i) + { + auto const column = columns[i]; + auto const order = orders[i]; + + WStandardItem *item1 = item->child(r1, column); + WStandardItem *item2 = item->child(r2, column); + + // First item valid + if(item1) { + // Second also valid + if(item2) { + // Save result, if we're at the last column + result = (order == SortOrder::AscendingOrder ? *item1 < *item2 : *item2 < *item1); + // Are the same, go to the next column comparison + if(!result && !(order == SortOrder::AscendingOrder ? *item2 < *item1 : *item1 < *item2)) + continue; + // Otherwise, we can stop here + else + break; + } + // Second not valid + else { + result = (order == SortOrder::AscendingOrder ? UNSPECIFIED_RESULT == -1 : UNSPECIFIED_RESULT != -1); + break; + } + } + // First not valid + else { + // Second valid + if(item2) { + result = (order == SortOrder::AscendingOrder ? UNSPECIFIED_RESULT != -1 : UNSPECIFIED_RESULT == -1); + break; + } + // Second also not valid, move to the next column + else { + result = (order == SortOrder::AscendingOrder ? false : true); + continue; + } + } + } + + return result; + } +#else + int compare(int r1, int r2) const { + // Always should have valid value at return + int result; + // Process columns by one + for(auto i = 0U; i < columns.size(); ++i) + { + auto const column = columns[i]; + auto const order = orders[i]; + + WStandardItem *item1 = item->child(r1, column); + WStandardItem *item2 = item->child(r2, column); + + // First item valid + if(item1) + { + // Second also valid + if(item2) + { + // Save result, if we're at the last column + result = (order == SortOrder::AscendingOrder ? item1->compare(*item2) : item2->compare(*item1)); + // Are the same, go to the next column comparison + if(result == 0) + continue; + // Otherwise, we can stop here + else + break; + } + // Second not valid + else + { + result = (order == SortOrder::AscendingOrder ? -UNSPECIFIED_RESULT : UNSPECIFIED_RESULT); + break; + } + } + // First not valid + else + { + // Second valid + if(item2) + { + result = (order == SortOrder::AscendingOrder ? UNSPECIFIED_RESULT : -UNSPECIFIED_RESULT); + break; + } + // Second also not valid, move to the next column + else + { + result = 0; + continue; + } + } + } + + return result; + } +#endif // WT_TARGET_JAVA + + WStandardItem *item; + std::vector const &columns; + std::vector const &orders; + }; + } namespace Wt { @@ -820,6 +944,17 @@ void WStandardItem::sortChildren(int column, SortOrder order) model_->layoutChanged().emit(); } +void WStandardItem::sortChildren(std::vector const &columns, std::vector const &orders) +{ + if(model_) + model_->layoutAboutToBeChanged().emit(); + + recursiveSortChildren(columns, orders); + + if(model_) + model_->layoutChanged().emit(); +} + bool WStandardItem::operator< (const WStandardItem& other) const { return compare(other) < 0; @@ -881,6 +1016,59 @@ void WStandardItem::recursiveSortChildren(int column, SortOrder order) } } +void WStandardItem::recursiveSortChildren(std::vector const &columns, std::vector const &orders) +{ + bool all_columns_valid = true; + for(auto const col : columns) + { + if((all_columns_valid = (col < columnCount())) == false) + break; + } + + if (all_columns_valid) { +#ifndef WT_TARGET_JAVA + std::vector permutation(rowCount()); + + for (unsigned i = 0; i < permutation.size(); ++i) + permutation[i] = i; +#else + std::vector permutation; + for (unsigned i = 0; i < rowCount(); ++i) + permutation.push_back(i); +#endif // WT_TARGET_JAVA + + Utils::stable_sort(permutation, WStandardItemCompareMulti(this, columns, orders)); + +#ifndef WT_TARGET_JAVA + Column temp(rowCount()); +#endif + + for (int c = 0; c < columnCount(); ++c) { +#ifdef WT_TARGET_JAVA + Column temp; +#endif // WT_TARGET_JAVA + Column& cc = (*columns_)[c]; + for (int r = 0; r < rowCount(); ++r) { +#ifndef WT_TARGET_JAVA + temp[r] = cc[permutation[r]]; +#else + temp.push_back(cc[permutation[r]]); +#endif // WT_TARGET_JAVA + if (temp[r]) + temp[r]->row_ = r; + } + (*columns_)[c] = temp; + } + } + + for (int c = 0; c < columnCount(); ++c) + for (int r = 0; r < rowCount(); ++r) { + WStandardItem *ch = child(r, c); + if (ch) + ch->recursiveSortChildren(columns, orders); + } +} + void WStandardItem::signalModelDataChange() { if (model_) { diff --git a/src/Wt/WStandardItemModel b/src/Wt/WStandardItemModel index 51d3828..965e87c 100644 --- a/src/Wt/WStandardItemModel +++ b/src/Wt/WStandardItemModel @@ -386,6 +386,8 @@ public: virtual void sort(int column, SortOrder order = AscendingOrder); + virtual void sort(std::vector const &columns, std::vector const &orders); + /*! \brief %Signal emitted when an item is changed. * * This signal is emitted whenever data for an item has changed. The diff --git a/src/Wt/WStandardItemModel.C b/src/Wt/WStandardItemModel.C index bbaab37..9263a8c 100644 --- a/src/Wt/WStandardItemModel.C +++ b/src/Wt/WStandardItemModel.C @@ -415,6 +415,11 @@ void WStandardItemModel::sort(int column, SortOrder order) invisibleRootItem_->sortChildren(column, order); } +void WStandardItemModel::sort(std::vector const &columns, std::vector const &orders) +{ + invisibleRootItem_->sortChildren(columns, orders); +} + } #endif // DOXYGEN_ONLY -- 1.9.5.msysgit.0