Fixed size calculation
[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 = wxDefaultSize;// Unknown / none for IsFullySpecified()
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)); // check if would be at this size
238 if(minimised != m_minimised)
239 {
240 m_minimised = minimised;
241 // Note that for sizers, this routine disallows the use of mixed shown
242 // and hidden controls
243 // TODO ? use some list of user set invisible children to restore status.
244 for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
245 node;
246 node = node->GetNext())
247 {
248 node->GetData()->Show(!minimised);
249 }
250
251 Refresh();
252 }
253
254 wxRibbonControl::DoSetSize(x, y, width, height, sizeFlags);
255 }
256
257 // Checks if panel would be minimised at (client size) at_size
258 bool wxRibbonPanel::IsMinimised(wxSize at_size) const
259 {
260 if(GetSizer())
261 {
262 // we have no information on size change direction
263 // so check both
264 wxSize size = GetMinNotMinimisedSize();
265 if(size.x > at_size.x || size.y > at_size.y)
266 return true;
267
268 return false;
269 }
270
271 if(!m_minimised_size.IsFullySpecified())
272 return false;
273
274 return (at_size.GetX() <= m_minimised_size.GetX() &&
275 at_size.GetY() <= m_minimised_size.GetY()) ||
276 at_size.GetX() < m_smallest_unminimised_size.GetX() ||
277 at_size.GetY() < m_smallest_unminimised_size.GetY();
278 }
279
280 void wxRibbonPanel::OnEraseBackground(wxEraseEvent& WXUNUSED(evt))
281 {
282 // All painting done in main paint handler to minimise flicker
283 }
284
285 void wxRibbonPanel::OnPaint(wxPaintEvent& WXUNUSED(evt))
286 {
287 wxAutoBufferedPaintDC dc(this);
288
289 if(m_art != NULL)
290 {
291 if(IsMinimised())
292 {
293 m_art->DrawMinimisedPanel(dc, this, GetSize(), m_minimised_icon_resized);
294 }
295 else
296 {
297 m_art->DrawPanelBackground(dc, this, GetSize());
298 }
299 }
300 }
301
302 bool wxRibbonPanel::IsSizingContinuous() const
303 {
304 // A panel never sizes continuously, even if all of its children can,
305 // as it would appear out of place along side non-continuous panels.
306
307 // JS 2012-03-09: introducing wxRIBBON_PANEL_STRETCH to allow
308 // the panel to fill its parent page. For example we might have
309 // a list of styles in one of the pages, which should stretch to
310 // fill available space.
311 return (m_flags & wxRIBBON_PANEL_STRETCH) != 0;
312 }
313
314 // Finds the best width and height given the parent's width and height
315 wxSize wxRibbonPanel::GetBestSizeForParentSize(const wxSize& parentSize) const
316 {
317 if (GetChildren().GetCount() == 1)
318 {
319 wxWindow* win = GetChildren().GetFirst()->GetData();
320 wxRibbonControl* control = wxDynamicCast(win, wxRibbonControl);
321 if (control)
322 {
323 wxClientDC temp_dc((wxRibbonPanel*) this);
324 wxSize clientParentSize = m_art->GetPanelClientSize(temp_dc, this, parentSize, NULL);
325 wxSize childSize = control->GetBestSizeForParentSize(clientParentSize);
326 wxSize overallSize = m_art->GetPanelSize(temp_dc, this, childSize, NULL);
327 return overallSize;
328 }
329 }
330 return GetSize();
331 }
332
333 wxSize wxRibbonPanel::DoGetNextSmallerSize(wxOrientation direction,
334 wxSize relative_to) const
335 {
336 if(m_expanded_panel != NULL)
337 {
338 // Next size depends upon children, who are currently in the
339 // expanded panel
340 return m_expanded_panel->DoGetNextSmallerSize(direction, relative_to);
341 }
342
343 if(m_art != NULL)
344 {
345 wxClientDC dc((wxRibbonPanel*) this);
346 wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, NULL);
347 wxSize smaller(-1, -1);
348 bool minimise = false;
349
350 if(GetSizer())
351 {
352 // Get smallest non minimised size
353 smaller = GetMinSize();
354 // and adjust to child_relative for parent page
355 if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
356 {
357 minimise = (child_relative.y <= smaller.y);
358 if(smaller.x < child_relative.x)
359 smaller.x = child_relative.x;
360 }
361 else
362 {
363 minimise = (child_relative.x <= smaller.x);
364 if(smaller.y < child_relative.y)
365 smaller.y = child_relative.y;
366 }
367 }
368 else if(GetChildren().GetCount() == 1)
369 {
370 // Simple (and common) case of single ribbon child or Sizer
371 wxWindow* child = GetChildren().Item(0)->GetData();
372 wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
373 if(ribbon_child != NULL)
374 {
375 smaller = ribbon_child->GetNextSmallerSize(direction, child_relative);
376 minimise = (smaller == child_relative);
377 }
378 }
379
380 if(minimise)
381 {
382 if(CanAutoMinimise())
383 {
384 wxSize minimised = m_minimised_size;
385 switch(direction)
386 {
387 case wxHORIZONTAL:
388 minimised.SetHeight(relative_to.GetHeight());
389 break;
390 case wxVERTICAL:
391 minimised.SetWidth(relative_to.GetWidth());
392 break;
393 default:
394 break;
395 }
396 return minimised;
397 }
398 else
399 {
400 return relative_to;
401 }
402 }
403 else if(smaller.IsFullySpecified()) // Use fallback if !(sizer/child = 1)
404 {
405 return m_art->GetPanelSize(dc, this, smaller, NULL);
406 }
407 }
408
409 // Fallback: Decrease by 20% (or minimum size, whichever larger)
410 wxSize current(relative_to);
411 wxSize minimum(GetMinSize());
412 if(direction & wxHORIZONTAL)
413 {
414 current.x = (current.x * 4) / 5;
415 if(current.x < minimum.x)
416 {
417 current.x = minimum.x;
418 }
419 }
420 if(direction & wxVERTICAL)
421 {
422 current.y = (current.y * 4) / 5;
423 if(current.y < minimum.y)
424 {
425 current.y = minimum.y;
426 }
427 }
428 return current;
429 }
430
431 wxSize wxRibbonPanel::DoGetNextLargerSize(wxOrientation direction,
432 wxSize relative_to) const
433 {
434 if(m_expanded_panel != NULL)
435 {
436 // Next size depends upon children, who are currently in the
437 // expanded panel
438 return m_expanded_panel->DoGetNextLargerSize(direction, relative_to);
439 }
440
441 if(IsMinimised(relative_to))
442 {
443 wxSize current = relative_to;
444 wxSize min_size = GetMinNotMinimisedSize();
445 switch(direction)
446 {
447 case wxHORIZONTAL:
448 if(min_size.x > current.x && min_size.y == current.y)
449 return min_size;
450 break;
451 case wxVERTICAL:
452 if(min_size.x == current.x && min_size.y > current.y)
453 return min_size;
454 break;
455 case wxBOTH:
456 if(min_size.x > current.x && min_size.y > current.y)
457 return min_size;
458 break;
459 default:
460 break;
461 }
462 }
463
464 if(m_art != NULL)
465 {
466 wxClientDC dc((wxRibbonPanel*) this);
467 wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, NULL);
468 wxSize larger(-1, -1);
469
470 if(GetSizer())
471 {
472 // We could just let the sizer expand in flow direction but see comment
473 // in IsSizingContinuous()
474 larger = GetPanelSizerBestSize();
475 // and adjust for page in non flow direction
476 if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
477 {
478 if(larger.x != child_relative.x)
479 larger.x = child_relative.x;
480 }
481 else if(larger.y != child_relative.y)
482 {
483 larger.y = child_relative.y;
484 }
485 }
486 else if(GetChildren().GetCount() == 1)
487 {
488 // Simple (and common) case of single ribbon child
489 wxWindow* child = GetChildren().Item(0)->GetData();
490 wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
491 if(ribbon_child != NULL)
492 {
493 larger = ribbon_child->GetNextLargerSize(direction, child_relative);
494 }
495 }
496
497 if(larger.IsFullySpecified()) // Use fallback if !(sizer/child = 1)
498 {
499 if(larger == child_relative)
500 {
501 return relative_to;
502 }
503 else
504 {
505 return m_art->GetPanelSize(dc, this, larger, NULL);
506 }
507 }
508 }
509
510 // Fallback: Increase by 25% (equal to a prior or subsequent 20% decrease)
511 // Note that due to rounding errors, this increase may not exactly equal a
512 // matching decrease - an ideal solution would not have these errors, but
513 // avoiding them is non-trivial unless an increase is by 100% rather than
514 // a fractional amount. This would then be non-ideal as the resizes happen
515 // at very large intervals.
516 wxSize current(relative_to);
517 if(direction & wxHORIZONTAL)
518 {
519 current.x = (current.x * 5 + 3) / 4;
520 }
521 if(direction & wxVERTICAL)
522 {
523 current.y = (current.y * 5 + 3) / 4;
524 }
525 return current;
526 }
527
528 bool wxRibbonPanel::CanAutoMinimise() const
529 {
530 return (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0
531 && m_minimised_size.IsFullySpecified();
532 }
533
534 wxSize wxRibbonPanel::GetMinSize() const
535 {
536 if(m_expanded_panel != NULL)
537 {
538 // Minimum size depends upon children, who are currently in the
539 // expanded panel
540 return m_expanded_panel->GetMinSize();
541 }
542
543 if(CanAutoMinimise())
544 {
545 return m_minimised_size;
546 }
547 else
548 {
549 return GetMinNotMinimisedSize();
550 }
551 }
552
553 wxSize wxRibbonPanel::GetMinNotMinimisedSize() const
554 {
555 // Ask sizer if present
556 if(GetSizer())
557 {
558 wxClientDC dc((wxRibbonPanel*) this);
559 return m_art->GetPanelSize(dc, this, GetPanelSizerMinSize(), NULL);
560 }
561 else if(GetChildren().GetCount() == 1)
562 {
563 // Common case of single child taking up the entire panel
564 wxWindow* child = GetChildren().Item(0)->GetData();
565 wxClientDC dc((wxRibbonPanel*) this);
566 return m_art->GetPanelSize(dc, this, child->GetMinSize(), NULL);
567 }
568
569 return wxRibbonControl::GetMinSize();
570 }
571
572 wxSize wxRibbonPanel::GetPanelSizerMinSize() const
573 {
574 // Called from Realize() to set m_smallest_unminimised_size and from other
575 // functions to get the minimum size.
576 // The panel will be invisible when minimised and sizer calcs will be 0
577 // Uses m_smallest_unminimised_size in preference to GetSizer()->CalcMin()
578 // to eliminate flicker.
579
580 // Check if is visible and not previously calculated
581 if(IsShown() && !m_smallest_unminimised_size.IsFullySpecified())
582 {
583 return GetSizer()->CalcMin();
584 }
585 // else use previously calculated m_smallest_unminimised_size
586 wxClientDC dc((wxRibbonPanel*) this);
587 return m_art->GetPanelClientSize(dc,
588 this,
589 m_smallest_unminimised_size,
590 NULL);
591 }
592
593 wxSize wxRibbonPanel::GetPanelSizerBestSize() const
594 {
595 wxSize size = GetPanelSizerMinSize();
596 // TODO allow panel to increase its size beyond minimum size
597 // by steps similarly to ribbon control panels (preferred for aesthetics)
598 // or continuously.
599 return size;
600 }
601
602 wxSize wxRibbonPanel::DoGetBestSize() const
603 {
604 // Ask sizer if present
605 if( GetSizer())
606 {
607 wxClientDC dc((wxRibbonPanel*) this);
608 return m_art->GetPanelSize(dc, this, GetPanelSizerBestSize(), NULL);
609 }
610 else if(GetChildren().GetCount() == 1)
611 {
612 // Common case of no sizer and single child taking up the entire panel
613 wxWindow* child = GetChildren().Item(0)->GetData();
614 wxClientDC dc((wxRibbonPanel*) this);
615 return m_art->GetPanelSize(dc, this, child->GetBestSize(), NULL);
616 }
617
618 return wxRibbonControl::DoGetBestSize();
619 }
620
621 bool wxRibbonPanel::Realize()
622 {
623 bool status = true;
624
625 for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
626 node;
627 node = node->GetNext())
628 {
629 wxRibbonControl* child = wxDynamicCast(node->GetData(), wxRibbonControl);
630 if(child == NULL)
631 {
632 continue;
633 }
634 if(!child->Realize())
635 {
636 status = false;
637 }
638 }
639
640 wxSize minimum_children_size(0, 0);
641
642 // Ask sizer if there is one present
643 if(GetSizer())
644 {
645 minimum_children_size = GetPanelSizerMinSize();
646 }
647 else if(GetChildren().GetCount() == 1)
648 {
649 minimum_children_size = GetChildren().GetFirst()->GetData()->GetMinSize();
650 }
651
652 if(m_art != NULL)
653 {
654 wxClientDC temp_dc(this);
655
656 m_smallest_unminimised_size =
657 m_art->GetPanelSize(temp_dc, this, minimum_children_size, NULL);
658
659 wxSize bitmap_size;
660 wxSize panel_min_size = GetMinNotMinimisedSize();
661 m_minimised_size = m_art->GetMinimisedPanelMinimumSize(temp_dc, this,
662 &bitmap_size, &m_preferred_expand_direction);
663 if(m_minimised_icon.IsOk() && m_minimised_icon.GetSize() != bitmap_size)
664 {
665 wxImage img(m_minimised_icon.ConvertToImage());
666 img.Rescale(bitmap_size.GetWidth(), bitmap_size.GetHeight(), wxIMAGE_QUALITY_HIGH);
667 m_minimised_icon_resized = wxBitmap(img);
668 }
669 else
670 {
671 m_minimised_icon_resized = m_minimised_icon;
672 }
673 if(m_minimised_size.x > panel_min_size.x &&
674 m_minimised_size.y > panel_min_size.y)
675 {
676 // No point in having a minimised size which is larger than the
677 // minimum size which the children can go to.
678 m_minimised_size = wxSize(-1, -1);
679 }
680 else
681 {
682 if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
683 {
684 m_minimised_size.x = panel_min_size.x;
685 }
686 else
687 {
688 m_minimised_size.y = panel_min_size.y;
689 }
690 }
691 }
692 else
693 {
694 m_minimised_size = wxSize(-1, -1);
695 }
696
697 return Layout() && status;
698 }
699
700 bool wxRibbonPanel::Layout()
701 {
702 if(IsMinimised())
703 {
704 // Children are all invisible when minimised
705 return true;
706 }
707
708 // Get wxRibbonPanel client size
709 wxPoint position;
710 wxClientDC dc(this);
711 wxSize size = m_art->GetPanelClientSize(dc, this, GetSize(), &position);
712
713 // If there is a sizer, use it
714 if(GetSizer())
715 {
716 GetSizer()->SetDimension(position, size); // SetSize and Layout()
717 }
718 else if(GetChildren().GetCount() == 1)
719 {
720 // Common case of no sizer and single child taking up the entire panel
721 wxWindow* child = GetChildren().Item(0)->GetData();
722 child->SetSize(position.x, position.y, size.GetWidth(), size.GetHeight());
723 }
724 return true;
725 }
726
727 void wxRibbonPanel::OnMouseClick(wxMouseEvent& WXUNUSED(evt))
728 {
729 if(IsMinimised())
730 {
731 if(m_expanded_panel != NULL)
732 {
733 HideExpanded();
734 }
735 else
736 {
737 ShowExpanded();
738 }
739 }
740 }
741
742 wxRibbonPanel* wxRibbonPanel::GetExpandedDummy()
743 {
744 return m_expanded_dummy;
745 }
746
747 wxRibbonPanel* wxRibbonPanel::GetExpandedPanel()
748 {
749 return m_expanded_panel;
750 }
751
752 bool wxRibbonPanel::ShowExpanded()
753 {
754 if(!IsMinimised())
755 {
756 return false;
757 }
758 if(m_expanded_dummy != NULL || m_expanded_panel != NULL)
759 {
760 return false;
761 }
762
763 wxSize size = GetBestSize();
764
765 // Special case for flexible panel layout, where GetBestSize doesn't work
766 if (GetFlags() & wxRIBBON_PANEL_FLEXIBLE)
767 {
768 size = GetBestSizeForParentSize(wxSize(400, 1000));
769 }
770
771 wxPoint pos = GetExpandedPosition(wxRect(GetScreenPosition(), GetSize()),
772 size, m_preferred_expand_direction).GetTopLeft();
773
774 // Need a top-level frame to contain the expanded panel
775 wxFrame *container = new wxFrame(NULL, wxID_ANY, GetLabel(),
776 pos, size, wxFRAME_NO_TASKBAR | wxBORDER_NONE);
777
778 m_expanded_panel = new wxRibbonPanel(container, wxID_ANY,
779 GetLabel(), m_minimised_icon, wxPoint(0, 0), size, (m_flags /* & ~wxRIBBON_PANEL_FLEXIBLE */));
780
781 m_expanded_panel->SetArtProvider(m_art);
782 m_expanded_panel->m_expanded_dummy = this;
783
784 // Move all children to the new panel.
785 // Conceptually it might be simpler to reparent this entire panel to the
786 // container and create a new panel to sit in its place while expanded.
787 // This approach has a problem though - when the panel is reinserted into
788 // its original parent, it'll be at a different position in the child list
789 // and thus assume a new position.
790 // NB: Children iterators not used as behaviour is not well defined
791 // when iterating over a container which is being emptied
792 while(!GetChildren().IsEmpty())
793 {
794 wxWindow *child = GetChildren().GetFirst()->GetData();
795 child->Reparent(m_expanded_panel);
796 child->Show();
797 }
798
799 // Move sizer to new panel
800 if(GetSizer())
801 {
802 wxSizer* sizer = GetSizer();
803 SetSizer(NULL, false);
804 m_expanded_panel->SetSizer(sizer);
805 }
806
807 m_expanded_panel->Realize();
808 Refresh();
809 container->SetMinClientSize(size);
810 container->Show();
811 m_expanded_panel->SetFocus();
812
813 return true;
814 }
815
816 bool wxRibbonPanel::ShouldSendEventToDummy(wxEvent& evt)
817 {
818 // For an expanded panel, filter events between being sent up to the
819 // floating top level window or to the dummy panel sitting in the ribbon
820 // bar.
821
822 // Child focus events should not be redirected, as the child would not be a
823 // child of the window the event is redirected to. All other command events
824 // seem to be suitable for redirecting.
825 return evt.IsCommandEvent() && evt.GetEventType() != wxEVT_CHILD_FOCUS;
826 }
827
828 bool wxRibbonPanel::TryAfter(wxEvent& evt)
829 {
830 if(m_expanded_dummy && ShouldSendEventToDummy(evt))
831 {
832 wxPropagateOnce propagateOnce(evt);
833 return m_expanded_dummy->GetEventHandler()->ProcessEvent(evt);
834 }
835 else
836 {
837 return wxRibbonControl::TryAfter(evt);
838 }
839 }
840
841 static bool IsAncestorOf(wxWindow *ancestor, wxWindow *window)
842 {
843 while(window != NULL)
844 {
845 wxWindow *parent = window->GetParent();
846 if(parent == ancestor)
847 return true;
848 else
849 window = parent;
850 }
851 return false;
852 }
853
854 void wxRibbonPanel::OnKillFocus(wxFocusEvent& evt)
855 {
856 if(m_expanded_dummy)
857 {
858 wxWindow *receiver = evt.GetWindow();
859 if(IsAncestorOf(this, receiver))
860 {
861 m_child_with_focus = receiver;
862 receiver->Connect(wxEVT_KILL_FOCUS,
863 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus),
864 NULL, this);
865 }
866 else if(receiver == NULL || receiver != m_expanded_dummy)
867 {
868 HideExpanded();
869 }
870 }
871 }
872
873 void wxRibbonPanel::OnChildKillFocus(wxFocusEvent& evt)
874 {
875 if(m_child_with_focus == NULL)
876 return; // Should never happen, but a check can't hurt
877
878 m_child_with_focus->Disconnect(wxEVT_KILL_FOCUS,
879 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus), NULL, this);
880 m_child_with_focus = NULL;
881
882 wxWindow *receiver = evt.GetWindow();
883 if(receiver == this || IsAncestorOf(this, receiver))
884 {
885 m_child_with_focus = receiver;
886 receiver->Connect(wxEVT_KILL_FOCUS,
887 wxFocusEventHandler(wxRibbonPanel::OnChildKillFocus), NULL, this);
888 evt.Skip();
889 }
890 else if(receiver == NULL || receiver != m_expanded_dummy)
891 {
892 HideExpanded();
893 // Do not skip event, as the panel has been de-expanded, causing the
894 // child with focus to be reparented (and hidden). If the event
895 // continues propogation then bad things happen.
896 }
897 else
898 {
899 evt.Skip();
900 }
901 }
902
903 bool wxRibbonPanel::HideExpanded()
904 {
905 if(m_expanded_dummy == NULL)
906 {
907 if(m_expanded_panel)
908 {
909 return m_expanded_panel->HideExpanded();
910 }
911 else
912 {
913 return false;
914 }
915 }
916
917 // Move children back to original panel
918 // NB: Children iterators not used as behaviour is not well defined
919 // when iterating over a container which is being emptied
920 while(!GetChildren().IsEmpty())
921 {
922 wxWindow *child = GetChildren().GetFirst()->GetData();
923 child->Reparent(m_expanded_dummy);
924 child->Hide();
925 }
926
927 // Move sizer back
928 if(GetSizer())
929 {
930 wxSizer* sizer = GetSizer();
931 SetSizer(NULL, false);
932 m_expanded_dummy->SetSizer(sizer);
933 }
934
935 m_expanded_dummy->m_expanded_panel = NULL;
936 m_expanded_dummy->Realize();
937 m_expanded_dummy->Refresh();
938 wxWindow *parent = GetParent();
939 Destroy();
940 parent->Destroy();
941
942 return true;
943 }
944
945 wxRect wxRibbonPanel::GetExpandedPosition(wxRect panel,
946 wxSize expanded_size,
947 wxDirection direction)
948 {
949 // Strategy:
950 // 1) Determine primary position based on requested direction
951 // 2) Move the position so that it sits entirely within a display
952 // (for single monitor systems, this moves it into the display region,
953 // but for multiple monitors, it does so without splitting it over
954 // more than one display)
955 // 2.1) Move in the primary axis
956 // 2.2) Move in the secondary axis
957
958 wxPoint pos;
959 bool primary_x = false;
960 int secondary_x = 0;
961 int secondary_y = 0;
962 switch(direction)
963 {
964 case wxNORTH:
965 pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
966 pos.y = panel.GetY() - expanded_size.GetHeight();
967 primary_x = true;
968 secondary_y = 1;
969 break;
970 case wxEAST:
971 pos.x = panel.GetRight();
972 pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
973 secondary_x = -1;
974 break;
975 case wxSOUTH:
976 pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
977 pos.y = panel.GetBottom();
978 primary_x = true;
979 secondary_y = -1;
980 break;
981 case wxWEST:
982 default:
983 pos.x = panel.GetX() - expanded_size.GetWidth();
984 pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
985 secondary_x = 1;
986 break;
987 }
988 wxRect expanded(pos, expanded_size);
989
990 wxRect best(expanded);
991 int best_distance = INT_MAX;
992
993 const unsigned display_n = wxDisplay::GetCount();
994 unsigned display_i;
995 for(display_i = 0; display_i < display_n; ++display_i)
996 {
997 wxRect display = wxDisplay(display_i).GetGeometry();
998
999 if(display.Contains(expanded))
1000 {
1001 return expanded;
1002 }
1003 else if(display.Intersects(expanded))
1004 {
1005 wxRect new_rect(expanded);
1006 int distance = 0;
1007
1008 if(primary_x)
1009 {
1010 if(expanded.GetRight() > display.GetRight())
1011 {
1012 distance = expanded.GetRight() - display.GetRight();
1013 new_rect.x -= distance;
1014 }
1015 else if(expanded.GetLeft() < display.GetLeft())
1016 {
1017 distance = display.GetLeft() - expanded.GetLeft();
1018 new_rect.x += distance;
1019 }
1020 }
1021 else
1022 {
1023 if(expanded.GetBottom() > display.GetBottom())
1024 {
1025 distance = expanded.GetBottom() - display.GetBottom();
1026 new_rect.y -= distance;
1027 }
1028 else if(expanded.GetTop() < display.GetTop())
1029 {
1030 distance = display.GetTop() - expanded.GetTop();
1031 new_rect.y += distance;
1032 }
1033 }
1034 if(!display.Contains(new_rect))
1035 {
1036 // Tried moving in primary axis, but failed.
1037 // Hence try moving in the secondary axis.
1038 int dx = secondary_x * (panel.GetWidth() + expanded_size.GetWidth());
1039 int dy = secondary_y * (panel.GetHeight() + expanded_size.GetHeight());
1040 new_rect.x += dx;
1041 new_rect.y += dy;
1042
1043 // Squaring makes secondary moves more expensive (and also
1044 // prevents a negative cost)
1045 distance += dx * dx + dy * dy;
1046 }
1047 if(display.Contains(new_rect) && distance < best_distance)
1048 {
1049 best = new_rect;
1050 best_distance = distance;
1051 }
1052 }
1053 }
1054
1055 return best;
1056 }
1057
1058 #endif // wxUSE_RIBBON