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