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