1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        checkedlistctrl.cpp
3 // Purpose:     wxCheckedListCtrl
4 // Author:      Uknown ? (found at http://wiki.wxwidgets.org/wiki.pl?WxListCtrl)
5 // Modified by: Francesco Montorsi
6 // Created:     2005/06/29
7 // RCS-ID:      $Id$
8 // Copyright:   (c) 2005 Francesco Montorsi
9 // Licence:     wxWidgets licence
10 /////////////////////////////////////////////////////////////////////////////
11 
12 
13 
14 
15 // For compilers that support precompilation, includes "wx.h".
16 #include "wx/wxprec.h"
17 
18 #ifdef __BORLANDC__
19 #pragma hdrstop
20 #endif
21 
22 // includes
23 #include "wx/checkedlistctrl.h"
24 #include <wx/icon.h>
25 #include <wx/settings.h>
26 
27 
28 #if wxUSE_CHECKEDLISTCTRL
29 
30 #if !CLC_VBAM_USAGE || !CLC_USE_SYSICONS
31 // resources
32 #include "wx/checked.xpm"
33 #include "wx/unchecked.xpm"
34 #include "wx/checked_dis.xpm"
35 #include "wx/unchecked_dis.xpm"
36 #else
37 #include <wx/dcbuffer.h>
38 #include <wx/renderer.h>
39 #include <wx/checkbox.h>
40 #endif
41 
42 #if CLC_VBAM_USAGE
43 IMPLEMENT_DYNAMIC_CLASS(wxCheckedListCtrl, wxListCtrl)
44 #else
45 IMPLEMENT_CLASS(wxCheckedListCtrl, wxListCtrl)
46 #endif
47 BEGIN_EVENT_TABLE(wxCheckedListCtrl, wxListCtrl)
48 	EVT_LEFT_DOWN(wxCheckedListCtrl::OnMouseEvent)
49 END_EVENT_TABLE()
50 
51 DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_CHECKED);
52 DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_UNCHECKED);
53 
54 
55 
56 // ------------------
57 // wxCHECKEDLISTCTRL
58 // ------------------
59 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pt,const wxSize & sz,long style,const wxValidator & validator,const wxString & name)60 bool wxCheckedListCtrl::Create(wxWindow* parent, wxWindowID id, const wxPoint &pt,
61                                const wxSize &sz, long style, const wxValidator &validator, const wxString &name)
62 {
63 	if (!wxListCtrl::Create(parent, id, pt, sz, style, validator, name))
64 		return FALSE;
65 
66 #if CLC_VBAM_USAGE
67 	// support xrc by making init separate
68 	return Init();
69 }
70 
Init()71 bool wxCheckedListCtrl::Init()
72 {
73 #if CLC_USE_SYSICONS
74 	// use native size images instead of 16x16
75 	wxCheckBox* cb = new wxCheckBox(GetParent(), wxID_ANY, wxEmptyString);
76 	wxSize cbsz = cb->GetBestSize();
77 	delete cb;
78 	m_imageList.Create(cbsz.GetWidth(), cbsz.GetHeight(), TRUE);
79 #else
80 	m_imageList.Create(16, 16, TRUE);
81 #endif
82 #endif
83 	SetImageList(&m_imageList, wxIMAGE_LIST_SMALL);
84 #if CLC_VBAM_USAGE && CLC_USE_SYSICONS
85 	// pasted from wxWiki
86 	// but with native size instead of 16x16
87 	// constructor only takes wxSize in 2.9+, apparently
88 	wxBitmap unchecked_bmp(cbsz.GetWidth(), cbsz.GetHeight()),
89 	         checked_bmp(cbsz.GetWidth(), cbsz.GetHeight()),
90 	         unchecked_disabled_bmp(cbsz.GetWidth(), cbsz.GetHeight()),
91 	         checked_disabled_bmp(cbsz.GetWidth(), cbsz.GetHeight());
92 	// Bitmaps must not be selected by a DC for addition to the image list but I don't see
93 	// a way of diselecting them in wxMemoryDC so let's just use a code block to end the scope
94 	{
95 		wxMemoryDC renderer_dc;
96 		// Unchecked
97 		renderer_dc.SelectObject(unchecked_bmp);
98 		renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
99 		renderer_dc.Clear();
100 		wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, 16, 16), 0);
101 		// Checked
102 		renderer_dc.SelectObject(checked_bmp);
103 		renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
104 		renderer_dc.Clear();
105 		wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, 16, 16), wxCONTROL_CHECKED);
106 		// Unchecked and Disabled
107 		renderer_dc.SelectObject(unchecked_disabled_bmp);
108 		renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
109 		renderer_dc.Clear();
110 		wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, 16, 16), 0 | wxCONTROL_DISABLED);
111 		// Checked and Disabled
112 		renderer_dc.SelectObject(checked_disabled_bmp);
113 		renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
114 		renderer_dc.Clear();
115 		wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, 16, 16), wxCONTROL_CHECKED | wxCONTROL_DISABLED);
116 	}
117 	// the add order must respect the wxCLC_XXX_IMGIDX defines in the headers !
118 	m_imageList.Add(unchecked_bmp);
119 	m_imageList.Add(checked_bmp);
120 	m_imageList.Add(unchecked_disabled_bmp);
121 	m_imageList.Add(checked_disabled_bmp);
122 #else
123 	// the add order must respect the wxCLC_XXX_IMGIDX defines in the headers !
124 	m_imageList.Add(wxIcon(unchecked_xpm));
125 	m_imageList.Add(wxIcon(checked_xpm));
126 	m_imageList.Add(wxIcon(unchecked_dis_xpm));
127 	m_imageList.Add(wxIcon(checked_dis_xpm));
128 #endif
129 	return TRUE;
130 }
131 
132 /* static */
GetItemImageFromAdditionalState(int addstate)133 int wxCheckedListCtrl::GetItemImageFromAdditionalState(int addstate)
134 {
135 	bool checked = (addstate & wxLIST_STATE_CHECKED) != 0;
136 	bool enabled = (addstate & wxLIST_STATE_ENABLED) != 0;
137 
138 	if (checked && enabled)
139 		return wxCLC_CHECKED_IMGIDX;
140 	else if (checked && !enabled)
141 		return wxCLC_DISABLED_CHECKED_IMGIDX;
142 	else if (!checked && enabled)
143 		return wxCLC_UNCHECKED_IMGIDX;
144 
145 	wxASSERT(!checked && !enabled);     // this is the last possibility
146 	return wxCLC_DISABLED_UNCHECKED_IMGIDX;
147 }
148 
GetBgColourFromAdditionalState(int additionalstate)149 wxColour wxCheckedListCtrl::GetBgColourFromAdditionalState(int additionalstate)
150 {
151 	if ((additionalstate & wxLIST_STATE_ENABLED) &&
152 	        this->IsEnabled())
153 		return *wxWHITE;
154 
155 #ifdef __WXMSW__
156 	return wxColour(212, 208, 200);
157 #else
158 	return wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
159 #endif
160 }
161 
162 /* static */
GetAndRemoveAdditionalState(long * state,int statemask)163 int wxCheckedListCtrl::GetAndRemoveAdditionalState(long* state, int statemask)
164 {
165 	int additionalstate = 0;
166 
167 	if (!state) return -1;
168 
169 	// extract the bits we are interested in
170 	bool checked = (*state & wxLIST_STATE_CHECKED) != 0;
171 	bool enabled = (*state & wxLIST_STATE_ENABLED) != 0;
172 
173 	// and set them in a different variable if they are included in the statemask
174 	if (checked && (statemask & wxLIST_STATE_CHECKED)) additionalstate |= wxLIST_STATE_CHECKED;
175 
176 	if (enabled && (statemask & wxLIST_STATE_ENABLED)) additionalstate |= wxLIST_STATE_ENABLED;
177 
178 	// remove them from the original state var...
179 	*state &= ~wxLIST_STATE_CHECKED;
180 	*state &= ~wxLIST_STATE_ENABLED;
181 	return additionalstate;
182 }
183 
GetItem(wxListItem & info) const184 bool wxCheckedListCtrl::GetItem(wxListItem &info) const
185 {
186 	// wx internal wxListCtrl::GetItem remove from the state mask the
187 	// wxLIST_STATE_CHECKED & wxLIST_STATE_ENABLED bits since they
188 	// are not part of wx standard flags... so we need to check those
189 	// flags against the original wxListItem's statemask...
190 	wxListItem original(info);
191 #ifdef __WXDEBUG__
192 	// we always want to retrieve also the image state for checking purposes...
193 	info.m_mask |= wxLIST_MASK_IMAGE;
194 #endif
195 
196 	if (!wxListCtrl::GetItem(info))
197 		return FALSE;
198 
199 	// these are our additional supported states: read them from m_stateList
200 	bool checked = (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED) != 0;
201 	bool enabled = (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED) != 0;
202 
203 	// now intercept state requests about enable or check mode
204 	if ((original.m_mask & wxLIST_MASK_STATE) &&
205 	        (original.m_stateMask & wxLIST_STATE_CHECKED))
206 	{
207 		info.m_state |= (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED);
208 		info.m_stateMask |= wxLIST_STATE_CHECKED;
209 		info.m_mask |= wxLIST_MASK_STATE;       // contains valid info !
210 	}
211 
212 	if ((original.m_mask & wxLIST_MASK_STATE) &&
213 	        (original.m_stateMask & wxLIST_STATE_ENABLED))
214 	{
215 		info.m_state |= (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED);
216 		info.m_stateMask |= wxLIST_STATE_ENABLED;
217 		info.m_mask |= wxLIST_MASK_STATE;       // contains valid info !
218 	}
219 
220 	// check that state & image are synch
221 #if CLC_VBAM_USAGE
222 
223 	if (info.GetColumn())
224 		return TRUE;
225 
226 #endif
227 #ifdef __WXDEBUG__
228 	wxASSERT_MSG((int)m_stateList.GetCount() == (int)GetItemCount(),
229 	             wxT("Something wrong ! See InsertItem()"));
230 	// read info by image index
231 	bool imagecheck = (info.m_image == wxCLC_CHECKED_IMGIDX) ||
232 	                  (info.m_image == wxCLC_DISABLED_CHECKED_IMGIDX);
233 	bool imageenabled = (info.m_image == wxCLC_CHECKED_IMGIDX) ||
234 	                    (info.m_image == wxCLC_UNCHECKED_IMGIDX);
235 	wxASSERT_MSG((checked && imagecheck) || (!checked && !imagecheck),
236 	             wxT("This is item has checked state but it's shown as unchecked (or viceversa)"));
237 	wxASSERT_MSG((enabled && imageenabled) || (!enabled && !imageenabled),
238 	             wxT("This is item has enabled state but it's shown as disabled (or viceversa)"));
239 #endif
240 	return TRUE;
241 }
242 
SetItem(wxListItem & info)243 bool wxCheckedListCtrl::SetItem(wxListItem &info)
244 {
245 #if CLC_VBAM_USAGE
246 
247 	// only col 0 gets a checkbox
248 	if (info.GetColumn())
249 		return wxListCtrl::SetItem(info);
250 
251 #endif
252 	// remove the checked & enabled states from the state flag:
253 	// we'll store them in our separate array
254 	int additionalstate = GetAndRemoveAdditionalState(&info.m_state, info.m_stateMask);
255 
256 	// set image index
257 	// we will ignore the info.m_image field since we need
258 	// to overwrite it...
259 	if (info.m_mask & wxLIST_MASK_STATE)
260 	{
261 		// if some state is not included in the state mask, then get the state info
262 		// from our internal state array
263 		if (!(info.m_stateMask & wxLIST_STATE_ENABLED))
264 			additionalstate |= (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED);
265 
266 		if (!(info.m_stateMask & wxLIST_STATE_CHECKED))
267 			additionalstate |= (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED);
268 
269 		// state is valid: use it to determine the image to set...
270 		info.m_mask |= wxLIST_MASK_IMAGE;
271 		info.m_image = GetItemImageFromAdditionalState(additionalstate);
272 		// since when changing the background color, also the foreground color
273 		// and the font of the item are changed, we try to respect the user
274 		// choices of such attributes
275 		info.SetTextColour(this->GetItemTextColour(info.GetId()));
276 #if wxCHECK_VERSION(2, 6, 2)
277 		// before wx 2.6.2 the wxListCtrl::SetItemFont function is missing
278 		info.SetFont(this->GetItemFont(info.GetId()));
279 #endif
280 		// change the background color to respect the enabled/disabled status...
281 		info.SetBackgroundColour(GetBgColourFromAdditionalState(additionalstate));
282 		m_stateList[info.m_itemId] = additionalstate;
283 	}
284 	else
285 	{
286 		// state is invalid; don't change image
287 		info.m_mask &= ~wxLIST_MASK_IMAGE;
288 	}
289 
290 	// save the changes
291 	return wxListCtrl::SetItem(info);
292 }
293 
InsertItem(wxListItem & info)294 long wxCheckedListCtrl::InsertItem(wxListItem &info)
295 {
296 	int additionalstate = GetAndRemoveAdditionalState(&info.m_state, info.m_stateMask);
297 
298 	if (!(info.m_mask & wxLIST_MASK_STATE) ||
299 	        !(info.m_stateMask & wxLIST_STATE_ENABLED))
300 	{
301 		// if not specified, the default additional state is ENABLED
302 		additionalstate = wxLIST_STATE_ENABLED;
303 	}
304 
305 	// we always want to insert items with images...
306 	info.m_mask |= wxLIST_MASK_IMAGE;
307 	info.m_image = GetItemImageFromAdditionalState(additionalstate);
308 	info.SetBackgroundColour(GetBgColourFromAdditionalState(additionalstate));
309 	int itemcount = GetItemCount();
310 	wxASSERT_MSG(info.m_itemId <= itemcount, wxT("Invalid index !"));
311 	wxASSERT_MSG((int)m_stateList.GetCount() == (int)GetItemCount(),
312 	             wxT("Something wrong !"));
313 
314 	if (info.m_itemId == itemcount)
315 	{
316 		// we are adding a new item at the end of the list
317 		m_stateList.Add(additionalstate);
318 	}
319 	else
320 	{
321 		// we must shift all following items
322 		m_stateList.Add(m_stateList[itemcount - 1]);
323 
324 		for (int i = itemcount - 1; i > info.m_itemId; i--)
325 			m_stateList[i] = m_stateList[i - 1];
326 
327 		m_stateList[info.m_itemId] = additionalstate;
328 	}
329 
330 	return wxListCtrl::InsertItem(info);
331 }
332 
SetItemState(long item,long state,long stateMask)333 bool wxCheckedListCtrl::SetItemState(long item, long state, long stateMask)
334 {
335 	wxListItem li;
336 	li.SetId(item);
337 	li.SetMask(wxLIST_MASK_STATE);
338 	li.SetState(state);
339 	li.SetStateMask(stateMask);
340 	// so we are sure to use wxCheckedListCtrl::SetItem
341 	// (and not wxListCtrl::SetItem)
342 	return SetItem(li);
343 }
344 
GetItemState(long item,long stateMask) const345 int wxCheckedListCtrl::GetItemState(long item, long stateMask) const
346 {
347 	wxListItem li;
348 	li.SetId(item);
349 	li.SetMask(wxLIST_MASK_STATE);
350 	li.SetStateMask(stateMask);
351 
352 	// so we are sure to use wxCheckedListCtrl::GetItem
353 	// (and not wxListCtrl::GetItem)
354 	if (!GetItem(li))
355 		return -1;
356 
357 	return li.GetState();
358 }
359 
SetItem(long index,int col,const wxString & label,int WXUNUSED (imageId))360 long wxCheckedListCtrl::SetItem(long index, int col, const wxString &label, int WXUNUSED(imageId))
361 {
362 	wxListItem li;
363 	li.SetId(index);
364 	li.SetColumn(col);
365 	li.SetText(label);
366 	li.SetMask(wxLIST_MASK_TEXT);
367 	// so we are sure to use wxCheckedListCtrl::SetItem
368 	// (and not wxListCtrl::SetItem)
369 	return SetItem(li);
370 }
371 
InsertItem(long index,const wxString & label,int WXUNUSED (imageIndex))372 long wxCheckedListCtrl::InsertItem(long index, const wxString &label, int WXUNUSED(imageIndex))
373 {
374 	wxListItem info;
375 	info.m_text = label;
376 	info.m_mask = wxLIST_MASK_TEXT;
377 	info.m_itemId = index;
378 	return InsertItem(info);
379 }
380 
Check(long item,bool checked)381 void wxCheckedListCtrl::Check(long item, bool checked)
382 {
383 	// NB: the "statemask" is not the "mask" of a list item;
384 	//     in the "mask" you use the wxLIST_MASK_XXXX defines;
385 	//     in the "statemask" you use the wxLIST_STATE_XXX defines
386 	//     to set a specific bit of the wxListInfo::m_state var
387 	if (checked)
388 		// the 2nd parameter says: activate the STATE bit relative to CHECK feature
389 		// the 3rd parameter says: set only *that* bit
390 		SetItemState(item, wxLIST_STATE_CHECKED, wxLIST_STATE_CHECKED);
391 	else
392 		SetItemState(item, 0, wxLIST_STATE_CHECKED);
393 }
394 
Enable(long item,bool enable)395 void wxCheckedListCtrl::Enable(long item, bool enable)
396 {
397 	if (enable)
398 		// the 2nd parameter says: activate the STATE bit relative to ENABLE feature
399 		// the 3rd parameter says: set only *that* bit
400 		SetItemState(item, wxLIST_STATE_ENABLED, wxLIST_STATE_ENABLED);
401 	else
402 		SetItemState(item, 0, wxLIST_STATE_ENABLED);
403 }
404 
EnableAll(bool enable)405 void wxCheckedListCtrl::EnableAll(bool enable)
406 {
407 	for (int i = 0; i < GetItemCount(); i++)
408 		Enable(i, enable);
409 }
410 
CheckAll(bool check)411 void wxCheckedListCtrl::CheckAll(bool check)
412 {
413 	for (int i = 0; i < GetItemCount(); i++)
414 		Check(i, check);
415 }
416 
DeleteItem(long item)417 bool wxCheckedListCtrl::DeleteItem(long item)
418 {
419 	// shift our additional state array
420 	//for (int i=item,max=GetItemCount(); i < max-1; i++)
421 	//  m_stateList[i] = m_stateList[i+1];
422 	m_stateList.RemoveAt(item, 1);
423 	return wxListCtrl::DeleteItem(item);
424 }
425 
GetCheckedItemCount() const426 int wxCheckedListCtrl::GetCheckedItemCount() const
427 {
428 	int res = 0;
429 
430 	for (int i = 0; i < GetItemCount(); i++)
431 		if (IsChecked(i))
432 			res++;
433 
434 	return res;
435 }
436 
437 // event handlers
438 
OnMouseEvent(wxMouseEvent & event)439 void wxCheckedListCtrl::OnMouseEvent(wxMouseEvent &event)
440 {
441 	if (!event.LeftDown())
442 	{
443 		event.Skip();
444 		return;
445 	}
446 
447 	int flags;
448 	long item = HitTest(event.GetPosition(), flags);
449 
450 	if (item == wxNOT_FOUND || !IsEnabled(item))
451 	{
452 		// skip this item
453 		event.Skip();
454 		return;
455 	}
456 
457 	// user clicked exactly on the checkbox or on the item row ?
458 	bool processcheck = (flags & wxLIST_HITTEST_ONITEMICON) ||
459 	                    ((GetWindowStyle() & wxCLC_CHECK_WHEN_SELECTING) &&
460 	                     (flags & wxLIST_HITTEST_ONITEM));
461 
462 	if (processcheck)
463 	{
464 		wxListEvent ev(wxEVT_NULL, GetId());
465 		ev.m_itemIndex = item;
466 
467 		// send the check event
468 		if (IsChecked(item))
469 		{
470 			ev.SetEventType(wxEVT_COMMAND_LIST_ITEM_UNCHECKED);
471 			Check(item, FALSE);
472 			AddPendingEvent(ev);
473 		}
474 		else
475 		{
476 			ev.SetEventType(wxEVT_COMMAND_LIST_ITEM_CHECKED);
477 			Check(item, TRUE);
478 			AddPendingEvent(ev);
479 		}
480 	}
481 
482 	event.Skip();
483 }
484 
485 #endif      // wxUSE_CHECKEDLISTCTRL
486