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