2 // This file is part of the aMule Project.
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 )
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
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.
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"
42 #include "pixmaps/sort_dn.xpm"
43 #include "pixmaps/sort_up.xpm"
44 #include "pixmaps/sort_dnx2.xpm"
45 #include "pixmaps/sort_upx2.xpm"
50 const int COL_SIZE_MIN
= 10;
51 #elif defined(__WXMSW__) || defined(__WXMAC__) || defined(__WXCOCOA__)
52 const int COL_SIZE_MIN
= 0;
54 #error Need to define COL_SIZE_MIN for your OS
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
)
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
)
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()) {
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
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.
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
;
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
;
172 // Set the column widths
174 if (cfg
->Read( wxT("/eMule/TableWidths") + m_name
, &buffer
, wxEmptyString
)) {
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
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.
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
206 } while ((Min
!= 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
);
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
);
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.
259 g_sort_func
= m_sort_func
;
262 SortItems(SortProc
, 0);
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
);
291 void CMuleListCtrl::OnColumnRClick(wxListEvent
& evt
)
296 for ( int i
= 0; i
< GetColumnCount() && i
< 15; ++i
) {
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);
314 SetColumnWidth(col
, wxLIST_AUTOSIZE
);
319 void CMuleListCtrl::OnColumnLClick(wxListEvent
& evt
)
321 // Stop if no sorter-function has been defined
324 } else if (evt
.GetColumn() == -1) {
325 // This happens if a user clicks past the last column header.
329 // Get the currently focused item
330 long pos
= GetNextItem( -1, wxLIST_NEXT_ALL
, wxLIST_STATE_FOCUSED
);
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
;
349 sort_order
= SORT_DES
| (sort_order
& SORT_ALT
);
352 m_sort_orders
.pop_front();
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
;
364 SetSorting(evt
.GetColumn(), sort_order
);
367 // Set focus on item if any was focused
369 long it_pos
= FindItem(-1, item
);
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
)
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
)
413 bool CMuleListCtrl::AltSortAllowed(unsigned WXUNUSED(column
)) const
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
);
436 m_sort_orders
.push_front(CColPair(column
, order
));
438 if (order
& SORT_DES
) {
439 SetColumnImage(column
, (order
& SORT_ALT
) ? 2 : 0);
441 SetColumnImage(column
, (order
& SORT_ALT
) ? 3 : 1);
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"));
454 wxUIntPtr data
= GetItemData(item
);
456 // Check that the item before the current item is smaller (or equal)
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);
470 void CMuleListCtrl::OnMouseWheel(wxMouseEvent
&event
)
472 // This enables scrolling with the mouse wheel
477 void CMuleListCtrl::SetColumnImage(unsigned col
, int image
)
480 item
.SetMask(wxLIST_MASK_IMAGE
);
481 item
.SetImage(image
);
482 SetColumn(col
, item
);
486 long CMuleListCtrl::CheckSelection(wxMouseEvent
& event
)
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
)) {
504 SetItemState(item
, wxLIST_STATE_SELECTED
, wxLIST_STATE_SELECTED
);
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();
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.
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
);
551 } else if (m_tts_time
+ 1500u < GetTickCount()) {
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()) {
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
)) {
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
);
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.
590 // Increment, so the next will be selected (or wrap around).
594 m_tts_text
.RemoveLast();
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()) {
610 // The item is changed so that the next TTS starts from the selected item.
611 m_tts_item
= evt
.GetIndex();
618 void CMuleListCtrl::OnItemDeleted(wxListEvent
& evt
)
620 if (evt
.GetIndex() <= m_tts_item
) {
628 void CMuleListCtrl::OnAllItemsDeleted(wxListEvent
& evt
)
636 void CMuleListCtrl::ResetTTS()
641 // File_checked_for_headers