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