It features:
-Unlimited layers of re-sorting(as opposed to just 4).
-Saving and loading of all of these layers(Yup, unlimited, you can have billions if you have the memory and disk space, and columns), so that the sorting style remains after a restart.
-Operates on all lists, not just the ones the devs remembered(and there are a couple they forgot). In fact, support from the child class is only an optimization, the modified parent class(CMuleListCtrl) can be used to patch even a CList that wasn't intended to have any form of layered sorting.
-As part of the above, it removes ugly OO evilness, which someone decided to copy over and over even though it was remarked "It's okay so long as it is only done once"(It's not "once" anymore).
So, here we go. First, the #1 helper function, using the neatly provided sort history(thanks for that, by the way):
MuleListCtrl.h
DWORD_PTR GetParamAt(POSITION pos, int iPos) { LPARAM lParam = m_Params.GetAt(pos); if(lParam == 0xFEEBDEEF) //same as MLC_MAGIC! m_Params.SetAt(pos, lParam = CListCtrl::GetItemData(iPos)); return lParam; } // SLUGFILLER: multiSort int MultiSortProc(LPARAM lParam1, LPARAM lParam2) { for (POSITION pos = m_liSortHistory.GetHeadPosition(); pos != NULL; ) { // Use sort history for layered sorting int dwParamSort = m_liSortHistory.GetNext(pos); int ret = m_SortProc(lParam1, lParam2, dwParamSort); if (ret) return ret; } return 0; // Failed to sort } static int CALLBACK MultiSortCallback(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { return ((CMuleListCtrl*)lParamSort)->MultiSortProc(lParam1, lParam2); } // SLUGFILLER: multiSort
A couple of fail-safes, for the lists that forget they're using multi-sort:
MuleListCtrl.cpp
CMuleListCtrl::CMuleListCtrl(PFNLVCOMPARE pfnCompare, DWORD dwParamSort) { m_SortProc = pfnCompare; m_dwParamSort = dwParamSort; UpdateSortHistory(m_dwParamSort, 0); // SLUGFILLER: multiSort - fail-safe, ensure it's in the sort history(no inverse check)
MuleListCtrl.cpp
case LVM_SORTITEMS: //book keeping... m_dwParamSort = (LPARAM)wParam; UpdateSortHistory(m_dwParamSort, 0); // SLUGFILLER: multiSort - fail-safe, ensure it's in the sort history(no inverse check) m_SortProc = (PFNLVCOMPARE)lParam;
And while we're at it, let's get the main sort do multi-sorting for us, so we don't have to change the load process in such forgetful lists:
MuleListCtrl.cpp
case LVM_SORTITEMS: //book keeping... m_dwParamSort = (LPARAM)wParam; UpdateSortHistory(m_dwParamSort, 0); // SLUGFILLER: multiSort - fail-safe, ensure it's in the sort history(no inverse check) m_SortProc = (PFNLVCOMPARE)lParam; // SLUGFILLER: multiSort - hook our own callback for automatic layered sorting lParam = (LPARAM)MultiSortCallback; wParam = (WPARAM)this; // SLUGFILLER: multiSort for(POSITION pos = m_Params.GetHeadPosition(); pos != NULL; m_Params.GetNext(pos)) m_Params.SetAt(pos, MLC_MAGIC); break;
Actually, thanks to this little piece of code, I was finally able to take my code out of all the lists, which now call a single sort, but get a multi-sort. All thanks to the fact that CMuleListCtrl is now in charge of settings, instead of CPreferences, so once again, thanks for that.
Now, I promised saving and loading, and I shall keep:
MuleListCtrl.cpp
void CMuleListCtrl::SaveSettings() { ASSERT(!m_Name.IsEmpty()); if (m_Name.IsEmpty()) return; CIni ini(thePrefs.GetConfigFile(), _T("ListControlSetup")); ShowWindow(SW_HIDE); // SLUGFILLER: multiSort - store unlimited sorts int i; CString strSortHist; POSITION pos = m_liSortHistory.GetTailPosition(); if (pos != NULL) { strSortHist.Format(_T("%d"), m_liSortHistory.GetPrev(pos)); while (pos != NULL) { strSortHist.AppendChar(_T(',')); strSortHist.AppendFormat(_T("%d"), m_liSortHistory.GetPrev(pos)); } } ini.WriteString(m_Name + _T("SortHistory"), strSortHist); // SLUGFILLER: multiSort // store additional settings
If you're wondering what the "int i;" is for, it's defined in the code I replaced and is used below, so I had to put it there. Speaking of things I've removed:
MuleListCtrl.cpp
ShowWindow(SW_SHOW); // SLUGFILLER: multiSort remove - unused delete[] piColOrders; delete[] piColWidths;
And on to loading:
MuleListCtrl.cpp
void CMuleListCtrl::LoadSettings() { ASSERT(!m_Name.IsEmpty()); if (m_Name.IsEmpty()) return; CIni ini(thePrefs.GetConfigFile(), _T("ListControlSetup")); CHeaderCtrl* pHeaderCtrl = GetHeaderCtrl(); // sort history // SLUGFILLER: multiSort - read unlimited sorts CString strSortHist = ini.GetString(m_Name + _T("SortHistory")); int nOffset = 0; CString strTemp; nOffset = ini.Parse(strSortHist, nOffset, strTemp); while (!strTemp.IsEmpty()) { UpdateSortHistory((int)_tstoi(strTemp), 0); // avoid duplicates(cannot detect inverse, but it does half the job) nOffset = ini.Parse(strSortHist, nOffset, strTemp); } // SLUGFILLER: multiSort m_iCurrentSortItem= ini.GetInt( m_Name + _T("TableSortItem"), 0);
It's nice that CIni::Parse is public. I could have used CString::Tokenize, but this is so much cleaner and more appropriate.
But it wouldn't be complete without:
MuleListCtrl.cpp
delete[] piColWidths; delete[] piColHidden; // SLUGFILLER: multiSort remove - unused }
Gotta love replacing staticly-sized arrays with native use of CLists. Maybe I should do that to the other settings as well. Oh well.
Now that we have an up-to-date history, and the list starts out well sorted, we need to keep it that way, once items change. Merely a matter of hooking all of the m_SortProc calls to something like this:
MuleListCtrl.cpp
int CMuleListCtrl::UpdateLocation(int iItem) { int iItemCount = GetItemCount(); if(iItem >= iItemCount || iItem < 0) return iItem; BOOL notLast = iItem + 1 < iItemCount; BOOL notFirst = iItem > 0; DWORD_PTR dwpItemData = GetItemData(iItem); if(dwpItemData == NULL) return iItem; if(notFirst) { int iNewIndex = iItem - 1; POSITION pos = m_Params.FindIndex(iNewIndex); int iResult = MultiSortProc(dwpItemData, GetParamAt(pos, iNewIndex)); // SLUGFILLER: multiSort if(iResult < 0) { POSITION posPrev = pos; int iDist = iNewIndex / 2; while(iDist > 1) { for(int i = 0; i < iDist; i++) m_Params.GetPrev(posPrev); if(MultiSortProc(dwpItemData, GetParamAt(posPrev, iNewIndex - iDist)) < 0) { // SLUGFILLER: multiSort iNewIndex = iNewIndex - iDist; pos = posPrev; } else { posPrev = pos; } iDist /= 2; } while(--iNewIndex >= 0) { m_Params.GetPrev(pos); if(MultiSortProc(dwpItemData, GetParamAt(pos, iNewIndex)) >= 0) // SLUGFILLER: multiSort break; } MoveItem(iItem, iNewIndex + 1); return iNewIndex + 1; } } if(notLast) { int iNewIndex = iItem + 1; POSITION pos = m_Params.FindIndex(iNewIndex); int iResult = MultiSortProc(dwpItemData, GetParamAt(pos, iNewIndex)); // SLUGFILLER: multiSort if(iResult > 0) { POSITION posNext = pos; int iDist = (GetItemCount() - iNewIndex) / 2; while(iDist > 1) { for(int i = 0; i < iDist; i++) m_Params.GetNext(posNext); if(MultiSortProc(dwpItemData, GetParamAt(posNext, iNewIndex + iDist)) > 0) { // SLUGFILLER: multiSort iNewIndex = iNewIndex + iDist; pos = posNext; } else { posNext = pos; } iDist /= 2; } while(++iNewIndex < iItemCount) { m_Params.GetNext(pos); if(MultiSortProc(dwpItemData, GetParamAt(pos, iNewIndex)) <= 0) // SLUGFILLER: multiSort break; } MoveItem(iItem, iNewIndex); return iNewIndex; } } return iItem; }
MuleListCtrl.cpp
case LVM_INSERTITEMA: case LVM_INSERTITEMW: //try to fix position of inserted items { LPLVITEM pItem = (LPLVITEM)lParam; int iItem = pItem->iItem; int iItemCount = GetItemCount(); BOOL notLast = iItem < iItemCount; BOOL notFirst = iItem > 0; if(notFirst) { int iNewIndex = iItem - 1; POSITION pos = m_Params.FindIndex(iNewIndex); int iResult = MultiSortProc(pItem->lParam, GetParamAt(pos, iNewIndex)); // SLUGFILLER: multiSort if(iResult < 0) { POSITION posPrev = pos; int iDist = iNewIndex / 2; while(iDist > 1) { for(int i = 0; i < iDist; i++) m_Params.GetPrev(posPrev); if(MultiSortProc(pItem->lParam, GetParamAt(posPrev, iNewIndex - iDist)) < 0) { // SLUGFILLER: multiSort iNewIndex = iNewIndex - iDist; pos = posPrev; } else { posPrev = pos; } iDist /= 2; } while(--iNewIndex >= 0) { m_Params.GetPrev(pos); if(MultiSortProc(pItem->lParam, GetParamAt(pos, iNewIndex)) >= 0) // SLUGFILLER: multiSort break; } pItem->iItem = iNewIndex + 1; notLast = false; } } if(notLast) { int iNewIndex = iItem; POSITION pos = m_Params.FindIndex(iNewIndex); int iResult = MultiSortProc(pItem->lParam, GetParamAt(pos, iNewIndex)); // SLUGFILLER: multiSort if(iResult > 0) { POSITION posNext = pos; int iDist = (GetItemCount() - iNewIndex) / 2; while(iDist > 1) { for(int i = 0; i < iDist; i++) m_Params.GetNext(posNext); if(MultiSortProc(pItem->lParam, GetParamAt(posNext, iNewIndex + iDist)) > 0) { // SLUGFILLER: multiSort iNewIndex = iNewIndex + iDist; pos = posNext; } else { posNext = pos; } iDist /= 2; } while(++iNewIndex < iItemCount) { m_Params.GetNext(pos); if(MultiSortProc(pItem->lParam, GetParamAt(pos, iNewIndex)) <= 0) // SLUGFILLER: multiSort break; } pItem->iItem = iNewIndex; } }
It may seem like alot of work, but it's actually just a little search and replace.
Now just a tiny fix:
MuleListCtrl.cpp
int dwInverse = (dwNewOrder >= dwInverseValue) ? (dwNewOrder-dwInverseValue) : (dwNewOrder+dwInverseValue); // SLUGFILLER: multiSort - changed to >= for sort #0
Not really an issue, and not the only bug of this type, but if you're going to optimize, might as well do it right.
And to make all of this worthwhile:
MuleListCtrl.cpp
void CMuleListCtrl::UpdateSortHistory(int dwNewOrder, int dwInverseValue){ int dwInverse = (dwNewOrder >= dwInverseValue) ? (dwNewOrder-dwInverseValue) : (dwNewOrder+dwInverseValue); // SLUGFILLER: multiSort - changed to >= for sort #0 // delete the value (or its inverse sorting value) if it appears already in the list POSITION pos1, pos2; for (pos1 = m_liSortHistory.GetHeadPosition();( pos2 = pos1 ) != NULL;) { m_liSortHistory.GetNext(pos1); if (m_liSortHistory.GetAt(pos2) == dwNewOrder || m_liSortHistory.GetAt(pos2) == dwInverse) m_liSortHistory.RemoveAt(pos2); } m_liSortHistory.AddHead(dwNewOrder); // SLUGFILLER: multiSort remove - do not limit, unlimited saving and loading available }
Now that we have the parent support, we need to remove the "client-side" support:
*ListCtrl.cpp and SharedFilesCtrl.cpp
default: iResult=0; break; } // SLUGFILLER: multiSort remove - handled in parent class return iResult;
There are also a couple of:
FileDetailDialogName.cpp
m_sortorder = !m_sortorder; m_sortindex = pNMLV->iSubItem; m_listFileNames.UpdateSortHistory(m_sortindex + (m_sortorder ? 0 : 10), 10); // SLUGFILLER: multiSort - forgot something? m_listFileNames.SetSortArrow(m_sortindex, m_sortorder); m_listFileNames.SortItems(&CompareListNameItems, m_sortindex + (m_sortorder ? 0 : 10));
But with the fail-safes in the parent, this is just an optimization that prevents 1 and 11 from both being saved in the preferences.ini, etc. Not really necessary. There are other places where I left it as is, since I know the end result will work the same anyway, no need to be nitpicky.
To make things really rule, here's how we add support for the CSearchListCtrl sort snapshot thingy:
SearchListCtrl.h
class CSortSelectionState{ public: uint32 m_nSortItem; bool m_bSortAscending; uint32 m_nScrollPosition; CArray<int, int> m_aSelectedItems; CList<int, int> m_liSortHistory; // SLUGFILLER: multiSort };
SearchListCtrl.cpp
pCurState->m_nScrollPosition = GetTopIndex(); // SLUGFILLER: multiSort - save sort history pos = m_liSortHistory.GetHeadPosition(); while (pos != NULL){ pCurState->m_liSortHistory.AddTail(m_liSortHistory.GetNext(pos)); } // SLUGFILLER: multiSort m_mapSortSelectionStates.SetAt(m_nResultsID, pCurState);
SearchListCtrl.cpp
// sort order // thePrefs.SetColumnSortItem(CPreferences::tableSearch, pNewState->m_nSortItem); // thePrefs.SetColumnSortAscending(CPreferences::tableSearch, pNewState->m_bSortAscending); // SLUGFILLER: multiSort - load sort history m_liSortHistory.RemoveAll(); for (POSITION pos = pNewState->m_liSortHistory.GetHeadPosition(); pos != NULL; ) m_liSortHistory.AddTail(pNewState->m_liSortHistory.GetNext(pos)); // SLUGFILLER: multiSort SetSortArrow(pNewState->m_nSortItem, pNewState->m_bSortAscending); SortItems(SortProc, pNewState->m_nSortItem + (pNewState->m_bSortAscending ? 0:100));
Now instead of merely saving the sort of every search tab, it saves the multi-sort of every search tab. Go ahead, tell me that ain't cool.
Well, have fun using this patch. It sure as hell gives you alot more sorts to play with.
This post has been edited by SlugFiller: 23 July 2005 - 12:20 PM