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