wxComboControl and wxOwnerDrawnComboBox (patch 1479938)
[wxWidgets.git] / src / generic / odcombo.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: odcombo.cpp
3 // Purpose: wxOwnerDrawnComboBox, wxVListBoxComboPopup
4 // Author: Jaakko Salli
5 // Modified by:
6 // Created: Apr-30-2006
7 // RCS-ID: $Id$
8 // Copyright: (c) 2005 Jaakko Salli
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_OWNERDRAWNCOMBOBOX
27
28 #ifndef WX_PRECOMP
29 #include "wx/log.h"
30 #include "wx/combobox.h"
31 #include "wx/dcclient.h"
32 #include "wx/settings.h"
33 #include "wx/dialog.h"
34 #endif
35
36 #include "wx/combo.h"
37 #include "wx/odcombo.h"
38
39
40 // ============================================================================
41 // implementation
42 // ============================================================================
43
44
45 // ----------------------------------------------------------------------------
46 // wxVListBoxComboPopup is a wxVListBox customized to act as a popup control
47 //
48 // ----------------------------------------------------------------------------
49
50
51 BEGIN_EVENT_TABLE(wxVListBoxComboPopup, wxVListBox)
52 EVT_MOTION(wxVListBoxComboPopup::OnMouseMove)
53 EVT_KEY_DOWN(wxVListBoxComboPopup::OnKey)
54 EVT_LEFT_UP(wxVListBoxComboPopup::OnLeftClick)
55 END_EVENT_TABLE()
56
57
58 wxVListBoxComboPopup::wxVListBoxComboPopup(wxComboControlBase* combo)
59 : wxVListBox(),
60 wxComboPopup(combo)
61 {
62 m_widestWidth = 0;
63 m_avgCharWidth = 0;
64 m_baseImageWidth = 0;
65 m_itemHeight = 0;
66 m_value = -1;
67 m_itemHover = -1;
68 m_clientDataItemsType = wxClientData_None;
69 }
70
71 bool wxVListBoxComboPopup::Create(wxWindow* parent)
72 {
73 if ( !wxVListBox::Create(parent,
74 wxID_ANY,
75 wxDefaultPosition,
76 wxDefaultSize,
77 wxBORDER_SIMPLE | wxLB_INT_HEIGHT | wxWANTS_CHARS) )
78 return false;
79
80 wxASSERT( GetParent()->GetParent() );
81 SetFont( GetParent()->GetParent()->GetFont() );
82
83 wxVListBox::SetItemCount(m_strings.GetCount());
84
85 // TODO: Move this to SetFont
86 m_itemHeight = GetCharHeight() + 0;
87
88 return true;
89 }
90
91 wxVListBoxComboPopup::~wxVListBoxComboPopup()
92 {
93 Clear();
94 }
95
96 bool wxVListBoxComboPopup::LazyCreate()
97 {
98 // NB: There is a bug with wxVListBox that can be avoided by creating
99 // it later (bug causes empty space to be shown if initial selection
100 // is at the end of a list longer than the control can show at once).
101 return true;
102 }
103
104 // paint the control itself
105 void wxVListBoxComboPopup::PaintComboControl( wxDC& dc, const wxRect& rect )
106 {
107 if ( !(m_combo->GetWindowStyle() & wxODCB_STD_CONTROL_PAINT) )
108 {
109 m_combo->DrawFocusBackground(dc,rect,0);
110 if ( m_value >= 0 )
111 {
112 if ( m_combo->OnDrawListItem(dc,rect,m_value,wxCC_PAINTING_CONTROL) )
113 return;
114 }
115 }
116
117 wxComboPopup::PaintComboControl(dc,rect);
118 }
119
120 void wxVListBoxComboPopup::OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const
121 {
122 dc.SetFont( m_font );
123
124 bool isHilited = GetSelection() == (int) n;
125
126 // Set correct text colour for selected items
127 // (must always set the correct colour - atleast GTK may have lost it
128 // in between calls).
129 if ( isHilited )
130 dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) );
131 else
132 dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) );
133
134 if ( !m_combo->OnDrawListItem(dc,rect,(int)n,0) )
135 dc.DrawText( GetString(n), rect.x + 2, rect.y );
136 }
137
138 wxCoord wxVListBoxComboPopup::OnMeasureItem(size_t n) const
139 {
140 int itemHeight = m_combo->OnMeasureListItem(n);
141 if ( itemHeight < 0 )
142 itemHeight = m_itemHeight;
143
144 return itemHeight;
145 }
146
147 void wxVListBoxComboPopup::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const
148 {
149 // we need to render selected and current items differently
150 if ( IsCurrent(n) )
151 {
152 m_combo->DrawFocusBackground( dc, rect, wxCONTROL_ISSUBMENU|wxCONTROL_SELECTED );
153 }
154 //else: do nothing for the normal items
155 }
156
157 void wxVListBoxComboPopup::SendComboBoxEvent()
158 {
159 wxCommandEvent evt(wxEVT_COMMAND_COMBOBOX_SELECTED,m_combo->GetId());
160 int selection = m_value;
161
162 evt.SetEventObject(m_combo);
163 evt.SetInt(selection);
164
165 // Set client data, if any
166 if ( selection >= 0 && (int)m_clientDatas.GetCount() > selection )
167 {
168 void* clientData = m_clientDatas[selection];
169 if ( m_clientDataItemsType == wxClientData_Object )
170 evt.SetClientObject((wxClientData*)clientData);
171 else
172 evt.SetClientData(clientData);
173 }
174
175 m_combo->GetEventHandler()->AddPendingEvent(evt);
176 }
177
178 // returns true if key was consumed
179 bool wxVListBoxComboPopup::HandleKey( int keycode, bool saturate )
180 {
181 int value = m_value;
182 int itemCount = GetCount();
183
184 if ( keycode == WXK_DOWN || keycode == WXK_RIGHT )
185 {
186 value++;
187 }
188 else if ( keycode == WXK_UP || keycode == WXK_LEFT )
189 {
190 value--;
191 }
192 else if ( keycode == WXK_PAGEDOWN )
193 {
194 value+=10;
195 }
196 else if ( keycode == WXK_PAGEUP )
197 {
198 value-=10;
199 }
200 /*
201 else if ( keycode == WXK_END )
202 {
203 value = itemCount-1;
204 }
205 else if ( keycode == WXK_HOME )
206 {
207 value = 0;
208 }
209 */
210 else
211 return false;
212
213 if ( saturate )
214 {
215 if ( value >= itemCount )
216 value = itemCount - 1;
217 else if ( value < 0 )
218 value = 0;
219 }
220 else
221 {
222 if ( value >= itemCount )
223 value -= itemCount;
224 else if ( value < 0 )
225 value += itemCount;
226 }
227
228 if ( value == m_value )
229 // Even if value was same, don't skip the event
230 // (good for consistency)
231 return true;
232
233 m_value = value;
234
235 wxString valStr;
236 if ( value >= 0 )
237 m_combo->SetValue(m_strings[value]);
238
239 SendComboBoxEvent();
240
241 return true;
242 }
243
244 void wxVListBoxComboPopup::OnComboDoubleClick()
245 {
246 // Cycle on dclick (disable saturation to allow true cycling).
247 if ( !::wxGetKeyState(WXK_SHIFT) )
248 HandleKey(WXK_DOWN,false);
249 else
250 HandleKey(WXK_UP,false);
251 }
252
253 void wxVListBoxComboPopup::OnComboKeyEvent( wxKeyEvent& event )
254 {
255 // Saturated key movement on
256 if ( !HandleKey(event.GetKeyCode(),true) )
257 event.Skip();
258 }
259
260 void wxVListBoxComboPopup::OnPopup()
261 {
262 // *must* set value after size is set (this is because of a vlbox bug)
263 wxVListBox::SetSelection(m_value);
264 }
265
266 void wxVListBoxComboPopup::OnMouseMove(wxMouseEvent& event)
267 {
268 // Move selection to cursor if it is inside the popup
269 int itemHere = GetItemAtPosition(event.GetPosition());
270 if ( itemHere >= 0 )
271 wxVListBox::SetSelection(itemHere);
272
273 event.Skip();
274 }
275
276 void wxVListBoxComboPopup::OnLeftClick(wxMouseEvent& WXUNUSED(event))
277 {
278 m_value = wxVListBox::GetSelection();
279 Dismiss();
280 SendComboBoxEvent();
281 }
282
283 void wxVListBoxComboPopup::OnKey(wxKeyEvent& event)
284 {
285 // Select item if ENTER is pressed
286 if ( event.GetKeyCode() == WXK_RETURN || event.GetKeyCode() == WXK_NUMPAD_ENTER )
287 {
288 m_value = wxVListBox::GetSelection();
289 Dismiss();
290 SendComboBoxEvent();
291 }
292 // Hide popup if ESC is pressed
293 else if ( event.GetKeyCode() == WXK_ESCAPE )
294 Dismiss();
295 else
296 event.Skip();
297 }
298
299 void wxVListBoxComboPopup::CheckWidth( int pos )
300 {
301 wxCoord x = m_combo->OnMeasureListItemWidth(pos);
302
303 if ( x < 0 )
304 {
305 if ( !m_font.Ok() )
306 m_font = m_combo->GetFont();
307
308 wxCoord y;
309 m_combo->GetTextExtent(m_strings[pos], &x, &y, 0, 0, &m_font);
310 x += 4;
311 }
312
313 if ( m_widestWidth < x )
314 {
315 m_widestWidth = x;
316 }
317 }
318
319 void wxVListBoxComboPopup::Insert( const wxString& item, int pos )
320 {
321 // Need to change selection?
322 wxString strValue;
323 if ( !(m_combo->GetWindowStyle() & wxCB_READONLY) &&
324 m_combo->GetValue() == item )
325 m_value = pos;
326
327 m_strings.Insert(item,pos);
328
329 if ( IsCreated() )
330 wxVListBox::SetItemCount( wxVListBox::GetItemCount()+1 );
331
332 // Calculate width
333 CheckWidth(pos);
334 }
335
336 int wxVListBoxComboPopup::Append(const wxString& item)
337 {
338 int pos = (int)m_strings.GetCount();
339
340 if ( m_combo->GetWindowStyle() & wxCB_SORT )
341 {
342 // Find position
343 // TODO: Could be optimized with binary search
344 wxArrayString strings = m_strings;
345 unsigned int i;
346
347 for ( i=0; i<strings.GetCount(); i++ )
348 {
349 if ( item.Cmp(strings.Item(i)) < 0 )
350 {
351 pos = (int)i;
352 break;
353 }
354 }
355 }
356
357 Insert(item,pos);
358
359 return pos;
360 }
361
362 void wxVListBoxComboPopup::Clear()
363 {
364 wxASSERT(m_combo);
365
366 m_strings.Empty();
367
368 ClearClientDatas();
369
370 if ( IsCreated() )
371 wxVListBox::SetItemCount(0);
372 }
373
374 void wxVListBoxComboPopup::ClearClientDatas()
375 {
376 if ( m_clientDataItemsType == wxClientData_Object )
377 {
378 size_t i;
379 for ( i=0; i<m_clientDatas.GetCount(); i++ )
380 delete (wxClientData*) m_clientDatas[i];
381 }
382
383 m_clientDatas.Empty();
384 }
385
386 void wxVListBoxComboPopup::SetItemClientData( unsigned int n,
387 void* clientData,
388 wxClientDataType clientDataItemsType )
389 {
390 // It should be sufficient to update this variable only here
391 m_clientDataItemsType = clientDataItemsType;
392
393 m_clientDatas.SetCount(n+1,NULL);
394 m_clientDatas[n] = clientData;
395 }
396
397 void* wxVListBoxComboPopup::GetItemClientData(unsigned int n) const
398 {
399 if ( m_clientDatas.GetCount() > n )
400 return m_clientDatas[n];
401
402 return NULL;
403 }
404
405 void wxVListBoxComboPopup::Delete( unsigned int item )
406 {
407 // Remove client data, if set
408 if ( m_clientDatas.GetCount() )
409 {
410 if ( m_clientDataItemsType == wxClientData_Object )
411 delete (wxClientData*) m_clientDatas[item];
412
413 m_clientDatas.RemoveAt(item);
414 }
415
416 m_strings.RemoveAt(item);
417
418 if ( IsCreated() )
419 wxVListBox::SetItemCount( wxVListBox::GetItemCount()-1 );
420 }
421
422 int wxVListBoxComboPopup::FindString(const wxString& s) const
423 {
424 return m_strings.Index(s);
425 }
426
427 unsigned int wxVListBoxComboPopup::GetCount() const
428 {
429 return m_strings.GetCount();
430 }
431
432 wxString wxVListBoxComboPopup::GetString( int item ) const
433 {
434 return m_strings[item];
435 }
436
437 void wxVListBoxComboPopup::SetString( int item, const wxString& str )
438 {
439 m_strings[item] = str;
440 }
441
442 wxString wxVListBoxComboPopup::GetStringValue() const
443 {
444 if ( m_value >= 0 )
445 return m_strings[m_value];
446 return wxEmptyString;
447 }
448
449 void wxVListBoxComboPopup::SetSelection( int item )
450 {
451 // This seems to be necessary (2.5.3 w/ MingW atleast)
452 if ( item < -1 || item >= (int)m_strings.GetCount() )
453 item = -1;
454
455 m_value = item;
456
457 if ( IsCreated() )
458 wxVListBox::SetSelection(item);
459 }
460
461 void wxVListBoxComboPopup::SetStringValue( const wxString& value )
462 {
463 int index = m_strings.Index(value);
464
465 m_value = index;
466
467 if ( index >= -1 && index < (int)wxVListBox::GetItemCount() )
468 wxVListBox::SetSelection(index);
469 }
470
471 wxSize wxVListBoxComboPopup::GetAdjustedSize( int minWidth, int prefHeight, int maxHeight )
472 {
473 int height = 250;
474
475 if ( m_strings.GetCount() )
476 {
477 if ( prefHeight > 0 )
478 height = prefHeight;
479
480 if ( height > maxHeight )
481 height = maxHeight;
482
483 int totalHeight = GetTotalHeight(); // + 3;
484 if ( height >= totalHeight )
485 {
486 height = totalHeight;
487 }
488 else
489 {
490 // Adjust height to a multiple of the height of the first item
491 // NB: Calculations that take variable height into account
492 // are unnecessary.
493 int fih = GetLineHeight(0);
494 int shown = height/fih;
495 height = shown * fih;
496 }
497 }
498 else
499 height = 50;
500
501 // Take scrollbar into account in width calculations
502 int widestWidth = m_widestWidth + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
503 return wxSize(minWidth > widestWidth ? minWidth : widestWidth,
504 height+2);
505 }
506
507 void wxVListBoxComboPopup::Populate( int n, const wxString choices[] )
508 {
509 int i;
510
511 for ( i=0; i<n; i++ )
512 {
513 const wxString& item = choices[i];
514 m_strings.Add(item);
515 CheckWidth(i);
516 }
517
518 if ( IsCreated() )
519 wxVListBox::SetItemCount(n);
520
521 // Sort the initial choices
522 if ( m_combo->GetWindowStyle() & wxCB_SORT )
523 m_strings.Sort();
524
525 // Find initial selection
526 wxString strValue = m_combo->GetValue();
527 if ( strValue.Length() )
528 m_value = m_strings.Index(strValue);
529 }
530
531 // ----------------------------------------------------------------------------
532 // wxOwnerDrawnComboBox
533 // ----------------------------------------------------------------------------
534
535
536 BEGIN_EVENT_TABLE(wxOwnerDrawnComboBox, wxComboControl)
537 END_EVENT_TABLE()
538
539
540 IMPLEMENT_DYNAMIC_CLASS2(wxOwnerDrawnComboBox, wxComboControl, wxControlWithItems)
541
542 void wxOwnerDrawnComboBox::Init()
543 {
544 }
545
546 bool wxOwnerDrawnComboBox::Create(wxWindow *parent,
547 wxWindowID id,
548 const wxString& value,
549 const wxPoint& pos,
550 const wxSize& size,
551 long style,
552 const wxValidator& validator,
553 const wxString& name)
554 {
555 return wxComboControl::Create(parent,id,value,pos,size,style,validator,name);
556 }
557
558 wxOwnerDrawnComboBox::wxOwnerDrawnComboBox(wxWindow *parent,
559 wxWindowID id,
560 const wxString& value,
561 const wxPoint& pos,
562 const wxSize& size,
563 const wxArrayString& choices,
564 long style,
565 const wxValidator& validator,
566 const wxString& name)
567 : wxComboControl()
568 {
569 Init();
570
571 Create(parent,id,value,pos,size,choices,style, validator, name);
572 }
573
574 bool wxOwnerDrawnComboBox::Create(wxWindow *parent,
575 wxWindowID id,
576 const wxString& value,
577 const wxPoint& pos,
578 const wxSize& size,
579 const wxArrayString& choices,
580 long style,
581 const wxValidator& validator,
582 const wxString& name)
583 {
584 wxCArrayString chs(choices);
585
586 return Create(parent, id, value, pos, size, chs.GetCount(),
587 chs.GetStrings(), style, validator, name);
588 }
589
590 bool wxOwnerDrawnComboBox::Create(wxWindow *parent,
591 wxWindowID id,
592 const wxString& value,
593 const wxPoint& pos,
594 const wxSize& size,
595 int n,
596 const wxString choices[],
597 long style,
598 const wxValidator& validator,
599 const wxString& name)
600 {
601
602 if ( !Create(parent, id, value, pos, size, style,
603 validator, name) )
604 {
605 return false;
606 }
607
608 wxVListBoxComboPopup* iface = new wxVListBoxComboPopup(this);
609 SetPopupControl(iface);
610
611 // m_popupInterface has been overridden as wxVListBoxComboPopup
612 m_popupInterface = iface;
613
614 // Add initial choices to the wxVListBox
615 iface->Populate(n,choices);
616
617 return true;
618 }
619
620 wxOwnerDrawnComboBox::~wxOwnerDrawnComboBox()
621 {
622 if ( m_popupInterface )
623 m_popupInterface->ClearClientDatas();
624 }
625
626 // ----------------------------------------------------------------------------
627 // wxOwnerDrawnComboBox item manipulation methods
628 // ----------------------------------------------------------------------------
629
630 void wxOwnerDrawnComboBox::Clear()
631 {
632 wxASSERT( m_popupInterface );
633
634 m_popupInterface->Clear();
635
636 GetTextCtrl()->SetValue(wxEmptyString);
637 }
638
639 void wxOwnerDrawnComboBox::Delete(unsigned int n)
640 {
641 wxCHECK_RET( (n >= 0) && (n < GetCount()), _T("invalid index in wxOwnerDrawnComboBox::Delete") );
642
643 if ( GetSelection() == (int) n )
644 SetValue(wxEmptyString);
645
646 m_popupInterface->Delete(n);
647 }
648
649 unsigned int wxOwnerDrawnComboBox::GetCount() const
650 {
651 wxASSERT( m_popupInterface );
652 return m_popupInterface->GetCount();
653 }
654
655 wxString wxOwnerDrawnComboBox::GetString(unsigned int n) const
656 {
657 wxCHECK_MSG( (n >= 0) && (n < GetCount()), wxEmptyString, _T("invalid index in wxOwnerDrawnComboBox::GetString") );
658 return m_popupInterface->GetString(n);
659 }
660
661 void wxOwnerDrawnComboBox::SetString(unsigned int n, const wxString& s)
662 {
663 wxCHECK_RET( (n >= 0) && (n < GetCount()), _T("invalid index in wxOwnerDrawnComboBox::SetString") );
664 m_popupInterface->SetString(n,s);
665 }
666
667 int wxOwnerDrawnComboBox::FindString(const wxString& s) const
668 {
669 wxASSERT( m_popupInterface );
670 return m_popupInterface->FindString(s);
671 }
672
673 void wxOwnerDrawnComboBox::Select(int n)
674 {
675 wxCHECK_RET( (n >= -1) && (n < (int)GetCount()), _T("invalid index in wxOwnerDrawnComboBox::Select") );
676 wxASSERT( m_popupInterface );
677
678 m_popupInterface->SetSelection(n);
679
680 wxString str;
681 if ( n >= 0 )
682 str = m_popupInterface->GetString(n);
683
684 // Refresh text portion in control
685 if ( m_text )
686 m_text->SetValue( str );
687 else
688 m_valueString = str;
689
690 Refresh();
691 }
692
693 int wxOwnerDrawnComboBox::GetSelection() const
694 {
695 wxASSERT( m_popupInterface );
696 return m_popupInterface->GetSelection();
697 }
698
699 int wxOwnerDrawnComboBox::DoAppend(const wxString& item)
700 {
701 wxASSERT( m_popupInterface );
702 return m_popupInterface->Append(item);
703 }
704
705 int wxOwnerDrawnComboBox::DoInsert(const wxString& item, unsigned int pos)
706 {
707 wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT), -1, wxT("can't insert into sorted list"));
708 wxCHECK_MSG((pos>=0) && (pos<=GetCount()), -1, wxT("invalid index"));
709
710 m_popupInterface->Insert(item,pos);
711
712 return pos;
713 }
714
715 void wxOwnerDrawnComboBox::DoSetItemClientData(unsigned int n, void* clientData)
716 {
717 wxASSERT(m_popupInterface);
718 m_popupInterface->SetItemClientData(n,clientData,m_clientDataItemsType);
719 }
720
721 void* wxOwnerDrawnComboBox::DoGetItemClientData(unsigned int n) const
722 {
723 wxASSERT(m_popupInterface);
724 return m_popupInterface->GetItemClientData(n);
725 }
726
727 void wxOwnerDrawnComboBox::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
728 {
729 DoSetItemClientData(n, (void*) clientData);
730 }
731
732 wxClientData* wxOwnerDrawnComboBox::DoGetItemClientObject(unsigned int n) const
733 {
734 return (wxClientData*) DoGetItemClientData(n);
735 }
736
737 #endif // wxUSE_OWNERDRAWNCOMBOBOX