Upstream tarball 20080304
[amule.git] / src / MuleListCtrl.cpp
bloba4783270a71a9624b83c595993a914a6a25b5b91
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_DELETE_ITEM(-1, CMuleListCtrl::OnItemDeleted)
63 EVT_LIST_DELETE_ALL_ITEMS(-1, CMuleListCtrl::OnAllItemsDeleted)
64 EVT_CHAR( CMuleListCtrl::OnChar)
65 EVT_MENU_RANGE(MP_LISTCOL_1, MP_LISTCOL_15, CMuleListCtrl::OnMenuSelected)
66 EVT_MOUSEWHEEL(CMuleListCtrl::OnMouseWheel)
67 END_EVENT_TABLE()
70 //! Shared list of arrow-pixmaps
71 static wxImageList imgList(16, 16, true, 0);
74 CMuleListCtrl::CMuleListCtrl(wxWindow *parent, wxWindowID winid, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name)
75 : MuleExtern::wxGenericListCtrl(parent, winid, pos, size, style, validator, name)
77 m_sort_func = NULL;
78 m_tts_time = 0;
79 m_tts_item = -1;
81 if (imgList.GetImageCount() == 0) {
82 imgList.Add(wxBitmap(sort_dn_xpm));
83 imgList.Add(wxBitmap(sort_up_xpm));
84 imgList.Add(wxBitmap(sort_dnx2_xpm));
85 imgList.Add(wxBitmap(sort_upx2_xpm));
88 // Default sort-order is to sort by the first column (asc).
89 m_sort_orders.push_back(CColPair(0, 0));
91 SetImageList(&imgList, wxIMAGE_LIST_SMALL);
95 CMuleListCtrl::~CMuleListCtrl()
97 if (!m_name.IsEmpty()) {
98 SaveSettings();
103 void CMuleListCtrl::SaveSettings()
105 wxCHECK_RET(!m_name.IsEmpty(), wxT("Cannot save settings for unnamed list"));
107 wxConfigBase* cfg = wxConfigBase::Get();
109 // Save sorting, column and order
110 wxString sortOrder;
111 for (CSortingList::iterator it = m_sort_orders.begin(); it != m_sort_orders.end(); ++it) {
112 sortOrder += wxString::Format(wxT("%u %u, "), it->first, it->second);
115 cfg->Write(wxT("/eMule/TableOrdering") + m_name, sortOrder);
117 // Save column widths. ATM this is also used to signify hidden columns.
118 wxString buffer;
119 for ( int i = 0; i < GetColumnCount(); ++i ) {
120 if ( i ) buffer << wxT(",");
122 buffer << GetColumnWidth(i);
125 cfg->Write( wxT("/eMule/TableWidths") +m_name, buffer );
129 void CMuleListCtrl::LoadSettings()
131 wxCHECK_RET(!m_name.IsEmpty(), wxT("Cannot load settings for unnamed list"));
133 wxConfigBase* cfg = wxConfigBase::Get();
135 // Load sort order (including sort-column)
136 m_sort_orders.clear();
137 wxString setting = cfg->Read(wxT("/eMule/TableOrdering") + m_name, wxEmptyString);
139 // Prevent sorting from occuring when calling SetSorting
140 MuleListCtrlCompare sortFunc = m_sort_func;
141 m_sort_func = NULL;
143 wxStringTokenizer tokens(setting, wxT(","));
144 while (tokens.HasMoreTokens()) {
145 wxString token = tokens.GetNextToken();
147 unsigned long column = 0, order = 0;
149 if (token.BeforeFirst(wxT(' ')).Strip(wxString::both).ToULong(&column)) {
150 if (token.AfterFirst(wxT(' ')).Strip(wxString::both).ToULong(&order)) {
151 // Sanity checking, to avoid asserting if column count changes.
152 if (column < (unsigned)GetColumnCount()) {
153 // Sanity checking, to avoid asserting if data-format changes.
154 if ((order & ~SORTING_MASK) == 0) {
155 // SetSorting will take care of duplicate entries
156 SetSorting(column, order);
163 // Must have at least one sort-order specified
164 if (m_sort_orders.empty()) {
165 m_sort_orders.push_back(CColPair(0, 0));
168 // Re-enable sorting and resort the contents (if any).
169 m_sort_func = sortFunc;
170 SortList();
172 // Set the column widths
173 wxString buffer;
174 if (cfg->Read( wxT("/eMule/TableWidths") + m_name, &buffer, wxEmptyString)) {
175 int counter = 0;
177 wxStringTokenizer tokenizer( buffer, wxT(",") );
178 while (tokenizer.HasMoreTokens() && (counter < GetColumnCount())) {
179 SetColumnWidth(counter++, StrToLong( tokenizer.GetNextToken()));
185 long CMuleListCtrl::GetInsertPos(wxUIntPtr data)
187 // Find the best place to position the item through a binary search
188 int Min = 0;
189 int Max = GetItemCount();
191 // Only do this if there are any items and a sorter function
192 if (Max && m_sort_func) {
193 // This search will narrow down the best place to position the new
194 // item. The result will be the item after that position, which is
195 // the format expected by the insertion function.
196 do {
197 int cur_pos = ( Max - Min ) / 2 + Min;
198 int cmp = CompareItems(data, GetItemData(cur_pos));
200 // Value is lesser than the one at the current pos
201 if ( cmp < 0 ) {
202 Max = cur_pos;
203 } else {
204 Min = cur_pos + 1;
206 } while ((Min != Max));
209 return Max;
214 int CMuleListCtrl::CompareItems(wxUIntPtr item1, wxUIntPtr item2)
216 CSortingList::const_iterator it = m_sort_orders.begin();
217 for (; it != m_sort_orders.end(); ++it) {
218 int result = m_sort_func(item1, item2, it->first | it->second);
219 if (result != 0) {
220 return result;
224 // Ensure that different items are never considered equal.
225 return CmpAny(item1, item2);
229 MuleListCtrlCompare g_sort_func = NULL;
230 CMuleListCtrl* g_sort_list = NULL;
233 int CMuleListCtrl::SortProc(wxUIntPtr item1, wxUIntPtr item2, long)
235 const CSortingList& orders = g_sort_list->m_sort_orders;
237 CSortingList::const_iterator it = orders.begin();
238 for (; it != orders.end(); ++it) {
239 int result = g_sort_func(item1, item2, it->first | it->second);
240 if (result != 0) {
241 return result;
245 // Ensure that different items are never considered equal.
246 return CmpAny(item1, item2);
250 void CMuleListCtrl::SortList()
252 wxCHECK_RET(g_sort_func == NULL, wxT("Sort-function already set"));
253 wxCHECK_RET(g_sort_list == NULL, wxT("Sort-list already set"));
255 if (m_sort_func && GetColumnCount()) {
256 // Positions are likely to be invalid after sorting.
257 ResetTTS();
259 g_sort_func = m_sort_func;
260 g_sort_list = this;
262 SortItems(SortProc, 0);
264 g_sort_func = NULL;
265 g_sort_list = NULL;
270 CMuleListCtrl::ItemDataList CMuleListCtrl::GetSelectedItems() const
272 // Create the initial vector with as many items as are selected
273 ItemDataList list( GetSelectedItemCount() );
275 // Current item being located
276 unsigned int current = 0;
278 long pos = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
279 while ( pos != -1 ) {
280 wxASSERT( current < list.size() );
282 list[ current++ ] = GetItemData( pos );
284 pos = GetNextItem( pos, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
287 return list;
291 void CMuleListCtrl::OnColumnRClick(wxListEvent& evt)
293 wxMenu menu;
294 wxListItem item;
296 for ( int i = 0; i < GetColumnCount() && i < 15; ++i) {
297 GetColumn(i, item);
299 menu.AppendCheckItem(i + MP_LISTCOL_1, item.GetText() );
300 menu.Check( i + MP_LISTCOL_1, GetColumnWidth(i) > COL_SIZE_MIN );
303 PopupMenu(&menu, evt.GetPoint());
307 void CMuleListCtrl::OnMenuSelected( wxCommandEvent& evt )
309 int col = evt.GetId() - MP_LISTCOL_1;
311 if (GetColumnWidth(col) > COL_SIZE_MIN) {
312 SetColumnWidth(col, 0);
313 } else {
314 SetColumnWidth(col, wxLIST_AUTOSIZE);
319 void CMuleListCtrl::OnColumnLClick(wxListEvent& evt)
321 // Stop if no sorter-function has been defined
322 if (!m_sort_func) {
323 return;
324 } else if (evt.GetColumn() == -1) {
325 // This happens if a user clicks past the last column header.
326 return;
329 // Get the currently focused item
330 long pos = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED );
331 wxUIntPtr item = 0;
332 if (pos != -1) {
333 item = GetItemData(pos);
337 unsigned sort_order = 0;
338 if (m_sort_orders.front().first == (unsigned)evt.GetColumn()) {
339 // Same column as before, flip the sort-order
340 sort_order = m_sort_orders.front().second;
342 if (sort_order & SORT_DES) {
343 if (AltSortAllowed(evt.GetColumn())) {
344 sort_order = (~sort_order) & SORT_ALT;
345 } else {
346 sort_order = 0;
348 } else {
349 sort_order = SORT_DES | (sort_order & SORT_ALT);
352 m_sort_orders.pop_front();
353 } else {
354 // Check if the column has already been set
355 CSortingList::iterator it = m_sort_orders.begin();
356 for (; it != m_sort_orders.end(); ++it) {
357 if ((unsigned)evt.GetColumn() == it->first) {
358 sort_order = it->second;
359 break;
364 SetSorting(evt.GetColumn(), sort_order);
367 // Set focus on item if any was focused
368 if (item != 0) {
369 long it_pos = FindItem(-1, item);
370 if (it_pos != -1) {
371 SetItemState(it_pos,wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
377 void CMuleListCtrl::ClearSelection()
379 if (GetSelectedItemCount()) {
380 long index = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
381 while (index != -1) {
382 SetItemState(index, 0, wxLIST_STATE_SELECTED);
383 index = GetNextItem(index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
389 void CMuleListCtrl::SetTableName(const wxString& name)
391 m_name = name;
395 unsigned CMuleListCtrl::GetSortColumn() const
397 return m_sort_orders.front().first;
401 unsigned CMuleListCtrl::GetSortOrder() const
403 return m_sort_orders.front().second;
407 void CMuleListCtrl::SetSortFunc(MuleListCtrlCompare func)
409 m_sort_func = func;
413 bool CMuleListCtrl::AltSortAllowed(unsigned WXUNUSED(column)) const
415 return false;
419 void CMuleListCtrl::SetSorting(unsigned column, unsigned order)
421 MULE_VALIDATE_PARAMS(column < (unsigned)GetColumnCount(), wxT("Invalid column to sort by."));
422 MULE_VALIDATE_PARAMS(!(order & ~SORTING_MASK), wxT("Sorting order contains invalid data."));
424 if (!m_sort_orders.empty()) {
425 SetColumnImage(m_sort_orders.front().first, -1);
427 CSortingList::iterator it = m_sort_orders.begin();
428 for (; it != m_sort_orders.end(); ++it) {
429 if (it->first == column) {
430 m_sort_orders.erase(it);
431 break;
436 m_sort_orders.push_front(CColPair(column, order));
438 if (order & SORT_DES) {
439 SetColumnImage(column, (order & SORT_ALT) ? 2 : 0);
440 } else {
441 SetColumnImage(column, (order & SORT_ALT) ? 3 : 1);
444 SortList();
448 bool CMuleListCtrl::IsItemSorted(long item)
450 wxCHECK_MSG(m_sort_func, true, wxT("No sort function specified!"));
451 wxCHECK_MSG((item >= 0) && (item < GetItemCount()), true, wxT("Invalid item"));
453 bool sorted = true;
454 wxUIntPtr data = GetItemData(item);
456 // Check that the item before the current item is smaller (or equal)
457 if (item > 0) {
458 sorted &= (CompareItems(GetItemData(item - 1), data) <= 0);
461 // Check that the item after the current item is greater (or equal)
462 if (sorted && (item < GetItemCount() - 1)) {
463 sorted &= (CompareItems(GetItemData(item + 1), data) >= 0);
466 return sorted;
470 void CMuleListCtrl::OnMouseWheel(wxMouseEvent &event)
472 // This enables scrolling with the mouse wheel
473 event.Skip();
477 void CMuleListCtrl::SetColumnImage(unsigned col, int image)
479 wxListItem item;
480 item.SetMask(wxLIST_MASK_IMAGE);
481 item.SetImage(image);
482 SetColumn(col, item);
486 long CMuleListCtrl::CheckSelection(wxMouseEvent& event)
488 int flags = 0;
489 wxListEvent evt;
490 evt.m_itemIndex = HitTest(event.GetPosition(), flags);
492 return CheckSelection(evt);
496 long CMuleListCtrl::CheckSelection(wxListEvent& event)
498 long item = event.GetIndex();
500 // Check if clicked item is selected. If not, unselect all and select it.
501 if ((item != -1) && !GetItemState(item, wxLIST_STATE_SELECTED)) {
502 ClearSelection();
504 SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
507 return item;
511 wxString CMuleListCtrl::GetTTSText(unsigned item) const
513 MULE_VALIDATE_PARAMS(item < (unsigned)GetItemCount(), wxT("Invalid row."));
514 MULE_VALIDATE_STATE((GetWindowStyle() & wxLC_OWNERDRAW) == 0,
515 wxT("GetTTSText must be overwritten for owner-drawn lists."));
517 return GetItemText(item);
521 void CMuleListCtrl::OnChar(wxKeyEvent& evt)
523 wxChar key = evt.GetKeyCode();
524 if (key == 0) {
525 // We prefer GetKeyCode() to GetUnicodeKey(), in order to work
526 // around a bug in the GetUnicodeKey(), that causes values to
527 // be returned untranslated. This means for instance, that if
528 // shift and '1' is pressed, the result is '1' rather than '!'
529 // (as it should be on my keyboard). This has been reported:
530 // http://sourceforge.net/tracker/index.php?func=detail&aid=1864810&group_id=9863&atid=109863
531 key = evt.GetUnicodeKey();
532 } else if (key >= WXK_START) {
533 // wxKeycodes are ignored, as they signify events such as the 'home'
534 // button. Unicoded chars are not checke as there is an overlap valid
535 // chars and the wx keycodes.
536 evt.Skip();
537 return;
540 // We wish to avoid handling shortcuts, with the exception of 'select-all'.
541 if (evt.AltDown() || evt.ControlDown() || evt.MetaDown()) {
542 if (evt.CmdDown() && (evt.GetKeyCode() == 0x01)) {
543 // Ctrl+a (Command+a on Mac) was pressed, select all items
544 for (int i = 0; i < GetItemCount(); ++i) {
545 SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
549 evt.Skip();
550 return;
551 } else if (m_tts_time + 1500u < GetTickCount()) {
552 m_tts_text.Clear();
555 m_tts_time = GetTickCount();
556 m_tts_text.Append(wxTolower(key));
558 // May happen if the subclass does not forward deletion events.
559 if (m_tts_item >= GetItemCount()) {
560 wxASSERT(0);
561 m_tts_item = -1;
564 unsigned next = (m_tts_item == -1) ? 0 : m_tts_item;
565 for (unsigned i = 0, count = GetItemCount(); i < count; ++i) {
566 wxString text = GetTTSText((next + i) % count).MakeLower();
568 if (text.StartsWith(m_tts_text)) {
569 ClearSelection();
571 m_tts_item = (next + i) % count;
572 SetItemState(m_tts_item, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
573 wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
574 EnsureVisible(m_tts_item);
576 return;
580 if (m_tts_item != -1) {
581 // Crop the string so that it matches the old item (avoid typos).
582 wxString text = GetTTSText(m_tts_item).MakeLower();
584 // If the last key didn't result in a hit, then we skip the event.
585 if (!text.StartsWith(m_tts_text)) {
586 if ((m_tts_text.Length() == 2) && (m_tts_text[0] == m_tts_text[1])) {
587 // Special case, single-char, repeated. This allows toggeling
588 // between items starting with a specific letter.
589 m_tts_text.Clear();
590 // Increment, so the next will be selected (or wrap around).
591 m_tts_item++;
592 OnChar(evt);
593 } else {
594 m_tts_text.RemoveLast();
595 evt.Skip(true);
598 } else {
599 evt.Skip(true);
604 void CMuleListCtrl::OnItemSelected(wxListEvent& evt)
606 // We reset the current TTS session if the user manually changes the selection
607 if (m_tts_item != evt.GetIndex()) {
608 ResetTTS();
610 // The item is changed so that the next TTS starts from the selected item.
611 m_tts_item = evt.GetIndex();
614 evt.Skip();
618 void CMuleListCtrl::OnItemDeleted(wxListEvent& evt)
620 if (evt.GetIndex() <= m_tts_item) {
621 m_tts_item--;
624 evt.Skip();
628 void CMuleListCtrl::OnAllItemsDeleted(wxListEvent& evt)
630 ResetTTS();
632 evt.Skip();
636 void CMuleListCtrl::ResetTTS()
638 m_tts_item = -1;
639 m_tts_time = 0;
641 // File_checked_for_headers