1/////////////////////////////////////////////////////////////////////////////// 2// Name: src/common/containr.cpp 3// Purpose: implementation of wxControlContainer 4// Author: Vadim Zeitlin 5// Modified by: 6// Created: 06.08.01 7// RCS-ID: $Id: containr.cpp 44273 2007-01-21 01:21:45Z VZ $ 8// Copyright: (c) 2001 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr> 9// License: 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#ifndef WX_PRECOMP 28 #include "wx/log.h" 29 #include "wx/event.h" 30 #include "wx/window.h" 31 #include "wx/scrolbar.h" 32 #include "wx/radiobut.h" 33 #include "wx/containr.h" 34#endif //WX_PRECOMP 35 36// trace mask for focus messages 37#define TRACE_FOCUS _T("focus") 38 39// ============================================================================ 40// implementation 41// ============================================================================ 42 43wxControlContainer::wxControlContainer(wxWindow *winParent) 44{ 45 m_winParent = winParent; 46 m_winLastFocused = NULL; 47 m_inSetFocus = false; 48} 49 50bool wxControlContainer::AcceptsFocus() const 51{ 52 // if we're not shown or disabled, we can't accept focus 53 if ( m_winParent->IsShown() && m_winParent->IsEnabled() ) 54 { 55 // otherwise we can accept focus either if we have no children at all 56 // (in this case we're probably not used as a container) or only when 57 // at least one child will accept focus 58 wxWindowList::compatibility_iterator node = m_winParent->GetChildren().GetFirst(); 59 if ( !node ) 60 return true; 61 62#ifdef __WXMAC__ 63 // wxMac has eventually the two scrollbars as children, they don't count 64 // as real children in the algorithm mentioned above 65 bool hasRealChildren = false ; 66#endif 67 68 while ( node ) 69 { 70 wxWindow *child = node->GetData(); 71 node = node->GetNext(); 72 73#ifdef __WXMAC__ 74 if ( m_winParent->MacIsWindowScrollbar( child ) ) 75 continue; 76 hasRealChildren = true ; 77#endif 78 if ( child->AcceptsFocus() ) 79 { 80 return true; 81 } 82 } 83 84#ifdef __WXMAC__ 85 if ( !hasRealChildren ) 86 return true ; 87#endif 88 } 89 90 return false; 91} 92 93void wxControlContainer::SetLastFocus(wxWindow *win) 94{ 95 // the panel itself should never get the focus at all but if it does happen 96 // temporarily (as it seems to do under wxGTK), at the very least don't 97 // forget our previous m_winLastFocused 98 if ( win != m_winParent ) 99 { 100 // if we're setting the focus 101 if ( win ) 102 { 103 // find the last _immediate_ child which got focus 104 wxWindow *winParent = win; 105 while ( winParent != m_winParent ) 106 { 107 win = winParent; 108 winParent = win->GetParent(); 109 110 // Yes, this can happen, though in a totally pathological case. 111 // like when detaching a menubar from a frame with a child 112 // which has pushed itself as an event handler for the menubar. 113 // (under wxGTK) 114 115 wxASSERT_MSG( winParent, 116 _T("Setting last focus for a window that is not our child?") ); 117 } 118 } 119 120 m_winLastFocused = win; 121 122 if ( win ) 123 { 124 wxLogTrace(TRACE_FOCUS, _T("Set last focus to %s(%s)"), 125 win->GetClassInfo()->GetClassName(), 126 win->GetLabel().c_str()); 127 } 128 else 129 { 130 wxLogTrace(TRACE_FOCUS, _T("No more last focus")); 131 } 132 } 133 134 // propagate the last focus upwards so that our parent can set focus back 135 // to us if it loses it now and regains later 136 wxWindow *parent = m_winParent->GetParent(); 137 if ( parent ) 138 { 139 wxChildFocusEvent eventFocus(m_winParent); 140 parent->GetEventHandler()->ProcessEvent(eventFocus); 141 } 142} 143 144// -------------------------------------------------------------------- 145// The following four functions are used to find other radio buttons 146// within the same group. Used by wxSetFocusToChild on wxMSW 147// -------------------------------------------------------------------- 148 149#ifdef __WXMSW__ 150 151wxRadioButton* wxGetPreviousButtonInGroup(wxRadioButton *btn) 152{ 153 if ( btn->HasFlag(wxRB_GROUP) || btn->HasFlag(wxRB_SINGLE) ) 154 return NULL; 155 156 const wxWindowList& siblings = btn->GetParent()->GetChildren(); 157 wxWindowList::compatibility_iterator nodeThis = siblings.Find(btn); 158 wxCHECK_MSG( nodeThis, NULL, _T("radio button not a child of its parent?") ); 159 160 // Iterate over all previous siblings until we find the next radio button 161 wxWindowList::compatibility_iterator nodeBefore = nodeThis->GetPrevious(); 162 wxRadioButton *prevBtn = 0; 163 while (nodeBefore) 164 { 165 prevBtn = wxDynamicCast(nodeBefore->GetData(), wxRadioButton); 166 if (prevBtn) 167 break; 168 169 nodeBefore = nodeBefore->GetPrevious(); 170 } 171 172 if (!prevBtn || prevBtn->HasFlag(wxRB_SINGLE)) 173 { 174 // no more buttons in group 175 return NULL; 176 } 177 178 return prevBtn; 179} 180 181wxRadioButton* wxGetNextButtonInGroup(wxRadioButton *btn) 182{ 183 if (btn->HasFlag(wxRB_SINGLE)) 184 return NULL; 185 186 const wxWindowList& siblings = btn->GetParent()->GetChildren(); 187 wxWindowList::compatibility_iterator nodeThis = siblings.Find(btn); 188 wxCHECK_MSG( nodeThis, NULL, _T("radio button not a child of its parent?") ); 189 190 // Iterate over all previous siblings until we find the next radio button 191 wxWindowList::compatibility_iterator nodeNext = nodeThis->GetNext(); 192 wxRadioButton *nextBtn = 0; 193 while (nodeNext) 194 { 195 nextBtn = wxDynamicCast(nodeNext->GetData(), wxRadioButton); 196 if (nextBtn) 197 break; 198 199 nodeNext = nodeNext->GetNext(); 200 } 201 202 if ( !nextBtn || nextBtn->HasFlag(wxRB_GROUP) || nextBtn->HasFlag(wxRB_SINGLE) ) 203 { 204 // no more buttons or the first button of the next group 205 return NULL; 206 } 207 208 return nextBtn; 209} 210 211wxRadioButton* wxGetFirstButtonInGroup(wxRadioButton *btn) 212{ 213 while (true) 214 { 215 wxRadioButton* prevBtn = wxGetPreviousButtonInGroup(btn); 216 if (!prevBtn) 217 return btn; 218 219 btn = prevBtn; 220 } 221} 222 223wxRadioButton* wxGetLastButtonInGroup(wxRadioButton *btn) 224{ 225 while (true) 226 { 227 wxRadioButton* nextBtn = wxGetNextButtonInGroup(btn); 228 if (!nextBtn) 229 return btn; 230 231 btn = nextBtn; 232 } 233} 234 235wxRadioButton* wxGetSelectedButtonInGroup(wxRadioButton *btn) 236{ 237 // Find currently selected button 238 if (btn->GetValue()) 239 return btn; 240 241 if (btn->HasFlag(wxRB_SINGLE)) 242 return NULL; 243 244 wxRadioButton *selBtn; 245 246 // First check all previous buttons 247 for (selBtn = wxGetPreviousButtonInGroup(btn); selBtn; selBtn = wxGetPreviousButtonInGroup(selBtn)) 248 if (selBtn->GetValue()) 249 return selBtn; 250 251 // Now all following buttons 252 for (selBtn = wxGetNextButtonInGroup(btn); selBtn; selBtn = wxGetNextButtonInGroup(selBtn)) 253 if (selBtn->GetValue()) 254 return selBtn; 255 256 return NULL; 257} 258 259#endif // __WXMSW__ 260 261// ---------------------------------------------------------------------------- 262// Keyboard handling - this is the place where the TAB traversal logic is 263// implemented. As this code is common to all ports, this ensures consistent 264// behaviour even if we don't specify how exactly the wxNavigationKeyEvent are 265// generated and this is done in platform specific code which also ensures that 266// we can follow the given platform standards. 267// ---------------------------------------------------------------------------- 268 269void wxControlContainer::HandleOnNavigationKey( wxNavigationKeyEvent& event ) 270{ 271 wxWindow *parent = m_winParent->GetParent(); 272 273 // the event is propagated downwards if the event emitter was our parent 274 bool goingDown = event.GetEventObject() == parent; 275 276 const wxWindowList& children = m_winParent->GetChildren(); 277 278 // if we have exactly one notebook-like child window (actually it could be 279 // any window that returns true from its HasMultiplePages()), then 280 // [Shift-]Ctrl-Tab and Ctrl-PageUp/Down keys should iterate over its pages 281 // even if the focus is outside of the control because this is how the 282 // standard MSW properties dialogs behave and we do it under other platforms 283 // as well because it seems like a good idea -- but we can always put this 284 // block inside "#ifdef __WXMSW__" if it's not suitable there 285 if ( event.IsWindowChange() && !goingDown ) 286 { 287 // check if we have a unique notebook-like child 288 wxWindow *bookctrl = NULL; 289 for ( wxWindowList::const_iterator i = children.begin(), 290 end = children.end(); 291 i != end; 292 ++i ) 293 { 294 wxWindow * const window = *i; 295 if ( window->HasMultiplePages() ) 296 { 297 if ( bookctrl ) 298 { 299 // this is the second book-like control already so don't do 300 // anything as we don't know which one should have its page 301 // changed 302 bookctrl = NULL; 303 break; 304 } 305 306 bookctrl = window; 307 } 308 } 309 310 if ( bookctrl ) 311 { 312 // make sure that we don't bubble up the event again from the book 313 // control resulting in infinite recursion 314 wxNavigationKeyEvent eventCopy(event); 315 eventCopy.SetEventObject(m_winParent); 316 if ( bookctrl->GetEventHandler()->ProcessEvent(eventCopy) ) 317 return; 318 } 319 } 320 321 // there is not much to do if we don't have children and we're not 322 // interested in "notebook page change" events here 323 if ( !children.GetCount() || event.IsWindowChange() ) 324 { 325 // let the parent process it unless it already comes from our parent 326 // of we don't have any 327 if ( goingDown || 328 !parent || !parent->GetEventHandler()->ProcessEvent(event) ) 329 { 330 event.Skip(); 331 } 332 333 return; 334 } 335 336 // where are we going? 337 const bool forward = event.GetDirection(); 338 339 // the node of the children list from which we should start looking for the 340 // next acceptable child 341 wxWindowList::compatibility_iterator node, start_node; 342 343 // we should start from the first/last control and not from the one which 344 // had focus the last time if we're propagating the event downwards because 345 // for our parent we look like a single control 346 if ( goingDown ) 347 { 348 // just to be sure it's not used (normally this is not necessary, but 349 // doesn't hurt neither) 350 m_winLastFocused = (wxWindow *)NULL; 351 352 // start from first or last depending on where we're going 353 node = forward ? children.GetFirst() : children.GetLast(); 354 } 355 else // going up 356 { 357 // try to find the child which has the focus currently 358 359 // the event emitter might have done this for us 360 wxWindow *winFocus = event.GetCurrentFocus(); 361 362 // but if not, we might know where the focus was ourselves 363 if (!winFocus) 364 winFocus = m_winLastFocused; 365 366 // if still no luck, do it the hard way 367 if (!winFocus) 368 winFocus = wxWindow::FindFocus(); 369 370 if ( winFocus ) 371 { 372#ifdef __WXMSW__ 373 // If we are in a radio button group, start from the first item in the 374 // group 375 if ( event.IsFromTab() && wxIsKindOf(winFocus, wxRadioButton ) ) 376 winFocus = wxGetFirstButtonInGroup((wxRadioButton*)winFocus); 377#endif 378 // ok, we found the focus - now is it our child? 379 start_node = children.Find( winFocus ); 380 } 381 382 if ( !start_node && m_winLastFocused ) 383 { 384 // window which has focus isn't our child, fall back to the one 385 // which had the focus the last time 386 start_node = children.Find( m_winLastFocused ); 387 } 388 389 // if we still didn't find anything, we should start with the first one 390 if ( !start_node ) 391 { 392 start_node = children.GetFirst(); 393 } 394 395 // and the first child which we can try setting focus to is the next or 396 // the previous one 397 node = forward ? start_node->GetNext() : start_node->GetPrevious(); 398 } 399 400 // we want to cycle over all elements passing by NULL 401 for ( ;; ) 402 { 403 // don't go into infinite loop 404 if ( start_node && node && node == start_node ) 405 break; 406 407 // Have we come to the last or first item on the panel? 408 if ( !node ) 409 { 410 if ( !start_node ) 411 { 412 // exit now as otherwise we'd loop forever 413 break; 414 } 415 416 if ( !goingDown ) 417 { 418 // Check if our (maybe grand) parent is another panel: if this 419 // is the case, they will know what to do with this navigation 420 // key and so give them the chance to process it instead of 421 // looping inside this panel (normally, the focus will go to 422 // the next/previous item after this panel in the parent 423 // panel). 424 wxWindow *focussed_child_of_parent = m_winParent; 425 while ( parent ) 426 { 427 // we don't want to tab into a different dialog or frame 428 if ( focussed_child_of_parent->IsTopLevel() ) 429 break; 430 431 event.SetCurrentFocus( focussed_child_of_parent ); 432 if ( parent->GetEventHandler()->ProcessEvent( event ) ) 433 return; 434 435 focussed_child_of_parent = parent; 436 437 parent = parent->GetParent(); 438 } 439 } 440 //else: as the focus came from our parent, we definitely don't want 441 // to send it back to it! 442 443 // no, we are not inside another panel so process this ourself 444 node = forward ? children.GetFirst() : children.GetLast(); 445 446 continue; 447 } 448 449 wxWindow *child = node->GetData(); 450 451#ifdef __WXMSW__ 452 if ( event.IsFromTab() ) 453 { 454 if ( wxIsKindOf(child, wxRadioButton) ) 455 { 456 // only radio buttons with either wxRB_GROUP or wxRB_SINGLE 457 // can be tabbed to 458 if ( child->HasFlag(wxRB_GROUP) ) 459 { 460 // need to tab into the active button within a group 461 wxRadioButton *rb = wxGetSelectedButtonInGroup((wxRadioButton*)child); 462 if ( rb ) 463 child = rb; 464 } 465 else if ( !child->HasFlag(wxRB_SINGLE) ) 466 { 467 node = forward ? node->GetNext() : node->GetPrevious(); 468 continue; 469 } 470 } 471 } 472 else if ( m_winLastFocused && 473 wxIsKindOf(m_winLastFocused, wxRadioButton) && 474 !m_winLastFocused->HasFlag(wxRB_SINGLE) ) 475 { 476 // cursor keys don't navigate out of a radio button group so 477 // find the correct radio button to focus 478 if ( forward ) 479 { 480 child = wxGetNextButtonInGroup((wxRadioButton*)m_winLastFocused); 481 if ( !child ) 482 { 483 // no next button in group, set it to the first button 484 child = wxGetFirstButtonInGroup((wxRadioButton*)m_winLastFocused); 485 } 486 } 487 else 488 { 489 child = wxGetPreviousButtonInGroup((wxRadioButton*)m_winLastFocused); 490 if ( !child ) 491 { 492 // no previous button in group, set it to the last button 493 child = wxGetLastButtonInGroup((wxRadioButton*)m_winLastFocused); 494 } 495 } 496 497 if ( child == m_winLastFocused ) 498 { 499 // must be a group consisting of only one button therefore 500 // no need to send a navigation event 501 event.Skip(false); 502 return; 503 } 504 } 505#endif // __WXMSW__ 506 507 if ( child->AcceptsFocusFromKeyboard() ) 508 { 509 // if we're setting the focus to a child panel we should prevent it 510 // from giving it to the child which had the focus the last time 511 // and instead give it to the first/last child depending from which 512 // direction we're coming 513 event.SetEventObject(m_winParent); 514 515 // disable propagation for this call as otherwise the event might 516 // bounce back to us. 517 wxPropagationDisabler disableProp(event); 518 if ( !child->GetEventHandler()->ProcessEvent(event) ) 519 { 520 // set it first in case SetFocusFromKbd() results in focus 521 // change too 522 m_winLastFocused = child; 523 524 // everything is simple: just give focus to it 525 child->SetFocusFromKbd(); 526 } 527 //else: the child manages its focus itself 528 529 event.Skip( false ); 530 531 return; 532 } 533 534 node = forward ? node->GetNext() : node->GetPrevious(); 535 } 536 537 // we cycled through all of our children and none of them wanted to accept 538 // focus 539 event.Skip(); 540} 541 542void wxControlContainer::HandleOnWindowDestroy(wxWindowBase *child) 543{ 544 if ( child == m_winLastFocused ) 545 m_winLastFocused = NULL; 546} 547 548// ---------------------------------------------------------------------------- 549// focus handling 550// ---------------------------------------------------------------------------- 551 552bool wxControlContainer::DoSetFocus() 553{ 554 wxLogTrace(TRACE_FOCUS, _T("SetFocus on wxPanel 0x%p."), 555 m_winParent->GetHandle()); 556 557 if (m_inSetFocus) 558 return true; 559 560 // when the panel gets the focus we move the focus to either the last 561 // window that had the focus or the first one that can get it unless the 562 // focus had been already set to some other child 563 564 wxWindow *win = wxWindow::FindFocus(); 565 while ( win ) 566 { 567 if ( win == m_winParent ) 568 { 569 // our child already has focus, don't take it away from it 570 return true; 571 } 572 573 if ( win->IsTopLevel() ) 574 { 575 // don't look beyond the first top level parent - useless and 576 // unnecessary 577 break; 578 } 579 580 win = win->GetParent(); 581 } 582 583 // protect against infinite recursion: 584 m_inSetFocus = true; 585 586 bool ret = SetFocusToChild(); 587 588 m_inSetFocus = false; 589 590 return ret; 591} 592 593void wxControlContainer::HandleOnFocus(wxFocusEvent& event) 594{ 595 wxLogTrace(TRACE_FOCUS, _T("OnFocus on wxPanel 0x%p, name: %s"), 596 m_winParent->GetHandle(), 597 m_winParent->GetName().c_str() ); 598 599 DoSetFocus(); 600 601 event.Skip(); 602} 603 604bool wxControlContainer::SetFocusToChild() 605{ 606 return wxSetFocusToChild(m_winParent, &m_winLastFocused); 607} 608 609// ---------------------------------------------------------------------------- 610// SetFocusToChild(): this function is used by wxPanel but also by wxFrame in 611// wxMSW, this is why it is outside of wxControlContainer class 612// ---------------------------------------------------------------------------- 613 614bool wxSetFocusToChild(wxWindow *win, wxWindow **childLastFocused) 615{ 616 wxCHECK_MSG( win, false, _T("wxSetFocusToChild(): invalid window") ); 617 wxCHECK_MSG( childLastFocused, false, 618 _T("wxSetFocusToChild(): NULL child poonter") ); 619 620 if ( *childLastFocused ) 621 { 622 // It might happen that the window got reparented 623 if ( (*childLastFocused)->GetParent() == win ) 624 { 625 wxLogTrace(TRACE_FOCUS, 626 _T("SetFocusToChild() => last child (0x%p)."), 627 (*childLastFocused)->GetHandle()); 628 629 // not SetFocusFromKbd(): we're restoring focus back to the old 630 // window and not setting it as the result of a kbd action 631 (*childLastFocused)->SetFocus(); 632 return true; 633 } 634 else 635 { 636 // it doesn't count as such any more 637 *childLastFocused = (wxWindow *)NULL; 638 } 639 } 640 641 // set the focus to the first child who wants it 642 wxWindowList::compatibility_iterator node = win->GetChildren().GetFirst(); 643 while ( node ) 644 { 645 wxWindow *child = node->GetData(); 646 node = node->GetNext(); 647 648#ifdef __WXMAC__ 649 if ( child->GetParent()->MacIsWindowScrollbar( child ) ) 650 continue; 651#endif 652 653 if ( child->AcceptsFocusFromKeyboard() && !child->IsTopLevel() ) 654 { 655#ifdef __WXMSW__ 656 // If a radiobutton is the first focusable child, search for the 657 // selected radiobutton in the same group 658 wxRadioButton* btn = wxDynamicCast(child, wxRadioButton); 659 if (btn) 660 { 661 wxRadioButton* selected = wxGetSelectedButtonInGroup(btn); 662 if (selected) 663 child = selected; 664 } 665#endif 666 667 wxLogTrace(TRACE_FOCUS, 668 _T("SetFocusToChild() => first child (0x%p)."), 669 child->GetHandle()); 670 671 *childLastFocused = child; 672 child->SetFocusFromKbd(); 673 return true; 674 } 675 } 676 677 return false; 678} 679