]> git.saurik.com Git - wxWidgets.git/blame - src/generic/vlbox.cpp
more wxPostEvent MT fixes
[wxWidgets.git] / src / generic / vlbox.cpp
CommitLineData
e0c6027b
VZ
1///////////////////////////////////////////////////////////////////////////////
2// Name: generic/vlbox.cpp
3// Purpose: implementation of wxVListBox
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 31.05.03
7// RCS-ID: $Id$
8// Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
0a53b9b8 9// License: wxWindows license
e0c6027b
VZ
10///////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20// For compilers that support precompilation, includes "wx.h".
21#include "wx/wxprec.h"
22
23#ifdef __BORLANDC__
24 #pragma hdrstop
25#endif
26
27#ifndef WX_PRECOMP
28 #include "wx/settings.h"
bb178b29 29 #include "wx/dcclient.h"
e0c6027b
VZ
30#endif //WX_PRECOMP
31
32#include "wx/vlbox.h"
be465555 33#include "wx/selstore.h"
e0c6027b
VZ
34
35// ----------------------------------------------------------------------------
36// event tables
37// ----------------------------------------------------------------------------
38
39BEGIN_EVENT_TABLE(wxVListBox, wxVScrolledWindow)
40 EVT_PAINT(wxVListBox::OnPaint)
41
42 EVT_KEY_DOWN(wxVListBox::OnKeyDown)
43 EVT_LEFT_DOWN(wxVListBox::OnLeftDown)
44 EVT_LEFT_DCLICK(wxVListBox::OnLeftDClick)
45END_EVENT_TABLE()
46
47// ============================================================================
48// implementation
49// ============================================================================
50
0c8392ca
RD
51IMPLEMENT_ABSTRACT_CLASS(wxVListBox, wxVScrolledWindow)
52
e0c6027b
VZ
53// ----------------------------------------------------------------------------
54// wxVListBox creation
55// ----------------------------------------------------------------------------
56
57void wxVListBox::Init()
58{
970b97a2
VZ
59 m_current =
60 m_anchor = wxNOT_FOUND;
be465555 61 m_selStore = NULL;
e0c6027b
VZ
62}
63
64bool wxVListBox::Create(wxWindow *parent,
65 wxWindowID id,
66 const wxPoint& pos,
67 const wxSize& size,
e0c6027b
VZ
68 long style,
69 const wxString& name)
70{
fef7400f 71 style |= wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE;
e0c6027b
VZ
72 if ( !wxVScrolledWindow::Create(parent, id, pos, size, style, name) )
73 return false;
74
be465555
VZ
75 if ( style & wxLB_MULTIPLE )
76 m_selStore = new wxSelectionStore;
e0c6027b 77
dc596072
RD
78 // make sure the native widget has the right colour since we do
79 // transparent drawing by default
80 SetBackgroundColour(GetBackgroundColour());
9a9b4940
VZ
81 m_colBgSel = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
82
e0c6027b
VZ
83 return true;
84}
85
be465555
VZ
86wxVListBox::~wxVListBox()
87{
88 delete m_selStore;
89}
90
91void wxVListBox::SetItemCount(size_t count)
92{
93 if ( m_selStore )
94 {
95 // tell the selection store that our number of items has changed
96 m_selStore->SetItemCount(count);
97 }
98
99 SetLineCount(count);
100}
101
e0c6027b
VZ
102// ----------------------------------------------------------------------------
103// selection handling
104// ----------------------------------------------------------------------------
105
be465555
VZ
106bool wxVListBox::IsSelected(size_t line) const
107{
108 return m_selStore ? m_selStore->IsSelected(line) : (int)line == m_current;
109}
110
111bool wxVListBox::Select(size_t item, bool select)
112{
113 wxCHECK_MSG( m_selStore, false,
114 _T("Select() may only be used with multiselection listbox") );
115
116 wxCHECK_MSG( item < GetItemCount(), false,
117 _T("Select(): invalid item index") );
118
119 bool changed = m_selStore->SelectItem(item, select);
120 if ( changed )
121 {
122 // selection really changed
123 RefreshLine(item);
124 }
125
126 DoSetCurrent(item);
127
128 return changed;
129}
130
131bool wxVListBox::SelectRange(size_t from, size_t to)
e0c6027b 132{
be465555
VZ
133 wxCHECK_MSG( m_selStore, false,
134 _T("SelectRange() may only be used with multiselection listbox") );
135
136 // make sure items are in correct order
137 if ( from > to )
138 {
139 size_t tmp = from;
140 from = to;
141 to = tmp;
142 }
143
144 wxCHECK_MSG( to < GetItemCount(), false,
145 _T("SelectRange(): invalid item index") );
146
147 wxArrayInt changed;
148 if ( !m_selStore->SelectRange(from, to, true, &changed) )
149 {
150 // too many items have changed, we didn't record them in changed array
151 // so we have no choice but to refresh everything between from and to
152 RefreshLines(from, to);
153 }
154 else // we've got the indices of the changed items
155 {
156 const size_t count = changed.GetCount();
157 if ( !count )
158 {
159 // nothing changed
160 return false;
161 }
162
163 // refresh just the lines which have really changed
164 for ( size_t n = 0; n < count; n++ )
165 {
166 RefreshLine(changed[n]);
167 }
168 }
169
170 // something changed
171 return true;
172}
173
174bool wxVListBox::DoSelectAll(bool select)
175{
176 wxCHECK_MSG( m_selStore, false,
177 _T("SelectAll may only be used with multiselection listbox") );
178
179 size_t count = GetItemCount();
180 if ( count )
181 {
182 wxArrayInt changed;
183 if ( !m_selStore->SelectRange(0, count - 1, select) ||
184 !changed.IsEmpty() )
185 {
186 Refresh();
187
188 // something changed
189 return true;
190 }
191 }
192
193 return false;
194}
195
196bool wxVListBox::DoSetCurrent(int current)
197{
6c9210a7
VZ
198 wxASSERT_MSG( current == wxNOT_FOUND ||
199 (current >= 0 && (size_t)current < GetItemCount()),
200 _T("wxVListBox::DoSetCurrent(): invalid item index") );
201
be465555 202 if ( current == m_current )
e0c6027b
VZ
203 {
204 // nothing to do
be465555 205 return false;
e0c6027b
VZ
206 }
207
be465555
VZ
208 if ( m_current != wxNOT_FOUND )
209 RefreshLine(m_current);
e0c6027b 210
be465555 211 m_current = current;
e0c6027b 212
be465555 213 if ( m_current != wxNOT_FOUND )
e0c6027b
VZ
214 {
215 // if the line is not visible at all, we scroll it into view but we
216 // don't need to refresh it -- it will be redrawn anyhow
be465555 217 if ( !IsVisible(m_current) )
e0c6027b 218 {
be465555 219 ScrollToLine(m_current);
e0c6027b
VZ
220 }
221 else // line is at least partly visible
222 {
223 // it is, indeed, only partly visible, so scroll it into view to
224 // make it entirely visible
be465555 225 if ( (size_t)m_current == GetLastVisibleLine() )
e0c6027b 226 {
be465555 227 ScrollToLine(m_current);
e0c6027b
VZ
228 }
229
230 // but in any case refresh it as even if it was only partly visible
231 // before we need to redraw it entirely as its background changed
be465555 232 RefreshLine(m_current);
e0c6027b 233 }
be465555 234 }
e0c6027b 235
be465555
VZ
236 return true;
237}
e0c6027b 238
be465555
VZ
239void wxVListBox::SendSelectedEvent()
240{
241 wxASSERT_MSG( m_current != wxNOT_FOUND,
242 _T("SendSelectedEvent() shouldn't be called") );
243
244 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId());
245 event.SetEventObject(this);
246 event.m_commandInt = m_current;
247
248 (void)GetEventHandler()->ProcessEvent(event);
249}
250
251void wxVListBox::SetSelection(int selection)
252{
6c9210a7
VZ
253 wxCHECK_RET( selection == wxNOT_FOUND ||
254 (selection >= 0 && (size_t)selection < GetItemCount()),
255 _T("wxVListBox::SetSelection(): invalid item index") );
256
de5d3a20
VZ
257 if ( HasMultipleSelection() )
258 {
259 Select(selection);
260 m_anchor = selection;
261 }
be465555
VZ
262
263 DoSetCurrent(selection);
264}
265
266size_t wxVListBox::GetSelectedCount() const
267{
268 return m_selStore ? m_selStore->GetSelectedCount()
269 : m_current == wxNOT_FOUND ? 0 : 1;
270}
271
272int wxVListBox::GetFirstSelected(unsigned long& cookie) const
273{
274 cookie = 0;
275
276 return GetNextSelected(cookie);
277}
278
279int wxVListBox::GetNextSelected(unsigned long& cookie) const
280{
281 wxCHECK_MSG( m_selStore, wxNOT_FOUND,
282 _T("GetFirst/NextSelected() may only be used with multiselection listboxes") );
283
284 while ( cookie < GetItemCount() )
285 {
286 if ( IsSelected(cookie++) )
287 return cookie - 1;
e0c6027b 288 }
be465555
VZ
289
290 return wxNOT_FOUND;
e0c6027b
VZ
291}
292
293// ----------------------------------------------------------------------------
9a9b4940 294// wxVListBox appearance parameters
e0c6027b
VZ
295// ----------------------------------------------------------------------------
296
297void wxVListBox::SetMargins(const wxPoint& pt)
298{
299 if ( pt != m_ptMargins )
300 {
301 m_ptMargins = pt;
302
303 Refresh();
304 }
305}
306
9a9b4940
VZ
307void wxVListBox::SetSelectionBackground(const wxColour& col)
308{
309 m_colBgSel = col;
310}
311
312// ----------------------------------------------------------------------------
313// wxVListBox painting
314// ----------------------------------------------------------------------------
315
e0c6027b
VZ
316wxCoord wxVListBox::OnGetLineHeight(size_t line) const
317{
318 return OnMeasureItem(line) + 2*m_ptMargins.y;
319}
320
321void wxVListBox::OnDrawSeparator(wxDC& WXUNUSED(dc),
322 wxRect& WXUNUSED(rect),
323 size_t WXUNUSED(n)) const
324{
325}
326
27d0dcd0
VZ
327void wxVListBox::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const
328{
329 // we need to render selected and current items differently
330 const bool isSelected = IsSelected(n),
331 isCurrent = IsCurrent(n);
332 if ( isSelected || isCurrent )
333 {
334 if ( isSelected )
335 {
336 dc.SetBrush(wxBrush(m_colBgSel, wxSOLID));
337 }
338 else // !selected
339 {
340 dc.SetBrush(*wxTRANSPARENT_BRUSH);
341 }
342
343 dc.SetPen(*(isCurrent ? wxBLACK_PEN : wxTRANSPARENT_PEN));
344
345 dc.DrawRectangle(rect);
346 }
347 //else: do nothing for the normal items
348}
349
350void wxVListBox::OnPaint(wxPaintEvent& WXUNUSED(event))
e0c6027b
VZ
351{
352 wxPaintDC dc(this);
353
354 // the update rectangle
355 wxRect rectUpdate = GetUpdateClientRect();
356
357 // the bounding rectangle of the current line
358 wxRect rectLine;
359 rectLine.width = GetClientSize().x;
360
361 // iterate over all visible lines
362 const size_t lineMax = GetLastVisibleLine();
363 for ( size_t line = GetFirstVisibleLine(); line <= lineMax; line++ )
364 {
365 const wxCoord hLine = OnGetLineHeight(line);
366
367 rectLine.height = hLine;
368
369 // and draw the ones which intersect the update rect
370 if ( rectLine.Intersects(rectUpdate) )
371 {
372 // don't allow drawing outside of the lines rectangle
373 wxDCClipper clip(dc, rectLine);
374
e0c6027b 375 wxRect rect = rectLine;
27d0dcd0
VZ
376 OnDrawBackground(dc, rect, line);
377
e0c6027b
VZ
378 OnDrawSeparator(dc, rect, line);
379
380 rect.Deflate(m_ptMargins.x, m_ptMargins.y);
381 OnDrawItem(dc, rect, line);
382 }
383 else // no intersection
384 {
385 if ( rectLine.GetTop() > rectUpdate.GetBottom() )
386 {
387 // we are already below the update rect, no need to continue
388 // further
389 break;
390 }
391 //else: the next line may intersect the update rect
392 }
393
394 rectLine.y += hLine;
395 }
396}
397
be465555
VZ
398// ============================================================================
399// wxVListBox keyboard/mouse handling
400// ============================================================================
401
970b97a2 402void wxVListBox::DoHandleItemClick(int item, int flags)
be465555
VZ
403{
404 // has anything worth telling the client code about happened?
405 bool notify = false;
406
407 if ( HasMultipleSelection() )
408 {
409 // select the iteem clicked?
410 bool select = true;
411
412 // NB: the keyboard interface we implement here corresponds to
413 // wxLB_EXTENDED rather than wxLB_MULTIPLE but this one makes more
414 // sense IMHO
970b97a2 415 if ( flags & ItemClick_Shift )
be465555
VZ
416 {
417 if ( m_current != wxNOT_FOUND )
418 {
970b97a2
VZ
419 if ( m_anchor == wxNOT_FOUND )
420 m_anchor = m_current;
421
be465555
VZ
422 select = false;
423
970b97a2
VZ
424 // only the range from the selection anchor to new m_current
425 // must be selected
be465555
VZ
426 if ( DeselectAll() )
427 notify = true;
428
970b97a2 429 if ( SelectRange(m_anchor, item) )
be465555
VZ
430 notify = true;
431 }
432 //else: treat it as ordinary click/keypress
433 }
970b97a2 434 else // Shift not pressed
be465555 435 {
970b97a2 436 m_anchor = item;
be465555 437
970b97a2
VZ
438 if ( flags & ItemClick_Ctrl )
439 {
440 select = false;
be465555 441
970b97a2
VZ
442 if ( !(flags & ItemClick_Kbd) )
443 {
444 Toggle(item);
445
446 // the status of the item has definitely changed
447 notify = true;
448 }
449 //else: Ctrl-arrow pressed, don't change selection
450 }
451 //else: behave as in single selection case
be465555 452 }
be465555
VZ
453
454 if ( select )
455 {
456 // make the clicked item the only selection
457 if ( DeselectAll() )
458 notify = true;
459
460 if ( Select(item) )
461 notify = true;
462 }
463 }
464
465 // in any case the item should become the current one
466 if ( DoSetCurrent(item) )
467 {
468 if ( !HasMultipleSelection() )
469 {
470 // this has also changed the selection for single selection case
471 notify = true;
472 }
473 }
474
475 if ( notify )
476 {
477 // notify the user about the selection change
478 SendSelectedEvent();
479 }
480 //else: nothing changed at all
481}
482
e0c6027b 483// ----------------------------------------------------------------------------
be465555 484// keyboard handling
e0c6027b
VZ
485// ----------------------------------------------------------------------------
486
487void wxVListBox::OnKeyDown(wxKeyEvent& event)
488{
970b97a2
VZ
489 // flags for DoHandleItemClick()
490 int flags = ItemClick_Kbd;
491
999836aa 492 int current;
e0c6027b
VZ
493 switch ( event.GetKeyCode() )
494 {
495 case WXK_HOME:
be465555 496 current = 0;
e0c6027b
VZ
497 break;
498
499 case WXK_END:
be465555 500 current = GetLineCount() - 1;
e0c6027b
VZ
501 break;
502
503 case WXK_DOWN:
be465555 504 if ( m_current == (int)GetLineCount() - 1 )
e0c6027b
VZ
505 return;
506
be465555 507 current = m_current + 1;
e0c6027b
VZ
508 break;
509
510 case WXK_UP:
be465555
VZ
511 if ( m_current == wxNOT_FOUND )
512 current = GetLineCount() - 1;
513 else if ( m_current != 0 )
514 current = m_current - 1;
515 else // m_current == 0
e0c6027b
VZ
516 return;
517 break;
518
519 case WXK_PAGEDOWN:
520 case WXK_NEXT:
521 PageDown();
be465555 522 current = GetFirstVisibleLine();
e0c6027b
VZ
523 break;
524
525 case WXK_PAGEUP:
526 case WXK_PRIOR:
be465555 527 if ( m_current == (int)GetFirstVisibleLine() )
e0c6027b
VZ
528 {
529 PageUp();
530 }
531
be465555 532 current = GetFirstVisibleLine();
e0c6027b
VZ
533 break;
534
970b97a2
VZ
535 case WXK_SPACE:
536 // hack: pressing space should work like a mouse click rather than
537 // like a keyboard arrow press, so trick DoHandleItemClick() in
538 // thinking we were clicked
539 flags &= ~ItemClick_Kbd;
540 current = m_current;
541 break;
542
80c700cb
RD
543#ifdef __WXMSW__
544 case WXK_TAB:
545 // Since we are using wxWANTS_CHARS we need to send navigation
546 // events for the tabs on MSW
547 {
548 wxNavigationKeyEvent ne;
549 ne.SetDirection(!event.ShiftDown());
550 ne.SetCurrentFocus(this);
551 ne.SetEventObject(this);
552 GetParent()->GetEventHandler()->ProcessEvent(ne);
553 }
554 // fall through to default
555#endif
e0c6027b
VZ
556 default:
557 event.Skip();
999836aa 558 current = 0; // just to silent the stupid compiler warnings
8703bc01 559 wxUnusedVar(current);
e0c6027b
VZ
560 return;
561 }
562
970b97a2
VZ
563 if ( event.ShiftDown() )
564 flags |= ItemClick_Shift;
565 if ( event.ControlDown() )
566 flags |= ItemClick_Ctrl;
567
568 DoHandleItemClick(current, flags);
e0c6027b
VZ
569}
570
571// ----------------------------------------------------------------------------
572// wxVListBox mouse handling
573// ----------------------------------------------------------------------------
574
575void wxVListBox::OnLeftDown(wxMouseEvent& event)
576{
577 int item = HitTest(event.GetPosition());
578
6c9210a7
VZ
579 if ( item != wxNOT_FOUND )
580 {
970b97a2
VZ
581 int flags = 0;
582 if ( event.ShiftDown() )
583 flags |= ItemClick_Shift;
584
6c9210a7
VZ
585 // under Mac Apple-click is used in the same way as Ctrl-click
586 // elsewhere
be465555 587#ifdef __WXMAC__
970b97a2 588 if ( event.MetaDown() )
be465555 589#else
970b97a2 590 if ( event.ControlDown() )
be465555 591#endif
970b97a2
VZ
592 flags |= ItemClick_Ctrl;
593
594 DoHandleItemClick(item, flags);
6c9210a7 595 }
e0c6027b
VZ
596}
597
598void wxVListBox::OnLeftDClick(wxMouseEvent& event)
599{
600 int item = HitTest(event.GetPosition());
be465555 601 if ( item != wxNOT_FOUND )
e0c6027b
VZ
602 {
603 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, GetId());
604 event.SetEventObject(this);
605 event.m_commandInt = item;
606
607 (void)GetEventHandler()->ProcessEvent(event);
608 }
609}
610
dc596072
RD
611
612// ----------------------------------------------------------------------------
613// use the same default attributes as wxListBox
614// ----------------------------------------------------------------------------
615
616#include "wx/listbox.h"
617
618//static
619wxVisualAttributes
620wxVListBox::GetClassDefaultAttributes(wxWindowVariant variant)
621{
622 return wxListBox::GetClassDefaultAttributes(variant);
623}