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