Fix a bug in Kademlia LAN mode detection which effectively disabled it
[amule.git] / src / MuleListCtrl.cpp
blob36228a672b2df1619bbaac991c177c729fd6dd0f
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 //
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
9 // respective authors.
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #include <wx/menu.h> // Needed for wxMenu
27 #include <wx/fileconf.h> // Needed for wxConfig
28 #include <wx/tokenzr.h> // Needed for wxStringTokenizer
29 #include <wx/imaglist.h> // Needed for wxImageList
31 #include <common/MuleDebug.h> // Needed for MULE_VALIDATE_
32 #include <common/StringFunctions.h> // Needed for StrToLong
34 #include <common/MenuIDs.h>
36 #include "MuleListCtrl.h" // Interface declarations
37 #include "GetTickCount.h" // Needed for GetTickCount()
38 #include "OtherFunctions.h"
41 // For arrow-pixmaps
42 #include "pixmaps/sort_dn.xpm"
43 #include "pixmaps/sort_up.xpm"
44 #include "pixmaps/sort_dnx2.xpm"
45 #include "pixmaps/sort_upx2.xpm"
48 // Global constants
49 #ifdef __WXGTK__
50 const int COL_SIZE_MIN = 10;
51 #elif defined(__WXMSW__) || defined(__WXMAC__) || defined(__WXCOCOA__)
52 const int COL_SIZE_MIN = 0;
53 #else
54 #error Need to define COL_SIZE_MIN for your OS
55 #endif
58 BEGIN_EVENT_TABLE(CMuleListCtrl, MuleExtern::wxGenericListCtrl)
59 EVT_LIST_COL_CLICK( -1, CMuleListCtrl::OnColumnLClick)
60 EVT_LIST_COL_RIGHT_CLICK( -1, CMuleListCtrl::OnColumnRClick)
61 EVT_LIST_ITEM_SELECTED(-1, CMuleListCtrl::OnItemSelected)
62 EVT_LIST_ITEM_DESELECTED(-1, CMuleListCtrl::OnItemSelected)
63 EVT_LIST_DELETE_ITEM(-1, CMuleListCtrl::OnItemDeleted)
64 EVT_LIST_DELETE_ALL_ITEMS(-1, CMuleListCtrl::OnAllItemsDeleted)
65 EVT_CHAR( CMuleListCtrl::OnChar)
66 EVT_MENU_RANGE(MP_LISTCOL_1, MP_LISTCOL_15, CMuleListCtrl::OnMenuSelected)
67 EVT_MOUSEWHEEL(CMuleListCtrl::OnMouseWheel)
68 END_EVENT_TABLE()
71 //! Shared list of arrow-pixmaps
72 static wxImageList imgList(16, 16, true, 0);
75 CMuleListCtrl::CMuleListCtrl(wxWindow *parent, wxWindowID winid, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name)
76 : MuleExtern::wxGenericListCtrl(parent, winid, pos, size, style, validator, name)
78 m_sort_func = NULL;
79 m_tts_time = 0;
80 m_tts_item = -1;
81 m_isSorting = false;
83 if (imgList.GetImageCount() == 0) {
84 imgList.Add(wxBitmap(sort_dn_xpm));
85 imgList.Add(wxBitmap(sort_up_xpm));
86 imgList.Add(wxBitmap(sort_dnx2_xpm));
87 imgList.Add(wxBitmap(sort_upx2_xpm));
90 // Default sort-order is to sort by the first column (asc).
91 m_sort_orders.push_back(CColPair(0, 0));
93 SetImageList(&imgList, wxIMAGE_LIST_SMALL);
97 CMuleListCtrl::~CMuleListCtrl()
99 if (!m_name.IsEmpty()) {
100 SaveSettings();
104 long CMuleListCtrl::InsertColumn(long col, const wxString& heading, int format, int width, const wxString& name)
106 if (!name.IsEmpty()) {
107 #ifdef __DEBUG__
108 // Check for valid names
109 wxASSERT_MSG(name.Find(wxT(':')) == wxNOT_FOUND, wxT("Column name \"") + name + wxT("\" contains invalid characters!"));
110 wxASSERT_MSG(name.Find(wxT(',')) == wxNOT_FOUND, wxT("Column name \"") + name + wxT("\" contains invalid characters!"));
112 // Check for uniqueness of names.
113 for (ColNameList::const_iterator it = m_column_names.begin(); it != m_column_names.end(); ++it) {
114 if (name == it->second) {
115 wxFAIL_MSG(wxT("Column name \"") + name + wxT("\" is not unique!"));
118 #endif
119 // Insert name at position col.
120 ColNameList::iterator it = m_column_names.begin();
121 while (it != m_column_names.end() && it->first < col) {
122 ++it;
124 m_column_names.insert(it, ColNameEntry(col, name));
125 while (it != m_column_names.end()) {
126 ++it;
127 ++(it->first);
131 return MuleExtern::wxGenericListCtrl::InsertColumn(col, heading, format, width);
134 void CMuleListCtrl::SaveSettings()
136 wxCHECK_RET(!m_name.IsEmpty(), wxT("Cannot save settings for unnamed list"));
138 wxConfigBase* cfg = wxConfigBase::Get();
140 // Save sorting, column and order
141 wxString sortOrder;
142 for (CSortingList::iterator it = m_sort_orders.begin(); it != m_sort_orders.end();) {
143 wxString columnName = GetColumnName(it->first);
144 if (!columnName.IsEmpty()) {
145 sortOrder += columnName;
146 sortOrder += wxT(":");
147 sortOrder += it->second & SORT_DES ? wxT("1") : wxT("0");
148 sortOrder += wxT(":");
149 sortOrder += it->second & SORT_ALT ? wxT("1") : wxT("0");
150 if (++it != m_sort_orders.end()) {
151 sortOrder += wxT(",");
153 } else {
154 ++it;
158 cfg->Write(wxT("/eMule/TableOrdering") + m_name, sortOrder);
160 // Save column widths. ATM this is also used to signify hidden columns.
161 wxString buffer;
162 for (int i = 0; i < GetColumnCount(); ++i) {
163 wxString columnName = GetColumnName(i);
164 if (!columnName.IsEmpty()) {
165 if (!buffer.IsEmpty()) {
166 buffer << wxT(",");
168 buffer << columnName << wxT(":") << GetColumnWidth(i);
172 cfg->Write(wxT("/eMule/TableWidths") + m_name, buffer);
175 void CMuleListCtrl::ParseOldConfigEntries(const wxString& sortOrders, const wxString& columnWidths)
177 // Set sort order (including sort column)
178 wxStringTokenizer tokens(sortOrders, wxT(","));
179 while (tokens.HasMoreTokens()) {
180 wxString token = tokens.GetNextToken();
182 long column = 0;
183 unsigned long order = 0;
185 if (token.BeforeFirst(wxT(' ')).Strip(wxString::both).ToLong(&column)) {
186 if (token.AfterFirst(wxT(' ')).Strip(wxString::both).ToULong(&order)) {
187 column = GetNewColumnIndex(column);
188 // Sanity checking, to avoid asserting if column count changes.
189 if (column >= 0 && column < GetColumnCount()) {
190 // Sanity checking, to avoid asserting if data-format changes.
191 if ((order & ~SORTING_MASK) == 0) {
192 // SetSorting will take care of duplicate entries
193 SetSorting(column, order);
200 // Set column widths
201 int counter = 0;
202 wxStringTokenizer tokenizer(columnWidths, wxT(","));
203 while (tokenizer.HasMoreTokens()) {
204 long idx = GetNewColumnIndex(counter++);
205 long width = StrToLong(tokenizer.GetNextToken());
206 if (idx >= 0) {
207 SetColumnWidth(idx, width);
212 void CMuleListCtrl::LoadSettings()
214 wxCHECK_RET(!m_name.IsEmpty(), wxT("Cannot load settings for unnamed list"));
216 wxConfigBase* cfg = wxConfigBase::Get();
218 // Load sort order (including sort-column)
219 m_sort_orders.clear();
220 wxString sortOrders = cfg->Read(wxT("/eMule/TableOrdering") + m_name, wxEmptyString);
221 wxString columnWidths = cfg->Read(wxT("/eMule/TableWidths") + m_name, wxEmptyString);
223 // Prevent sorting from occuring when calling SetSorting
224 MuleListCtrlCompare sortFunc = m_sort_func;
225 m_sort_func = NULL;
227 if (columnWidths.Find(wxT(':')) == wxNOT_FOUND) {
228 // Old-style config entries...
229 ParseOldConfigEntries(sortOrders, columnWidths);
230 } else {
231 // Sort orders
232 wxStringTokenizer tokens(sortOrders, wxT(","));
233 while (tokens.HasMoreTokens()) {
234 wxString token = tokens.GetNextToken();
235 wxString name = token.BeforeFirst(wxT(':'));
236 long order = StrToLong(token.AfterFirst(wxT(':')).BeforeLast(wxT(':')));
237 long alt = StrToLong(token.AfterLast(wxT(':')));
238 int col = GetColumnIndex(name);
239 if (col > 0) {
240 SetSorting(col, (order ? SORT_DES : 0) | (alt ? SORT_ALT : 0));
244 // Column widths
245 wxStringTokenizer tkz(columnWidths, wxT(","));
246 while (tkz.HasMoreTokens()) {
247 wxString token = tkz.GetNextToken();
248 wxString name = token.BeforeFirst(wxT(':'));
249 long width = StrToLong(token.AfterFirst(wxT(':')));
250 int col = GetColumnIndex(name);
251 if (col > 0) {
252 SetColumnWidth(col, width);
257 // Must have at least one sort-order specified
258 if (m_sort_orders.empty()) {
259 m_sort_orders.push_back(CColPair(0, 0));
262 // Re-enable sorting and resort the contents (if any).
263 m_sort_func = sortFunc;
264 SortList();
268 const wxString& CMuleListCtrl::GetColumnName(int index) const
270 for (ColNameList::const_iterator it = m_column_names.begin(); it != m_column_names.end(); ++it) {
271 if (it->first == index) {
272 return it->second;
275 return EmptyString;
278 int CMuleListCtrl::GetColumnIndex(const wxString& name) const
280 for (ColNameList::const_iterator it = m_column_names.begin(); it != m_column_names.end(); ++it) {
281 if (it->second == name) {
282 return it->first;
285 return -1;
288 int CMuleListCtrl::GetNewColumnIndex(int oldindex) const
290 wxStringTokenizer oldcolumns(GetOldColumnOrder(), wxT(","), wxTOKEN_RET_EMPTY_ALL);
292 while (oldcolumns.HasMoreTokens()) {
293 wxString name = oldcolumns.GetNextToken();
294 if (oldindex == 0) {
295 return GetColumnIndex(name);
297 --oldindex;
299 return -1;
302 long CMuleListCtrl::GetInsertPos(wxUIntPtr data)
304 // Find the best place to position the item through a binary search
305 int Min = 0;
306 int Max = GetItemCount();
308 // Only do this if there are any items and a sorter function
309 if (Max && m_sort_func) {
310 // This search will narrow down the best place to position the new
311 // item. The result will be the item after that position, which is
312 // the format expected by the insertion function.
313 do {
314 int cur_pos = ( Max - Min ) / 2 + Min;
315 int cmp = CompareItems(data, GetItemData(cur_pos));
317 // Value is lesser than the one at the current pos
318 if ( cmp < 0 ) {
319 Max = cur_pos;
320 } else {
321 Min = cur_pos + 1;
323 } while ((Min != Max));
326 return Max;
331 int CMuleListCtrl::CompareItems(wxUIntPtr item1, wxUIntPtr item2)
333 CSortingList::const_iterator it = m_sort_orders.begin();
334 for (; it != m_sort_orders.end(); ++it) {
335 int result = m_sort_func(item1, item2, it->first | it->second);
336 if (result != 0) {
337 return result;
341 // Ensure that different items are never considered equal.
342 return CmpAny(item1, item2);
345 int CMuleListCtrl::SortProc(wxUIntPtr item1, wxUIntPtr item2, long data)
347 MuleSortData* sortdata = (MuleSortData*) data;
348 const CSortingList& orders = sortdata->m_sort_orders;
350 CSortingList::const_iterator it = orders.begin();
351 for (; it != orders.end(); ++it) {
352 int result = sortdata->m_sort_func(item1, item2, it->first | it->second);
353 if (result != 0) {
354 return result;
358 // Ensure that different items are never considered equal.
359 return CmpAny(item1, item2);
362 void CMuleListCtrl::SortList()
364 if (!IsSorting() && (m_sort_func && GetColumnCount())) {
366 m_isSorting = true;
368 // Positions are likely to be invalid after sorting.
369 ResetTTS();
371 MuleSortData sortdata(m_sort_orders, m_sort_func);
373 // Store the current selected items
374 ItemDataList selectedItems = GetSelectedItems();
375 // Store the focused item
376 long pos = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED );
377 wxUIntPtr focused = (pos == -1) ? 0 : GetItemData(pos);
379 SortItems(SortProc, (long int)&sortdata);
381 // Re-select the selected items.
382 for (unsigned i = 0; i < selectedItems.size(); ++i) {
383 long it_pos = FindItem(-1, selectedItems[i]);
384 if (it_pos != -1) {
385 SetItemState(it_pos, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
389 // Set focus on item if any was focused
390 if (focused) {
391 long it_pos = FindItem(-1, focused);
392 if (it_pos != -1) {
393 SetItemState(it_pos, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
397 m_isSorting = false;
401 CMuleListCtrl::ItemDataList CMuleListCtrl::GetSelectedItems() const
403 // Create the initial vector with as many items as are selected
404 ItemDataList list( GetSelectedItemCount() );
406 // Current item being located
407 unsigned int current = 0;
409 long pos = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
410 while ( pos != -1 ) {
411 wxASSERT( current < list.size() );
413 list[ current++ ] = GetItemData( pos );
415 pos = GetNextItem( pos, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
418 return list;
422 void CMuleListCtrl::OnColumnRClick(wxListEvent& evt)
424 wxMenu menu;
425 wxListItem item;
427 for ( int i = 0; i < GetColumnCount() && i < 15; ++i) {
428 GetColumn(i, item);
430 menu.AppendCheckItem(i + MP_LISTCOL_1, item.GetText() );
431 menu.Check( i + MP_LISTCOL_1, GetColumnWidth(i) > COL_SIZE_MIN );
434 PopupMenu(&menu, evt.GetPoint());
438 void CMuleListCtrl::OnMenuSelected( wxCommandEvent& evt )
440 int col = evt.GetId() - MP_LISTCOL_1;
442 if (GetColumnWidth(col) > COL_SIZE_MIN) {
443 SetColumnWidth(col, 0);
444 } else {
445 SetColumnWidth(col, wxLIST_AUTOSIZE);
450 void CMuleListCtrl::OnColumnLClick(wxListEvent& evt)
452 // Stop if no sorter-function has been defined
453 if (!m_sort_func) {
454 return;
455 } else if (evt.GetColumn() == -1) {
456 // This happens if a user clicks past the last column header.
457 return;
460 unsigned sort_order = 0;
461 if (m_sort_orders.front().first == (unsigned)evt.GetColumn()) {
462 // Same column as before, flip the sort-order
463 sort_order = m_sort_orders.front().second;
465 if (sort_order & SORT_DES) {
466 if (AltSortAllowed(evt.GetColumn())) {
467 sort_order = (~sort_order) & SORT_ALT;
468 } else {
469 sort_order = 0;
471 } else {
472 sort_order = SORT_DES | (sort_order & SORT_ALT);
475 m_sort_orders.pop_front();
476 } else {
477 // Check if the column has already been set
478 CSortingList::iterator it = m_sort_orders.begin();
479 for (; it != m_sort_orders.end(); ++it) {
480 if ((unsigned)evt.GetColumn() == it->first) {
481 sort_order = it->second;
482 break;
488 SetSorting(evt.GetColumn(), sort_order);
492 void CMuleListCtrl::ClearSelection()
494 if (GetSelectedItemCount()) {
495 long index = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
496 while (index != -1) {
497 SetItemState(index, 0, wxLIST_STATE_SELECTED);
498 index = GetNextItem(index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
504 bool CMuleListCtrl::AltSortAllowed(unsigned WXUNUSED(column)) const
506 return false;
510 void CMuleListCtrl::SetSorting(unsigned column, unsigned order)
512 MULE_VALIDATE_PARAMS(column < (unsigned)GetColumnCount(), wxT("Invalid column to sort by."));
513 MULE_VALIDATE_PARAMS(!(order & ~SORTING_MASK), wxT("Sorting order contains invalid data."));
515 if (!m_sort_orders.empty()) {
516 SetColumnImage(m_sort_orders.front().first, -1);
518 CSortingList::iterator it = m_sort_orders.begin();
519 for (; it != m_sort_orders.end(); ++it) {
520 if (it->first == column) {
521 m_sort_orders.erase(it);
522 break;
527 m_sort_orders.push_front(CColPair(column, order));
529 if (order & SORT_DES) {
530 SetColumnImage(column, (order & SORT_ALT) ? 2 : 0);
531 } else {
532 SetColumnImage(column, (order & SORT_ALT) ? 3 : 1);
535 SortList();
539 bool CMuleListCtrl::IsItemSorted(long item)
541 wxCHECK_MSG(m_sort_func, true, wxT("No sort function specified!"));
542 wxCHECK_MSG((item >= 0) && (item < GetItemCount()), true, wxT("Invalid item"));
544 bool sorted = true;
545 wxUIntPtr data = GetItemData(item);
547 // Check that the item before the current item is smaller (or equal)
548 if (item > 0) {
549 sorted &= (CompareItems(GetItemData(item - 1), data) <= 0);
552 // Check that the item after the current item is greater (or equal)
553 if (sorted && (item < GetItemCount() - 1)) {
554 sorted &= (CompareItems(GetItemData(item + 1), data) >= 0);
557 return sorted;
561 void CMuleListCtrl::OnMouseWheel(wxMouseEvent &event)
563 // This enables scrolling with the mouse wheel
564 event.Skip();
568 void CMuleListCtrl::SetColumnImage(unsigned col, int image)
570 wxListItem item;
571 item.SetMask(wxLIST_MASK_IMAGE);
572 item.SetImage(image);
573 SetColumn(col, item);
577 long CMuleListCtrl::CheckSelection(wxMouseEvent& event)
579 int flags = 0;
580 wxListEvent evt;
581 evt.m_itemIndex = HitTest(event.GetPosition(), flags);
583 return CheckSelection(evt);
587 long CMuleListCtrl::CheckSelection(wxListEvent& event)
589 long item = event.GetIndex();
591 // Check if clicked item is selected. If not, unselect all and select it.
592 if ((item != -1) && !GetItemState(item, wxLIST_STATE_SELECTED)) {
593 ClearSelection();
595 SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
598 return item;
602 wxString CMuleListCtrl::GetTTSText(unsigned item) const
604 MULE_VALIDATE_PARAMS(item < (unsigned)GetItemCount(), wxT("Invalid row."));
605 MULE_VALIDATE_STATE((GetWindowStyle() & wxLC_OWNERDRAW) == 0,
606 wxT("GetTTSText must be overwritten for owner-drawn lists."));
608 return GetItemText(item);
612 void CMuleListCtrl::OnChar(wxKeyEvent& evt)
614 int key = evt.GetKeyCode();
615 if (key == 0) {
616 // We prefer GetKeyCode() to GetUnicodeKey(), in order to work
617 // around a bug in the GetUnicodeKey(), that causes values to
618 // be returned untranslated. This means for instance, that if
619 // shift and '1' is pressed, the result is '1' rather than '!'
620 // (as it should be on my keyboard). This has been reported:
621 // http://sourceforge.net/tracker/index.php?func=detail&aid=1864810&group_id=9863&atid=109863
622 key = evt.GetUnicodeKey();
623 } else if (key >= WXK_START) {
624 // wxKeycodes are ignored, as they signify events such as the 'home'
625 // button. Unicoded chars are not checked as there is an overlap valid
626 // chars and the wx keycodes.
627 evt.Skip();
628 return;
631 // We wish to avoid handling shortcuts, with the exception of 'select-all'.
632 if (evt.AltDown() || evt.ControlDown() || evt.MetaDown()) {
633 if (evt.CmdDown() && (evt.GetKeyCode() == 0x01)) {
634 // Ctrl+a (Command+a on Mac) was pressed, select all items
635 for (int i = 0; i < GetItemCount(); ++i) {
636 SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
640 evt.Skip();
641 return;
642 } else if (m_tts_time + 1500u < GetTickCount()) {
643 m_tts_text.Clear();
646 m_tts_time = GetTickCount();
647 m_tts_text.Append(wxTolower(key));
649 // May happen if the subclass does not forward deletion events.
650 // Or rather when single-char-repeated (see below) wraps around, so don't assert.
651 if (m_tts_item >= GetItemCount()) {
652 m_tts_item = -1;
655 unsigned next = (m_tts_item == -1) ? 0 : m_tts_item;
656 for (unsigned i = 0, count = GetItemCount(); i < count; ++i) {
657 wxString text = GetTTSText((next + i) % count).MakeLower();
659 if (text.StartsWith(m_tts_text)) {
660 ClearSelection();
662 m_tts_item = (next + i) % count;
663 SetItemState(m_tts_item, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
664 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
665 EnsureVisible(m_tts_item);
667 return;
671 if (m_tts_item != -1) {
672 // Crop the string so that it matches the old item (avoid typos).
673 wxString text = GetTTSText(m_tts_item).MakeLower();
675 // If the last key didn't result in a hit, then we skip the event.
676 if (!text.StartsWith(m_tts_text)) {
677 if ((m_tts_text.Length() == 2) && (m_tts_text[0] == m_tts_text[1])) {
678 // Special case, single-char, repeated. This allows toggeling
679 // between items starting with a specific letter.
680 m_tts_text.Clear();
681 // Increment, so the next will be selected (or wrap around).
682 m_tts_item++;
683 OnChar(evt);
684 } else {
685 m_tts_text.RemoveLast();
686 evt.Skip(true);
689 } else {
690 evt.Skip(true);
695 void CMuleListCtrl::OnItemSelected(wxListEvent& evt)
697 if (IsSorting()) {
698 // Selection/Deselection that happened while sorting.
699 evt.Veto();
700 } else {
701 // We reset the current TTS session if the user manually changes the selection
702 if (m_tts_item != evt.GetIndex()) {
703 ResetTTS();
705 // The item is changed so that the next TTS starts from the selected item.
706 m_tts_item = evt.GetIndex();
708 evt.Skip();
713 void CMuleListCtrl::OnItemDeleted(wxListEvent& evt)
715 if (evt.GetIndex() <= m_tts_item) {
716 m_tts_item--;
719 evt.Skip();
723 void CMuleListCtrl::OnAllItemsDeleted(wxListEvent& evt)
725 ResetTTS();
727 evt.Skip();
731 void CMuleListCtrl::ResetTTS()
733 m_tts_item = -1;
734 m_tts_time = 0;
737 wxString CMuleListCtrl::GetOldColumnOrder() const
739 return wxEmptyString;
741 // File_checked_for_headers