]> git.saurik.com Git - wxWidgets.git/blob - src/msw/combo.cpp
0fd2c511df0a90438157c2d08a8bc42db79e7bad
[wxWidgets.git] / src / msw / combo.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/combo.cpp
3 // Purpose: wxMSW wxComboCtrl
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_COMBOCTRL
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 #include "wx/stopwatch.h"
35 #endif
36
37 #include "wx/dcbuffer.h"
38 #include "wx/combo.h"
39
40 #include "wx/msw/registry.h"
41 #include "wx/msw/uxtheme.h"
42
43 // Change to #if 1 to include tmschema.h for easier testing of theme
44 // parameters.
45 #if 0
46 #include <tmschema.h>
47 #else
48 //----------------------------------
49 #define EP_EDITTEXT 1
50 #define ETS_NORMAL 1
51 #define ETS_HOT 2
52 #define ETS_SELECTED 3
53 #define ETS_DISABLED 4
54 #define ETS_FOCUSED 5
55 #define ETS_READONLY 6
56 #define ETS_ASSIST 7
57 #define TMT_FILLCOLOR 3802
58 #define TMT_TEXTCOLOR 3803
59 #define TMT_BORDERCOLOR 3801
60 #define TMT_EDGEFILLCOLOR 3808
61 //----------------------------------
62 #endif
63
64
65 #define NATIVE_TEXT_INDENT_XP 4
66 #define NATIVE_TEXT_INDENT_CLASSIC 2
67
68 #define TEXTCTRLXADJUST_XP 1
69 #define TEXTCTRLYADJUST_XP 3
70 #define TEXTCTRLXADJUST_CLASSIC 1
71 #define TEXTCTRLYADJUST_CLASSIC 3
72
73 #define COMBOBOX_ANIMATION_RESOLUTION 10
74
75 #define COMBOBOX_ANIMATION_DURATION 200 // In milliseconds
76
77 #define wxMSW_DESKTOP_USERPREFERENCESMASK_COMBOBOXANIM (1<<26)
78
79
80 // ============================================================================
81 // implementation
82 // ============================================================================
83
84
85 BEGIN_EVENT_TABLE(wxComboCtrl, wxComboCtrlBase)
86 EVT_PAINT(wxComboCtrl::OnPaintEvent)
87 EVT_MOUSE_EVENTS(wxComboCtrl::OnMouseEvent)
88 EVT_TIMER(wxID_ANY, wxComboCtrl::OnTimerEvent)
89 END_EVENT_TABLE()
90
91
92 IMPLEMENT_DYNAMIC_CLASS(wxComboCtrl, wxComboCtrlBase)
93
94 void wxComboCtrl::Init()
95 {
96 }
97
98 bool wxComboCtrl::Create(wxWindow *parent,
99 wxWindowID id,
100 const wxString& value,
101 const wxPoint& pos,
102 const wxSize& size,
103 long style,
104 const wxValidator& validator,
105 const wxString& name)
106 {
107
108 // Set border
109 long border = style & wxBORDER_MASK;
110
111 wxUxThemeEngine* theme = wxUxThemeEngine::GetIfActive();
112
113 if ( !border )
114 {
115 // For XP, have 1-width custom border, for older version use sunken
116 if ( theme )
117 {
118 border = wxBORDER_NONE;
119 m_widthCustomBorder = 1;
120 }
121 else
122 border = wxBORDER_SUNKEN;
123
124 style = (style & ~(wxBORDER_MASK)) | border;
125 }
126
127 // create main window
128 if ( !wxComboCtrlBase::Create(parent,
129 id,
130 value,
131 pos,
132 size,
133 style | wxFULL_REPAINT_ON_RESIZE,
134 wxDefaultValidator,
135 name) )
136 return false;
137
138 if ( style & wxCC_STD_BUTTON )
139 m_iFlags |= wxCC_POPUP_ON_MOUSE_UP;
140
141 // Create textctrl, if necessary
142 CreateTextCtrl( wxNO_BORDER, validator );
143
144 // Add keyboard input handlers for main control and textctrl
145 InstallInputHandlers();
146
147 // Prepare background for double-buffering
148 SetBackgroundStyle( wxBG_STYLE_CUSTOM );
149
150 // SetBestSize should be called last
151 SetBestSize(size);
152
153 return true;
154 }
155
156 wxComboCtrl::~wxComboCtrl()
157 {
158 }
159
160 void wxComboCtrl::OnThemeChange()
161 {
162 wxUxThemeEngine* theme = wxUxThemeEngine::GetIfActive();
163 if ( theme )
164 {
165 wxUxThemeHandle hTheme(this, L"COMBOBOX");
166
167 COLORREF col;
168 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_FILLCOLOR,&col);
169 SetBackgroundColour(wxRGBToColour(col));
170 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_TEXTCOLOR,&col);
171 SetForegroundColour(wxRGBToColour(col));
172 }
173 else
174 {
175 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
176 SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
177 }
178 }
179
180 void wxComboCtrl::OnResize()
181 {
182 //
183 // Recalculates button and textctrl areas
184
185 int textCtrlXAdjust;
186 int textCtrlYAdjust;
187
188 if ( wxUxThemeEngine::GetIfActive() )
189 {
190 textCtrlXAdjust = TEXTCTRLXADJUST_XP;
191 textCtrlYAdjust = TEXTCTRLYADJUST_XP;
192 }
193 else
194 {
195 textCtrlXAdjust = TEXTCTRLXADJUST_CLASSIC;
196 textCtrlYAdjust = TEXTCTRLYADJUST_CLASSIC;
197 }
198
199 // Technically Classic Windows style combo has more narrow button,
200 // but the native renderer doesn't paint it well like that.
201 int btnWidth = 17;
202 CalculateAreas(btnWidth);
203
204 // Position textctrl using standard routine
205 PositionTextCtrl(textCtrlXAdjust,textCtrlYAdjust);
206 }
207
208 // Draws non-XP GUI dotted line around the focus area
209 static void wxMSWDrawFocusRect( wxDC& dc, const wxRect& rect )
210 {
211 #if !defined(__WXWINCE__)
212 /*
213 RECT mswRect;
214 mswRect.left = rect.x;
215 mswRect.top = rect.y;
216 mswRect.right = rect.x + rect.width;
217 mswRect.bottom = rect.y + rect.height;
218 HDC hdc = (HDC) dc.GetHDC();
219 SetMapMode(hdc,MM_TEXT); // Just in case...
220 DrawFocusRect(hdc,&mswRect);
221 */
222 // FIXME: Use DrawFocusRect code above (currently it draws solid line
223 // for caption focus but works ok for other stuff).
224 // Also, this code below may not work in future wx versions, since
225 // it employs wxCAP_BUTT hack to have line of width 1.
226 dc.SetLogicalFunction(wxINVERT);
227
228 wxPen pen(*wxBLACK,1,wxDOT);
229 pen.SetCap(wxCAP_BUTT);
230 dc.SetPen(pen);
231 dc.SetBrush(*wxTRANSPARENT_BRUSH);
232
233 dc.DrawRectangle(rect);
234
235 dc.SetLogicalFunction(wxCOPY);
236 #else
237 dc.SetLogicalFunction(wxINVERT);
238
239 dc.SetPen(wxPen(*wxBLACK,1,wxDOT));
240 dc.SetBrush(*wxTRANSPARENT_BRUSH);
241
242 dc.DrawRectangle(rect);
243
244 dc.SetLogicalFunction(wxCOPY);
245 #endif
246 }
247
248 // draw focus background on area in a way typical on platform
249 void
250 wxComboCtrl::PrepareBackground( wxDC& dc, const wxRect& rect, int flags ) const
251 {
252 wxUxThemeHandle hTheme(this, L"COMBOBOX");
253 //COLORREF cref;
254
255 wxSize sz = GetClientSize();
256 bool isEnabled;
257 bool isFocused; // also selected
258
259 // For smaller size control (and for disabled background) use less spacing
260 int focusSpacingX;
261 int focusSpacingY;
262
263 if ( !(flags & wxCONTROL_ISSUBMENU) )
264 {
265 // Drawing control
266 isEnabled = IsEnabled();
267 isFocused = ShouldDrawFocus();
268
269 // Windows-style: for smaller size control (and for disabled background) use less spacing
270 if ( hTheme )
271 {
272 // WinXP Theme
273 focusSpacingX = isEnabled ? 2 : 1;
274 focusSpacingY = sz.y > (GetCharHeight()+2) && isEnabled ? 2 : 1;
275 }
276 else
277 {
278 // Classic Theme
279 if ( isEnabled )
280 {
281 focusSpacingX = 1;
282 focusSpacingY = 1;
283 }
284 else
285 {
286 focusSpacingX = 0;
287 focusSpacingY = 0;
288 }
289 }
290 }
291 else
292 {
293 // Drawing a list item
294 isEnabled = true; // they are never disabled
295 isFocused = flags & wxCONTROL_SELECTED ? true : false;
296
297 focusSpacingX = 0;
298 focusSpacingY = 0;
299 }
300
301 // Set the background sub-rectangle for selection, disabled etc
302 wxRect selRect(rect);
303 selRect.y += focusSpacingY;
304 selRect.height -= (focusSpacingY*2);
305
306 int wcp = 0;
307
308 if ( !(flags & wxCONTROL_ISSUBMENU) )
309 wcp += m_widthCustomPaint;
310
311 selRect.x += wcp + focusSpacingX;
312 selRect.width -= wcp + (focusSpacingX*2);
313
314 //wxUxThemeEngine* theme = (wxUxThemeEngine*) NULL;
315 //if ( hTheme )
316 // theme = wxUxThemeEngine::GetIfActive();
317
318 wxColour bgCol;
319 bool drawDottedEdge = false;
320
321 if ( isEnabled )
322 {
323 // If popup is hidden and this control is focused,
324 // then draw the focus-indicator (selbgcolor background etc.).
325 if ( isFocused )
326 {
327 #if 0
328 // TODO: Proper theme color getting (JMS: I don't know which parts/colors to use,
329 // those below don't work)
330 if ( hTheme )
331 {
332 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_SELECTED,TMT_TEXTCOLOR,&cref);
333 dc.SetTextForeground( wxRGBToColour(cref) );
334 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_SELECTED,TMT_FILLCOLOR,&cref);
335 bgCol = wxRGBToColour(cref);
336 }
337 else
338 #endif
339 {
340 dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) );
341 bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
342 if ( m_windowStyle & wxCB_READONLY )
343 drawDottedEdge = true;
344 }
345 }
346 else
347 {
348 /*if ( hTheme )
349 {
350 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_TEXTCOLOR,&cref);
351 dc.SetTextForeground( wxRGBToColour(cref) );
352 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_FILLCOLOR,&cref);
353 bgCol = wxRGBToColour(cref);
354 }
355 else
356 {*/
357 dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) );
358 bgCol = GetBackgroundColour();
359 //}
360 }
361 }
362 else
363 {
364 /*if ( hTheme )
365 {
366 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_DISABLED,TMT_TEXTCOLOR,&cref);
367 dc.SetTextForeground( wxRGBToColour(cref) );
368 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_DISABLED,TMT_EDGEFILLCOLOR,&cref);
369 bgCol = wxRGBToColour(cref);
370 }
371 else
372 {*/
373 dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT) );
374 bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
375 //}
376 }
377
378 dc.SetBrush(bgCol);
379 dc.SetPen(bgCol);
380 dc.DrawRectangle(selRect);
381 if ( drawDottedEdge )
382 wxMSWDrawFocusRect(dc,selRect);
383
384 // Don't clip exactly to the selection rectangle so we can draw
385 // to the non-selected area in front of it.
386 wxRect clipRect(rect.x,rect.y,
387 (selRect.x+selRect.width)-rect.x-1,rect.height);
388 dc.SetClippingRegion(clipRect);
389 }
390
391 void wxComboCtrl::OnPaintEvent( wxPaintEvent& WXUNUSED(event) )
392 {
393 // TODO: Convert drawing in this function to Windows API Code
394
395 wxSize sz = GetClientSize();
396 wxAutoBufferedPaintDC dc(this);
397
398 const wxRect& rectb = m_btnArea;
399 wxRect rect = m_tcArea;
400 bool isEnabled = IsEnabled();
401 wxColour bgCol = GetBackgroundColour();
402 wxColour fgCol;
403
404 wxUxThemeEngine* theme = NULL;
405 wxUxThemeHandle hTheme(this, L"COMBOBOX");
406 int etsState;
407
408 // area around both controls
409 wxRect rect2(0,0,sz.x,sz.y);
410 if ( m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE )
411 {
412 rect2 = m_tcArea;
413 rect2.Inflate(1);
414 }
415
416 // Use theme to draw border on XP
417 if ( hTheme )
418 {
419 theme = wxUxThemeEngine::GetIfActive();
420 COLORREF cref;
421
422 // Select correct border colour
423 if ( !isEnabled )
424 etsState = ETS_DISABLED;
425 else
426 etsState = ETS_NORMAL;
427
428 if ( m_widthCustomBorder )
429 {
430 theme->GetThemeColor(hTheme,EP_EDITTEXT,etsState,TMT_BORDERCOLOR,&cref);
431
432 // Set border colour
433 dc.SetPen( wxRGBToColour(cref) );
434
435 dc.SetBrush( *wxTRANSPARENT_BRUSH );
436 dc.DrawRectangle(rect2);
437 }
438
439 theme->GetThemeColor(hTheme,EP_EDITTEXT,etsState,TMT_TEXTCOLOR,&cref);
440 fgCol = wxRGBToColour(cref);
441 }
442 else
443 {
444 // draw regular background
445 fgCol = GetForegroundColour();
446 }
447
448 rect2.Deflate(m_widthCustomBorder);
449
450 dc.SetBrush(bgCol);
451 dc.SetPen(bgCol);
452
453 // clear main background
454 dc.DrawRectangle(rect);
455
456 // Button background with theme?
457 bool drawButBg = true;
458 if ( hTheme && m_blankButtonBg )
459 {
460 RECT r;
461 wxCopyRectToRECT(rectb, r);
462
463 // Draw parent background if needed (since button looks like its out of
464 // the combo, this is preferred).
465 theme->DrawThemeParentBackground(GetHwndOf(this),
466 GetHdcOf(dc),
467 &r);
468
469 drawButBg = false;
470 }
471
472 // Standard button rendering
473 DrawButton(dc,rectb,drawButBg);
474
475 // paint required portion on the control
476 if ( (!m_text || m_widthCustomPaint) )
477 {
478 wxASSERT( m_widthCustomPaint >= 0 );
479
480 // this is intentionally here to allow drawed rectangle's
481 // right edge to be hidden
482 if ( m_text )
483 rect.width = m_widthCustomPaint;
484
485 dc.SetFont( GetFont() );
486
487 dc.SetClippingRegion(rect);
488 if ( m_popupInterface )
489 m_popupInterface->PaintComboControl(dc,rect);
490 else
491 wxComboPopup::DefaultPaintComboControl(this,dc,rect);
492 }
493 }
494
495 void wxComboCtrl::OnMouseEvent( wxMouseEvent& event )
496 {
497 int mx = event.m_x;
498 bool isOnButtonArea = m_btnArea.Contains(mx,event.m_y);
499 int handlerFlags = isOnButtonArea ? wxCC_MF_ON_BUTTON : 0;
500
501 if ( PreprocessMouseEvent(event,isOnButtonArea) )
502 return;
503
504 if ( (m_windowStyle & (wxCC_SPECIAL_DCLICK|wxCB_READONLY)) == wxCB_READONLY )
505 {
506 // if no textctrl and no special double-click, then the entire control acts
507 // as a button
508 handlerFlags |= wxCC_MF_ON_BUTTON;
509 if ( HandleButtonMouseEvent(event,handlerFlags) )
510 return;
511 }
512 else
513 {
514 if ( isOnButtonArea || HasCapture() ||
515 (m_widthCustomPaint && mx < (m_tcArea.x+m_widthCustomPaint)) )
516 {
517 handlerFlags |= wxCC_MF_ON_CLICK_AREA;
518
519 if ( HandleButtonMouseEvent(event,handlerFlags) )
520 return;
521 }
522 else if ( m_btnState )
523 {
524 // otherwise need to clear the hover status
525 m_btnState = 0;
526 RefreshRect(m_btnArea);
527 }
528 }
529
530 //
531 // This will handle left_down and left_dclick events outside button in a Windows-like manner.
532 // See header file for further information on this method.
533 HandleNormalMouseEvent(event);
534
535 }
536
537 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
538 static wxUint32 GetUserPreferencesMask()
539 {
540 static wxUint32 userPreferencesMask = 0;
541 static bool valueSet = false;
542
543 if ( valueSet )
544 return userPreferencesMask;
545
546 wxRegKey key(wxRegKey::HKCU, wxT("Control Panel\\Desktop"));
547 if( key.Open(wxRegKey::Read) )
548 {
549 wxMemoryBuffer buf;
550 if ( key.QueryValue(wxT("UserPreferencesMask"), buf) )
551 {
552 if ( buf.GetDataLen() >= 4 )
553 {
554 wxByte* p = (wxByte*) buf.GetData();
555 userPreferencesMask = p[3] + (p[2]<<8) + (p[1]<<16) + (p[0]<<24);
556 }
557 }
558 }
559
560 valueSet = true;
561
562 return userPreferencesMask;
563 }
564 #endif
565
566 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
567 void wxComboCtrl::OnTimerEvent( wxTimerEvent& WXUNUSED(event) )
568 {
569 bool stopTimer = false;
570
571 wxWindow* popup = GetPopupControl()->GetControl();
572
573 // Popup was hidden before it was fully shown?
574 if ( IsPopupWindowState(Hidden) )
575 {
576 stopTimer = true;
577 }
578 else
579 {
580 wxLongLong t = ::wxGetLocalTimeMillis();
581 const wxRect& rect = m_animRect;
582 wxWindow* win = GetPopupWindow();
583
584 int pos = (int) (t-m_animStart).GetLo();
585 if ( pos < COMBOBOX_ANIMATION_DURATION )
586 {
587 int height = rect.height;
588 //int h0 = rect.height;
589 int h = (((pos*256)/COMBOBOX_ANIMATION_DURATION)*height)/256;
590 int y = (height - h);
591 if ( y < 0 )
592 y = 0;
593
594 if ( m_animFlags & ShowAbove )
595 {
596 win->SetSize( rect.x, rect.y + height - h, rect.width, h );
597 }
598 else
599 {
600 popup->Move( 0, -y );
601 win->SetSize( rect.x, rect.y, rect.width, h );
602 }
603 }
604 else
605 {
606 stopTimer = true;
607 }
608 }
609
610 if ( stopTimer )
611 {
612 popup->Move( 0, 0 );
613 m_animTimer.Stop();
614 DoShowPopup( m_animRect, m_animFlags );
615 }
616 }
617 #endif
618
619 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
620 bool wxComboCtrl::AnimateShow( const wxRect& rect, int flags )
621 {
622 if ( GetUserPreferencesMask() & wxMSW_DESKTOP_USERPREFERENCESMASK_COMBOBOXANIM )
623 {
624 m_animStart = ::wxGetLocalTimeMillis();
625 m_animRect = rect;
626 m_animFlags = flags;
627
628 wxWindow* win = GetPopupWindow();
629 win->SetSize( rect.x, rect.y, rect.width, 0 );
630 win->Show();
631
632 m_animTimer.SetOwner( this, wxID_ANY );
633 m_animTimer.Start( COMBOBOX_ANIMATION_RESOLUTION, wxTIMER_CONTINUOUS );
634
635 OnTimerEvent(*((wxTimerEvent*)NULL)); // Event is never used, so we can give NULL
636
637 return false;
638 }
639
640 return true;
641 }
642 #endif
643
644 wxCoord wxComboCtrl::GetNativeTextIndent() const
645 {
646 if ( wxUxThemeEngine::GetIfActive() )
647 return NATIVE_TEXT_INDENT_XP;
648 return NATIVE_TEXT_INDENT_CLASSIC;
649 }
650
651 bool wxComboCtrl::IsKeyPopupToggle(const wxKeyEvent& event) const
652 {
653 const bool isPopupShown = IsPopupShown();
654
655 switch ( event.GetKeyCode() )
656 {
657 case WXK_F4:
658 // F4 toggles the popup in the native comboboxes, so emulate them
659 if ( !event.AltDown() )
660 return true;
661 break;
662
663 case WXK_ESCAPE:
664 if ( isPopupShown )
665 return true;
666 break;
667
668 case WXK_DOWN:
669 case WXK_UP:
670 // On XP or with writable combo in Classic, arrows don't open the
671 // popup but Alt-arrow does
672 if ( event.AltDown() ||
673 ( !isPopupShown &&
674 HasFlag(wxCB_READONLY) &&
675 !wxUxThemeEngine::GetIfActive()
676 ) )
677 {
678 return true;
679 }
680 break;
681 }
682
683 return false;
684 }
685
686 #endif // wxUSE_COMBOCTRL