1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2011 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.
10 //
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.
15 //
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
24 //
25 
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
30 
31 #include <common/MuleDebug.h>			// Needed for MULE_VALIDATE_
32 #include <common/StringFunctions.h>		// Needed for StrToLong
33 
34 #include <common/MenuIDs.h>
35 
36 #include "MuleListCtrl.h"		// Interface declarations
37 #include "GetTickCount.h"		// Needed for GetTickCount()
38 #include "OtherFunctions.h"
39 
40 
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"
46 
47 
48 // Global constants
49 #ifdef __WXGTK__
50 	const int COL_SIZE_MIN = 10;
51 #elif defined(__WINDOWS__) || 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
56 
57 
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()
69 
70 
71 //! Shared list of arrow-pixmaps
72 static wxImageList imgList(16, 16, true, 0);
73 
74 
CMuleListCtrl(wxWindow * parent,wxWindowID winid,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)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)
77 {
78 	m_sort_func = NULL;
79 	m_tts_time = 0;
80 	m_tts_item = -1;
81 	m_isSorting = false;
82 
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));
88 	}
89 
90 	// Default sort-order is to sort by the first column (asc).
91 	m_sort_orders.push_back(CColPair(0, 0));
92 
93 	SetImageList(&imgList, wxIMAGE_LIST_SMALL);
94 }
95 
96 
~CMuleListCtrl()97 CMuleListCtrl::~CMuleListCtrl()
98 {
99 	if (!m_name.IsEmpty()) {
100 		SaveSettings();
101 	}
102 }
103 
InsertColumn(long col,const wxString & heading,int format,int width,const wxString & name)104 long CMuleListCtrl::InsertColumn(long col, const wxString& heading, int format, int width, const wxString& name)
105 {
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!"));
111 
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->name) {
115 				wxFAIL_MSG(wxT("Column name \"") + name + wxT("\" is not unique!"));
116 			}
117 		}
118 #endif
119 		// Insert name at position col.
120 		ColNameList::iterator it = m_column_names.begin();
121 		while (it != m_column_names.end() && it->index < col) {
122 			++it;
123 		}
124 		m_column_names.insert(it, ColNameEntry(col, width, name));
125 		while (it != m_column_names.end()) {
126 			++it;
127 			++(it->index);
128 		}
129 	}
130 
131 	return MuleExtern::wxGenericListCtrl::InsertColumn(col, heading, format, width);
132 }
133 
SaveSettings()134 void CMuleListCtrl::SaveSettings()
135 {
136 	wxCHECK_RET(!m_name.IsEmpty(), wxT("Cannot save settings for unnamed list"));
137 
138 	wxConfigBase* cfg = wxConfigBase::Get();
139 
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(",");
152 			}
153 		} else {
154 			++it;
155 		}
156 	}
157 
158 	cfg->Write(wxT("/eMule/TableOrdering") + m_name, sortOrder);
159 
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(",");
167 			}
168 			int currentwidth = GetColumnWidth(i);
169 			int savedsize = (m_column_sizes.size() && (i < (int) m_column_sizes.size())) ? m_column_sizes[i] : 0;
170 			buffer << columnName << wxT(":") << ((currentwidth > 0) ? currentwidth : (-1 * savedsize));
171 		}
172 	}
173 
174 	cfg->Write(wxT("/eMule/TableWidths") + m_name, buffer);
175 }
176 
ParseOldConfigEntries(const wxString & sortOrders,const wxString & columnWidths)177 void CMuleListCtrl::ParseOldConfigEntries(const wxString& sortOrders, const wxString& columnWidths)
178 {
179 	// Set sort order (including sort column)
180 	wxStringTokenizer tokens(sortOrders, wxT(","));
181 	while (tokens.HasMoreTokens()) {
182 		wxString token = tokens.GetNextToken();
183 
184 		long column = 0;
185 		unsigned long order = 0;
186 
187 		if (token.BeforeFirst(wxT(' ')).Strip(wxString::both).ToLong(&column)) {
188 			if (token.AfterFirst(wxT(' ')).Strip(wxString::both).ToULong(&order)) {
189 				column = GetNewColumnIndex(column);
190 				// Sanity checking, to avoid asserting if column count changes.
191 				if (column >= 0 && column < GetColumnCount()) {
192 					// Sanity checking, to avoid asserting if data-format changes.
193 					if ((order & ~SORTING_MASK) == 0) {
194 						// SetSorting will take care of duplicate entries
195 						SetSorting(column, order);
196 					}
197 				}
198 			}
199 		}
200 	}
201 
202 	// Set column widths
203 	int counter = 0;
204 	wxStringTokenizer tokenizer(columnWidths, wxT(","));
205 	while (tokenizer.HasMoreTokens()) {
206 		long idx = GetNewColumnIndex(counter++);
207 		long width = StrToLong(tokenizer.GetNextToken());
208 		if (idx >= 0) {
209 			SetColumnWidth(idx, width);
210 		}
211 	}
212 }
213 
LoadSettings()214 void CMuleListCtrl::LoadSettings()
215 {
216 	wxCHECK_RET(!m_name.IsEmpty(), wxT("Cannot load settings for unnamed list"));
217 
218 	wxConfigBase* cfg = wxConfigBase::Get();
219 
220 	// Load sort order (including sort-column)
221 	m_sort_orders.clear();
222 	wxString sortOrders = cfg->Read(wxT("/eMule/TableOrdering") + m_name, wxEmptyString);
223 	wxString columnWidths = cfg->Read(wxT("/eMule/TableWidths") + m_name, wxEmptyString);
224 
225 	// Prevent sorting from occuring when calling SetSorting
226 	MuleListCtrlCompare sortFunc = m_sort_func;
227 	m_sort_func = NULL;
228 
229 	if (columnWidths.Find(wxT(':')) == wxNOT_FOUND) {
230 		// Old-style config entries...
231 		ParseOldConfigEntries(sortOrders, columnWidths);
232 	} else {
233 		// Sort orders
234 		wxStringTokenizer tokens(sortOrders, wxT(","));
235 		// Sort orders are stored in order primary, secondary, ...
236 		// We want to apply them with SetSorting(), so we have to apply them in reverse order,
237 		// so that the primary order is applied last and wins.
238 		// Read them with tokenizer and store them in a list in reverse order.
239 		CStringList tokenList;
240 		while (tokens.HasMoreTokens()) {
241 			tokenList.push_front(tokens.GetNextToken());
242 		}
243 		for (CStringList::iterator it = tokenList.begin(); it != tokenList.end(); ++it) {
244 			wxString token = *it;
245 			wxString name = token.BeforeFirst(wxT(':'));
246 			long order = StrToLong(token.AfterFirst(wxT(':')).BeforeLast(wxT(':')));
247 			long alt = StrToLong(token.AfterLast(wxT(':')));
248 			int col = GetColumnIndex(name);
249 			if (col >= 0) {
250 				SetSorting(col, (order ? SORT_DES : 0) | (alt ? SORT_ALT : 0));
251 			}
252 		}
253 
254 		// Column widths
255 		wxStringTokenizer tkz(columnWidths, wxT(","));
256 		while (tkz.HasMoreTokens()) {
257 			wxString token = tkz.GetNextToken();
258 			wxString name = token.BeforeFirst(wxT(':'));
259 			long width = StrToLong(token.AfterFirst(wxT(':')));
260 			int col = GetColumnIndex(name);
261 			if (col >= 0) {
262 				if (col >= (int) m_column_sizes.size()) {
263 					m_column_sizes.resize(col + 1, 0);
264 				}
265 				m_column_sizes[col] = abs(width);
266 				SetColumnWidth(col, (width > 0) ? width : 0);
267 			}
268 		}
269 	}
270 
271 	// Must have at least one sort-order specified
272 	if (m_sort_orders.empty()) {
273 		m_sort_orders.push_back(CColPair(0, 0));
274 	}
275 
276 	// Re-enable sorting and resort the contents (if any).
277 	m_sort_func = sortFunc;
278 	SortList();
279 }
280 
281 
GetColumnName(int index) const282 const wxString& CMuleListCtrl::GetColumnName(int index) const
283 {
284 	for (ColNameList::const_iterator it = m_column_names.begin(); it != m_column_names.end(); ++it) {
285 		if (it->index == index) {
286 			return it->name;
287 		}
288 	}
289 	return EmptyString;
290 }
291 
GetColumnDefaultWidth(int index) const292 int CMuleListCtrl::GetColumnDefaultWidth(int index) const
293 {
294 	for (ColNameList::const_iterator it = m_column_names.begin(); it != m_column_names.end(); ++it) {
295 		if (it->index == index) {
296 			return it->defaultWidth;
297 		}
298 	}
299 	return wxLIST_AUTOSIZE;
300 }
301 
GetColumnIndex(const wxString & name) const302 int CMuleListCtrl::GetColumnIndex(const wxString& name) const
303 {
304 	for (ColNameList::const_iterator it = m_column_names.begin(); it != m_column_names.end(); ++it) {
305 		if (it->name == name) {
306 			return it->index;
307 		}
308 	}
309 	return -1;
310 }
311 
GetNewColumnIndex(int oldindex) const312 int CMuleListCtrl::GetNewColumnIndex(int oldindex) const
313 {
314 	wxStringTokenizer oldcolumns(GetOldColumnOrder(), wxT(","), wxTOKEN_RET_EMPTY_ALL);
315 
316 	while (oldcolumns.HasMoreTokens()) {
317 		wxString name = oldcolumns.GetNextToken();
318 		if (oldindex == 0) {
319 			return GetColumnIndex(name);
320 		}
321 		--oldindex;
322 	}
323 	return -1;
324 }
325 
GetInsertPos(wxUIntPtr data)326 long CMuleListCtrl::GetInsertPos(wxUIntPtr data)
327 {
328 	// Find the best place to position the item through a binary search
329 	int Min = 0;
330 	int Max = GetItemCount();
331 
332 	// Only do this if there are any items and a sorter function.
333 	// Otherwise insert at end.
334 	if (Max && m_sort_func) {
335 		// This search will find the place to position the new item
336 		// so it matches current sorting.
337 		// The result will be the position the new item will have
338 		// after insertion, which is the format expected by the InsertItem function.
339 		// If the item equals another item it will be inserted after it.
340 		do {
341 			int cur_pos = ( Max - Min ) / 2 + Min;
342 			int cmp = CompareItems(data, GetItemData(cur_pos));
343 
344 			// Value is less than the one at the current pos
345 			if ( cmp < 0 ) {
346 				Max = cur_pos;
347 			} else {
348 				Min = cur_pos + 1;
349 			}
350 		} while ((Min != Max));
351 	}
352 
353 	return Max;
354 }
355 
356 
CompareItems(wxUIntPtr item1,wxUIntPtr item2)357 int CMuleListCtrl::CompareItems(wxUIntPtr item1, wxUIntPtr item2)
358 {
359 	CSortingList::const_iterator it = m_sort_orders.begin();
360 	for (; it != m_sort_orders.end(); ++it) {
361 		int result = m_sort_func(item1, item2, it->first | it->second);
362 		if (result != 0) {
363 			return result;
364 		}
365 	}
366 
367 	// Ensure that different items are never considered equal.
368 	return CmpAny(item1, item2);
369 }
370 
SortProc(wxUIntPtr item1,wxUIntPtr item2,long data)371 int CMuleListCtrl::SortProc(wxUIntPtr item1, wxUIntPtr item2, long data)
372 {
373 	MuleSortData* sortdata = reinterpret_cast<MuleSortData*>(data);
374 	const CSortingList& orders = sortdata->m_sort_orders;
375 
376 	CSortingList::const_iterator it = orders.begin();
377 	for (; it != orders.end(); ++it) {
378 		int result = sortdata->m_sort_func(item1, item2, it->first | it->second);
379 		if (result != 0) {
380 			return result;
381 		}
382 	}
383 
384 	// Ensure that different items are never considered equal.
385 	return CmpAny(item1, item2);
386 }
387 
SortList()388 void CMuleListCtrl::SortList()
389 {
390 	if (!IsSorting() && (m_sort_func && GetColumnCount())) {
391 
392 		m_isSorting = true;
393 
394 		MuleSortData sortdata(m_sort_orders, m_sort_func);
395 
396 		// In many cases control already has correct order, and sorting causes nasty flickering.
397 		// Make one pass through it to check if sorting is necessary at all.
398 		int nrItems = GetItemCount();
399 		bool clean = true;
400 		long lastItemdata = 0;
401 		if (nrItems > 1) {
402 			lastItemdata = GetItemData(0);
403 		}
404 		for (int i = 1; i < nrItems; i++) {
405 			long nextItemdata = GetItemData(i);
406 			if (SortProc(lastItemdata, nextItemdata, (long int)&sortdata) > 0) {
407 				// ok - we need to sort
408 				clean = false;
409 				break;
410 			}
411 			lastItemdata = nextItemdata;
412 		}
413 		if (clean) {
414 			// no need to sort
415 			m_isSorting = false;
416 			return;
417 		}
418 
419 		// Positions are likely to be invalid after sorting.
420 		ResetTTS();
421 
422 		// Store the current selected items
423 		ItemDataList selectedItems = GetSelectedItems();
424 		// Store the focused item
425 		long pos = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED );
426 		wxUIntPtr focused = (pos == -1) ? 0 : GetItemData(pos);
427 
428 		SortItems(SortProc, (long int)&sortdata);
429 
430 		// Re-select the selected items.
431 		for (unsigned i = 0; i < selectedItems.size(); ++i) {
432 			long it_pos = FindItem(-1, selectedItems[i]);
433 			if (it_pos != -1) {
434 				SetItemState(it_pos, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
435 			}
436 		}
437 
438 		// Set focus on item if any was focused
439 		if (focused) {
440 			long it_pos = FindItem(-1, focused);
441 			if (it_pos != -1) {
442 				SetItemState(it_pos, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
443 			}
444 		}
445 
446 		m_isSorting = false;
447 	}
448 }
449 
GetSelectedItems() const450 CMuleListCtrl::ItemDataList CMuleListCtrl::GetSelectedItems() const
451 {
452 	// Create the initial vector with as many items as are selected
453 	ItemDataList list( GetSelectedItemCount() );
454 
455 	// Current item being located
456 	unsigned int current = 0;
457 
458 	long pos = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
459 	while ( pos != -1 ) {
460 		wxASSERT( current < list.size() );
461 
462 		list[ current++ ] = GetItemData( pos );
463 
464 		pos = GetNextItem( pos, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
465 	}
466 
467 	return list;
468 }
469 
470 
OnColumnRClick(wxListEvent & evt)471 void CMuleListCtrl::OnColumnRClick(wxListEvent& evt)
472 {
473 	wxMenu menu;
474 	wxListItem item;
475 
476 	for ( int i = 0; i < GetColumnCount() && i < 15; ++i) {
477 		GetColumn(i, item);
478 
479 		menu.AppendCheckItem(i + MP_LISTCOL_1, item.GetText() );
480 		menu.Check( i + MP_LISTCOL_1, GetColumnWidth(i) > COL_SIZE_MIN );
481 	}
482 
483 	PopupMenu(&menu, evt.GetPoint());
484 }
485 
486 
OnMenuSelected(wxCommandEvent & evt)487 void CMuleListCtrl::OnMenuSelected( wxCommandEvent& evt )
488 {
489 	unsigned int col = evt.GetId() - MP_LISTCOL_1;
490 
491 	if (col >= m_column_sizes.size()) {
492 		m_column_sizes.resize(col + 1, 0);
493 	}
494 
495 	if (GetColumnWidth(col) > COL_SIZE_MIN) {
496 		m_column_sizes[col] = GetColumnWidth(col);
497 		SetColumnWidth(col, 0);
498 	} else {
499 		int oldsize = m_column_sizes[col];
500 		SetColumnWidth(col, (oldsize > 0) ? oldsize : GetColumnDefaultWidth(col));
501 	}
502 }
503 
504 
OnColumnLClick(wxListEvent & evt)505 void CMuleListCtrl::OnColumnLClick(wxListEvent& evt)
506 {
507 	// Stop if no sorter-function has been defined
508 	if (!m_sort_func) {
509 		return;
510 	} else if (evt.GetColumn() == -1) {
511 		// This happens if a user clicks past the last column header.
512 		return;
513 	}
514 
515 	unsigned sort_order = 0;
516 	if (m_sort_orders.front().first == (unsigned)evt.GetColumn()) {
517 		// Same column as before, flip the sort-order
518 		sort_order = m_sort_orders.front().second;
519 
520 		if (sort_order & SORT_DES) {
521 			if (AltSortAllowed(evt.GetColumn())) {
522 				sort_order = (~sort_order) & SORT_ALT;
523 			} else {
524 				sort_order = 0;
525 			}
526 		} else {
527 			sort_order = SORT_DES | (sort_order & SORT_ALT);
528 		}
529 
530 		m_sort_orders.pop_front();
531 	} else {
532 		// Check if the column has already been set
533 		CSortingList::iterator it = m_sort_orders.begin();
534 		for (; it != m_sort_orders.end(); ++it) {
535 			if ((unsigned)evt.GetColumn() == it->first) {
536 				sort_order = it->second;
537 				break;
538 			}
539 		}
540 	}
541 
542 
543 	SetSorting(evt.GetColumn(), sort_order);
544 }
545 
546 
ClearSelection()547 void CMuleListCtrl::ClearSelection()
548 {
549 	if (GetSelectedItemCount()) {
550 		long index = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
551 		while (index != -1) {
552 			SetItemState(index, 0, wxLIST_STATE_SELECTED);
553 			index = GetNextItem(index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
554 		}
555 	}
556 }
557 
558 
AltSortAllowed(unsigned WXUNUSED (column)) const559 bool CMuleListCtrl::AltSortAllowed(unsigned WXUNUSED(column)) const
560 {
561 	return false;
562 }
563 
564 
SetSorting(unsigned column,unsigned order)565 void CMuleListCtrl::SetSorting(unsigned column, unsigned order)
566 {
567 	MULE_VALIDATE_PARAMS(column < (unsigned)GetColumnCount(), wxT("Invalid column to sort by."));
568 	MULE_VALIDATE_PARAMS(!(order & ~SORTING_MASK), wxT("Sorting order contains invalid data."));
569 
570 	if (!m_sort_orders.empty()) {
571 		SetColumnImage(m_sort_orders.front().first, -1);
572 
573 		CSortingList::iterator it = m_sort_orders.begin();
574 		for (; it != m_sort_orders.end(); ++it) {
575 			if (it->first == column) {
576 				m_sort_orders.erase(it);
577 				break;
578 			}
579 		}
580 	}
581 
582 	m_sort_orders.push_front(CColPair(column, order));
583 
584 	if (order & SORT_DES) {
585 		SetColumnImage(column, (order & SORT_ALT) ? 2 : 0);
586 	} else {
587 		SetColumnImage(column, (order & SORT_ALT) ? 3 : 1);
588 	}
589 
590 	SortList();
591 }
592 
593 
IsItemSorted(long item)594 bool CMuleListCtrl::IsItemSorted(long item)
595 {
596 	wxCHECK_MSG(m_sort_func, true, wxT("No sort function specified!"));
597 	wxCHECK_MSG((item >= 0) && (item < GetItemCount()), true, wxT("Invalid item"));
598 
599 	bool sorted = true;
600 	wxUIntPtr data = GetItemData(item);
601 
602 	// Check that the item before the current item is smaller (or equal)
603 	if (item > 0) {
604 		sorted &= (CompareItems(GetItemData(item - 1), data) <= 0);
605 	}
606 
607 	// Check that the item after the current item is greater (or equal)
608 	if (sorted && (item < GetItemCount() - 1)) {
609 		sorted &= (CompareItems(GetItemData(item + 1), data) >= 0);
610 	}
611 
612 	return sorted;
613 }
614 
615 
OnMouseWheel(wxMouseEvent & event)616 void CMuleListCtrl::OnMouseWheel(wxMouseEvent &event)
617 {
618 	// This enables scrolling with the mouse wheel
619 	event.Skip();
620 }
621 
622 
SetColumnImage(unsigned col,int image)623 void CMuleListCtrl::SetColumnImage(unsigned col, int image)
624 {
625 	wxListItem item;
626 	item.SetMask(wxLIST_MASK_IMAGE);
627 	item.SetImage(image);
628 	SetColumn(col, item);
629 }
630 
631 
CheckSelection(wxMouseEvent & event)632 long CMuleListCtrl::CheckSelection(wxMouseEvent& event)
633 {
634 	int flags = 0;
635 	wxListEvent evt;
636 	evt.m_itemIndex = HitTest(event.GetPosition(), flags);
637 
638 	return CheckSelection(evt);
639 }
640 
641 
CheckSelection(wxListEvent & event)642 long CMuleListCtrl::CheckSelection(wxListEvent& event)
643 {
644 	long item = event.GetIndex();
645 
646 	// Check if clicked item is selected. If not, unselect all and select it.
647 	if ((item != -1) && !GetItemState(item, wxLIST_STATE_SELECTED)) {
648 		ClearSelection();
649 
650 		SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
651 	}
652 
653 	return item;
654 }
655 
656 
GetTTSText(unsigned item) const657 wxString CMuleListCtrl::GetTTSText(unsigned item) const
658 {
659 	MULE_VALIDATE_PARAMS(item < (unsigned)GetItemCount(), wxT("Invalid row."));
660 	MULE_VALIDATE_STATE((GetWindowStyle() & wxLC_OWNERDRAW) == 0,
661 		wxT("GetTTSText must be overwritten for owner-drawn lists."));
662 
663 	return GetItemText(item);
664 }
665 
666 
OnChar(wxKeyEvent & evt)667 void CMuleListCtrl::OnChar(wxKeyEvent& evt)
668 {
669 	int key = evt.GetKeyCode();
670 	if (key == 0) {
671 		// We prefer GetKeyCode() to GetUnicodeKey(), in order to work
672 		// around a bug in the GetUnicodeKey(), that causes values to
673 		// be returned untranslated. This means for instance, that if
674 		// shift and '1' is pressed, the result is '1' rather than '!'
675 		// (as it should be on my keyboard). This has been reported:
676 		// http://sourceforge.net/tracker/index.php?func=detail&aid=1864810&group_id=9863&atid=109863
677 		key = evt.GetUnicodeKey();
678 	} else if (key >= WXK_START) {
679 		// wxKeycodes are ignored, as they signify events such as the 'home'
680 		// button. Unicoded chars are not checked as there is an overlap valid
681 		// chars and the wx keycodes.
682 		evt.Skip();
683 		return;
684 	}
685 
686 	// We wish to avoid handling shortcuts, with the exception of 'select-all'.
687 	if (evt.AltDown() || evt.ControlDown() || evt.MetaDown()) {
688 		if (evt.CmdDown() && (evt.GetKeyCode() == 0x01)) {
689 			// Ctrl+a (Command+a on Mac) was pressed, select all items
690 			for (int i = 0; i < GetItemCount(); ++i) {
691 				SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
692 			}
693 		}
694 
695 		evt.Skip();
696 		return;
697 	} else if (m_tts_time + 1500u < GetTickCount()) {
698 		m_tts_text.Clear();
699 	}
700 
701 	m_tts_time = GetTickCount();
702 	m_tts_text.Append(wxTolower(key));
703 
704 	// May happen if the subclass does not forward deletion events.
705 	// Or rather when single-char-repeated (see below) wraps around, so don't assert.
706 	if (m_tts_item >= GetItemCount()) {
707 		m_tts_item = -1;
708 	}
709 
710 	unsigned next = (m_tts_item == -1) ? 0 : m_tts_item;
711 	for (unsigned i = 0, count = GetItemCount(); i < count; ++i) {
712 		wxString text = GetTTSText((next + i) % count).MakeLower();
713 
714 		if (text.StartsWith(m_tts_text)) {
715 			ClearSelection();
716 
717 			m_tts_item = (next + i) % count;
718 			SetItemState(m_tts_item, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED,
719 					wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
720 			EnsureVisible(m_tts_item);
721 
722 			return;
723 		}
724 	}
725 
726 	if (m_tts_item != -1) {
727 		// Crop the string so that it matches the old item (avoid typos).
728 		wxString text = GetTTSText(m_tts_item).MakeLower();
729 
730 		// If the last key didn't result in a hit, then we skip the event.
731 		if (!text.StartsWith(m_tts_text)) {
732 			if ((m_tts_text.Length() == 2) && (m_tts_text[0] == m_tts_text[1])) {
733 				// Special case, single-char, repeated. This allows toggeling
734 				// between items starting with a specific letter.
735 				m_tts_text.Clear();
736 				// Increment, so the next will be selected (or wrap around).
737 				m_tts_item++;
738 				OnChar(evt);
739 			} else {
740 				m_tts_text.RemoveLast();
741 				evt.Skip(true);
742 			}
743 		}
744 	} else {
745 		evt.Skip(true);
746 	}
747 }
748 
749 
OnItemSelected(wxListEvent & evt)750 void CMuleListCtrl::OnItemSelected(wxListEvent& evt)
751 {
752 	if (IsSorting()) {
753 		// Selection/Deselection that happened while sorting.
754 		evt.Veto();
755 	} else {
756 			// We reset the current TTS session if the user manually changes the selection
757 		if (m_tts_item != evt.GetIndex()) {
758 			ResetTTS();
759 
760 			// The item is changed so that the next TTS starts from the selected item.
761 			m_tts_item = evt.GetIndex();
762 		}
763 		evt.Skip();
764 	}
765 }
766 
767 
OnItemDeleted(wxListEvent & evt)768 void CMuleListCtrl::OnItemDeleted(wxListEvent& evt)
769 {
770 	if (evt.GetIndex() <= m_tts_item) {
771 		m_tts_item--;
772 	}
773 
774 	evt.Skip();
775 }
776 
777 
OnAllItemsDeleted(wxListEvent & evt)778 void CMuleListCtrl::OnAllItemsDeleted(wxListEvent& evt)
779 {
780 	ResetTTS();
781 
782 	evt.Skip();
783 }
784 
785 
ResetTTS()786 void CMuleListCtrl::ResetTTS()
787 {
788 	m_tts_item = -1;
789 	m_tts_time =  0;
790 }
791 
GetOldColumnOrder() const792 wxString CMuleListCtrl::GetOldColumnOrder() const
793 {
794 	return wxEmptyString;
795 }
796 // File_checked_for_headers
797