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