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