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