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 // Copyright:   (C) Peter Cawley
8 // Licence:     wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 #include "wx/wxprec.h"
12 
13 #ifdef __BORLANDC__
14     #pragma hdrstop
15 #endif
16 
17 #if wxUSE_RIBBON
18 
19 #include "wx/ribbon/panel.h"
20 #include "wx/ribbon/art.h"
21 #include "wx/ribbon/bar.h"
22 #include "wx/dcbuffer.h"
23 #include "wx/display.h"
24 #include "wx/sizer.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 wxDEFINE_EVENT(wxEVT_RIBBONPANEL_EXTBUTTON_ACTIVATED, wxRibbonPanelEvent);
35 
IMPLEMENT_DYNAMIC_CLASS(wxRibbonPanelEvent,wxCommandEvent)36 IMPLEMENT_DYNAMIC_CLASS(wxRibbonPanelEvent, wxCommandEvent)
37 
38 IMPLEMENT_CLASS(wxRibbonPanel, wxRibbonControl)
39 
40 BEGIN_EVENT_TABLE(wxRibbonPanel, wxRibbonControl)
41     EVT_ENTER_WINDOW(wxRibbonPanel::OnMouseEnter)
42     EVT_ERASE_BACKGROUND(wxRibbonPanel::OnEraseBackground)
43     EVT_KILL_FOCUS(wxRibbonPanel::OnKillFocus)
44     EVT_LEAVE_WINDOW(wxRibbonPanel::OnMouseLeave)
45     EVT_MOTION(wxRibbonPanel::OnMotion)
46     EVT_LEFT_DOWN(wxRibbonPanel::OnMouseClick)
47     EVT_PAINT(wxRibbonPanel::OnPaint)
48     EVT_SIZE(wxRibbonPanel::OnSize)
49 END_EVENT_TABLE()
50 
51 wxRibbonPanel::wxRibbonPanel() : m_expanded_dummy(NULL), m_expanded_panel(NULL)
52 {
53 }
54 
wxRibbonPanel(wxWindow * parent,wxWindowID id,const wxString & label,const wxBitmap & minimised_icon,const wxPoint & pos,const wxSize & size,long style)55 wxRibbonPanel::wxRibbonPanel(wxWindow* parent,
56                   wxWindowID id,
57                   const wxString& label,
58                   const wxBitmap& minimised_icon,
59                   const wxPoint& pos,
60                   const wxSize& size,
61                   long style)
62     : wxRibbonControl(parent, id, pos, size, wxBORDER_NONE)
63 {
64     CommonInit(label, minimised_icon, style);
65 }
66 
~wxRibbonPanel()67 wxRibbonPanel::~wxRibbonPanel()
68 {
69     if(m_expanded_panel)
70     {
71         m_expanded_panel->m_expanded_dummy = NULL;
72         m_expanded_panel->GetParent()->Destroy();
73     }
74 }
75 
Create(wxWindow * parent,wxWindowID id,const wxString & label,const wxBitmap & icon,const wxPoint & pos,const wxSize & size,long style)76 bool wxRibbonPanel::Create(wxWindow* parent,
77                 wxWindowID id,
78                 const wxString& label,
79                 const wxBitmap& icon,
80                 const wxPoint& pos,
81                 const wxSize& size,
82                 long style)
83 {
84     if(!wxRibbonControl::Create(parent, id, pos, size, wxBORDER_NONE))
85     {
86         return false;
87     }
88 
89     CommonInit(label, icon, style);
90 
91     return true;
92 }
93 
SetArtProvider(wxRibbonArtProvider * art)94 void wxRibbonPanel::SetArtProvider(wxRibbonArtProvider* art)
95 {
96     m_art = art;
97     for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
98           node;
99           node = node->GetNext() )
100     {
101         wxWindow* child = node->GetData();
102         wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
103         if(ribbon_child)
104         {
105             ribbon_child->SetArtProvider(art);
106         }
107     }
108     if(m_expanded_panel)
109         m_expanded_panel->SetArtProvider(art);
110 }
111 
CommonInit(const wxString & label,const wxBitmap & icon,long style)112 void wxRibbonPanel::CommonInit(const wxString& label, const wxBitmap& icon, long style)
113 {
114     SetName(label);
115     SetLabel(label);
116 
117     m_minimised_size = wxDefaultSize; // Unknown / none
118     m_smallest_unminimised_size = wxDefaultSize;// Unknown / none for IsFullySpecified()
119     m_preferred_expand_direction = wxSOUTH;
120     m_expanded_dummy = NULL;
121     m_expanded_panel = NULL;
122     m_flags = style;
123     m_minimised_icon = icon;
124     m_minimised = false;
125     m_hovered = false;
126     m_ext_button_hovered = false;
127 
128     if(m_art == NULL)
129     {
130         wxRibbonControl* parent = wxDynamicCast(GetParent(), wxRibbonControl);
131         if(parent != NULL)
132         {
133             m_art = parent->GetArtProvider();
134         }
135     }
136 
137     SetAutoLayout(true);
138     SetBackgroundStyle(wxBG_STYLE_CUSTOM);
139     SetMinSize(wxSize(20, 20));
140 }
141 
IsMinimised() const142 bool wxRibbonPanel::IsMinimised() const
143 {
144     return m_minimised;
145 }
146 
IsHovered() const147 bool wxRibbonPanel::IsHovered() const
148 {
149     return m_hovered;
150 }
151 
IsExtButtonHovered() const152 bool wxRibbonPanel::IsExtButtonHovered() const
153 {
154     return m_ext_button_hovered;
155 }
156 
OnMouseEnter(wxMouseEvent & evt)157 void wxRibbonPanel::OnMouseEnter(wxMouseEvent& evt)
158 {
159     TestPositionForHover(evt.GetPosition());
160 }
161 
OnMouseEnterChild(wxMouseEvent & evt)162 void wxRibbonPanel::OnMouseEnterChild(wxMouseEvent& evt)
163 {
164     wxPoint pos = evt.GetPosition();
165     wxWindow *child = wxDynamicCast(evt.GetEventObject(), wxWindow);
166     if(child)
167     {
168         pos += child->GetPosition();
169         TestPositionForHover(pos);
170     }
171     evt.Skip();
172 }
173 
OnMouseLeave(wxMouseEvent & evt)174 void wxRibbonPanel::OnMouseLeave(wxMouseEvent& evt)
175 {
176     TestPositionForHover(evt.GetPosition());
177 }
178 
OnMouseLeaveChild(wxMouseEvent & evt)179 void wxRibbonPanel::OnMouseLeaveChild(wxMouseEvent& evt)
180 {
181     wxPoint pos = evt.GetPosition();
182     wxWindow *child = wxDynamicCast(evt.GetEventObject(), wxWindow);
183     if(child)
184     {
185         pos += child->GetPosition();
186         TestPositionForHover(pos);
187     }
188     evt.Skip();
189 }
190 
OnMotion(wxMouseEvent & evt)191 void wxRibbonPanel::OnMotion(wxMouseEvent& evt)
192 {
193     TestPositionForHover(evt.GetPosition());
194 }
195 
TestPositionForHover(const wxPoint & pos)196 void wxRibbonPanel::TestPositionForHover(const wxPoint& pos)
197 {
198     bool hovered = false, ext_button_hovered = false;
199     if(pos.x >= 0 && pos.y >= 0)
200     {
201         wxSize size = GetSize();
202         if(pos.x < size.GetWidth() && pos.y < size.GetHeight())
203         {
204             hovered = true;
205         }
206     }
207     if(hovered)
208     {
209         if(HasExtButton())
210             ext_button_hovered = m_ext_button_rect.Contains(pos);
211         else
212             ext_button_hovered = false;
213     }
214     if(hovered != m_hovered || ext_button_hovered != m_ext_button_hovered)
215     {
216         m_hovered = hovered;
217         m_ext_button_hovered = ext_button_hovered;
218         Refresh(false);
219     }
220 }
221 
AddChild(wxWindowBase * child)222 void wxRibbonPanel::AddChild(wxWindowBase *child)
223 {
224     wxRibbonControl::AddChild(child);
225 
226     // Window enter / leave events count for only the window in question, not
227     // for children of the window. The panel wants to be in the hovered state
228     // whenever the mouse cursor is within its boundary, so the events need to
229     // be attached to children too.
230     child->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(wxRibbonPanel::OnMouseEnterChild), NULL, this);
231     child->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(wxRibbonPanel::OnMouseLeaveChild), NULL, this);
232 }
233 
RemoveChild(wxWindowBase * child)234 void wxRibbonPanel::RemoveChild(wxWindowBase *child)
235 {
236     child->Disconnect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(wxRibbonPanel::OnMouseEnterChild), NULL, this);
237     child->Disconnect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(wxRibbonPanel::OnMouseLeaveChild), NULL, this);
238 
239     wxRibbonControl::RemoveChild(child);
240 }
241 
HasExtButton() const242 bool wxRibbonPanel::HasExtButton()const
243 {
244     wxRibbonBar* bar = GetAncestorRibbonBar();
245     if(bar==NULL)
246         return false;
247     return (m_flags & wxRIBBON_PANEL_EXT_BUTTON) &&
248         (bar->GetWindowStyleFlag() & wxRIBBON_BAR_SHOW_PANEL_EXT_BUTTONS);
249 }
250 
OnSize(wxSizeEvent & evt)251 void wxRibbonPanel::OnSize(wxSizeEvent& evt)
252 {
253     if(GetAutoLayout())
254         Layout();
255 
256     evt.Skip();
257 }
258 
DoSetSize(int x,int y,int width,int height,int sizeFlags)259 void wxRibbonPanel::DoSetSize(int x, int y, int width, int height, int sizeFlags)
260 {
261     // At least on MSW, changing the size of a window will cause GetSize() to
262     // report the new size, but a size event may not be handled immediately.
263     // If this minimised check was performed in the OnSize handler, then
264     // GetSize() could return a size much larger than the minimised size while
265     // IsMinimised() returns true. This would then affect layout, as the panel
266     // will refuse to grow any larger while in limbo between minimised and non.
267 
268     bool minimised = (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0 &&
269         IsMinimised(wxSize(width, height)); // check if would be at this size
270     if(minimised != m_minimised)
271     {
272         m_minimised = minimised;
273         // Note that for sizers, this routine disallows the use of mixed shown
274         // and hidden controls
275         // TODO ? use some list of user set invisible children to restore status.
276         for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
277                   node;
278                   node = node->GetNext())
279         {
280             node->GetData()->Show(!minimised);
281         }
282 
283         Refresh();
284     }
285 
286     wxRibbonControl::DoSetSize(x, y, width, height, sizeFlags);
287 }
288 
289 // Checks if panel would be minimised at (client size) at_size
IsMinimised(wxSize at_size) const290 bool wxRibbonPanel::IsMinimised(wxSize at_size) const
291 {
292     if(GetSizer())
293     {
294         // we have no information on size change direction
295         // so check both
296         wxSize size = GetMinNotMinimisedSize();
297         if(size.x > at_size.x || size.y > at_size.y)
298             return true;
299 
300         return false;
301     }
302 
303     if(!m_minimised_size.IsFullySpecified())
304         return false;
305 
306     return (at_size.GetX() <= m_minimised_size.GetX() &&
307         at_size.GetY() <= m_minimised_size.GetY()) ||
308         at_size.GetX() < m_smallest_unminimised_size.GetX() ||
309         at_size.GetY() < m_smallest_unminimised_size.GetY();
310 }
311 
OnEraseBackground(wxEraseEvent & WXUNUSED (evt))312 void wxRibbonPanel::OnEraseBackground(wxEraseEvent& WXUNUSED(evt))
313 {
314     // All painting done in main paint handler to minimise flicker
315 }
316 
OnPaint(wxPaintEvent & WXUNUSED (evt))317 void wxRibbonPanel::OnPaint(wxPaintEvent& WXUNUSED(evt))
318 {
319     wxAutoBufferedPaintDC dc(this);
320 
321     if(m_art != NULL)
322     {
323         if(IsMinimised())
324         {
325             m_art->DrawMinimisedPanel(dc, this, GetSize(), m_minimised_icon_resized);
326         }
327         else
328         {
329             m_art->DrawPanelBackground(dc, this, GetSize());
330         }
331     }
332 }
333 
IsSizingContinuous() const334 bool wxRibbonPanel::IsSizingContinuous() const
335 {
336     // A panel never sizes continuously, even if all of its children can,
337     // as it would appear out of place along side non-continuous panels.
338 
339     // JS 2012-03-09: introducing wxRIBBON_PANEL_STRETCH to allow
340     // the panel to fill its parent page. For example we might have
341     // a list of styles in one of the pages, which should stretch to
342     // fill available space.
343     return (m_flags & wxRIBBON_PANEL_STRETCH) != 0;
344 }
345 
346 // Finds the best width and height given the parent's width and height
GetBestSizeForParentSize(const wxSize & parentSize) const347 wxSize wxRibbonPanel::GetBestSizeForParentSize(const wxSize& parentSize) const
348 {
349     if (GetChildren().GetCount() == 1)
350     {
351         wxWindow* win = GetChildren().GetFirst()->GetData();
352         wxRibbonControl* control = wxDynamicCast(win, wxRibbonControl);
353         if (control)
354         {
355             wxClientDC temp_dc((wxRibbonPanel*) this);
356             wxSize clientParentSize = m_art->GetPanelClientSize(temp_dc, this, parentSize, NULL);
357             wxSize childSize = control->GetBestSizeForParentSize(clientParentSize);
358             wxSize overallSize = m_art->GetPanelSize(temp_dc, this, childSize, NULL);
359             return overallSize;
360         }
361     }
362     return GetSize();
363 }
364 
DoGetNextSmallerSize(wxOrientation direction,wxSize relative_to) const365 wxSize wxRibbonPanel::DoGetNextSmallerSize(wxOrientation direction,
366                                          wxSize relative_to) const
367 {
368     if(m_expanded_panel != NULL)
369     {
370         // Next size depends upon children, who are currently in the
371         // expanded panel
372         return m_expanded_panel->DoGetNextSmallerSize(direction, relative_to);
373     }
374 
375     if(m_art != NULL)
376     {
377         wxClientDC dc((wxRibbonPanel*) this);
378         wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, NULL);
379         wxSize smaller(-1, -1);
380         bool minimise = false;
381 
382         if(GetSizer())
383         {
384             // Get smallest non minimised size
385             smaller = GetMinSize();
386             // and adjust to child_relative for parent page
387             if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
388             {
389                  minimise = (child_relative.y <= smaller.y);
390                  if(smaller.x < child_relative.x)
391                     smaller.x = child_relative.x;
392             }
393             else
394             {
395                 minimise = (child_relative.x <= smaller.x);
396                 if(smaller.y < child_relative.y)
397                     smaller.y = child_relative.y;
398             }
399         }
400         else if(GetChildren().GetCount() == 1)
401         {
402             // Simple (and common) case of single ribbon child or Sizer
403             wxWindow* child = GetChildren().Item(0)->GetData();
404             wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
405             if(ribbon_child != NULL)
406             {
407                 smaller = ribbon_child->GetNextSmallerSize(direction, child_relative);
408                 minimise = (smaller == child_relative);
409             }
410         }
411 
412         if(minimise)
413         {
414             if(CanAutoMinimise())
415             {
416                 wxSize minimised = m_minimised_size;
417                 switch(direction)
418                 {
419                 case wxHORIZONTAL:
420                     minimised.SetHeight(relative_to.GetHeight());
421                     break;
422                 case wxVERTICAL:
423                     minimised.SetWidth(relative_to.GetWidth());
424                     break;
425                 default:
426                     break;
427                 }
428                 return minimised;
429             }
430             else
431             {
432                 return relative_to;
433             }
434         }
435         else if(smaller.IsFullySpecified()) // Use fallback if !(sizer/child = 1)
436         {
437             return m_art->GetPanelSize(dc, this, smaller, NULL);
438         }
439     }
440 
441     // Fallback: Decrease by 20% (or minimum size, whichever larger)
442     wxSize current(relative_to);
443     wxSize minimum(GetMinSize());
444     if(direction & wxHORIZONTAL)
445     {
446         current.x = (current.x * 4) / 5;
447         if(current.x < minimum.x)
448         {
449             current.x = minimum.x;
450         }
451     }
452     if(direction & wxVERTICAL)
453     {
454         current.y = (current.y * 4) / 5;
455         if(current.y < minimum.y)
456         {
457             current.y = minimum.y;
458         }
459     }
460     return current;
461 }
462 
DoGetNextLargerSize(wxOrientation direction,wxSize relative_to) const463 wxSize wxRibbonPanel::DoGetNextLargerSize(wxOrientation direction,
464                                         wxSize relative_to) const
465 {
466     if(m_expanded_panel != NULL)
467     {
468         // Next size depends upon children, who are currently in the
469         // expanded panel
470         return m_expanded_panel->DoGetNextLargerSize(direction, relative_to);
471     }
472 
473     if(IsMinimised(relative_to))
474     {
475         wxSize current = relative_to;
476         wxSize min_size = GetMinNotMinimisedSize();
477         switch(direction)
478         {
479         case wxHORIZONTAL:
480             if(min_size.x > current.x && min_size.y == current.y)
481                 return min_size;
482             break;
483         case wxVERTICAL:
484             if(min_size.x == current.x && min_size.y > current.y)
485                 return min_size;
486             break;
487         case wxBOTH:
488             if(min_size.x > current.x && min_size.y > current.y)
489                 return min_size;
490             break;
491         default:
492             break;
493         }
494     }
495 
496     if(m_art != NULL)
497     {
498         wxClientDC dc((wxRibbonPanel*) this);
499         wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, NULL);
500         wxSize larger(-1, -1);
501 
502         if(GetSizer())
503         {
504             // We could just let the sizer expand in flow direction but see comment
505             // in IsSizingContinuous()
506             larger = GetPanelSizerBestSize();
507             // and adjust for page in non flow direction
508             if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
509             {
510                  if(larger.x != child_relative.x)
511                     larger.x = child_relative.x;
512             }
513             else if(larger.y != child_relative.y)
514             {
515                 larger.y = child_relative.y;
516             }
517         }
518         else if(GetChildren().GetCount() == 1)
519         {
520             // Simple (and common) case of single ribbon child
521             wxWindow* child = GetChildren().Item(0)->GetData();
522             wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
523             if(ribbon_child != NULL)
524             {
525                 larger = ribbon_child->GetNextLargerSize(direction, child_relative);
526             }
527         }
528 
529         if(larger.IsFullySpecified()) // Use fallback if !(sizer/child = 1)
530         {
531             if(larger == child_relative)
532             {
533                 return relative_to;
534             }
535             else
536             {
537                 return m_art->GetPanelSize(dc, this, larger, NULL);
538             }
539         }
540     }
541 
542     // Fallback: Increase by 25% (equal to a prior or subsequent 20% decrease)
543     // Note that due to rounding errors, this increase may not exactly equal a
544     // matching decrease - an ideal solution would not have these errors, but
545     // avoiding them is non-trivial unless an increase is by 100% rather than
546     // a fractional amount. This would then be non-ideal as the resizes happen
547     // at very large intervals.
548     wxSize current(relative_to);
549     if(direction & wxHORIZONTAL)
550     {
551         current.x = (current.x * 5 + 3) / 4;
552     }
553     if(direction & wxVERTICAL)
554     {
555         current.y = (current.y * 5 + 3) / 4;
556     }
557     return current;
558 }
559 
CanAutoMinimise() const560 bool wxRibbonPanel::CanAutoMinimise() const
561 {
562     return (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0
563         && m_minimised_size.IsFullySpecified();
564 }
565 
GetMinSize() const566 wxSize wxRibbonPanel::GetMinSize() const
567 {
568     if(m_expanded_panel != NULL)
569     {
570         // Minimum size depends upon children, who are currently in the
571         // expanded panel
572         return m_expanded_panel->GetMinSize();
573     }
574 
575     if(CanAutoMinimise())
576     {
577         return m_minimised_size;
578     }
579     else
580     {
581         return GetMinNotMinimisedSize();
582     }
583 }
584 
GetMinNotMinimisedSize() const585 wxSize wxRibbonPanel::GetMinNotMinimisedSize() const
586 {
587     // Ask sizer if present
588     if(GetSizer())
589     {
590         wxClientDC dc((wxRibbonPanel*) this);
591         return m_art->GetPanelSize(dc, this, GetPanelSizerMinSize(), NULL);
592     }
593     else if(GetChildren().GetCount() == 1)
594     {
595         // Common case of single child taking up the entire panel
596         wxWindow* child = GetChildren().Item(0)->GetData();
597         wxClientDC dc((wxRibbonPanel*) this);
598         return m_art->GetPanelSize(dc, this, child->GetMinSize(), NULL);
599     }
600 
601     return wxRibbonControl::GetMinSize();
602 }
603 
GetPanelSizerMinSize() const604 wxSize wxRibbonPanel::GetPanelSizerMinSize() const
605 {
606     // Called from Realize() to set m_smallest_unminimised_size and from other
607     // functions to get the minimum size.
608     // The panel will be invisible when minimised and sizer calcs will be 0
609     // Uses m_smallest_unminimised_size in preference to GetSizer()->CalcMin()
610     // to eliminate flicker.
611 
612     // Check if is visible and not previously calculated
613     if(IsShown() && !m_smallest_unminimised_size.IsFullySpecified())
614     {
615          return GetSizer()->CalcMin();
616     }
617     // else use previously calculated m_smallest_unminimised_size
618     wxClientDC dc((wxRibbonPanel*) this);
619     return m_art->GetPanelClientSize(dc,
620                                     this,
621                                     m_smallest_unminimised_size,
622                                     NULL);
623 }
624 
GetPanelSizerBestSize() const625 wxSize wxRibbonPanel::GetPanelSizerBestSize() const
626 {
627     wxSize size = GetPanelSizerMinSize();
628     // TODO allow panel to increase its size beyond minimum size
629     // by steps similarly to ribbon control panels (preferred for aesthetics)
630     // or continuously.
631     return size;
632 }
633 
DoGetBestSize() const634 wxSize wxRibbonPanel::DoGetBestSize() const
635 {
636     // Ask sizer if present
637     if( GetSizer())
638     {
639         wxClientDC dc((wxRibbonPanel*) this);
640         return m_art->GetPanelSize(dc, this, GetPanelSizerBestSize(), NULL);
641     }
642     else if(GetChildren().GetCount() == 1)
643     {
644         // Common case of no sizer and single child taking up the entire panel
645         wxWindow* child = GetChildren().Item(0)->GetData();
646         wxClientDC dc((wxRibbonPanel*) this);
647         return m_art->GetPanelSize(dc, this, child->GetBestSize(), NULL);
648     }
649 
650     return wxRibbonControl::DoGetBestSize();
651 }
652 
Realize()653 bool wxRibbonPanel::Realize()
654 {
655     bool status = true;
656 
657     for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
658                   node;
659                   node = node->GetNext())
660     {
661         wxRibbonControl* child = wxDynamicCast(node->GetData(), wxRibbonControl);
662         if(child == NULL)
663         {
664             continue;
665         }
666         if(!child->Realize())
667         {
668             status = false;
669         }
670     }
671 
672     wxSize minimum_children_size(0, 0);
673 
674     // Reset it before calling GetPanelSizerMinSize() below as it shouldn't use
675     // the old value, if we had any.
676     m_smallest_unminimised_size = wxDefaultSize;
677 
678     // Ask sizer if there is one present
679     if(GetSizer())
680     {
681         minimum_children_size = GetPanelSizerMinSize();
682     }
683     else if(GetChildren().GetCount() == 1)
684     {
685         minimum_children_size = GetChildren().GetFirst()->GetData()->GetMinSize();
686     }
687 
688     if(m_art != NULL)
689     {
690         wxClientDC temp_dc(this);
691 
692         m_smallest_unminimised_size =
693             m_art->GetPanelSize(temp_dc, this, minimum_children_size, NULL);
694 
695         wxSize bitmap_size;
696         wxSize panel_min_size = GetMinNotMinimisedSize();
697         m_minimised_size = m_art->GetMinimisedPanelMinimumSize(temp_dc, this,
698             &bitmap_size, &m_preferred_expand_direction);
699         if(m_minimised_icon.IsOk() && m_minimised_icon.GetSize() != bitmap_size)
700         {
701             wxImage img(m_minimised_icon.ConvertToImage());
702             img.Rescale(bitmap_size.GetWidth(), bitmap_size.GetHeight(), wxIMAGE_QUALITY_HIGH);
703             m_minimised_icon_resized = wxBitmap(img);
704         }
705         else
706         {
707             m_minimised_icon_resized = m_minimised_icon;
708         }
709         if(m_minimised_size.x > panel_min_size.x &&
710             m_minimised_size.y > panel_min_size.y)
711         {
712             // No point in having a minimised size which is larger than the
713             // minimum size which the children can go to.
714             m_minimised_size = wxSize(-1, -1);
715         }
716         else
717         {
718             if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
719             {
720                 m_minimised_size.x = panel_min_size.x;
721             }
722             else
723             {
724                 m_minimised_size.y = panel_min_size.y;
725             }
726         }
727     }
728     else
729     {
730         m_minimised_size = wxSize(-1, -1);
731     }
732 
733     return Layout() && status;
734 }
735 
Layout()736 bool wxRibbonPanel::Layout()
737 {
738     if(IsMinimised())
739     {
740         // Children are all invisible when minimised
741         return true;
742     }
743 
744     // Get wxRibbonPanel client size
745     wxPoint position;
746     wxClientDC dc(this);
747     wxSize size = m_art->GetPanelClientSize(dc, this, GetSize(), &position);
748 
749     // If there is a sizer, use it
750     if(GetSizer())
751     {
752         GetSizer()->SetDimension(position, size); // SetSize and Layout()
753     }
754     else if(GetChildren().GetCount() == 1)
755     {
756         // Common case of no sizer and single child taking up the entire panel
757         wxWindow* child = GetChildren().Item(0)->GetData();
758         child->SetSize(position.x, position.y, size.GetWidth(), size.GetHeight());
759     }
760 
761     if(HasExtButton())
762         m_ext_button_rect = m_art->GetPanelExtButtonArea(dc, this, GetSize());
763 
764     return true;
765 }
766 
OnMouseClick(wxMouseEvent & WXUNUSED (evt))767 void wxRibbonPanel::OnMouseClick(wxMouseEvent& WXUNUSED(evt))
768 {
769     if(IsMinimised())
770     {
771         if(m_expanded_panel != NULL)
772         {
773             HideExpanded();
774         }
775         else
776         {
777             ShowExpanded();
778         }
779     }
780     else if(IsExtButtonHovered())
781     {
782         wxRibbonPanelEvent notification(wxEVT_RIBBONPANEL_EXTBUTTON_ACTIVATED, GetId());
783         notification.SetEventObject(this);
784         notification.SetPanel(this);
785         ProcessEvent(notification);
786     }
787 }
788 
GetExpandedDummy()789 wxRibbonPanel* wxRibbonPanel::GetExpandedDummy()
790 {
791     return m_expanded_dummy;
792 }
793 
GetExpandedPanel()794 wxRibbonPanel* wxRibbonPanel::GetExpandedPanel()
795 {
796     return m_expanded_panel;
797 }
798 
ShowExpanded()799 bool wxRibbonPanel::ShowExpanded()
800 {
801     if(!IsMinimised())
802     {
803         return false;
804     }
805     if(m_expanded_dummy != NULL || m_expanded_panel != NULL)
806     {
807         return false;
808     }
809 
810     wxSize size = GetBestSize();
811 
812     // Special case for flexible panel layout, where GetBestSize doesn't work
813     if (GetFlags() & wxRIBBON_PANEL_FLEXIBLE)
814     {
815         size = GetBestSizeForParentSize(wxSize(400, 1000));
816     }
817 
818     wxPoint pos = GetExpandedPosition(wxRect(GetScreenPosition(), GetSize()),
819         size, m_preferred_expand_direction).GetTopLeft();
820 
821     // Need a top-level frame to contain the expanded panel
822     wxFrame *container = new wxFrame(NULL, wxID_ANY, GetLabel(),
823         pos, size, wxFRAME_NO_TASKBAR | wxBORDER_NONE);
824 
825     m_expanded_panel = new wxRibbonPanel(container, wxID_ANY,
826         GetLabel(), m_minimised_icon, wxPoint(0, 0), size, (m_flags /* & ~wxRIBBON_PANEL_FLEXIBLE */));
827 
828     m_expanded_panel->SetArtProvider(m_art);
829     m_expanded_panel->m_expanded_dummy = this;
830 
831     // Move all children to the new panel.
832     // Conceptually it might be simpler to reparent this entire panel to the
833     // container and create a new panel to sit in its place while expanded.
834     // This approach has a problem though - when the panel is reinserted into
835     // its original parent, it'll be at a different position in the child list
836     // and thus assume a new position.
837     // NB: Children iterators not used as behaviour is not well defined
838     // when iterating over a container which is being emptied
839     while(!GetChildren().IsEmpty())
840     {
841         wxWindow *child = GetChildren().GetFirst()->GetData();
842         child->Reparent(m_expanded_panel);
843         child->Show();
844     }
845 
846     // Move sizer to new panel
847     if(GetSizer())
848     {
849         wxSizer* sizer = GetSizer();
850         SetSizer(NULL, false);
851         m_expanded_panel->SetSizer(sizer);
852     }
853 
854     m_expanded_panel->Realize();
855     Refresh();
856     container->SetMinClientSize(size);
857     container->Show();
858     m_expanded_panel->SetFocus();
859 
860     return true;
861 }
862 
ShouldSendEventToDummy(wxEvent & evt)863 bool wxRibbonPanel::ShouldSendEventToDummy(wxEvent& evt)
864 {
865     // For an expanded panel, filter events between being sent up to the
866     // floating top level window or to the dummy panel sitting in the ribbon
867     // bar.
868 
869     // Child focus events should not be redirected, as the child would not be a
870     // child of the window the event is redirected to. All other command events
871     // seem to be suitable for redirecting.
872     return evt.IsCommandEvent() && evt.GetEventType() != wxEVT_CHILD_FOCUS;
873 }
874 
TryAfter(wxEvent & evt)875 bool wxRibbonPanel::TryAfter(wxEvent& evt)
876 {
877     if(m_expanded_dummy && ShouldSendEventToDummy(evt))
878     {
879         wxPropagateOnce propagateOnce(evt);
880         return m_expanded_dummy->GetEventHandler()->ProcessEvent(evt);
881     }
882     else
883     {
884         return wxRibbonControl::TryAfter(evt);
885     }
886 }
887 
IsAncestorOf(wxWindow * ancestor,wxWindow * window)888 static bool IsAncestorOf(wxWindow *ancestor, wxWindow *window)
889 {
890     while(window != NULL)
891     {
892         wxWindow *parent = window->GetParent();
893         if(parent == ancestor)
894             return true;
895         else
896             window = parent;
897     }
898     return false;
899 }
900 
OnKillFocus(wxFocusEvent & evt)901 void wxRibbonPanel::OnKillFocus(wxFocusEvent& evt)
902 {
903     if(m_expanded_dummy)
904     {
905         wxWindow *receiver = evt.GetWindow();
906         if(IsAncestorOf(this, receiver))
907         {
908             m_child_with_focus = receiver;
909             receiver->Connect(wxEVT_KILL_FOCUS,
910                 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus),
911                 NULL, this);
912         }
913         else if(receiver == NULL || receiver != m_expanded_dummy)
914         {
915             HideExpanded();
916         }
917     }
918 }
919 
OnChildKillFocus(wxFocusEvent & evt)920 void wxRibbonPanel::OnChildKillFocus(wxFocusEvent& evt)
921 {
922     if(m_child_with_focus == NULL)
923         return; // Should never happen, but a check can't hurt
924 
925     m_child_with_focus->Disconnect(wxEVT_KILL_FOCUS,
926       wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus), NULL, this);
927     m_child_with_focus = NULL;
928 
929     wxWindow *receiver = evt.GetWindow();
930     if(receiver == this || IsAncestorOf(this, receiver))
931     {
932         m_child_with_focus = receiver;
933         receiver->Connect(wxEVT_KILL_FOCUS,
934             wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus), NULL, this);
935         evt.Skip();
936     }
937     else if(receiver == NULL || receiver != m_expanded_dummy)
938     {
939         HideExpanded();
940         // Do not skip event, as the panel has been de-expanded, causing the
941         // child with focus to be reparented (and hidden). If the event
942         // continues propagation then bad things happen.
943     }
944     else
945     {
946         evt.Skip();
947     }
948 }
949 
HideExpanded()950 bool wxRibbonPanel::HideExpanded()
951 {
952     if(m_expanded_dummy == NULL)
953     {
954         if(m_expanded_panel)
955         {
956             return m_expanded_panel->HideExpanded();
957         }
958         else
959         {
960             return false;
961         }
962     }
963 
964     // Move children back to original panel
965     // NB: Children iterators not used as behaviour is not well defined
966     // when iterating over a container which is being emptied
967     while(!GetChildren().IsEmpty())
968     {
969         wxWindow *child = GetChildren().GetFirst()->GetData();
970         child->Reparent(m_expanded_dummy);
971         child->Hide();
972     }
973 
974     // Move sizer back
975     if(GetSizer())
976     {
977         wxSizer* sizer = GetSizer();
978         SetSizer(NULL, false);
979         m_expanded_dummy->SetSizer(sizer);
980     }
981 
982     m_expanded_dummy->m_expanded_panel = NULL;
983     m_expanded_dummy->Realize();
984     m_expanded_dummy->Refresh();
985     wxWindow *parent = GetParent();
986     Destroy();
987     parent->Destroy();
988 
989     return true;
990 }
991 
GetExpandedPosition(wxRect panel,wxSize expanded_size,wxDirection direction)992 wxRect wxRibbonPanel::GetExpandedPosition(wxRect panel,
993                                           wxSize expanded_size,
994                                           wxDirection direction)
995 {
996     // Strategy:
997     // 1) Determine primary position based on requested direction
998     // 2) Move the position so that it sits entirely within a display
999     //    (for single monitor systems, this moves it into the display region,
1000     //     but for multiple monitors, it does so without splitting it over
1001     //     more than one display)
1002     // 2.1) Move in the primary axis
1003     // 2.2) Move in the secondary axis
1004 
1005     wxPoint pos;
1006     bool primary_x = false;
1007     int secondary_x = 0;
1008     int secondary_y = 0;
1009     switch(direction)
1010     {
1011     case wxNORTH:
1012         pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
1013         pos.y = panel.GetY() - expanded_size.GetHeight();
1014         primary_x = true;
1015         secondary_y = 1;
1016         break;
1017     case wxEAST:
1018         pos.x = panel.GetRight();
1019         pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
1020         secondary_x = -1;
1021         break;
1022     case wxSOUTH:
1023         pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
1024         pos.y = panel.GetBottom();
1025         primary_x = true;
1026         secondary_y = -1;
1027         break;
1028     case wxWEST:
1029     default:
1030         pos.x = panel.GetX() - expanded_size.GetWidth();
1031         pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
1032         secondary_x = 1;
1033         break;
1034     }
1035     wxRect expanded(pos, expanded_size);
1036 
1037     wxRect best(expanded);
1038     int best_distance = INT_MAX;
1039 
1040     const unsigned display_n = wxDisplay::GetCount();
1041     unsigned display_i;
1042     for(display_i = 0; display_i < display_n; ++display_i)
1043     {
1044         wxRect display = wxDisplay(display_i).GetGeometry();
1045 
1046         if(display.Contains(expanded))
1047         {
1048             return expanded;
1049         }
1050         else if(display.Intersects(expanded))
1051         {
1052             wxRect new_rect(expanded);
1053             int distance = 0;
1054 
1055             if(primary_x)
1056             {
1057                 if(expanded.GetRight() > display.GetRight())
1058                 {
1059                     distance = expanded.GetRight() - display.GetRight();
1060                     new_rect.x -= distance;
1061                 }
1062                 else if(expanded.GetLeft() < display.GetLeft())
1063                 {
1064                     distance = display.GetLeft() - expanded.GetLeft();
1065                     new_rect.x += distance;
1066                 }
1067             }
1068             else
1069             {
1070                 if(expanded.GetBottom() > display.GetBottom())
1071                 {
1072                     distance = expanded.GetBottom() - display.GetBottom();
1073                     new_rect.y -= distance;
1074                 }
1075                 else if(expanded.GetTop() < display.GetTop())
1076                 {
1077                     distance = display.GetTop() - expanded.GetTop();
1078                     new_rect.y += distance;
1079                 }
1080             }
1081             if(!display.Contains(new_rect))
1082             {
1083                 // Tried moving in primary axis, but failed.
1084                 // Hence try moving in the secondary axis.
1085                 int dx = secondary_x * (panel.GetWidth() + expanded_size.GetWidth());
1086                 int dy = secondary_y * (panel.GetHeight() + expanded_size.GetHeight());
1087                 new_rect.x += dx;
1088                 new_rect.y += dy;
1089 
1090                 // Squaring makes secondary moves more expensive (and also
1091                 // prevents a negative cost)
1092                 distance += dx * dx + dy * dy;
1093             }
1094             if(display.Contains(new_rect) && distance < best_distance)
1095             {
1096                 best = new_rect;
1097                 best_distance = distance;
1098             }
1099         }
1100     }
1101 
1102     return best;
1103 }
1104 
HideIfExpanded()1105 void wxRibbonPanel::HideIfExpanded()
1106 {
1107     wxRibbonPage* const containingPage = wxDynamicCast(m_parent, wxRibbonPage);
1108     if (containingPage)
1109         containingPage->HideIfExpanded();
1110 }
1111 
1112 #endif // wxUSE_RIBBON
1113