1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/ribbon/panel.cpp
3 // Purpose: Ribbon-style container for a group of related tools / controls
4 // Author: Peter Cawley
8 // Copyright: (C) Peter Cawley
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
12 #include "wx/wxprec.h"
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"
31 #include "wx/msw/private.h"
34 IMPLEMENT_CLASS(wxRibbonPanel
, wxRibbonControl
)
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
)
46 wxRibbonPanel::wxRibbonPanel() : m_expanded_dummy(NULL
), m_expanded_panel(NULL
)
50 wxRibbonPanel::wxRibbonPanel(wxWindow
* parent
,
52 const wxString
& label
,
53 const wxBitmap
& minimised_icon
,
57 : wxRibbonControl(parent
, id
, pos
, size
, wxBORDER_NONE
)
59 CommonInit(label
, minimised_icon
, style
);
62 wxRibbonPanel::~wxRibbonPanel()
66 m_expanded_panel
->m_expanded_dummy
= NULL
;
67 m_expanded_panel
->GetParent()->Destroy();
71 bool wxRibbonPanel::Create(wxWindow
* parent
,
73 const wxString
& label
,
79 if(!wxRibbonControl::Create(parent
, id
, pos
, size
, wxBORDER_NONE
))
84 CommonInit(label
, icon
, style
);
89 void wxRibbonPanel::SetArtProvider(wxRibbonArtProvider
* art
)
92 for ( wxWindowList::compatibility_iterator node
= GetChildren().GetFirst();
94 node
= node
->GetNext() )
96 wxWindow
* child
= node
->GetData();
97 wxRibbonControl
* ribbon_child
= wxDynamicCast(child
, wxRibbonControl
);
100 ribbon_child
->SetArtProvider(art
);
104 m_expanded_panel
->SetArtProvider(art
);
107 void wxRibbonPanel::CommonInit(const wxString
& label
, const wxBitmap
& icon
, long style
)
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
;
118 m_minimised_icon
= icon
;
124 wxRibbonControl
* parent
= wxDynamicCast(GetParent(), wxRibbonControl
);
127 m_art
= parent
->GetArtProvider();
132 SetBackgroundStyle(wxBG_STYLE_CUSTOM
);
133 SetMinSize(wxSize(20, 20));
136 bool wxRibbonPanel::IsMinimised() const
141 bool wxRibbonPanel::IsHovered() const
146 void wxRibbonPanel::OnMouseEnter(wxMouseEvent
& evt
)
148 TestPositionForHover(evt
.GetPosition());
151 void wxRibbonPanel::OnMouseEnterChild(wxMouseEvent
& evt
)
153 wxPoint pos
= evt
.GetPosition();
154 wxWindow
*child
= wxDynamicCast(evt
.GetEventObject(), wxWindow
);
157 pos
+= child
->GetPosition();
158 TestPositionForHover(pos
);
163 void wxRibbonPanel::OnMouseLeave(wxMouseEvent
& evt
)
165 TestPositionForHover(evt
.GetPosition());
168 void wxRibbonPanel::OnMouseLeaveChild(wxMouseEvent
& evt
)
170 wxPoint pos
= evt
.GetPosition();
171 wxWindow
*child
= wxDynamicCast(evt
.GetEventObject(), wxWindow
);
174 pos
+= child
->GetPosition();
175 TestPositionForHover(pos
);
180 void wxRibbonPanel::TestPositionForHover(const wxPoint
& pos
)
182 bool hovered
= false;
183 if(pos
.x
>= 0 && pos
.y
>= 0)
185 wxSize size
= GetSize();
186 if(pos
.x
< size
.GetWidth() && pos
.y
< size
.GetHeight())
191 if(hovered
!= m_hovered
)
198 void wxRibbonPanel::AddChild(wxWindowBase
*child
)
200 wxRibbonControl::AddChild(child
);
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);
210 void wxRibbonPanel::RemoveChild(wxWindowBase
*child
)
212 child
->Disconnect(wxEVT_ENTER_WINDOW
, (wxObjectEventFunction
)&wxRibbonPanel::OnMouseEnterChild
, NULL
, this);
213 child
->Disconnect(wxEVT_LEAVE_WINDOW
, (wxObjectEventFunction
)&wxRibbonPanel::OnMouseLeaveChild
, NULL
, this);
215 wxRibbonControl::RemoveChild(child
);
218 void wxRibbonPanel::OnSize(wxSizeEvent
& evt
)
226 void wxRibbonPanel::DoSetSize(int x
, int y
, int width
, int height
, int sizeFlags
)
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.
235 bool minimised
= (m_flags
& wxRIBBON_PANEL_NO_AUTO_MINIMISE
) == 0 &&
236 IsMinimised(wxSize(width
, height
));
237 if(minimised
!= m_minimised
)
239 m_minimised
= minimised
;
241 for (wxWindowList::compatibility_iterator node
= GetChildren().GetFirst();
243 node
= node
->GetNext())
245 node
->GetData()->Show(!minimised
);
251 wxRibbonControl::DoSetSize(x
, y
, width
, height
, sizeFlags
);
254 bool wxRibbonPanel::IsMinimised(wxSize at_size
) const
256 if(!m_minimised_size
.IsFullySpecified())
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();
265 void wxRibbonPanel::OnEraseBackground(wxEraseEvent
& WXUNUSED(evt
))
267 // All painting done in main paint handler to minimise flicker
270 void wxRibbonPanel::OnPaint(wxPaintEvent
& WXUNUSED(evt
))
272 wxAutoBufferedPaintDC
dc(this);
278 m_art
->DrawMinimisedPanel(dc
, this, GetSize(), m_minimised_icon_resized
);
282 m_art
->DrawPanelBackground(dc
, this, GetSize());
287 bool wxRibbonPanel::IsSizingContinuous() const
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.
294 wxSize
wxRibbonPanel::DoGetNextSmallerSize(wxOrientation direction
,
295 wxSize relative_to
) const
297 if(m_expanded_panel
!= NULL
)
299 // Next size depends upon children, who are currently in the
301 return m_expanded_panel
->DoGetNextSmallerSize(direction
, relative_to
);
304 // TODO: Check for, and delegate to, a sizer
306 // Simple (and common) case of single ribbon child
307 if(GetChildren().GetCount() == 1)
309 wxWindow
* child
= GetChildren().Item(0)->GetData();
310 wxRibbonControl
* ribbon_child
= wxDynamicCast(child
, wxRibbonControl
);
311 if(m_art
!= NULL
&& ribbon_child
!= NULL
)
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
)
318 if(CanAutoMinimise())
320 wxSize minimised
= m_minimised_size
;
324 minimised
.SetHeight(relative_to
.GetHeight());
327 minimised
.SetWidth(relative_to
.GetWidth());
341 return m_art
->GetPanelSize(dc
, this, smaller
, NULL
);
346 // Fallback: Decrease by 20% (or minimum size, whichever larger)
347 wxSize
current(relative_to
);
348 wxSize
minimum(GetMinSize());
349 if(direction
& wxHORIZONTAL
)
351 current
.x
= (current
.x
* 4) / 5;
352 if(current
.x
< minimum
.x
)
354 current
.x
= minimum
.x
;
357 if(direction
& wxVERTICAL
)
359 current
.y
= (current
.y
* 4) / 5;
360 if(current
.y
< minimum
.y
)
362 current
.y
= minimum
.y
;
368 wxSize
wxRibbonPanel::DoGetNextLargerSize(wxOrientation direction
,
369 wxSize relative_to
) const
371 if(m_expanded_panel
!= NULL
)
373 // Next size depends upon children, who are currently in the
375 return m_expanded_panel
->DoGetNextLargerSize(direction
, relative_to
);
378 if(IsMinimised(relative_to
))
380 wxSize current
= relative_to
;
381 wxSize min_size
= GetMinNotMinimisedSize();
385 if(min_size
.x
> current
.x
&& min_size
.y
== current
.y
)
389 if(min_size
.x
== current
.x
&& min_size
.y
> current
.y
)
393 if(min_size
.x
> current
.x
&& min_size
.y
> current
.y
)
401 // TODO: Check for, and delegate to, a sizer
403 // Simple (and common) case of single ribbon child
404 if(GetChildren().GetCount() == 1)
406 wxWindow
* child
= GetChildren().Item(0)->GetData();
407 wxRibbonControl
* ribbon_child
= wxDynamicCast(child
, wxRibbonControl
);
408 if(ribbon_child
!= NULL
)
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
)
419 wxClientDC
dc((wxRibbonPanel
*) this);
420 return m_art
->GetPanelSize(dc
, this, larger
, NULL
);
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
)
434 current
.x
= (current
.x
* 5 + 3) / 4;
436 if(direction
& wxVERTICAL
)
438 current
.y
= (current
.y
* 5 + 3) / 4;
443 bool wxRibbonPanel::CanAutoMinimise() const
445 return (m_flags
& wxRIBBON_PANEL_NO_AUTO_MINIMISE
) == 0
446 && m_minimised_size
.IsFullySpecified();
449 wxSize
wxRibbonPanel::GetMinSize() const
451 if(m_expanded_panel
!= NULL
)
453 // Minimum size depends upon children, who are currently in the
455 return m_expanded_panel
->GetMinSize();
458 if(CanAutoMinimise())
460 return m_minimised_size
;
464 return GetMinNotMinimisedSize();
468 wxSize
wxRibbonPanel::GetMinNotMinimisedSize() const
472 // Common case of no sizer and single child taking up the entire panel
473 if(GetChildren().GetCount() == 1)
475 wxWindow
* child
= GetChildren().Item(0)->GetData();
476 wxClientDC
dc((wxRibbonPanel
*) this);
477 return m_art
->GetPanelSize(dc
, this, child
->GetMinSize(), NULL
);
480 return wxRibbonControl::GetMinSize();
483 wxSize
wxRibbonPanel::DoGetBestSize() const
487 // Common case of no sizer and single child taking up the entire panel
488 if(GetChildren().GetCount() == 1)
490 wxWindow
* child
= GetChildren().Item(0)->GetData();
491 wxClientDC
dc((wxRibbonPanel
*) this);
492 return m_art
->GetPanelSize(dc
, this, child
->GetBestSize(), NULL
);
495 return wxRibbonControl::DoGetBestSize();
498 bool wxRibbonPanel::Realize()
502 for (wxWindowList::compatibility_iterator node
= GetChildren().GetFirst();
504 node
= node
->GetNext())
506 wxRibbonControl
* child
= wxDynamicCast(node
->GetData(), wxRibbonControl
);
511 if(!child
->Realize())
517 wxSize
minimum_children_size(0, 0);
518 // TODO: Ask sizer if there is one
519 if(GetChildren().GetCount() == 1)
521 minimum_children_size
= GetChildren().GetFirst()->GetData()->GetMinSize();
526 wxClientDC
temp_dc(this);
528 m_smallest_unminimised_size
=
529 m_art
->GetPanelSize(temp_dc
, this, minimum_children_size
, NULL
);
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
)
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
);
543 m_minimised_icon_resized
= m_minimised_icon
;
545 if(m_minimised_size
.x
> panel_min_size
.x
&&
546 m_minimised_size
.y
> panel_min_size
.y
)
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);
554 if(m_art
->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL
)
556 m_minimised_size
.x
= panel_min_size
.x
;
560 m_minimised_size
.y
= panel_min_size
.y
;
566 m_minimised_size
= wxSize(-1, -1);
569 return Layout() && status
;
572 bool wxRibbonPanel::Layout()
576 // Children are all invisible when minimised
580 // TODO: Delegate to a sizer
582 // Common case of no sizer and single child taking up the entire panel
583 if(GetChildren().GetCount() == 1)
585 wxWindow
* child
= GetChildren().Item(0)->GetData();
588 wxSize size
= m_art
->GetPanelClientSize(dc
, this, GetSize(), &position
);
589 child
->SetSize(position
.x
, position
.y
, size
.GetWidth(), size
.GetHeight());
594 void wxRibbonPanel::OnMouseClick(wxMouseEvent
& WXUNUSED(evt
))
598 if(m_expanded_panel
!= NULL
)
609 wxRibbonPanel
* wxRibbonPanel::GetExpandedDummy()
611 return m_expanded_dummy
;
614 wxRibbonPanel
* wxRibbonPanel::GetExpandedPanel()
616 return m_expanded_panel
;
619 bool wxRibbonPanel::ShowExpanded()
625 if(m_expanded_dummy
!= NULL
|| m_expanded_panel
!= NULL
)
630 wxSize size
= GetBestSize();
631 wxPoint pos
= GetExpandedPosition(wxRect(GetScreenPosition(), GetSize()),
632 size
, m_preferred_expand_direction
).GetTopLeft();
634 // Need a top-level frame to contain the expanded panel
635 wxFrame
*container
= new wxFrame(NULL
, wxID_ANY
, GetLabel(),
636 pos
, size
, wxFRAME_NO_TASKBAR
| wxBORDER_NONE
);
638 m_expanded_panel
= new wxRibbonPanel(container
, wxID_ANY
,
639 GetLabel(), m_minimised_icon
, wxPoint(0, 0), size
, m_flags
);
641 m_expanded_panel
->SetArtProvider(m_art
);
642 m_expanded_panel
->m_expanded_dummy
= this;
644 // Move all children to the new panel.
645 // Conceptually it might be simpler to reparent this entire panel to the
646 // container and create a new panel to sit in its place while expanded.
647 // This approach has a problem though - when the panel is reinserted into
648 // its original parent, it'll be at a different position in the child list
649 // and thus assume a new position.
650 // NB: Children iterators not used as behaviour is not well defined
651 // when iterating over a container which is being emptied
652 while(!GetChildren().IsEmpty())
654 wxWindow
*child
= GetChildren().GetFirst()->GetData();
655 child
->Reparent(m_expanded_panel
);
659 // TODO: Move sizer to new panel
661 m_expanded_panel
->Realize();
664 m_expanded_panel
->SetFocus();
669 bool wxRibbonPanel::ShouldSendEventToDummy(wxEvent
& evt
)
671 // For an expanded panel, filter events between being sent up to the
672 // floating top level window or to the dummy panel sitting in the ribbon
675 // Child focus events should not be redirected, as the child would not be a
676 // child of the window the event is redirected to. All other command events
677 // seem to be suitable for redirecting.
678 return evt
.IsCommandEvent() && evt
.GetEventType() != wxEVT_CHILD_FOCUS
;
681 bool wxRibbonPanel::TryAfter(wxEvent
& evt
)
683 if(m_expanded_dummy
&& ShouldSendEventToDummy(evt
))
685 wxPropagateOnce
propagateOnce(evt
);
686 return m_expanded_dummy
->GetEventHandler()->ProcessEvent(evt
);
690 return wxRibbonControl::TryAfter(evt
);
694 static bool IsAncestorOf(wxWindow
*ancestor
, wxWindow
*window
)
696 while(window
!= NULL
)
698 wxWindow
*parent
= window
->GetParent();
699 if(parent
== ancestor
)
707 void wxRibbonPanel::OnKillFocus(wxFocusEvent
& evt
)
711 wxWindow
*receiver
= evt
.GetWindow();
712 if(IsAncestorOf(this, receiver
))
714 m_child_with_focus
= receiver
;
715 receiver
->Connect(wxEVT_KILL_FOCUS
,
716 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus
),
719 else if(receiver
== NULL
|| receiver
!= m_expanded_dummy
)
726 void wxRibbonPanel::OnChildKillFocus(wxFocusEvent
& evt
)
728 if(m_child_with_focus
== NULL
)
729 return; // Should never happen, but a check can't hurt
731 m_child_with_focus
->Disconnect(wxEVT_KILL_FOCUS
,
732 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus
), NULL
, this);
733 m_child_with_focus
= NULL
;
735 wxWindow
*receiver
= evt
.GetWindow();
736 if(receiver
== this || IsAncestorOf(this, receiver
))
738 m_child_with_focus
= receiver
;
739 receiver
->Connect(wxEVT_KILL_FOCUS
,
740 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus
), NULL
, this);
743 else if(receiver
== NULL
|| receiver
!= m_expanded_dummy
)
746 // Do not skip event, as the panel has been de-expanded, causing the
747 // child with focus to be reparented (and hidden). If the event
748 // continues propogation then bad things happen.
756 bool wxRibbonPanel::HideExpanded()
758 if(m_expanded_dummy
== NULL
)
762 return m_expanded_panel
->HideExpanded();
770 // Move children back to original panel
771 // NB: Children iterators not used as behaviour is not well defined
772 // when iterating over a container which is being emptied
773 while(!GetChildren().IsEmpty())
775 wxWindow
*child
= GetChildren().GetFirst()->GetData();
776 child
->Reparent(m_expanded_dummy
);
780 // TODO: Move sizer back
782 m_expanded_dummy
->m_expanded_panel
= NULL
;
783 m_expanded_dummy
->Realize();
784 m_expanded_dummy
->Refresh();
785 wxWindow
*parent
= GetParent();
792 wxRect
wxRibbonPanel::GetExpandedPosition(wxRect panel
,
793 wxSize expanded_size
,
794 wxDirection direction
)
797 // 1) Determine primary position based on requested direction
798 // 2) Move the position so that it sits entirely within a display
799 // (for single monitor systems, this moves it into the display region,
800 // but for multiple monitors, it does so without splitting it over
801 // more than one display)
802 // 2.1) Move in the primary axis
803 // 2.2) Move in the secondary axis
806 bool primary_x
= false;
812 pos
.x
= panel
.GetX() + (panel
.GetWidth() - expanded_size
.GetWidth()) / 2;
813 pos
.y
= panel
.GetY() - expanded_size
.GetHeight();
818 pos
.x
= panel
.GetRight();
819 pos
.y
= panel
.GetY() + (panel
.GetHeight() - expanded_size
.GetHeight()) / 2;
823 pos
.x
= panel
.GetX() + (panel
.GetWidth() - expanded_size
.GetWidth()) / 2;
824 pos
.y
= panel
.GetBottom();
830 pos
.x
= panel
.GetX() - expanded_size
.GetWidth();
831 pos
.y
= panel
.GetY() + (panel
.GetHeight() - expanded_size
.GetHeight()) / 2;
835 wxRect
expanded(pos
, expanded_size
);
837 wxRect
best(expanded
);
838 int best_distance
= INT_MAX
;
840 const unsigned display_n
= wxDisplay::GetCount();
842 for(display_i
= 0; display_i
< display_n
; ++display_i
)
844 wxRect display
= wxDisplay(display_i
).GetGeometry();
846 if(display
.Contains(expanded
))
850 else if(display
.Intersects(expanded
))
852 wxRect
new_rect(expanded
);
857 if(expanded
.GetRight() > display
.GetRight())
859 distance
= expanded
.GetRight() - display
.GetRight();
860 new_rect
.x
-= distance
;
862 else if(expanded
.GetLeft() < display
.GetLeft())
864 distance
= display
.GetLeft() - expanded
.GetLeft();
865 new_rect
.x
+= distance
;
870 if(expanded
.GetBottom() > display
.GetBottom())
872 distance
= expanded
.GetBottom() - display
.GetBottom();
873 new_rect
.y
-= distance
;
875 else if(expanded
.GetTop() < display
.GetTop())
877 distance
= display
.GetTop() - expanded
.GetTop();
878 new_rect
.y
+= distance
;
881 if(!display
.Contains(new_rect
))
883 // Tried moving in primary axis, but failed.
884 // Hence try moving in the secondary axis.
885 int dx
= secondary_x
* (panel
.GetWidth() + expanded_size
.GetWidth());
886 int dy
= secondary_y
* (panel
.GetHeight() + expanded_size
.GetHeight());
890 // Squaring makes secondary moves more expensive (and also
891 // prevents a negative cost)
892 distance
+= dx
* dx
+ dy
* dy
;
894 if(display
.Contains(new_rect
) && distance
< best_distance
)
897 best_distance
= distance
;
905 #endif // wxUSE_RIBBON