1///////////////////////////////////////////////////////////////////////////// 2// Name: src/msw/checkbox.cpp 3// Purpose: wxCheckBox 4// Author: Julian Smart 5// Modified by: 6// Created: 04/01/98 7// RCS-ID: $Id: checkbox.cpp 40331 2006-07-25 18:47:39Z VZ $ 8// Copyright: (c) Julian Smart 9// Licence: wxWindows licence 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_CHECKBOX 28 29#include "wx/checkbox.h" 30 31#ifndef WX_PRECOMP 32 #include "wx/brush.h" 33 #include "wx/dcscreen.h" 34 #include "wx/settings.h" 35#endif 36 37#include "wx/msw/uxtheme.h" 38#include "wx/msw/private.h" 39 40// ---------------------------------------------------------------------------- 41// constants 42// ---------------------------------------------------------------------------- 43 44#ifndef BST_UNCHECKED 45 #define BST_UNCHECKED 0x0000 46#endif 47 48#ifndef BST_CHECKED 49 #define BST_CHECKED 0x0001 50#endif 51 52#ifndef BST_INDETERMINATE 53 #define BST_INDETERMINATE 0x0002 54#endif 55 56#ifndef DFCS_HOT 57 #define DFCS_HOT 0x1000 58#endif 59 60#ifndef DT_HIDEPREFIX 61 #define DT_HIDEPREFIX 0x00100000 62#endif 63 64#ifndef BP_CHECKBOX 65 #define BP_CHECKBOX 3 66#endif 67 68// these values are defined in tmschema.h (except the first one) 69enum 70{ 71 CBS_INVALID, 72 CBS_UNCHECKEDNORMAL, 73 CBS_UNCHECKEDHOT, 74 CBS_UNCHECKEDPRESSED, 75 CBS_UNCHECKEDDISABLED, 76 CBS_CHECKEDNORMAL, 77 CBS_CHECKEDHOT, 78 CBS_CHECKEDPRESSED, 79 CBS_CHECKEDDISABLED, 80 CBS_MIXEDNORMAL, 81 CBS_MIXEDHOT, 82 CBS_MIXEDPRESSED, 83 CBS_MIXEDDISABLED 84}; 85 86// these are our own 87enum 88{ 89 CBS_HOT_OFFSET = 1, 90 CBS_PRESSED_OFFSET = 2, 91 CBS_DISABLED_OFFSET = 3 92}; 93 94// ============================================================================ 95// implementation 96// ============================================================================ 97 98#if wxUSE_EXTENDED_RTTI 99WX_DEFINE_FLAGS( wxCheckBoxStyle ) 100 101wxBEGIN_FLAGS( wxCheckBoxStyle ) 102 // new style border flags, we put them first to 103 // use them for streaming out 104 wxFLAGS_MEMBER(wxBORDER_SIMPLE) 105 wxFLAGS_MEMBER(wxBORDER_SUNKEN) 106 wxFLAGS_MEMBER(wxBORDER_DOUBLE) 107 wxFLAGS_MEMBER(wxBORDER_RAISED) 108 wxFLAGS_MEMBER(wxBORDER_STATIC) 109 wxFLAGS_MEMBER(wxBORDER_NONE) 110 111 // old style border flags 112 wxFLAGS_MEMBER(wxSIMPLE_BORDER) 113 wxFLAGS_MEMBER(wxSUNKEN_BORDER) 114 wxFLAGS_MEMBER(wxDOUBLE_BORDER) 115 wxFLAGS_MEMBER(wxRAISED_BORDER) 116 wxFLAGS_MEMBER(wxSTATIC_BORDER) 117 wxFLAGS_MEMBER(wxNO_BORDER) 118 119 // standard window styles 120 wxFLAGS_MEMBER(wxTAB_TRAVERSAL) 121 wxFLAGS_MEMBER(wxCLIP_CHILDREN) 122 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW) 123 wxFLAGS_MEMBER(wxWANTS_CHARS) 124 wxFLAGS_MEMBER(wxNO_FULL_REPAINT_ON_RESIZE) 125 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB ) 126 wxFLAGS_MEMBER(wxVSCROLL) 127 wxFLAGS_MEMBER(wxHSCROLL) 128 129wxEND_FLAGS( wxCheckBoxStyle ) 130 131IMPLEMENT_DYNAMIC_CLASS_XTI(wxCheckBox, wxControl,"wx/checkbox.h") 132 133wxBEGIN_PROPERTIES_TABLE(wxCheckBox) 134 wxEVENT_PROPERTY( Click , wxEVT_COMMAND_CHECKBOX_CLICKED , wxCommandEvent ) 135 136 wxPROPERTY( Font , wxFont , SetFont , GetFont , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) 137 wxPROPERTY( Label,wxString, SetLabel, GetLabel, wxString() , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) 138 wxPROPERTY( Value ,bool, SetValue, GetValue, EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) 139 wxPROPERTY_FLAGS( WindowStyle , wxCheckBoxStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style 140wxEND_PROPERTIES_TABLE() 141 142wxBEGIN_HANDLERS_TABLE(wxCheckBox) 143wxEND_HANDLERS_TABLE() 144 145wxCONSTRUCTOR_6( wxCheckBox , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle ) 146#else 147IMPLEMENT_DYNAMIC_CLASS(wxCheckBox, wxControl) 148#endif 149 150 151// ---------------------------------------------------------------------------- 152// wxCheckBox creation 153// ---------------------------------------------------------------------------- 154 155void wxCheckBox::Init() 156{ 157 m_state = wxCHK_UNCHECKED; 158 m_isPressed = 159 m_isHot = false; 160} 161 162bool wxCheckBox::Create(wxWindow *parent, 163 wxWindowID id, 164 const wxString& label, 165 const wxPoint& pos, 166 const wxSize& size, long style, 167 const wxValidator& validator, 168 const wxString& name) 169{ 170 Init(); 171 172 if ( !CreateControl(parent, id, pos, size, style, validator, name) ) 173 return false; 174 175 long msStyle = WS_TABSTOP; 176 177 if ( style & wxCHK_3STATE ) 178 { 179 msStyle |= BS_3STATE; 180 } 181 else 182 { 183 wxASSERT_MSG( !Is3rdStateAllowedForUser(), 184 wxT("Using wxCH_ALLOW_3RD_STATE_FOR_USER") 185 wxT(" style flag for a 2-state checkbox is useless") ); 186 msStyle |= BS_CHECKBOX; 187 } 188 189 if ( style & wxALIGN_RIGHT ) 190 { 191 msStyle |= BS_LEFTTEXT | BS_RIGHT; 192 } 193 194 return MSWCreateControl(wxT("BUTTON"), msStyle, pos, size, label, 0); 195} 196 197// ---------------------------------------------------------------------------- 198// wxCheckBox geometry 199// ---------------------------------------------------------------------------- 200 201wxSize wxCheckBox::DoGetBestSize() const 202{ 203 static int s_checkSize = 0; 204 205 if ( !s_checkSize ) 206 { 207 wxScreenDC dc; 208 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); 209 210 s_checkSize = dc.GetCharHeight(); 211 } 212 213 wxString str = wxGetWindowText(GetHWND()); 214 215 int wCheckbox, hCheckbox; 216 if ( !str.empty() ) 217 { 218 GetTextExtent(GetLabelText(str), &wCheckbox, &hCheckbox); 219 wCheckbox += s_checkSize + GetCharWidth(); 220 221 if ( hCheckbox < s_checkSize ) 222 hCheckbox = s_checkSize; 223 } 224 else 225 { 226 wCheckbox = s_checkSize; 227 hCheckbox = s_checkSize; 228 } 229#ifdef __WXWINCE__ 230 hCheckbox += 1; 231#endif 232 233 wxSize best(wCheckbox, hCheckbox); 234 CacheBestSize(best); 235 return best; 236} 237 238// ---------------------------------------------------------------------------- 239// wxCheckBox operations 240// ---------------------------------------------------------------------------- 241 242void wxCheckBox::SetValue(bool val) 243{ 244 Set3StateValue(val ? wxCHK_CHECKED : wxCHK_UNCHECKED); 245} 246 247bool wxCheckBox::GetValue() const 248{ 249 return Get3StateValue() != wxCHK_UNCHECKED; 250} 251 252void wxCheckBox::Command(wxCommandEvent& event) 253{ 254 int state = event.GetInt(); 255 wxCHECK_RET( (state == wxCHK_UNCHECKED) || (state == wxCHK_CHECKED) 256 || (state == wxCHK_UNDETERMINED), 257 wxT("event.GetInt() returned an invalid checkbox state") ); 258 259 Set3StateValue((wxCheckBoxState) state); 260 ProcessCommand(event); 261} 262 263wxCOMPILE_TIME_ASSERT(wxCHK_UNCHECKED == BST_UNCHECKED 264 && wxCHK_CHECKED == BST_CHECKED 265 && wxCHK_UNDETERMINED == BST_INDETERMINATE, EnumValuesIncorrect); 266 267void wxCheckBox::DoSet3StateValue(wxCheckBoxState state) 268{ 269 m_state = state; 270 if ( !IsOwnerDrawn() ) 271 ::SendMessage(GetHwnd(), BM_SETCHECK, (WPARAM) state, 0); 272 else // owner drawn buttons don't react to this message 273 Refresh(); 274} 275 276wxCheckBoxState wxCheckBox::DoGet3StateValue() const 277{ 278 return m_state; 279} 280 281bool wxCheckBox::MSWCommand(WXUINT cmd, WXWORD WXUNUSED(id)) 282{ 283 if ( cmd != BN_CLICKED && cmd != BN_DBLCLK ) 284 return false; 285 286 // first update the value so that user event handler gets the new checkbox 287 // value 288 289 // ownerdrawn buttons don't manage their state themselves unlike usual 290 // auto checkboxes so do it ourselves in any case 291 wxCheckBoxState state; 292 if ( Is3rdStateAllowedForUser() ) 293 { 294 state = (wxCheckBoxState)((m_state + 1) % 3); 295 } 296 else // 2 state checkbox (at least from users point of view) 297 { 298 // note that wxCHK_UNDETERMINED also becomes unchecked when clicked 299 state = m_state == wxCHK_UNCHECKED ? wxCHK_CHECKED : wxCHK_UNCHECKED; 300 } 301 302 DoSet3StateValue(state); 303 304 305 // generate the event 306 wxCommandEvent event(wxEVT_COMMAND_CHECKBOX_CLICKED, m_windowId); 307 308 event.SetInt(state); 309 event.SetEventObject(this); 310 ProcessCommand(event); 311 312 return true; 313} 314 315// ---------------------------------------------------------------------------- 316// owner drawn checkboxes stuff 317// ---------------------------------------------------------------------------- 318 319bool wxCheckBox::SetForegroundColour(const wxColour& colour) 320{ 321 if ( !wxCheckBoxBase::SetForegroundColour(colour) ) 322 return false; 323 324 // the only way to change the checkbox foreground colour under Windows XP 325 // is to owner draw it 326 if ( wxUxThemeEngine::GetIfActive() ) 327 MakeOwnerDrawn(colour.Ok()); 328 329 return true; 330} 331 332bool wxCheckBox::IsOwnerDrawn() const 333{ 334 return 335 (::GetWindowLong(GetHwnd(), GWL_STYLE) & BS_OWNERDRAW) == BS_OWNERDRAW; 336} 337 338void wxCheckBox::MakeOwnerDrawn(bool ownerDrawn) 339{ 340 long style = ::GetWindowLong(GetHwnd(), GWL_STYLE); 341 342 // note that BS_CHECKBOX & BS_OWNERDRAW != 0 so we can't operate on 343 // them as on independent style bits 344 if ( ownerDrawn ) 345 { 346 style &= ~(BS_CHECKBOX | BS_3STATE); 347 style |= BS_OWNERDRAW; 348 349 Connect(wxEVT_ENTER_WINDOW, 350 wxMouseEventHandler(wxCheckBox::OnMouseEnterOrLeave)); 351 Connect(wxEVT_LEAVE_WINDOW, 352 wxMouseEventHandler(wxCheckBox::OnMouseEnterOrLeave)); 353 Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxCheckBox::OnMouseLeft)); 354 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(wxCheckBox::OnMouseLeft)); 355 Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxCheckBox::OnFocus)); 356 Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(wxCheckBox::OnFocus)); 357 } 358 else // reset to default colour 359 { 360 style &= ~BS_OWNERDRAW; 361 style |= HasFlag(wxCHK_3STATE) ? BS_3STATE : BS_CHECKBOX; 362 363 Disconnect(wxEVT_ENTER_WINDOW, 364 wxMouseEventHandler(wxCheckBox::OnMouseEnterOrLeave)); 365 Disconnect(wxEVT_LEAVE_WINDOW, 366 wxMouseEventHandler(wxCheckBox::OnMouseEnterOrLeave)); 367 Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxCheckBox::OnMouseLeft)); 368 Disconnect(wxEVT_LEFT_UP, wxMouseEventHandler(wxCheckBox::OnMouseLeft)); 369 Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxCheckBox::OnFocus)); 370 Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(wxCheckBox::OnFocus)); 371 } 372 373 ::SetWindowLong(GetHwnd(), GWL_STYLE, style); 374 375 if ( !ownerDrawn ) 376 { 377 // ensure that controls state is consistent with internal state 378 DoSet3StateValue(m_state); 379 } 380} 381 382void wxCheckBox::OnMouseEnterOrLeave(wxMouseEvent& event) 383{ 384 m_isHot = event.GetEventType() == wxEVT_ENTER_WINDOW; 385 if ( !m_isHot ) 386 m_isPressed = false; 387 388 Refresh(); 389 390 event.Skip(); 391} 392 393void wxCheckBox::OnMouseLeft(wxMouseEvent& event) 394{ 395 // TODO: we should capture the mouse here to be notified about left up 396 // event but this interferes with BN_CLICKED generation so if we 397 // want to do this we'd need to generate them ourselves 398 m_isPressed = event.GetEventType() == wxEVT_LEFT_DOWN; 399 Refresh(); 400 401 event.Skip(); 402} 403 404void wxCheckBox::OnFocus(wxFocusEvent& event) 405{ 406 Refresh(); 407 408 event.Skip(); 409} 410 411bool wxCheckBox::MSWOnDraw(WXDRAWITEMSTRUCT *item) 412{ 413 DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)item; 414 415 if ( !IsOwnerDrawn() || dis->CtlType != ODT_BUTTON ) 416 return wxCheckBoxBase::MSWOnDraw(item); 417 418 // calculate the rectangles for the check mark itself and the label 419 HDC hdc = dis->hDC; 420 RECT& rect = dis->rcItem; 421 RECT rectCheck, 422 rectLabel; 423 rectCheck.top = 424 rectLabel.top = rect.top; 425 rectCheck.bottom = 426 rectLabel.bottom = rect.bottom; 427 const int checkSize = GetBestSize().y; 428 const int MARGIN = 3; 429 430 const bool isRightAligned = HasFlag(wxALIGN_RIGHT); 431 if ( isRightAligned ) 432 { 433 rectCheck.right = rect.right; 434 rectCheck.left = rectCheck.right - checkSize; 435 436 rectLabel.right = rectCheck.left - MARGIN; 437 rectLabel.left = rect.left; 438 } 439 else // normal, left-aligned checkbox 440 { 441 rectCheck.left = rect.left; 442 rectCheck.right = rectCheck.left + checkSize; 443 444 rectLabel.left = rectCheck.right + MARGIN; 445 rectLabel.right = rect.right; 446 } 447 448 // show we draw a focus rect? 449 const bool isFocused = m_isPressed || FindFocus() == this; 450 451 452 // draw the checkbox itself: note that this should really, really be in 453 // wxRendererNative but unfortunately we can't add a new virtual function 454 // to it without breaking backwards compatibility 455 456 // classic Win32 version -- this can be useful when we move this into 457 // wxRendererNative 458#if defined(__WXWINCE__) || !wxUSE_UXTHEME 459 UINT state = DFCS_BUTTONCHECK; 460 if ( !IsEnabled() ) 461 state |= DFCS_INACTIVE; 462 switch ( Get3StateValue() ) 463 { 464 case wxCHK_CHECKED: 465 state |= DFCS_CHECKED; 466 break; 467 468 case wxCHK_UNDETERMINED: 469 state |= DFCS_PUSHED; 470 break; 471 472 default: 473 wxFAIL_MSG( _T("unexpected Get3StateValue() return value") ); 474 // fall through 475 476 case wxCHK_UNCHECKED: 477 // no extra styles needed 478 break; 479 } 480 481 if ( wxFindWindowAtPoint(wxGetMousePosition()) == this ) 482 state |= DFCS_HOT; 483 484 if ( !::DrawFrameControl(hdc, &rectCheck, DFC_BUTTON, state) ) 485 { 486 wxLogLastError(_T("DrawFrameControl(DFC_BUTTON)")); 487 } 488#else // XP version 489 wxUxThemeEngine *themeEngine = wxUxThemeEngine::GetIfActive(); 490 if ( !themeEngine ) 491 return false; 492 493 wxUxThemeHandle theme(this, L"BUTTON"); 494 if ( !theme ) 495 return false; 496 497 int state; 498 switch ( Get3StateValue() ) 499 { 500 case wxCHK_CHECKED: 501 state = CBS_CHECKEDNORMAL; 502 break; 503 504 case wxCHK_UNDETERMINED: 505 state = CBS_MIXEDNORMAL; 506 break; 507 508 default: 509 wxFAIL_MSG( _T("unexpected Get3StateValue() return value") ); 510 // fall through 511 512 case wxCHK_UNCHECKED: 513 state = CBS_UNCHECKEDNORMAL; 514 break; 515 } 516 517 if ( !IsEnabled() ) 518 state += CBS_DISABLED_OFFSET; 519 else if ( m_isPressed ) 520 state += CBS_PRESSED_OFFSET; 521 else if ( m_isHot ) 522 state += CBS_HOT_OFFSET; 523 524 HRESULT hr = themeEngine->DrawThemeBackground 525 ( 526 theme, 527 hdc, 528 BP_CHECKBOX, 529 state, 530 &rectCheck, 531 NULL 532 ); 533 if ( FAILED(hr) ) 534 { 535 wxLogApiError(_T("DrawThemeBackground(BP_CHECKBOX)"), hr); 536 } 537#endif // 0/1 538 539 // draw the text 540 const wxString& label = GetLabel(); 541 542 // first we need to measure it 543 UINT fmt = DT_NOCLIP; 544 545 // drawing underlying doesn't look well with focus rect (and the native 546 // control doesn't do it) 547 if ( isFocused ) 548 fmt |= DT_HIDEPREFIX; 549 if ( isRightAligned ) 550 fmt |= DT_RIGHT; 551 // TODO: also use DT_HIDEPREFIX if the system is configured so 552 553 // we need to get the label real size first if we have to draw a focus rect 554 // around it 555 if ( isFocused ) 556 { 557 if ( !::DrawText(hdc, label, label.length(), &rectLabel, 558 fmt | DT_CALCRECT) ) 559 { 560 wxLogLastError(_T("DrawText(DT_CALCRECT)")); 561 } 562 } 563 564 if ( !IsEnabled() ) 565 { 566 ::SetTextColor(hdc, ::GetSysColor(COLOR_GRAYTEXT)); 567 } 568 569 if ( !::DrawText(hdc, label, label.length(), &rectLabel, fmt) ) 570 { 571 wxLogLastError(_T("DrawText()")); 572 } 573 574 // finally draw the focus 575 if ( isFocused ) 576 { 577 rectLabel.left--; 578 rectLabel.right++; 579 if ( !::DrawFocusRect(hdc, &rectLabel) ) 580 { 581 wxLogLastError(_T("DrawFocusRect()")); 582 } 583 } 584 585 return true; 586} 587 588#endif // wxUSE_CHECKBOX 589