]> git.saurik.com Git - wxWidgets.git/blob - src/common/containr.cpp
700d9eb782df27545b9c194ba7faf93ba8d2f610
[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 // 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 #endif //WX_PRECOMP
34
35 #include "wx/containr.h"
36
37 // trace mask for focus messages
38 #define TRACE_FOCUS _T("focus")
39
40 // ============================================================================
41 // implementation
42 // ============================================================================
43
44 wxControlContainer::wxControlContainer(wxWindow *winParent)
45 {
46 m_winParent = winParent;
47
48 m_winLastFocused =
49 m_winTmpDefault =
50 m_winDefault = NULL;
51 m_inSetFocus = false;
52 }
53
54 bool wxControlContainer::AcceptsFocus() const
55 {
56 // if we're not shown or disabled, we can't accept focus
57 if ( m_winParent->IsShown() && m_winParent->IsEnabled() )
58 {
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
62 wxWindowList::compatibility_iterator node = m_winParent->GetChildren().GetFirst();
63 if ( !node )
64 return true;
65
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
71
72 while ( node )
73 {
74 wxWindow *child = node->GetData();
75
76 if ( child->AcceptsFocus() )
77 {
78 return true;
79 }
80
81 #ifdef __WXMAC__
82 wxScrollBar *sb = wxDynamicCast( child , wxScrollBar ) ;
83 if ( sb == NULL || !m_winParent->MacIsWindowScrollbar( sb ) )
84 hasRealChildren = true ;
85 #endif
86 node = node->GetNext();
87 }
88
89 #ifdef __WXMAC__
90 if ( !hasRealChildren )
91 return true ;
92 #endif
93 }
94
95 return false;
96 }
97
98 void wxControlContainer::SetLastFocus(wxWindow *win)
99 {
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
103 if ( win != m_winParent )
104 {
105 // if we're setting the focus
106 if ( win )
107 {
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();
114
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)
119
120 wxASSERT_MSG( winParent,
121 _T("Setting last focus for a window that is not our child?") );
122 }
123 }
124
125 m_winLastFocused = win;
126
127 if ( win )
128 {
129 wxLogTrace(TRACE_FOCUS, _T("Set last focus to %s(%s)"),
130 win->GetClassInfo()->GetClassName(),
131 win->GetLabel().c_str());
132 }
133 else
134 {
135 wxLogTrace(TRACE_FOCUS, _T("No more last focus"));
136 }
137 }
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 )
143 {
144 wxChildFocusEvent eventFocus(m_winParent);
145 parent->GetEventHandler()->ProcessEvent(eventFocus);
146 }
147 }
148
149 // --------------------------------------------------------------------
150 // The following four functions are used to find other radio buttons
151 // within the same group. Used by wxSetFocusToChild on wxMSW
152 // --------------------------------------------------------------------
153
154 #ifdef __WXMSW__
155
156 wxRadioButton* 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 }
176
177 if (!prevBtn || prevBtn->HasFlag(wxRB_SINGLE))
178 {
179 // no more buttons in group
180 return NULL;
181 }
182
183 return prevBtn;
184 }
185
186 wxRadioButton* 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 }
212
213 return nextBtn;
214 }
215
216 wxRadioButton* wxGetFirstButtonInGroup(wxRadioButton *btn)
217 {
218 while (true)
219 {
220 wxRadioButton* prevBtn = wxGetPreviousButtonInGroup(btn);
221 if (!prevBtn)
222 return btn;
223
224 btn = prevBtn;
225 }
226 }
227
228 wxRadioButton* 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
240 wxRadioButton* 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
264 #endif // __WXMSW__
265
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
274 void 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?
299 const bool forward = event.GetDirection();
300
301 // the node of the children list from which we should start looking for the
302 // next acceptable child
303 wxWindowList::compatibility_iterator node, start_node;
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();
316 }
317 else // going up
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 {
334 #ifdef __WXMSW__
335 // If we are in a radio button group, start from the first item in the
336 // group
337 if ( event.IsFromTab() && wxIsKindOf(winFocus, wxRadioButton ) )
338 winFocus = wxGetFirstButtonInGroup((wxRadioButton*)winFocus);
339 #endif
340 // ok, we found the focus - now is it our child?
341 start_node = children.Find( winFocus );
342 }
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
363 for ( ;; )
364 {
365 // don't go into infinite loop
366 if ( start_node && node && node == start_node )
367 break;
368
369 // Have we come to the last or first item on the panel?
370 if ( !node )
371 {
372 if ( !start_node )
373 {
374 // exit now as otherwise we'd loop forever
375 break;
376 }
377
378 if ( !goingDown )
379 {
380 // Check if our (maybe grand) parent is another panel: if this
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
413 #ifdef __WXMSW__
414 if ( event.IsFromTab() )
415 {
416 if ( wxIsKindOf(child, wxRadioButton) )
417 {
418 // only radio buttons with either wxRB_GROUP or wxRB_SINGLE
419 // can be tabbed to
420 if ( child->HasFlag(wxRB_GROUP) )
421 {
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;
431 }
432 }
433 }
434 else if ( m_winLastFocused &&
435 wxIsKindOf(m_winLastFocused, wxRadioButton) &&
436 !m_winLastFocused->HasFlag(wxRB_SINGLE) )
437 {
438 // cursor keys don't navigate out of a radio button group so
439 // find the correct radio button to focus
440 if ( forward )
441 {
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;
465 }
466 }
467 #endif // __WXMSW__
468
469 if ( child->AcceptsFocusFromKeyboard() )
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);
476
477 // disable propagation for this call as otherwise the event might
478 // bounce back to us.
479 wxPropagationDisabler disableProp(event);
480 if ( !child->GetEventHandler()->ProcessEvent(event) )
481 {
482 // set it first in case SetFocusFromKbd() results in focus
483 // change too
484 m_winLastFocused = child;
485
486 // everything is simple: just give focus to it
487 child->SetFocusFromKbd();
488 }
489 //else: the child manages its focus itself
490
491 event.Skip( false );
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
504 void wxControlContainer::HandleOnWindowDestroy(wxWindowBase *child)
505 {
506 if ( child == m_winLastFocused )
507 m_winLastFocused = NULL;
508
509 if ( child == m_winDefault )
510 m_winDefault = NULL;
511
512 if ( child == m_winTmpDefault )
513 m_winTmpDefault = NULL;
514 }
515
516 // ----------------------------------------------------------------------------
517 // focus handling
518 // ----------------------------------------------------------------------------
519
520 bool wxControlContainer::DoSetFocus()
521 {
522 wxLogTrace(TRACE_FOCUS, _T("SetFocus on wxPanel 0x%p."),
523 m_winParent->GetHandle());
524
525 if (m_inSetFocus)
526 return true;
527
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
532 wxWindow *win = wxWindow::FindFocus();
533 while ( win )
534 {
535 if ( win == m_winParent )
536 {
537 // our child already has focus, don't take it away from it
538 return true;
539 }
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 }
550
551 // protect against infinite recursion:
552 m_inSetFocus = true;
553
554 bool ret = SetFocusToChild();
555
556 m_inSetFocus = false;
557
558 return ret;
559 }
560
561 void wxControlContainer::HandleOnFocus(wxFocusEvent& event)
562 {
563 wxLogTrace(TRACE_FOCUS, _T("OnFocus on wxPanel 0x%p, name: %s"),
564 m_winParent->GetHandle(),
565 m_winParent->GetName().c_str() );
566
567 DoSetFocus();
568
569 event.Skip();
570 }
571
572 bool 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
582 bool wxSetFocusToChild(wxWindow *win, wxWindow **childLastFocused)
583 {
584 wxCHECK_MSG( win, false, _T("wxSetFocusToChild(): invalid window") );
585 wxCHECK_MSG( childLastFocused, false,
586 _T("wxSetFocusToChild(): NULL child poonter") );
587
588 if ( *childLastFocused )
589 {
590 // It might happen that the window got reparented
591 if ( (*childLastFocused)->GetParent() == win )
592 {
593 wxLogTrace(TRACE_FOCUS,
594 _T("SetFocusToChild() => last child (0x%p)."),
595 (*childLastFocused)->GetHandle());
596
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();
600 return true;
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
610 wxWindowList::compatibility_iterator node = win->GetChildren().GetFirst();
611 while ( node )
612 {
613 wxWindow *child = node->GetData();
614
615 if ( child->AcceptsFocusFromKeyboard() && !child->IsTopLevel() )
616 {
617 #ifdef __WXMSW__
618 // If a radiobutton is the first focusable child, search for the
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
629 wxLogTrace(TRACE_FOCUS,
630 _T("SetFocusToChild() => first child (0x%p)."),
631 child->GetHandle());
632
633 *childLastFocused = child;
634 child->SetFocusFromKbd();
635 return true;
636 }
637
638 node = node->GetNext();
639 }
640
641 return false;
642 }