]> git.saurik.com Git - wxWidgets.git/blob - src/ribbon/panel.cpp
Add possibility to use sizers in ribbon panel, fixes #12404: wxRibbonPanel and wxSizer
[wxWidgets.git] / src / ribbon / panel.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/ribbon/panel.cpp
3 // Purpose: Ribbon-style container for a group of related tools / controls
4 // Author: Peter Cawley
5 // Modified by:
6 // Created: 2009-05-25
7 // RCS-ID: $Id$
8 // Copyright: (C) Peter Cawley
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #if wxUSE_RIBBON
19
20 #include "wx/ribbon/panel.h"
21 #include "wx/ribbon/art.h"
22 #include "wx/ribbon/bar.h"
23 #include "wx/dcbuffer.h"
24 #include "wx/display.h"
25
26 #ifndef WX_PRECOMP
27 #include "wx/frame.h"
28 #endif
29
30 #ifdef __WXMSW__
31 #include "wx/msw/private.h"
32 #endif
33
34 IMPLEMENT_CLASS(wxRibbonPanel, wxRibbonControl)
35
36 BEGIN_EVENT_TABLE(wxRibbonPanel, wxRibbonControl)
37 EVT_ENTER_WINDOW(wxRibbonPanel::OnMouseEnter)
38 EVT_ERASE_BACKGROUND(wxRibbonPanel::OnEraseBackground)
39 EVT_KILL_FOCUS(wxRibbonPanel::OnKillFocus)
40 EVT_LEAVE_WINDOW(wxRibbonPanel::OnMouseLeave)
41 EVT_LEFT_DOWN(wxRibbonPanel::OnMouseClick)
42 EVT_PAINT(wxRibbonPanel::OnPaint)
43 EVT_SIZE(wxRibbonPanel::OnSize)
44 END_EVENT_TABLE()
45
46 wxRibbonPanel::wxRibbonPanel() : m_expanded_dummy(NULL), m_expanded_panel(NULL)
47 {
48 }
49
50 wxRibbonPanel::wxRibbonPanel(wxWindow* parent,
51 wxWindowID id,
52 const wxString& label,
53 const wxBitmap& minimised_icon,
54 const wxPoint& pos,
55 const wxSize& size,
56 long style)
57 : wxRibbonControl(parent, id, pos, size, wxBORDER_NONE)
58 {
59 CommonInit(label, minimised_icon, style);
60 }
61
62 wxRibbonPanel::~wxRibbonPanel()
63 {
64 if(m_expanded_panel)
65 {
66 m_expanded_panel->m_expanded_dummy = NULL;
67 m_expanded_panel->GetParent()->Destroy();
68 }
69 }
70
71 bool wxRibbonPanel::Create(wxWindow* parent,
72 wxWindowID id,
73 const wxString& label,
74 const wxBitmap& icon,
75 const wxPoint& pos,
76 const wxSize& size,
77 long style)
78 {
79 if(!wxRibbonControl::Create(parent, id, pos, size, wxBORDER_NONE))
80 {
81 return false;
82 }
83
84 CommonInit(label, icon, style);
85
86 return true;
87 }
88
89 void wxRibbonPanel::SetArtProvider(wxRibbonArtProvider* art)
90 {
91 m_art = art;
92 for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
93 node;
94 node = node->GetNext() )
95 {
96 wxWindow* child = node->GetData();
97 wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
98 if(ribbon_child)
99 {
100 ribbon_child->SetArtProvider(art);
101 }
102 }
103 if(m_expanded_panel)
104 m_expanded_panel->SetArtProvider(art);
105 }
106
107 void wxRibbonPanel::CommonInit(const wxString& label, const wxBitmap& icon, long style)
108 {
109 SetName(label);
110 SetLabel(label);
111
112 m_minimised_size = wxDefaultSize; // Unknown / none
113 m_smallest_unminimised_size = wxSize(INT_MAX, INT_MAX); // Unknown / none
114 m_preferred_expand_direction = wxSOUTH;
115 m_expanded_dummy = NULL;
116 m_expanded_panel = NULL;
117 m_flags = style;
118 m_minimised_icon = icon;
119 m_minimised = false;
120 m_hovered = false;
121
122 if(m_art == NULL)
123 {
124 wxRibbonControl* parent = wxDynamicCast(GetParent(), wxRibbonControl);
125 if(parent != NULL)
126 {
127 m_art = parent->GetArtProvider();
128 }
129 }
130
131 SetAutoLayout(true);
132 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
133 SetMinSize(wxSize(20, 20));
134 }
135
136 bool wxRibbonPanel::IsMinimised() const
137 {
138 return m_minimised;
139 }
140
141 bool wxRibbonPanel::IsHovered() const
142 {
143 return m_hovered;
144 }
145
146 void wxRibbonPanel::OnMouseEnter(wxMouseEvent& evt)
147 {
148 TestPositionForHover(evt.GetPosition());
149 }
150
151 void wxRibbonPanel::OnMouseEnterChild(wxMouseEvent& evt)
152 {
153 wxPoint pos = evt.GetPosition();
154 wxWindow *child = wxDynamicCast(evt.GetEventObject(), wxWindow);
155 if(child)
156 {
157 pos += child->GetPosition();
158 TestPositionForHover(pos);
159 }
160 evt.Skip();
161 }
162
163 void wxRibbonPanel::OnMouseLeave(wxMouseEvent& evt)
164 {
165 TestPositionForHover(evt.GetPosition());
166 }
167
168 void wxRibbonPanel::OnMouseLeaveChild(wxMouseEvent& evt)
169 {
170 wxPoint pos = evt.GetPosition();
171 wxWindow *child = wxDynamicCast(evt.GetEventObject(), wxWindow);
172 if(child)
173 {
174 pos += child->GetPosition();
175 TestPositionForHover(pos);
176 }
177 evt.Skip();
178 }
179
180 void wxRibbonPanel::TestPositionForHover(const wxPoint& pos)
181 {
182 bool hovered = false;
183 if(pos.x >= 0 && pos.y >= 0)
184 {
185 wxSize size = GetSize();
186 if(pos.x < size.GetWidth() && pos.y < size.GetHeight())
187 {
188 hovered = true;
189 }
190 }
191 if(hovered != m_hovered)
192 {
193 m_hovered = hovered;
194 Refresh(false);
195 }
196 }
197
198 void wxRibbonPanel::AddChild(wxWindowBase *child)
199 {
200 wxRibbonControl::AddChild(child);
201
202 // Window enter / leave events count for only the window in question, not
203 // for children of the window. The panel wants to be in the hovered state
204 // whenever the mouse cursor is within its boundary, so the events need to
205 // be attached to children too.
206 child->Connect(wxEVT_ENTER_WINDOW, (wxObjectEventFunction)&wxRibbonPanel::OnMouseEnterChild, NULL, this);
207 child->Connect(wxEVT_LEAVE_WINDOW, (wxObjectEventFunction)&wxRibbonPanel::OnMouseLeaveChild, NULL, this);
208 }
209
210 void wxRibbonPanel::RemoveChild(wxWindowBase *child)
211 {
212 child->Disconnect(wxEVT_ENTER_WINDOW, (wxObjectEventFunction)&wxRibbonPanel::OnMouseEnterChild, NULL, this);
213 child->Disconnect(wxEVT_LEAVE_WINDOW, (wxObjectEventFunction)&wxRibbonPanel::OnMouseLeaveChild, NULL, this);
214
215 wxRibbonControl::RemoveChild(child);
216 }
217
218 void wxRibbonPanel::OnSize(wxSizeEvent& evt)
219 {
220 if(GetAutoLayout())
221 Layout();
222
223 evt.Skip();
224 }
225
226 void wxRibbonPanel::DoSetSize(int x, int y, int width, int height, int sizeFlags)
227 {
228 // At least on MSW, changing the size of a window will cause GetSize() to
229 // report the new size, but a size event may not be handled immediately.
230 // If this minimised check was performed in the OnSize handler, then
231 // GetSize() could return a size much larger than the minimised size while
232 // IsMinimised() returns true. This would then affect layout, as the panel
233 // will refuse to grow any larger while in limbo between minimised and non.
234
235 bool minimised = (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0 &&
236 IsMinimised(wxSize(width, height));
237 if(minimised != m_minimised)
238 {
239 m_minimised = minimised;
240
241 for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
242 node;
243 node = node->GetNext())
244 {
245 node->GetData()->Show(!minimised);
246 }
247
248 Refresh();
249 }
250
251 wxRibbonControl::DoSetSize(x, y, width, height, sizeFlags);
252 }
253
254 bool wxRibbonPanel::IsMinimised(wxSize at_size) const
255 {
256 if(!m_minimised_size.IsFullySpecified())
257 return false;
258
259 return (at_size.GetX() <= m_minimised_size.GetX() &&
260 at_size.GetY() <= m_minimised_size.GetY()) ||
261 at_size.GetX() < m_smallest_unminimised_size.GetX() ||
262 at_size.GetY() < m_smallest_unminimised_size.GetY();
263 }
264
265 void wxRibbonPanel::OnEraseBackground(wxEraseEvent& WXUNUSED(evt))
266 {
267 // All painting done in main paint handler to minimise flicker
268 }
269
270 void wxRibbonPanel::OnPaint(wxPaintEvent& WXUNUSED(evt))
271 {
272 wxAutoBufferedPaintDC dc(this);
273
274 if(m_art != NULL)
275 {
276 if(IsMinimised())
277 {
278 m_art->DrawMinimisedPanel(dc, this, GetSize(), m_minimised_icon_resized);
279 }
280 else
281 {
282 m_art->DrawPanelBackground(dc, this, GetSize());
283 }
284 }
285 }
286
287 bool wxRibbonPanel::IsSizingContinuous() const
288 {
289 // A panel never sizes continuously, even if all of its children can,
290 // as it would appear out of place along side non-continuous panels.
291 return false;
292 }
293
294 wxSize wxRibbonPanel::DoGetNextSmallerSize(wxOrientation direction,
295 wxSize relative_to) const
296 {
297 if(m_expanded_panel != NULL)
298 {
299 // Next size depends upon children, who are currently in the
300 // expanded panel
301 return m_expanded_panel->DoGetNextSmallerSize(direction, relative_to);
302 }
303
304 // TODO: Check for, and delegate to, a sizer
305
306 // Simple (and common) case of single ribbon child
307 if(GetChildren().GetCount() == 1)
308 {
309 wxWindow* child = GetChildren().Item(0)->GetData();
310 wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
311 if(m_art != NULL && ribbon_child != NULL)
312 {
313 wxClientDC dc((wxRibbonPanel*) this);
314 wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, NULL);
315 wxSize smaller = ribbon_child->GetNextSmallerSize(direction, child_relative);
316 if(smaller == child_relative)
317 {
318 if(CanAutoMinimise())
319 {
320 wxSize minimised = m_minimised_size;
321 switch(direction)
322 {
323 case wxHORIZONTAL:
324 minimised.SetHeight(relative_to.GetHeight());
325 break;
326 case wxVERTICAL:
327 minimised.SetWidth(relative_to.GetWidth());
328 break;
329 default:
330 break;
331 }
332 return minimised;
333 }
334 else
335 {
336 return relative_to;
337 }
338 }
339 else
340 {
341 return m_art->GetPanelSize(dc, this, smaller, NULL);
342 }
343 }
344 }
345
346 // Fallback: Decrease by 20% (or minimum size, whichever larger)
347 wxSize current(relative_to);
348 wxSize minimum(GetMinSize());
349 if(direction & wxHORIZONTAL)
350 {
351 current.x = (current.x * 4) / 5;
352 if(current.x < minimum.x)
353 {
354 current.x = minimum.x;
355 }
356 }
357 if(direction & wxVERTICAL)
358 {
359 current.y = (current.y * 4) / 5;
360 if(current.y < minimum.y)
361 {
362 current.y = minimum.y;
363 }
364 }
365 return current;
366 }
367
368 wxSize wxRibbonPanel::DoGetNextLargerSize(wxOrientation direction,
369 wxSize relative_to) const
370 {
371 if(m_expanded_panel != NULL)
372 {
373 // Next size depends upon children, who are currently in the
374 // expanded panel
375 return m_expanded_panel->DoGetNextLargerSize(direction, relative_to);
376 }
377
378 if(IsMinimised(relative_to))
379 {
380 wxSize current = relative_to;
381 wxSize min_size = GetMinNotMinimisedSize();
382 switch(direction)
383 {
384 case wxHORIZONTAL:
385 if(min_size.x > current.x && min_size.y == current.y)
386 return min_size;
387 break;
388 case wxVERTICAL:
389 if(min_size.x == current.x && min_size.y > current.y)
390 return min_size;
391 break;
392 case wxBOTH:
393 if(min_size.x > current.x && min_size.y > current.y)
394 return min_size;
395 break;
396 default:
397 break;
398 }
399 }
400
401 // TODO: Check for, and delegate to, a sizer
402
403 // Simple (and common) case of single ribbon child
404 if(GetChildren().GetCount() == 1)
405 {
406 wxWindow* child = GetChildren().Item(0)->GetData();
407 wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
408 if(ribbon_child != NULL)
409 {
410 wxClientDC dc((wxRibbonPanel*) this);
411 wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, NULL);
412 wxSize larger = ribbon_child->GetNextLargerSize(direction, child_relative);
413 if(larger == child_relative)
414 {
415 return relative_to;
416 }
417 else
418 {
419 wxClientDC dc((wxRibbonPanel*) this);
420 return m_art->GetPanelSize(dc, this, larger, NULL);
421 }
422 }
423 }
424
425 // Fallback: Increase by 25% (equal to a prior or subsequent 20% decrease)
426 // Note that due to rounding errors, this increase may not exactly equal a
427 // matching decrease - an ideal solution would not have these errors, but
428 // avoiding them is non-trivial unless an increase is by 100% rather than
429 // a fractional amount. This would then be non-ideal as the resizes happen
430 // at very large intervals.
431 wxSize current(relative_to);
432 if(direction & wxHORIZONTAL)
433 {
434 current.x = (current.x * 5 + 3) / 4;
435 }
436 if(direction & wxVERTICAL)
437 {
438 current.y = (current.y * 5 + 3) / 4;
439 }
440 return current;
441 }
442
443 bool wxRibbonPanel::CanAutoMinimise() const
444 {
445 return (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0
446 && m_minimised_size.IsFullySpecified();
447 }
448
449 wxSize wxRibbonPanel::GetMinSize() const
450 {
451 if(m_expanded_panel != NULL)
452 {
453 // Minimum size depends upon children, who are currently in the
454 // expanded panel
455 return m_expanded_panel->GetMinSize();
456 }
457
458 if(CanAutoMinimise())
459 {
460 return m_minimised_size;
461 }
462 else
463 {
464 return GetMinNotMinimisedSize();
465 }
466 }
467
468 wxSize wxRibbonPanel::GetMinNotMinimisedSize() const
469 {
470 // TODO: Ask sizer
471
472 // Common case of no sizer and single child taking up the entire panel
473 if(GetChildren().GetCount() == 1)
474 {
475 wxWindow* child = GetChildren().Item(0)->GetData();
476 wxClientDC dc((wxRibbonPanel*) this);
477 return m_art->GetPanelSize(dc, this, child->GetMinSize(), NULL);
478 }
479
480 return wxRibbonControl::GetMinSize();
481 }
482
483 wxSize wxRibbonPanel::DoGetBestSize() const
484 {
485 // TODO: Ask sizer
486
487 // Common case of no sizer and single child taking up the entire panel
488 if(GetChildren().GetCount() == 1)
489 {
490 wxWindow* child = GetChildren().Item(0)->GetData();
491 wxClientDC dc((wxRibbonPanel*) this);
492 return m_art->GetPanelSize(dc, this, child->GetBestSize(), NULL);
493 }
494
495 return wxRibbonControl::DoGetBestSize();
496 }
497
498 bool wxRibbonPanel::Realize()
499 {
500 bool status = true;
501
502 for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
503 node;
504 node = node->GetNext())
505 {
506 wxRibbonControl* child = wxDynamicCast(node->GetData(), wxRibbonControl);
507 if(child == NULL)
508 {
509 continue;
510 }
511 if(!child->Realize())
512 {
513 status = false;
514 }
515 }
516
517 wxSize minimum_children_size(0, 0);
518 // TODO: Ask sizer if there is one
519 if(GetChildren().GetCount() == 1)
520 {
521 minimum_children_size = GetChildren().GetFirst()->GetData()->GetMinSize();
522 }
523
524 if(m_art != NULL)
525 {
526 wxClientDC temp_dc(this);
527
528 m_smallest_unminimised_size =
529 m_art->GetPanelSize(temp_dc, this, minimum_children_size, NULL);
530
531 wxSize bitmap_size;
532 wxSize panel_min_size = GetMinNotMinimisedSize();
533 m_minimised_size = m_art->GetMinimisedPanelMinimumSize(temp_dc, this,
534 &bitmap_size, &m_preferred_expand_direction);
535 if(m_minimised_icon.IsOk() && m_minimised_icon.GetSize() != bitmap_size)
536 {
537 wxImage img(m_minimised_icon.ConvertToImage());
538 img.Rescale(bitmap_size.GetWidth(), bitmap_size.GetHeight(), wxIMAGE_QUALITY_HIGH);
539 m_minimised_icon_resized = wxBitmap(img);
540 }
541 else
542 {
543 m_minimised_icon_resized = m_minimised_icon;
544 }
545 if(m_minimised_size.x > panel_min_size.x &&
546 m_minimised_size.y > panel_min_size.y)
547 {
548 // No point in having a minimised size which is larger than the
549 // minimum size which the children can go to.
550 m_minimised_size = wxSize(-1, -1);
551 }
552 else
553 {
554 if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
555 {
556 m_minimised_size.x = panel_min_size.x;
557 }
558 else
559 {
560 m_minimised_size.y = panel_min_size.y;
561 }
562 }
563 }
564 else
565 {
566 m_minimised_size = wxSize(-1, -1);
567 }
568
569 return Layout() && status;
570 }
571
572 bool wxRibbonPanel::Layout()
573 {
574 if(IsMinimised())
575 {
576 // Children are all invisible when minimised
577 return true;
578 }
579
580 // Get wxRibbonPanel client size
581 wxPoint position;
582 wxClientDC dc(this);
583 wxSize size = m_art->GetPanelClientSize(dc, this, GetSize(), &position);
584
585 // If there is a sizer, use it instead
586 if ( GetSizer() )
587 {
588 GetSizer()->SetDimension(position.x, position.y, size.GetWidth(), size.GetHeight());
589 }
590 else if(GetChildren().GetCount() == 1)
591 {
592 // Common case of no sizer and single child taking up the entire panel
593 wxWindow* child = GetChildren().Item(0)->GetData();
594 child->SetSize(position.x, position.y, size.GetWidth(), size.GetHeight());
595 }
596 return true;
597 }
598
599 void wxRibbonPanel::OnMouseClick(wxMouseEvent& WXUNUSED(evt))
600 {
601 if(IsMinimised())
602 {
603 if(m_expanded_panel != NULL)
604 {
605 HideExpanded();
606 }
607 else
608 {
609 ShowExpanded();
610 }
611 }
612 }
613
614 wxRibbonPanel* wxRibbonPanel::GetExpandedDummy()
615 {
616 return m_expanded_dummy;
617 }
618
619 wxRibbonPanel* wxRibbonPanel::GetExpandedPanel()
620 {
621 return m_expanded_panel;
622 }
623
624 bool wxRibbonPanel::ShowExpanded()
625 {
626 if(!IsMinimised())
627 {
628 return false;
629 }
630 if(m_expanded_dummy != NULL || m_expanded_panel != NULL)
631 {
632 return false;
633 }
634
635 wxSize size = GetBestSize();
636 wxPoint pos = GetExpandedPosition(wxRect(GetScreenPosition(), GetSize()),
637 size, m_preferred_expand_direction).GetTopLeft();
638
639 // Need a top-level frame to contain the expanded panel
640 wxFrame *container = new wxFrame(NULL, wxID_ANY, GetLabel(),
641 pos, size, wxFRAME_NO_TASKBAR | wxBORDER_NONE);
642
643 m_expanded_panel = new wxRibbonPanel(container, wxID_ANY,
644 GetLabel(), m_minimised_icon, wxPoint(0, 0), size, m_flags);
645
646 m_expanded_panel->SetArtProvider(m_art);
647 m_expanded_panel->m_expanded_dummy = this;
648
649 // Move all children to the new panel.
650 // Conceptually it might be simpler to reparent this entire panel to the
651 // container and create a new panel to sit in its place while expanded.
652 // This approach has a problem though - when the panel is reinserted into
653 // its original parent, it'll be at a different position in the child list
654 // and thus assume a new position.
655 // NB: Children iterators not used as behaviour is not well defined
656 // when iterating over a container which is being emptied
657 while(!GetChildren().IsEmpty())
658 {
659 wxWindow *child = GetChildren().GetFirst()->GetData();
660 child->Reparent(m_expanded_panel);
661 child->Show();
662 }
663
664 // TODO: Move sizer to new panel
665
666 m_expanded_panel->Realize();
667 Refresh();
668 container->Show();
669 m_expanded_panel->SetFocus();
670
671 return true;
672 }
673
674 bool wxRibbonPanel::ShouldSendEventToDummy(wxEvent& evt)
675 {
676 // For an expanded panel, filter events between being sent up to the
677 // floating top level window or to the dummy panel sitting in the ribbon
678 // bar.
679
680 // Child focus events should not be redirected, as the child would not be a
681 // child of the window the event is redirected to. All other command events
682 // seem to be suitable for redirecting.
683 return evt.IsCommandEvent() && evt.GetEventType() != wxEVT_CHILD_FOCUS;
684 }
685
686 bool wxRibbonPanel::TryAfter(wxEvent& evt)
687 {
688 if(m_expanded_dummy && ShouldSendEventToDummy(evt))
689 {
690 wxPropagateOnce propagateOnce(evt);
691 return m_expanded_dummy->GetEventHandler()->ProcessEvent(evt);
692 }
693 else
694 {
695 return wxRibbonControl::TryAfter(evt);
696 }
697 }
698
699 static bool IsAncestorOf(wxWindow *ancestor, wxWindow *window)
700 {
701 while(window != NULL)
702 {
703 wxWindow *parent = window->GetParent();
704 if(parent == ancestor)
705 return true;
706 else
707 window = parent;
708 }
709 return false;
710 }
711
712 void wxRibbonPanel::OnKillFocus(wxFocusEvent& evt)
713 {
714 if(m_expanded_dummy)
715 {
716 wxWindow *receiver = evt.GetWindow();
717 if(IsAncestorOf(this, receiver))
718 {
719 m_child_with_focus = receiver;
720 receiver->Connect(wxEVT_KILL_FOCUS,
721 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus),
722 NULL, this);
723 }
724 else if(receiver == NULL || receiver != m_expanded_dummy)
725 {
726 HideExpanded();
727 }
728 }
729 }
730
731 void wxRibbonPanel::OnChildKillFocus(wxFocusEvent& evt)
732 {
733 if(m_child_with_focus == NULL)
734 return; // Should never happen, but a check can't hurt
735
736 m_child_with_focus->Disconnect(wxEVT_KILL_FOCUS,
737 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus), NULL, this);
738 m_child_with_focus = NULL;
739
740 wxWindow *receiver = evt.GetWindow();
741 if(receiver == this || IsAncestorOf(this, receiver))
742 {
743 m_child_with_focus = receiver;
744 receiver->Connect(wxEVT_KILL_FOCUS,
745 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus), NULL, this);
746 evt.Skip();
747 }
748 else if(receiver == NULL || receiver != m_expanded_dummy)
749 {
750 HideExpanded();
751 // Do not skip event, as the panel has been de-expanded, causing the
752 // child with focus to be reparented (and hidden). If the event
753 // continues propogation then bad things happen.
754 }
755 else
756 {
757 evt.Skip();
758 }
759 }
760
761 bool wxRibbonPanel::HideExpanded()
762 {
763 if(m_expanded_dummy == NULL)
764 {
765 if(m_expanded_panel)
766 {
767 return m_expanded_panel->HideExpanded();
768 }
769 else
770 {
771 return false;
772 }
773 }
774
775 // Move children back to original panel
776 // NB: Children iterators not used as behaviour is not well defined
777 // when iterating over a container which is being emptied
778 while(!GetChildren().IsEmpty())
779 {
780 wxWindow *child = GetChildren().GetFirst()->GetData();
781 child->Reparent(m_expanded_dummy);
782 child->Hide();
783 }
784
785 // TODO: Move sizer back
786
787 m_expanded_dummy->m_expanded_panel = NULL;
788 m_expanded_dummy->Realize();
789 m_expanded_dummy->Refresh();
790 wxWindow *parent = GetParent();
791 Destroy();
792 parent->Destroy();
793
794 return true;
795 }
796
797 wxRect wxRibbonPanel::GetExpandedPosition(wxRect panel,
798 wxSize expanded_size,
799 wxDirection direction)
800 {
801 // Strategy:
802 // 1) Determine primary position based on requested direction
803 // 2) Move the position so that it sits entirely within a display
804 // (for single monitor systems, this moves it into the display region,
805 // but for multiple monitors, it does so without splitting it over
806 // more than one display)
807 // 2.1) Move in the primary axis
808 // 2.2) Move in the secondary axis
809
810 wxPoint pos;
811 bool primary_x = false;
812 int secondary_x = 0;
813 int secondary_y = 0;
814 switch(direction)
815 {
816 case wxNORTH:
817 pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
818 pos.y = panel.GetY() - expanded_size.GetHeight();
819 primary_x = true;
820 secondary_y = 1;
821 break;
822 case wxEAST:
823 pos.x = panel.GetRight();
824 pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
825 secondary_x = -1;
826 break;
827 case wxSOUTH:
828 pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
829 pos.y = panel.GetBottom();
830 primary_x = true;
831 secondary_y = -1;
832 break;
833 case wxWEST:
834 default:
835 pos.x = panel.GetX() - expanded_size.GetWidth();
836 pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
837 secondary_x = 1;
838 break;
839 }
840 wxRect expanded(pos, expanded_size);
841
842 wxRect best(expanded);
843 int best_distance = INT_MAX;
844
845 const unsigned display_n = wxDisplay::GetCount();
846 unsigned display_i;
847 for(display_i = 0; display_i < display_n; ++display_i)
848 {
849 wxRect display = wxDisplay(display_i).GetGeometry();
850
851 if(display.Contains(expanded))
852 {
853 return expanded;
854 }
855 else if(display.Intersects(expanded))
856 {
857 wxRect new_rect(expanded);
858 int distance = 0;
859
860 if(primary_x)
861 {
862 if(expanded.GetRight() > display.GetRight())
863 {
864 distance = expanded.GetRight() - display.GetRight();
865 new_rect.x -= distance;
866 }
867 else if(expanded.GetLeft() < display.GetLeft())
868 {
869 distance = display.GetLeft() - expanded.GetLeft();
870 new_rect.x += distance;
871 }
872 }
873 else
874 {
875 if(expanded.GetBottom() > display.GetBottom())
876 {
877 distance = expanded.GetBottom() - display.GetBottom();
878 new_rect.y -= distance;
879 }
880 else if(expanded.GetTop() < display.GetTop())
881 {
882 distance = display.GetTop() - expanded.GetTop();
883 new_rect.y += distance;
884 }
885 }
886 if(!display.Contains(new_rect))
887 {
888 // Tried moving in primary axis, but failed.
889 // Hence try moving in the secondary axis.
890 int dx = secondary_x * (panel.GetWidth() + expanded_size.GetWidth());
891 int dy = secondary_y * (panel.GetHeight() + expanded_size.GetHeight());
892 new_rect.x += dx;
893 new_rect.y += dy;
894
895 // Squaring makes secondary moves more expensive (and also
896 // prevents a negative cost)
897 distance += dx * dx + dy * dy;
898 }
899 if(display.Contains(new_rect) && distance < best_distance)
900 {
901 best = new_rect;
902 best_distance = distance;
903 }
904 }
905 }
906
907 return best;
908 }
909
910 #endif // wxUSE_RIBBON