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