Feature #4380 » 0001-Add-support-for-multiple-column-sort-for-WStandardIt.patch
src/Wt/WSortFilterProxyModel | ||
---|---|---|
195 | 195 |
* |
196 | 196 |
* \sa sort() |
197 | 197 |
*/ |
198 |
int sortColumn() const { return sortKeyColumn_; } |
|
198 |
int sortColumn() const { return sortKeyColumns_.empty() ? -1 : sortKeyColumns_.front(); } |
|
199 | ||
200 |
/*! \brief Returns the all sort columns. |
|
201 |
* |
|
202 |
* When sort() has not been called, the model is unsorted, and this method returns empty container. |
|
203 |
* |
|
204 |
* \sa sort() |
|
205 |
*/ |
|
206 |
std::vector<int> const &sortColumnsAll() const { return sortKeyColumns_; } |
|
199 | 207 | |
200 | 208 |
/*! \brief Returns the current sort order. |
201 | 209 |
* |
202 | 210 |
* \sa sort() |
203 | 211 |
*/ |
204 |
SortOrder sortOrder() const { return sortOrder_; } |
|
212 |
SortOrder sortOrder() const { return sortOrders_.empty() ? SortOrder::AscendingOrder : sortOrders_.front(); } |
|
213 | ||
214 |
/*! \brief Returns the sort order for all sorted columns. |
|
215 |
* |
|
216 |
* \sa sort() |
|
217 |
*/ |
|
218 |
std::vector<SortOrder> const & sortOrdersAll() const { return sortOrders_; } |
|
205 | 219 | |
206 | 220 |
/*! \brief Configure the proxy to dynamically track changes in the |
207 | 221 |
* source model. |
... | ... | |
275 | 289 | |
276 | 290 |
virtual void sort(int column, SortOrder order = AscendingOrder); |
277 | 291 | |
292 |
virtual void sort(std::vector<int> const &columns, std::vector<SortOrder> const &orders); |
|
293 | ||
278 | 294 |
protected: |
279 | 295 |
/*! \brief Returns whether a source row is accepted by the filter. |
280 | 296 |
* |
... | ... | |
347 | 363 |
WRegExp *regex_; |
348 | 364 | |
349 | 365 |
int filterKeyColumn_, filterRole_; |
350 |
int sortKeyColumn_, sortRole_; |
|
351 |
SortOrder sortOrder_; |
|
366 |
int sortRole_; |
|
367 |
std::vector<int> sortKeyColumns_; |
|
368 |
std::vector<SortOrder> sortOrders_; |
|
352 | 369 |
bool dynamic_, inserting_; |
353 | 370 | |
354 | 371 |
std::vector<Wt::Signals::connection> modelConnections_; |
src/Wt/WSortFilterProxyModel.C | ||
---|---|---|
16 | 16 |
bool WSortFilterProxyModel::Compare::operator()(int sourceRow1, |
17 | 17 |
int sourceRow2) const |
18 | 18 |
{ |
19 |
if (model->sortOrder_ == AscendingOrder) |
|
20 |
return lessThan(sourceRow1, sourceRow2); |
|
21 |
else |
|
22 |
return lessThan(sourceRow2, sourceRow1); |
|
19 |
return lessThan(sourceRow1, sourceRow2); |
|
23 | 20 |
} |
24 | 21 | |
25 | 22 |
bool WSortFilterProxyModel::Compare::lessThan(int sourceRow1, int sourceRow2) |
26 | 23 |
const |
27 | 24 |
{ |
28 |
if (model->sortKeyColumn_ == -1) |
|
29 |
return sourceRow1 < sourceRow2; |
|
25 |
bool result; |
|
30 | 26 | |
31 |
WModelIndex lhs |
|
32 |
= model->sourceModel()->index(sourceRow1, model->sortKeyColumn_, |
|
27 |
if (model->sortKeyColumns_.empty()) |
|
28 |
result = sourceRow1 < sourceRow2; |
|
29 |
else |
|
30 |
{ |
|
31 |
// Process columns by one |
|
32 |
for(auto i = 0U; i < model->sortKeyColumns_.size(); ++i) |
|
33 |
{ |
|
34 |
auto const column = model->sortKeyColumns_[i]; |
|
35 |
auto const order = model->sortOrders_[i]; |
|
36 | ||
37 |
WModelIndex lhs |
|
38 |
= model->sourceModel()->index(sourceRow1, column, |
|
33 | 39 |
item->sourceIndex_); |
34 | 40 | |
35 |
WModelIndex rhs |
|
36 |
= model->sourceModel()->index(sourceRow2, model->sortKeyColumn_,
|
|
41 |
WModelIndex rhs
|
|
42 |
= model->sourceModel()->index(sourceRow2, column,
|
|
37 | 43 |
item->sourceIndex_); |
38 | 44 | |
39 |
return model->lessThan(lhs, rhs); |
|
45 | ||
46 |
// Save result, if we're at the last column |
|
47 |
result = (order == SortOrder::AscendingOrder ? model->lessThan(lhs, rhs) : model->lessThan(rhs, lhs)); |
|
48 |
// Are the same, go to the next column comparison |
|
49 |
if(!result && !(order == SortOrder::AscendingOrder ? model->lessThan(rhs, lhs) : model->lessThan(lhs, rhs))) |
|
50 |
continue; |
|
51 |
// Otherwise, we can stop here |
|
52 |
else |
|
53 |
break; |
|
54 |
} |
|
55 |
} |
|
56 |
|
|
57 |
return result; |
|
40 | 58 |
} |
41 | 59 |
#endif // WT_TARGET_JAVA |
42 | 60 | |
43 | 61 |
int WSortFilterProxyModel::Compare::compare(int sourceRow1, int sourceRow2) |
44 | 62 |
const |
45 | 63 |
{ |
46 |
int factor = (model->sortOrder_ == AscendingOrder) ? 1 : -1;
|
|
64 |
int result;
|
|
47 | 65 | |
48 |
if (model->sortKeyColumn_ == -1) |
|
49 |
return factor * (sourceRow1 - sourceRow2); |
|
66 |
if(model->sortKeyColumns_.empty()) |
|
67 |
result = (sourceRow1 - sourceRow2); |
|
68 |
else |
|
69 |
{ |
|
70 |
// Process columns by one |
|
71 |
for(auto i = 0U; i < model->sortKeyColumns_.size(); ++i) |
|
72 |
{ |
|
73 |
auto const column = model->sortKeyColumns_[i]; |
|
74 |
auto const order = model->sortOrders_[i]; |
|
75 | ||
76 |
int factor = (order == AscendingOrder) ? 1 : -1; |
|
50 | 77 | |
51 |
WModelIndex lhs |
|
52 |
= model->sourceModel()->index(sourceRow1, model->sortKeyColumn_,
|
|
78 |
WModelIndex lhs
|
|
79 |
= model->sourceModel()->index(sourceRow1, column,
|
|
53 | 80 |
item->sourceIndex_); |
54 | 81 | |
55 |
WModelIndex rhs |
|
56 |
= model->sourceModel()->index(sourceRow2, model->sortKeyColumn_,
|
|
82 |
WModelIndex rhs
|
|
83 |
= model->sourceModel()->index(sourceRow2, column,
|
|
57 | 84 |
item->sourceIndex_); |
58 | 85 | |
59 |
return factor * model->compare(lhs, rhs); |
|
86 |
result = factor * model->compare(lhs, rhs); |
|
87 |
// Aren't same, we're done |
|
88 |
if(result != 0) |
|
89 |
break; |
|
90 |
} |
|
91 |
} |
|
92 | ||
93 |
return result; |
|
60 | 94 |
} |
61 | 95 | |
62 | 96 |
#endif // DOXYGEN_ONLY |
... | ... | |
69 | 103 |
regex_(0), |
70 | 104 |
filterKeyColumn_(0), |
71 | 105 |
filterRole_(DisplayRole), |
72 |
sortKeyColumn_(-1), |
|
73 | 106 |
sortRole_(DisplayRole), |
74 |
sortOrder_(AscendingOrder), |
|
75 | 107 |
dynamic_(false), |
76 | 108 |
inserting_(false), |
77 | 109 |
mappedRootItem_(0) |
... | ... | |
179 | 211 | |
180 | 212 |
void WSortFilterProxyModel::sort(int column, SortOrder order) |
181 | 213 |
{ |
182 |
sortKeyColumn_ = column; |
|
183 |
sortOrder_ = order; |
|
214 |
sortKeyColumns_.clear(); |
|
215 |
sortOrders_.clear(); |
|
216 | ||
217 |
sortKeyColumns_.push_back(column); |
|
218 |
sortOrders_.push_back(order); |
|
184 | 219 | |
185 | 220 |
invalidate(); |
186 | 221 |
} |
187 | 222 | |
223 |
void WSortFilterProxyModel::sort(std::vector<int> const &columns, std::vector<SortOrder> const &orders) |
|
224 |
{ |
|
225 |
sortKeyColumns_ = columns; |
|
226 |
sortOrders_ = orders; |
|
227 |
} |
|
228 | ||
188 | 229 |
void WSortFilterProxyModel::invalidate() |
189 | 230 |
{ |
190 | 231 |
if (sourceModel()) { |
... | ... | |
322 | 363 |
/* |
323 | 364 |
* Sort... |
324 | 365 |
*/ |
325 |
if (sortKeyColumn_ != -1) {
|
|
366 |
if (!sortKeyColumns_.empty()) {
|
|
326 | 367 |
Utils::stable_sort(item->proxyRowMap_, Compare(this, item)); |
327 | 368 | |
328 | 369 |
rebuildSourceRowMap(item); |
... | ... | |
565 | 606 |
= dynamic_ && (filterKeyColumn_ >= topLeft.column() |
566 | 607 |
&& filterKeyColumn_ <= bottomRight.column()); |
567 | 608 | |
568 |
bool resort |
|
569 |
= dynamic_ && (sortKeyColumn_ >= topLeft.column() |
|
570 |
&& sortKeyColumn_ <= bottomRight.column()); |
|
609 |
bool resort = dynamic_; |
|
610 |
if(dynamic_) |
|
611 |
{ |
|
612 |
for(auto const &column : sortKeyColumns_) |
|
613 |
{ |
|
614 |
resort = (column >= topLeft.column() |
|
615 |
&& column <= bottomRight.column()); |
|
616 |
// At least 1 column in range, no need to continue |
|
617 |
if(resort) |
|
618 |
break; |
|
619 |
} |
|
620 |
} |
|
571 | 621 | |
572 | 622 |
WModelIndex parent = mapFromSource(topLeft.parent()); |
573 | 623 |
// distinguish between invalid parent being root item or being filtered out |
src/Wt/WStandardItem | ||
---|---|---|
651 | 651 |
*/ |
652 | 652 |
virtual void sortChildren(int column, SortOrder order); |
653 | 653 | |
654 |
/*! \brief Sorts the children according to a given column and sort order. |
|
655 |
*/ |
|
656 |
virtual void sortChildren(std::vector<int> const &columns, std::vector<SortOrder> const &orders); |
|
657 | ||
654 | 658 |
protected: |
655 | 659 |
/*! \brief Set the model for this WStandardItem and its children. |
656 | 660 |
* |
... | ... | |
692 | 696 |
void adoptChild(int row, int column, WStandardItem *item); |
693 | 697 |
void orphanChild(WStandardItem *item); |
694 | 698 |
void recursiveSortChildren(int column, SortOrder order); |
699 |
void recursiveSortChildren(std::vector<int> const &columns, std::vector<SortOrder> const &orders); |
|
695 | 700 |
void renumberColumns(int column); |
696 | 701 |
void renumberRows(int row); |
697 | 702 |
src/Wt/WStandardItem.C | ||
---|---|---|
78 | 78 |
SortOrder order; |
79 | 79 |
}; |
80 | 80 | |
81 |
// Item comparer for multiple columns |
|
82 |
struct WStandardItemCompareMulti W_JAVA_COMPARATOR(int) |
|
83 |
{ |
|
84 |
WStandardItemCompareMulti(WStandardItem *anItem, std::vector<int> const &aColumns, std::vector<SortOrder> const &anOrders) |
|
85 |
: item(anItem), |
|
86 |
columns(aColumns), |
|
87 |
orders(anOrders) |
|
88 |
{ } |
|
89 | ||
90 |
#ifndef WT_TARGET_JAVA |
|
91 |
bool operator()(int r1, int r2) const { |
|
92 |
return compare(r1, r2); |
|
93 |
} |
|
94 | ||
95 |
bool compare(int r1, int r2) const { |
|
96 |
// Always should have valid value at return |
|
97 |
bool result; |
|
98 |
// Process columns by one |
|
99 |
for(auto i = 0U; i < columns.size(); ++i) |
|
100 |
{ |
|
101 |
auto const column = columns[i]; |
|
102 |
auto const order = orders[i]; |
|
103 | ||
104 |
WStandardItem *item1 = item->child(r1, column); |
|
105 |
WStandardItem *item2 = item->child(r2, column); |
|
106 | ||
107 |
// First item valid |
|
108 |
if(item1) { |
|
109 |
// Second also valid |
|
110 |
if(item2) { |
|
111 |
// Save result, if we're at the last column |
|
112 |
result = (order == SortOrder::AscendingOrder ? *item1 < *item2 : *item2 < *item1); |
|
113 |
// Are the same, go to the next column comparison |
|
114 |
if(!result && !(order == SortOrder::AscendingOrder ? *item2 < *item1 : *item1 < *item2)) |
|
115 |
continue; |
|
116 |
// Otherwise, we can stop here |
|
117 |
else |
|
118 |
break; |
|
119 |
} |
|
120 |
// Second not valid |
|
121 |
else { |
|
122 |
result = (order == SortOrder::AscendingOrder ? UNSPECIFIED_RESULT == -1 : UNSPECIFIED_RESULT != -1); |
|
123 |
break; |
|
124 |
} |
|
125 |
} |
|
126 |
// First not valid |
|
127 |
else { |
|
128 |
// Second valid |
|
129 |
if(item2) { |
|
130 |
result = (order == SortOrder::AscendingOrder ? UNSPECIFIED_RESULT != -1 : UNSPECIFIED_RESULT == -1); |
|
131 |
break; |
|
132 |
} |
|
133 |
// Second also not valid, move to the next column |
|
134 |
else { |
|
135 |
result = (order == SortOrder::AscendingOrder ? false : true); |
|
136 |
continue; |
|
137 |
} |
|
138 |
} |
|
139 |
} |
|
140 | ||
141 |
return result; |
|
142 |
} |
|
143 |
#else |
|
144 |
int compare(int r1, int r2) const { |
|
145 |
// Always should have valid value at return |
|
146 |
int result; |
|
147 |
// Process columns by one |
|
148 |
for(auto i = 0U; i < columns.size(); ++i) |
|
149 |
{ |
|
150 |
auto const column = columns[i]; |
|
151 |
auto const order = orders[i]; |
|
152 | ||
153 |
WStandardItem *item1 = item->child(r1, column); |
|
154 |
WStandardItem *item2 = item->child(r2, column); |
|
155 | ||
156 |
// First item valid |
|
157 |
if(item1) |
|
158 |
{ |
|
159 |
// Second also valid |
|
160 |
if(item2) |
|
161 |
{ |
|
162 |
// Save result, if we're at the last column |
|
163 |
result = (order == SortOrder::AscendingOrder ? item1->compare(*item2) : item2->compare(*item1)); |
|
164 |
// Are the same, go to the next column comparison |
|
165 |
if(result == 0) |
|
166 |
continue; |
|
167 |
// Otherwise, we can stop here |
|
168 |
else |
|
169 |
break; |
|
170 |
} |
|
171 |
// Second not valid |
|
172 |
else |
|
173 |
{ |
|
174 |
result = (order == SortOrder::AscendingOrder ? -UNSPECIFIED_RESULT : UNSPECIFIED_RESULT); |
|
175 |
break; |
|
176 |
} |
|
177 |
} |
|
178 |
// First not valid |
|
179 |
else |
|
180 |
{ |
|
181 |
// Second valid |
|
182 |
if(item2) |
|
183 |
{ |
|
184 |
result = (order == SortOrder::AscendingOrder ? UNSPECIFIED_RESULT : -UNSPECIFIED_RESULT); |
|
185 |
break; |
|
186 |
} |
|
187 |
// Second also not valid, move to the next column |
|
188 |
else |
|
189 |
{ |
|
190 |
result = 0; |
|
191 |
continue; |
|
192 |
} |
|
193 |
} |
|
194 |
} |
|
195 | ||
196 |
return result; |
|
197 |
} |
|
198 |
#endif // WT_TARGET_JAVA |
|
199 | ||
200 |
WStandardItem *item; |
|
201 |
std::vector<int> const &columns; |
|
202 |
std::vector<SortOrder> const &orders; |
|
203 |
}; |
|
204 | ||
81 | 205 |
} |
82 | 206 | |
83 | 207 |
namespace Wt { |
... | ... | |
820 | 944 |
model_->layoutChanged().emit(); |
821 | 945 |
} |
822 | 946 | |
947 |
void WStandardItem::sortChildren(std::vector<int> const &columns, std::vector<SortOrder> const &orders) |
|
948 |
{ |
|
949 |
if(model_) |
|
950 |
model_->layoutAboutToBeChanged().emit(); |
|
951 | ||
952 |
recursiveSortChildren(columns, orders); |
|
953 | ||
954 |
if(model_) |
|
955 |
model_->layoutChanged().emit(); |
|
956 |
} |
|
957 | ||
823 | 958 |
bool WStandardItem::operator< (const WStandardItem& other) const |
824 | 959 |
{ |
825 | 960 |
return compare(other) < 0; |
... | ... | |
881 | 1016 |
} |
882 | 1017 |
} |
883 | 1018 | |
1019 |
void WStandardItem::recursiveSortChildren(std::vector<int> const &columns, std::vector<SortOrder> const &orders) |
|
1020 |
{ |
|
1021 |
bool all_columns_valid = true; |
|
1022 |
for(auto const col : columns) |
|
1023 |
{ |
|
1024 |
if((all_columns_valid = (col < columnCount())) == false) |
|
1025 |
break; |
|
1026 |
} |
|
1027 | ||
1028 |
if (all_columns_valid) { |
|
1029 |
#ifndef WT_TARGET_JAVA |
|
1030 |
std::vector<int> permutation(rowCount()); |
|
1031 | ||
1032 |
for (unsigned i = 0; i < permutation.size(); ++i) |
|
1033 |
permutation[i] = i; |
|
1034 |
#else |
|
1035 |
std::vector<int> permutation; |
|
1036 |
for (unsigned i = 0; i < rowCount(); ++i) |
|
1037 |
permutation.push_back(i); |
|
1038 |
#endif // WT_TARGET_JAVA |
|
1039 | ||
1040 |
Utils::stable_sort(permutation, WStandardItemCompareMulti(this, columns, orders)); |
|
1041 | ||
1042 |
#ifndef WT_TARGET_JAVA |
|
1043 |
Column temp(rowCount()); |
|
1044 |
#endif |
|
1045 | ||
1046 |
for (int c = 0; c < columnCount(); ++c) { |
|
1047 |
#ifdef WT_TARGET_JAVA |
|
1048 |
Column temp; |
|
1049 |
#endif // WT_TARGET_JAVA |
|
1050 |
Column& cc = (*columns_)[c]; |
|
1051 |
for (int r = 0; r < rowCount(); ++r) { |
|
1052 |
#ifndef WT_TARGET_JAVA |
|
1053 |
temp[r] = cc[permutation[r]]; |
|
1054 |
#else |
|
1055 |
temp.push_back(cc[permutation[r]]); |
|
1056 |
#endif // WT_TARGET_JAVA |
|
1057 |
if (temp[r]) |
|
1058 |
temp[r]->row_ = r; |
|
1059 |
} |
|
1060 |
(*columns_)[c] = temp; |
|
1061 |
} |
|
1062 |
} |
|
1063 | ||
1064 |
for (int c = 0; c < columnCount(); ++c) |
|
1065 |
for (int r = 0; r < rowCount(); ++r) { |
|
1066 |
WStandardItem *ch = child(r, c); |
|
1067 |
if (ch) |
|
1068 |
ch->recursiveSortChildren(columns, orders); |
|
1069 |
} |
|
1070 |
} |
|
1071 | ||
884 | 1072 |
void WStandardItem::signalModelDataChange() |
885 | 1073 |
{ |
886 | 1074 |
if (model_) { |
src/Wt/WStandardItemModel | ||
---|---|---|
386 | 386 | |
387 | 387 |
virtual void sort(int column, SortOrder order = AscendingOrder); |
388 | 388 | |
389 |
virtual void sort(std::vector<int> const &columns, std::vector<SortOrder> const &orders); |
|
390 | ||
389 | 391 |
/*! \brief %Signal emitted when an item is changed. |
390 | 392 |
* |
391 | 393 |
* This signal is emitted whenever data for an item has changed. The |
src/Wt/WStandardItemModel.C | ||
---|---|---|
415 | 415 |
invisibleRootItem_->sortChildren(column, order); |
416 | 416 |
} |
417 | 417 | |
418 |
void WStandardItemModel::sort(std::vector<int> const &columns, std::vector<SortOrder> const &orders) |
|
419 |
{ |
|
420 |
invisibleRootItem_->sortChildren(columns, orders); |
|
421 |
} |
|
422 | ||
418 | 423 |
} |
419 | 424 | |
420 | 425 |
#endif // DOXYGEN_ONLY |
421 |
- |
- « Previous
- 1
- 2
- Next »