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