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