I've worked on a QAbstractProxyModel to group source model data into a tree, based upon specifying a column to group by.

It works a charm, and all in all I'm happy.

BUT...

Arrow keys no longer work and I really cannot work out why?

Any hints?

Qt Code:
  1. // Proxy model for doing groupBy
  2. class GroupByModel : public QAbstractProxyModel
  3. {
  4. Q_OBJECT
  5.  
  6. private:
  7. int groupBy;
  8.  
  9. QList<QString> groups;
  10. QList<QModelIndex> groupIndexes;
  11. QMap<QString, QVector<int>*> groupToSourceRow;
  12. QVector<int> sourceRowToGroupRow;
  13.  
  14. public:
  15.  
  16. GroupByModel(QObject *parent = NULL) : QAbstractProxyModel(parent), groupBy(-1) {
  17. setParent(parent);
  18. }
  19. ~GroupByModel() {}
  20.  
  21. void setSourceModel(QAbstractItemModel *model) {
  22. QAbstractProxyModel::setSourceModel(model);
  23. setGroupBy(groupBy);
  24. }
  25.  
  26. QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const {
  27. if (parent.isValid()) {
  28. return createIndex(row,column, (void*)&groupIndexes[parent.row()]);
  29. } else {
  30. if (column == 0)
  31. return groupIndexes[row];
  32. else {
  33. return createIndex(row,column,NULL);
  34. }
  35. }
  36. }
  37.  
  38. QModelIndex parent(const QModelIndex &index) const {
  39. // parent should be encoded in the index if we supplied it, if
  40. // we didn't then return a duffer
  41. if (index == QModelIndex() || index.internalPointer() == NULL) {
  42. return QModelIndex();
  43. } else if (index.column()) {
  44. return QModelIndex();
  45. } else {
  46. return *static_cast<QModelIndex*>(index.internalPointer());
  47. }
  48. }
  49.  
  50. QModelIndex mapToSource(const QModelIndex &proxyIndex) const {
  51.  
  52. if (proxyIndex.internalPointer() != NULL) {
  53.  
  54. int groupNo = ((QModelIndex*)proxyIndex.internalPointer())->row();
  55. if (groupNo < 0 || groupNo >= groups.count() || proxyIndex.column() == 0) {
  56. return QModelIndex();
  57. }
  58.  
  59. return sourceModel()->index(groupToSourceRow.value(groups[groupNo])->at(proxyIndex.row()),
  60. proxyIndex.column()-1, // accomodate virtual column
  61. }
  62. return QModelIndex();
  63. }
  64.  
  65. QModelIndex mapFromSource(const QModelIndex &sourceIndex) const {
  66.  
  67. // which group did we put this row into?
  68. QString group = whichGroup(sourceIndex.row());
  69. int groupNo = groups.indexOf(group);
  70.  
  71. if (groupNo < 0) {
  72. return QModelIndex();
  73. } else {
  74. QModelIndex *p = new QModelIndex(createIndex(groupNo, 0, NULL));
  75. return createIndex(sourceRowToGroupRow[sourceIndex.row()], sourceIndex.column()+1, &p); // accomodate virtual column
  76. }
  77. }
  78.  
  79. QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const {
  80.  
  81. if (!proxyIndex.isValid()) return QVariant();
  82.  
  83. QVariant returning;
  84.  
  85. // if we are not at column 0 or we have a parent
  86. //if (proxyIndex.internalPointer() != NULL || proxyIndex.column() > 0) {
  87. if (proxyIndex.column() > 0) {
  88.  
  89. returning = sourceModel()->data(mapToSource(proxyIndex), role);
  90.  
  91. } else if (proxyIndex.internalPointer() == NULL) {
  92.  
  93. // its our group by!
  94. if (proxyIndex.row() < groups.count()) {
  95.  
  96. // blank values become "(blank)"
  97. QString group = groups[proxyIndex.row()];
  98. if (group == "") group = QString("(blank)");
  99.  
  100. // format the group by with ride count etc
  101. if (groupBy != -1) {
  102. QString returnString = QString("%1: %2 (%3 rides)")
  103. .arg(sourceModel()->headerData(groupBy, Qt::Horizontal).toString())
  104. .arg(group)
  105. .arg(groupToSourceRow.value(groups[proxyIndex.row()])->count());
  106. returning = QVariant(returnString);
  107. } else {
  108. QString returnString = QString("All %1 rides")
  109. .arg(groupToSourceRow.value(groups[proxyIndex.row()])->count());
  110. returning = QVariant(returnString);
  111. }
  112. }
  113. }
  114.  
  115. return returning;
  116.  
  117. }
  118.  
  119. QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const {
  120. if (section)
  121. return sourceModel()->headerData(section-1, orientation, role);
  122. else
  123. return QVariant("*");
  124. }
  125.  
  126. bool setHeaderData (int section, Qt::Orientation orientation, const QVariant & value, int role = Qt::EditRole) {
  127. if (section)
  128. return sourceModel()->setHeaderData(section-1, orientation, value, role);
  129. else
  130. return true;
  131. }
  132.  
  133. int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const {
  134. return sourceModel()->columnCount(QModelIndex())+1; // accomodate virtual group column
  135. }
  136.  
  137. int rowCount(const QModelIndex &parent = QModelIndex()) const {
  138. if (parent == QModelIndex()) {
  139.  
  140. // top level return count of groups
  141. return groups.count();
  142.  
  143. } else if (parent.column() == 0 && parent.internalPointer() == NULL) {
  144.  
  145. // second level return count of rows for group
  146. return groupToSourceRow.value(groups[parent.row()])->count();
  147.  
  148. } else {
  149.  
  150. // no children any lower
  151. return 0;
  152. }
  153. }
  154.  
  155. // does this index have children?
  156. bool hasChildren(const QModelIndex &index) const {
  157.  
  158. if (index == QModelIndex()) {
  159.  
  160. // at top
  161. return (groups.count() > 0);
  162.  
  163. } else if (index.column() == 0 && index.internalPointer() == NULL) {
  164.  
  165. // first column - the group bys
  166. return (groupToSourceRow.value(groups[index.row()])->count() > 0);
  167.  
  168. } else {
  169.  
  170. return false;
  171.  
  172. }
  173. }
  174.  
  175. //
  176. // GroupBy features
  177. //
  178. void setGroupBy(int column) {
  179.  
  180. // shift down
  181. if (column >= 0) column -= 1;
  182.  
  183. groupBy = column; // accomodate virtual column
  184. setGroups();
  185. }
  186.  
  187. QString whichGroup(int row) const {
  188.  
  189. if (groupBy == -1) return tr("All Rides");
  190. else return groupFromValue(headerData(groupBy+1, Qt::Horizontal).toString(),
  191. sourceModel()->data(sourceModel()->index(row,groupBy)).toString());
  192.  
  193. }
  194.  
  195. // implemented in RideNavigator.cpp, to avoid developers
  196. // from working out how this QAbstractProxy works, or
  197. // perhaps breaking it by accident ;-)
  198. QString groupFromValue(QString, QString) const;
  199.  
  200. void clearGroups() {
  201. // Wipe current
  202. QMapIterator<QString, QVector<int>*> i(groupToSourceRow);
  203. while (i.hasNext()) {
  204. i.next();
  205. delete i.value();
  206. }
  207. groups.clear();
  208. groupIndexes.clear();
  209. groupToSourceRow.clear();
  210. sourceRowToGroupRow.clear();
  211. }
  212.  
  213. int groupCount() {
  214. return groups.count();
  215. }
  216.  
  217. void setGroups() {
  218.  
  219. // let the views know we're doing this
  220. beginResetModel();
  221.  
  222. // wipe whatever is there first
  223. clearGroups();
  224.  
  225. if (groupBy >= 0) {
  226.  
  227. // create a QMap from 'group' string to list of rows in that group
  228. for (int i=0; i<sourceModel()->rowCount(QModelIndex()); i++) {
  229. QString value = whichGroup(i);
  230.  
  231. QVector<int> *rows;
  232. if ((rows=groupToSourceRow.value(value,NULL)) == NULL) {
  233. // add to list of groups
  234. rows = new QVector<int>;
  235. groupToSourceRow.insert(value, rows);
  236. }
  237.  
  238. // rowmap is an array corresponding to each row in the
  239. // source model, and maps to its row # within the group
  240. sourceRowToGroupRow.append(rows->count());
  241.  
  242. // add to this groups rows
  243. rows->append(i);
  244. }
  245.  
  246. } else {
  247.  
  248. // Just one group by 'All Rides'
  249. QVector<int> *rows = new QVector<int>;
  250. for (int i=0; i<sourceModel()->rowCount(QModelIndex()); i++) {
  251. rows->append(i);
  252. sourceRowToGroupRow.append(i);
  253. }
  254. groupToSourceRow.insert("All Rides", rows);
  255.  
  256. }
  257.  
  258. // Update list of groups
  259. int group=0;
  260. QMapIterator<QString, QVector<int>*> j(groupToSourceRow);
  261. while (j.hasNext()) {
  262. j.next();
  263. groups << j.key();
  264. groupIndexes << createIndex(group++,0,NULL);
  265. }
  266.  
  267. // all done. let the views know everything changed
  268. endResetModel();
  269. }
  270. };
To copy to clipboard, switch view to plain text mode 

Many thanks for taking time to look at this! it really is appreciated.

Cheers,
Mark