don't send CHECKLISTBOX_TOGGLE event when Check() is called (closes bug 651140)
[wxWidgets.git] / src / msw / checklst.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: msw/checklst.cpp
3 // Purpose: implementation of wxCheckListBox class
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 16.11.97
7 // RCS-ID: $Id$
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "checklst.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #if wxUSE_OWNER_DRAWN
32
33 #ifndef WX_PRECOMP
34 #include "wx/object.h"
35 #include "wx/colour.h"
36 #include "wx/font.h"
37 #include "wx/bitmap.h"
38 #include "wx/window.h"
39 #include "wx/listbox.h"
40 #include "wx/dcmemory.h"
41
42 #include "wx/settings.h"
43
44 #include "wx/log.h"
45 #endif
46
47 #include "wx/ownerdrw.h"
48 #include "wx/checklst.h"
49
50 #include <windows.h>
51 #include <windowsx.h>
52
53 #if defined(__GNUWIN32_OLD__)
54 #include "wx/msw/gnuwin32/extra.h"
55 #endif
56
57 // ----------------------------------------------------------------------------
58 // private functions
59 // ----------------------------------------------------------------------------
60
61 // get item (converted to right type)
62 #define GetItem(n) ((wxCheckListBoxItem *)(GetItem(n)))
63
64 // ============================================================================
65 // implementation
66 // ============================================================================
67
68 IMPLEMENT_DYNAMIC_CLASS(wxCheckListBox, wxListBox)
69
70 // ----------------------------------------------------------------------------
71 // declaration and implementation of wxCheckListBoxItem class
72 // ----------------------------------------------------------------------------
73
74 class wxCheckListBoxItem : public wxOwnerDrawn
75 {
76 friend class WXDLLEXPORT wxCheckListBox;
77 public:
78 // ctor
79 wxCheckListBoxItem(wxCheckListBox *pParent, size_t nIndex);
80
81 // drawing functions
82 virtual bool OnDrawItem(wxDC& dc, const wxRect& rc, wxODAction act, wxODStatus stat);
83
84 // simple accessors and operations
85 bool IsChecked() const { return m_bChecked; }
86
87 void Check(bool bCheck);
88 void Toggle() { Check(!IsChecked()); }
89
90 void SendEvent();
91
92 private:
93 bool m_bChecked;
94 wxCheckListBox *m_pParent;
95 size_t m_nIndex;
96 };
97
98 wxCheckListBoxItem::wxCheckListBoxItem(wxCheckListBox *pParent, size_t nIndex)
99 : wxOwnerDrawn(wxEmptyString, TRUE) // checkable
100 {
101 m_bChecked = FALSE;
102 m_pParent = pParent;
103 m_nIndex = nIndex;
104
105 // we don't initialize m_nCheckHeight/Width vars because it's
106 // done in OnMeasure while they are used only in OnDraw and we
107 // know that there will always be OnMeasure before OnDraw
108
109 // fix appearance
110 SetMarginWidth(GetDefaultMarginWidth());
111 }
112
113 /*
114 * JACS - I've got the owner-draw stuff partially working with WIN16,
115 * with a really horrible-looking cross for wxCheckListBox instead of a
116 * check - could use a bitmap check-mark instead, defined in wx.rc.
117 * Also there's a refresh problem whereby it doesn't always draw the
118 * check until you click to the right of it, which is OK for WIN32.
119 */
120
121 bool wxCheckListBoxItem::OnDrawItem(wxDC& dc, const wxRect& rc,
122 wxODAction act, wxODStatus stat)
123 {
124 if ( IsChecked() )
125 stat = (wxOwnerDrawn::wxODStatus)(stat | wxOwnerDrawn::wxODChecked);
126
127 if ( wxOwnerDrawn::OnDrawItem(dc, rc, act, stat) ) {
128 // ## using native API for performance and precision
129 size_t nCheckWidth = GetDefaultMarginWidth(),
130 nCheckHeight = m_pParent->GetItemHeight();
131
132 int x = rc.GetX(),
133 y = rc.GetY();
134
135 HDC hdc = (HDC)dc.GetHDC();
136
137 // create pens
138 HPEN hpenBack = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_WINDOW)),
139 hpenGray = CreatePen(PS_SOLID, 0, RGB(128, 128, 128)),
140 hpenPrev = (HPEN)SelectObject(hdc, hpenBack);
141
142 // we erase the 1-pixel border
143 Rectangle(hdc, x, y, x + nCheckWidth, y + nCheckHeight);
144
145 // shift check mark 1 pixel to the right (it looks better like this)
146 x++;
147
148 if ( IsChecked() ) {
149 // first create a monochrome bitmap in a memory DC
150 HDC hdcMem = CreateCompatibleDC(hdc);
151 HBITMAP hbmpCheck = CreateBitmap(nCheckWidth, nCheckHeight, 1, 1, 0);
152 HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcMem, hbmpCheck);
153
154 // then draw a check mark into it
155 #if defined(__WIN32__) && !defined(__SC__)
156 RECT rect;
157 rect.left = 0;
158 rect.top = 0;
159 rect.right = nCheckWidth;
160 rect.bottom = nCheckHeight;
161
162 DrawFrameControl(hdcMem, &rect, DFC_MENU, DFCS_MENUCHECK);
163 #else
164 // In WIN16, draw a cross
165 HPEN blackPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
166 HPEN whiteBrush = (HPEN)GetStockObject(WHITE_BRUSH);
167 HPEN hPenOld = (HPEN)::SelectObject(hdcMem, blackPen);
168 HPEN hBrushOld = (HPEN)::SelectObject(hdcMem, whiteBrush);
169 ::SetROP2(hdcMem, R2_COPYPEN);
170 Rectangle(hdcMem, 0, 0, nCheckWidth, nCheckHeight);
171 MoveTo(hdcMem, 0, 0);
172 LineTo(hdcMem, nCheckWidth, nCheckHeight);
173 MoveTo(hdcMem, nCheckWidth, 0);
174 LineTo(hdcMem, 0, nCheckHeight);
175 ::SelectObject(hdcMem, hPenOld);
176 ::SelectObject(hdcMem, hBrushOld);
177 ::DeleteObject(blackPen);
178 #endif
179
180 // finally copy it to screen DC and clean up
181 BitBlt(hdc, x, y, nCheckWidth - 1, nCheckHeight,
182 hdcMem, 0, 0, SRCCOPY);
183
184 SelectObject(hdcMem, hbmpOld);
185 DeleteObject(hbmpCheck);
186 DeleteDC(hdcMem);
187 }
188
189 // now we draw the smaller rectangle
190 y++;
191 nCheckWidth -= 2;
192 nCheckHeight -= 2;
193
194 // draw hollow gray rectangle
195 (void)SelectObject(hdc, hpenGray);
196 HBRUSH hbrPrev = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH));
197 Rectangle(hdc, x, y, x + nCheckWidth, y + nCheckHeight);
198
199 // clean up
200 (void)SelectObject(hdc, hpenPrev);
201 (void)SelectObject(hdc, hbrPrev);
202
203 DeleteObject(hpenBack);
204 DeleteObject(hpenGray);
205
206 /*
207 dc.DrawRectangle(x, y, nCheckWidth, nCheckHeight);
208
209 if ( IsChecked() ) {
210 dc.DrawLine(x, y, x + nCheckWidth, y + nCheckHeight);
211 dc.DrawLine(x, y + nCheckHeight, x + nCheckWidth, y);
212 }
213 */
214
215 return TRUE;
216 }
217
218 return FALSE;
219 }
220
221 // change the state of the item and redraw it
222 void wxCheckListBoxItem::Check(bool check)
223 {
224 m_bChecked = check;
225
226 // index may be changed because new items were added/deleted
227 if ( m_pParent->GetItemIndex(this) != (int)m_nIndex )
228 {
229 // update it
230 int index = m_pParent->GetItemIndex(this);
231
232 wxASSERT_MSG( index != wxNOT_FOUND, wxT("what does this item do here?") );
233
234 m_nIndex = (size_t)index;
235 }
236
237 HWND hwndListbox = (HWND)m_pParent->GetHWND();
238
239 #ifdef __WIN32__
240 RECT rcUpdate;
241
242 if ( ::SendMessage(hwndListbox, LB_GETITEMRECT,
243 m_nIndex, (LPARAM)&rcUpdate) == LB_ERR )
244 {
245 wxLogDebug(wxT("LB_GETITEMRECT failed"));
246 }
247 #else // Win16
248 // FIXME this doesn't work if the listbox is scrolled!
249 size_t nHeight = m_pParent->GetItemHeight();
250 size_t y = m_nIndex * nHeight;
251 RECT rcUpdate ;
252 rcUpdate.left = 0 ;
253 rcUpdate.top = y ;
254 rcUpdate.right = GetDefaultMarginWidth() ;
255 rcUpdate.bottom = y + nHeight ;
256 #endif // Win32/16
257
258 InvalidateRect(hwndListbox, &rcUpdate, FALSE);
259 }
260
261 // send an "item checked" event
262 void wxCheckListBoxItem::SendEvent()
263 {
264 wxCommandEvent event(wxEVT_COMMAND_CHECKLISTBOX_TOGGLED, m_pParent->GetId());
265 event.SetInt(m_nIndex);
266 event.SetEventObject(m_pParent);
267 m_pParent->ProcessCommand(event);
268 }
269
270 // ----------------------------------------------------------------------------
271 // implementation of wxCheckListBox class
272 // ----------------------------------------------------------------------------
273
274 // define event table
275 // ------------------
276 BEGIN_EVENT_TABLE(wxCheckListBox, wxListBox)
277 EVT_KEY_DOWN(wxCheckListBox::OnKeyDown)
278 EVT_LEFT_DOWN(wxCheckListBox::OnLeftClick)
279 END_EVENT_TABLE()
280
281 // control creation
282 // ----------------
283
284 // def ctor: use Create() to really create the control
285 wxCheckListBox::wxCheckListBox()
286 {
287 }
288
289 // ctor which creates the associated control
290 wxCheckListBox::wxCheckListBox(wxWindow *parent, wxWindowID id,
291 const wxPoint& pos, const wxSize& size,
292 int nStrings, const wxString choices[],
293 long style, const wxValidator& val,
294 const wxString& name)
295 {
296 Create(parent, id, pos, size, nStrings, choices, style, val, name);
297 }
298
299 bool wxCheckListBox::Create(wxWindow *parent, wxWindowID id,
300 const wxPoint& pos, const wxSize& size,
301 int n, const wxString choices[],
302 long style,
303 const wxValidator& validator, const wxString& name)
304 {
305 return wxListBox::Create(parent, id, pos, size, n, choices,
306 style | wxLB_OWNERDRAW, validator, name);
307 }
308
309 // misc overloaded methods
310 // -----------------------
311
312 void wxCheckListBox::Delete(int N)
313 {
314 wxCHECK_RET( N >= 0 && N < m_noItems,
315 wxT("invalid index in wxListBox::Delete") );
316
317 wxListBox::Delete(N);
318
319 // free memory
320 delete m_aItems[N];
321
322 m_aItems.RemoveAt(N);
323 }
324
325 bool wxCheckListBox::SetFont( const wxFont &font )
326 {
327 size_t i;
328 for ( i = 0; i < m_aItems.GetCount(); i++ )
329 m_aItems[i]->SetFont(font);
330
331 wxListBox::SetFont(font);
332
333 return TRUE;
334 }
335
336 // create/retrieve item
337 // --------------------
338
339 // create a check list box item
340 wxOwnerDrawn *wxCheckListBox::CreateLboxItem(size_t nIndex)
341 {
342 wxCheckListBoxItem *pItem = new wxCheckListBoxItem(this, nIndex);
343 return pItem;
344 }
345
346 // return item size
347 // ----------------
348 bool wxCheckListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT *item)
349 {
350 if ( wxListBox::MSWOnMeasure(item) ) {
351 MEASUREITEMSTRUCT *pStruct = (MEASUREITEMSTRUCT *)item;
352
353 // save item height
354 m_nItemHeight = pStruct->itemHeight;
355
356 // add place for the check mark
357 pStruct->itemWidth += wxOwnerDrawn::GetDefaultMarginWidth();
358
359 return TRUE;
360 }
361
362 return FALSE;
363 }
364
365 // check items
366 // -----------
367
368 bool wxCheckListBox::IsChecked(size_t uiIndex) const
369 {
370 wxCHECK_MSG( uiIndex < (size_t)GetCount(), FALSE, _T("bad wxCheckListBox index") );
371
372 return GetItem(uiIndex)->IsChecked();
373 }
374
375 void wxCheckListBox::Check(size_t uiIndex, bool bCheck)
376 {
377 wxCHECK_RET( uiIndex < (size_t)GetCount(), _T("bad wxCheckListBox index") );
378
379 GetItem(uiIndex)->Check(bCheck);
380 }
381
382 // process events
383 // --------------
384
385 void wxCheckListBox::OnKeyDown(wxKeyEvent& event)
386 {
387 // what do we do?
388 enum
389 {
390 None,
391 Toggle,
392 Set,
393 Clear
394 } oper;
395
396 switch ( event.KeyCode() )
397 {
398 case WXK_SPACE:
399 oper = Toggle;
400 break;
401
402 case WXK_NUMPAD_ADD:
403 case '+':
404 oper = Set;
405 break;
406
407 case WXK_NUMPAD_SUBTRACT:
408 case '-':
409 oper = Clear;
410 break;
411
412 default:
413 oper = None;
414 }
415
416 if ( oper != None )
417 {
418 wxArrayInt selections;
419 int count;
420 if ( HasMultipleSelection() )
421 {
422 count = GetSelections(selections);
423 }
424 else
425 {
426 count = 1;
427 selections.Add(GetSelection());
428 }
429
430 for ( int i = 0; i < count; i++ )
431 {
432 wxCheckListBoxItem *item = GetItem(selections[i]);
433 if ( !item )
434 {
435 wxFAIL_MSG( _T("no wxCheckListBoxItem?") );
436 continue;
437 }
438
439 switch ( oper )
440 {
441 case Toggle:
442 item->Toggle();
443 break;
444
445 case Set:
446 case Clear:
447 item->Check( oper == Set );
448 break;
449
450 default:
451 wxFAIL_MSG( _T("what should this key do?") );
452 }
453
454 // we should send an event as this has been done by the user and
455 // not by the program
456 item->SendEvent();
457 }
458 }
459 else // nothing to do
460 {
461 event.Skip();
462 }
463 }
464
465 void wxCheckListBox::OnLeftClick(wxMouseEvent& event)
466 {
467 // clicking on the item selects it, clicking on the checkmark toggles
468 if ( event.GetX() <= wxOwnerDrawn::GetDefaultMarginWidth() ) {
469 int nItem = HitTest(event.GetX(), event.GetY());
470
471 if ( nItem != wxNOT_FOUND ) {
472 wxCheckListBoxItem *item = GetItem(nItem);
473 item->Toggle();
474 item->SendEvent();
475 }
476 //else: it's not an error, just click outside of client zone
477 }
478 else {
479 // implement default behaviour: clicking on the item selects it
480 event.Skip();
481 }
482 }
483
484 int wxCheckListBox::DoHitTestItem(wxCoord x, wxCoord y) const
485 {
486 #ifdef __WIN32__
487 int nItem = (int)::SendMessage
488 (
489 (HWND)GetHWND(),
490 LB_ITEMFROMPOINT,
491 0,
492 MAKELPARAM(x, y)
493 );
494 #else // Win16
495 // FIXME this doesn't work when the listbox is scrolled!
496 int nItem = y / m_nItemHeight;
497 #endif // Win32/16
498
499 return nItem >= m_noItems ? wxNOT_FOUND : nItem;
500 }
501
502 #endif
503