Redone (generic) wxComboCtrl background painting and handling. The 'actual' wxWindow...
[wxWidgets.git] / src / generic / combog.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/combog.cpp
3 // Purpose: Generic 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 #include "wx/combo.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/log.h"
32 #include "wx/combobox.h"
33 #include "wx/dcclient.h"
34 #include "wx/settings.h"
35 #include "wx/textctrl.h"
36 #endif
37
38 #include "wx/dcbuffer.h"
39
40 // ----------------------------------------------------------------------------
41 // Some constant adjustments to make the generic more bearable
42
43 #if defined(__WXUNIVERSAL__)
44
45 // position adjustment for wxTextCtrl, to achieve zero left margin
46 // meaningless if LEFT_MARGIN_CAN_BE_SET set to 1 in combocmn.cpp
47 #define TEXTCTRLXADJUST 0
48
49 #define TEXTCTRLYADJUST 0
50 #define TEXTXADJUST 0 // how much is read-only text's x adjusted
51 #define DEFAULT_DROPBUTTON_WIDTH 19
52
53 #elif defined(__WXMSW__)
54
55 // position adjustment for wxTextCtrl, to achieve zero left margin
56 // meaningless if LEFT_MARGIN_CAN_BE_SET set to 1 in combocmn.cpp
57 #define TEXTCTRLXADJUST 2
58
59 #define TEXTCTRLYADJUST 3
60 #define TEXTXADJUST 0 // how much is read-only text's x adjusted
61 #define DEFAULT_DROPBUTTON_WIDTH 17
62
63 #elif defined(__WXGTK__)
64
65 // position adjustment for wxTextCtrl, to achieve zero left margin
66 // meaningless if LEFT_MARGIN_CAN_BE_SET set to 1 in combocmn.cpp
67 #define TEXTCTRLXADJUST -1
68
69 #define TEXTCTRLYADJUST 0
70 #define TEXTXADJUST 1 // how much is read-only text's x adjusted
71 #define DEFAULT_DROPBUTTON_WIDTH 23
72
73 #elif defined(__WXMAC__)
74
75 // position adjustment for wxTextCtrl, to achieve zero left margin
76 // meaningless if LEFT_MARGIN_CAN_BE_SET set to 1 in combocmn.cpp
77 #define TEXTCTRLXADJUST 0
78
79 #define TEXTCTRLYADJUST 0
80 #define TEXTXADJUST 0 // how much is read-only text's x adjusted
81 #define DEFAULT_DROPBUTTON_WIDTH 22
82
83 #else
84
85 // position adjustment for wxTextCtrl, to achieve zero left margin
86 // meaningless if LEFT_MARGIN_CAN_BE_SET set to 1 in combocmn.cpp
87 #define TEXTCTRLXADJUST 0
88
89 #define TEXTCTRLYADJUST 0
90 #define TEXTXADJUST 0 // how much is read-only text's x adjusted
91 #define DEFAULT_DROPBUTTON_WIDTH 19
92
93 #endif
94
95
96 // ============================================================================
97 // implementation
98 // ============================================================================
99
100 // Only implement if no native or it wasn't fully featured
101 #ifndef wxCOMBOCONTROL_FULLY_FEATURED
102
103
104 // ----------------------------------------------------------------------------
105 // wxGenericComboCtrl
106 // ----------------------------------------------------------------------------
107
108 BEGIN_EVENT_TABLE(wxGenericComboCtrl, wxComboCtrlBase)
109 EVT_PAINT(wxGenericComboCtrl::OnPaintEvent)
110 EVT_MOUSE_EVENTS(wxGenericComboCtrl::OnMouseEvent)
111 END_EVENT_TABLE()
112
113
114 IMPLEMENT_DYNAMIC_CLASS(wxGenericComboCtrl, wxComboCtrlBase)
115
116 void wxGenericComboCtrl::Init()
117 {
118 }
119
120 bool wxGenericComboCtrl::Create(wxWindow *parent,
121 wxWindowID id,
122 const wxString& value,
123 const wxPoint& pos,
124 const wxSize& size,
125 long style,
126 const wxValidator& validator,
127 const wxString& name)
128 {
129 //
130 // Note that technically we only support 'default' border and wxNO_BORDER.
131 long border = style & wxBORDER_MASK;
132 int tcBorder = wxNO_BORDER;
133
134 #if defined(__WXUNIVERSAL__)
135 if ( !border )
136 border = wxBORDER_SIMPLE;
137 #elif defined(__WXMSW__)
138 if ( !border )
139 // For XP, have 1-width custom border, for older version use sunken
140 /*if ( wxUxThemeEngine::GetIfActive() )
141 {
142 border = wxBORDER_NONE;
143 m_widthCustomBorder = 1;
144 }
145 else*/
146 border = wxBORDER_SUNKEN;
147 #else
148
149 //
150 // Generic version is optimized for wxGTK
151 //
152
153 #define UNRELIABLE_TEXTCTRL_BORDER
154
155 if ( !border )
156 {
157 if ( style & wxCB_READONLY )
158 {
159 m_widthCustomBorder = 1;
160 }
161 else
162 {
163 m_widthCustomBorder = 0;
164 tcBorder = 0;
165 }
166 }
167 else
168 {
169 // Have textctrl instead use the border given.
170 tcBorder = border;
171 }
172
173 // Because we are going to have button outside the border,
174 // let's use wxBORDER_NONE for the whole control.
175 border = wxBORDER_NONE;
176
177 Customize( wxCC_BUTTON_OUTSIDE_BORDER |
178 wxCC_NO_TEXT_AUTO_SELECT |
179 wxCC_BUTTON_STAYS_DOWN );
180
181 #endif
182
183 style = (style & ~(wxBORDER_MASK)) | border;
184 if ( style & wxCC_STD_BUTTON )
185 m_iFlags |= wxCC_POPUP_ON_MOUSE_UP;
186
187 // create main window
188 if ( !wxComboCtrlBase::Create(parent,
189 id,
190 value,
191 pos,
192 size,
193 style | wxFULL_REPAINT_ON_RESIZE,
194 validator,
195 name) )
196 return false;
197
198 // Create textctrl, if necessary
199 CreateTextCtrl( tcBorder );
200
201 // Add keyboard input handlers for main control and textctrl
202 InstallInputHandlers();
203
204 // Set background style for double-buffering, when needed
205 // (cannot use when system draws background automatically)
206 if ( !HasTransparentBackground() )
207 SetBackgroundStyle( wxBG_STYLE_PAINT );
208
209 // SetInitialSize should be called last
210 SetInitialSize(size);
211
212 return true;
213 }
214
215 wxGenericComboCtrl::~wxGenericComboCtrl()
216 {
217 }
218
219 void wxGenericComboCtrl::OnResize()
220 {
221
222 // Recalculates button and textctrl areas
223 CalculateAreas(DEFAULT_DROPBUTTON_WIDTH);
224
225 #if 0
226 // Move separate button control, if any, to correct position
227 if ( m_btn )
228 {
229 wxSize sz = GetClientSize();
230 m_btn->SetSize( m_btnArea.x + m_btnSpacingX,
231 (sz.y-m_btnSize.y)/2,
232 m_btnSize.x,
233 m_btnSize.y );
234 }
235 #endif
236
237 // Move textctrl, if any, accordingly
238 PositionTextCtrl( TEXTCTRLXADJUST, TEXTCTRLYADJUST );
239 }
240
241 void wxGenericComboCtrl::OnPaintEvent( wxPaintEvent& WXUNUSED(event) )
242 {
243 // Determine wxDC to use based on need to double-buffer or
244 // use system-generated transparent background portions
245 wxDC* dcPtr;
246 if ( HasTransparentBackground() )
247 dcPtr = new wxPaintDC(this);
248 else
249 dcPtr = new wxAutoBufferedPaintDC(this);
250 wxDC& dc = *dcPtr;
251
252 wxSize sz = GetClientSize();
253 const wxRect& butRect = m_btnArea;
254 wxRect tcRect = m_tcArea;
255 wxRect fullRect(0, 0, sz.x, sz.y);
256
257 // artificial simple border
258 if ( m_widthCustomBorder )
259 {
260 int customBorder = m_widthCustomBorder;
261
262 // Set border colour
263 wxPen pen1( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT),
264 customBorder,
265 wxPENSTYLE_SOLID);
266 dc.SetPen( pen1 );
267
268 // area around both controls
269 wxRect rect2(fullRect);
270 if ( m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE )
271 {
272 rect2 = tcRect;
273 if ( customBorder == 1 )
274 {
275 rect2.Inflate(1);
276 }
277 else
278 {
279 #ifdef __WXGTK__
280 rect2.x -= 1;
281 rect2.y -= 1;
282 #else
283 rect2.x -= customBorder;
284 rect2.y -= customBorder;
285 #endif
286 rect2.width += 1 + customBorder;
287 rect2.height += 1 + customBorder;
288 }
289 }
290
291 dc.SetBrush( *wxTRANSPARENT_BRUSH );
292 dc.DrawRectangle(rect2);
293 }
294
295 // Clear the main background if the system doesn't do it by itself
296 if ( !HasTransparentBackground() &&
297 (tcRect.x > 0 || tcRect.y > 0) )
298 {
299 wxColour winCol = GetParent()->GetBackgroundColour();
300 dc.SetBrush(winCol);
301 dc.SetPen(winCol);
302
303 dc.DrawRectangle(fullRect);
304 }
305
306 if ( !m_btn )
307 {
308 // Standard button rendering
309 DrawButton(dc, butRect);
310 }
311
312 // paint required portion on the control
313 if ( !m_text || m_widthCustomPaint )
314 {
315 wxASSERT( m_widthCustomPaint >= 0 );
316
317 // Clear the text-control area background
318 wxColour tcCol = GetBackgroundColour();
319 dc.SetBrush(tcCol);
320 dc.SetPen(tcCol);
321 dc.DrawRectangle(tcRect);
322
323 // this is intentionally here to allow drawed rectangle's
324 // right edge to be hidden
325 if ( m_text )
326 tcRect.width = m_widthCustomPaint;
327
328 dc.SetFont( GetFont() );
329
330 dc.SetClippingRegion(tcRect);
331 if ( m_popupInterface )
332 m_popupInterface->PaintComboControl(dc, tcRect);
333 else
334 wxComboPopup::DefaultPaintComboControl(this, dc, tcRect);
335 }
336
337 delete dcPtr;
338 }
339
340 void wxGenericComboCtrl::OnMouseEvent( wxMouseEvent& event )
341 {
342 int mx = event.m_x;
343 bool isOnButtonArea = m_btnArea.Contains(mx,event.m_y);
344 int handlerFlags = isOnButtonArea ? wxCC_MF_ON_BUTTON : 0;
345
346 if ( PreprocessMouseEvent(event,handlerFlags) )
347 return;
348
349 const bool ctrlIsButton = wxPlatformIs(wxOS_WINDOWS);
350
351 if ( ctrlIsButton &&
352 (m_windowStyle & (wxCC_SPECIAL_DCLICK|wxCB_READONLY)) == wxCB_READONLY )
353 {
354 // if no textctrl and no special double-click, then the entire control acts
355 // as a button
356 handlerFlags |= wxCC_MF_ON_BUTTON;
357 if ( HandleButtonMouseEvent(event,handlerFlags) )
358 return;
359 }
360 else
361 {
362 if ( isOnButtonArea || HasCapture() ||
363 (m_widthCustomPaint && mx < (m_tcArea.x+m_widthCustomPaint)) )
364 {
365 handlerFlags |= wxCC_MF_ON_CLICK_AREA;
366
367 if ( HandleButtonMouseEvent(event,handlerFlags) )
368 return;
369 }
370 else if ( m_btnState )
371 {
372 // otherwise need to clear the hover status
373 m_btnState = 0;
374 RefreshRect(m_btnArea);
375 }
376 }
377
378 //
379 // This will handle left_down and left_dclick events outside button in a Windows/GTK-like manner.
380 // See header file for further information on this method.
381 HandleNormalMouseEvent(event);
382
383 }
384
385 void wxGenericComboCtrl::SetCustomPaintWidth( int width )
386 {
387 #ifdef UNRELIABLE_TEXTCTRL_BORDER
388 //
389 // If starting/stopping to show an image in front
390 // of a writable text-field, then re-create textctrl
391 // with different kind of border (because we can't
392 // assume that textctrl fully supports wxNO_BORDER).
393 //
394 wxTextCtrl* tc = GetTextCtrl();
395
396 if ( tc && (m_iFlags & wxCC_BUTTON_OUTSIDE_BORDER) )
397 {
398 int borderType = tc->GetWindowStyle() & wxBORDER_MASK;
399 int tcCreateStyle = -1;
400
401 if ( width > 0 )
402 {
403 // Re-create textctrl with no border
404 if ( borderType != wxNO_BORDER )
405 {
406 m_widthCustomBorder = 1;
407 tcCreateStyle = wxNO_BORDER;
408 }
409 }
410 else if ( width == 0 )
411 {
412 // Re-create textctrl with normal border
413 if ( borderType == wxNO_BORDER )
414 {
415 m_widthCustomBorder = 0;
416 tcCreateStyle = 0;
417 }
418 }
419
420 // Common textctrl re-creation code
421 if ( tcCreateStyle != -1 )
422 {
423 tc->RemoveEventHandler(m_textEvtHandler);
424 delete m_textEvtHandler;
425
426 CreateTextCtrl( tcCreateStyle );
427
428 InstallInputHandlers();
429 }
430 }
431 #endif // UNRELIABLE_TEXTCTRL_BORDER
432
433 wxComboCtrlBase::SetCustomPaintWidth( width );
434 }
435
436 bool wxGenericComboCtrl::IsKeyPopupToggle(const wxKeyEvent& event) const
437 {
438 int keycode = event.GetKeyCode();
439 bool isPopupShown = IsPopupShown();
440
441 // This code is AFAIK appropriate for wxGTK.
442
443 if ( isPopupShown )
444 {
445 if ( keycode == WXK_ESCAPE ||
446 ( keycode == WXK_UP && event.AltDown() ) )
447 return true;
448 }
449 else
450 {
451 if ( (keycode == WXK_DOWN && event.AltDown()) ||
452 (keycode == WXK_F4) )
453 return true;
454 }
455
456 return false;
457 }
458
459 #ifdef __WXUNIVERSAL__
460
461 bool wxGenericComboCtrl::PerformAction(const wxControlAction& action,
462 long numArg,
463 const wxString& strArg)
464 {
465 bool processed = false;
466 if ( action == wxACTION_COMBOBOX_POPUP )
467 {
468 if ( !IsPopupShown() )
469 {
470 ShowPopup();
471
472 processed = true;
473 }
474 }
475 else if ( action == wxACTION_COMBOBOX_DISMISS )
476 {
477 if ( IsPopupShown() )
478 {
479 HidePopup();
480
481 processed = true;
482 }
483 }
484
485 if ( !processed )
486 {
487 // pass along
488 return wxControl::PerformAction(action, numArg, strArg);
489 }
490
491 return true;
492 }
493
494 #endif // __WXUNIVERSAL__
495
496 // If native wxComboCtrl was not defined, then prepare a simple
497 // front-end so that wxRTTI works as expected.
498 #ifndef _WX_COMBOCONTROL_H_
499 IMPLEMENT_DYNAMIC_CLASS(wxComboCtrl, wxGenericComboCtrl)
500 #endif
501
502 #endif // !wxCOMBOCONTROL_FULLY_FEATURED
503
504 #endif // wxUSE_COMBOCTRL