Reset m_smallest_unminimised_size variable in Realize() before calling GetPanelSizerMinSize() to prevent the latter from using the old value of that variable, which could result in panel elements being cut off. Closes #18226.
1116 lines
32 KiB
C++
1116 lines
32 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/ribbon/panel.cpp
|
|
// Purpose: Ribbon-style container for a group of related tools / controls
|
|
// Author: Peter Cawley
|
|
// Modified by:
|
|
// Created: 2009-05-25
|
|
// Copyright: (C) Peter Cawley
|
|
// Licence: wxWindows licence
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if wxUSE_RIBBON
|
|
|
|
#include "wx/ribbon/panel.h"
|
|
#include "wx/ribbon/art.h"
|
|
#include "wx/ribbon/bar.h"
|
|
#include "wx/dcbuffer.h"
|
|
#include "wx/display.h"
|
|
#include "wx/sizer.h"
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/frame.h"
|
|
#endif
|
|
|
|
#ifdef __WXMSW__
|
|
#include "wx/msw/private.h"
|
|
#endif
|
|
|
|
wxDEFINE_EVENT(wxEVT_RIBBONPANEL_EXTBUTTON_ACTIVATED, wxRibbonPanelEvent);
|
|
|
|
wxIMPLEMENT_DYNAMIC_CLASS(wxRibbonPanelEvent, wxCommandEvent);
|
|
|
|
wxIMPLEMENT_CLASS(wxRibbonPanel, wxRibbonControl);
|
|
|
|
wxBEGIN_EVENT_TABLE(wxRibbonPanel, wxRibbonControl)
|
|
EVT_ENTER_WINDOW(wxRibbonPanel::OnMouseEnter)
|
|
EVT_ERASE_BACKGROUND(wxRibbonPanel::OnEraseBackground)
|
|
EVT_KILL_FOCUS(wxRibbonPanel::OnKillFocus)
|
|
EVT_LEAVE_WINDOW(wxRibbonPanel::OnMouseLeave)
|
|
EVT_MOTION(wxRibbonPanel::OnMotion)
|
|
EVT_LEFT_DOWN(wxRibbonPanel::OnMouseClick)
|
|
EVT_PAINT(wxRibbonPanel::OnPaint)
|
|
EVT_SIZE(wxRibbonPanel::OnSize)
|
|
wxEND_EVENT_TABLE()
|
|
|
|
wxRibbonPanel::wxRibbonPanel() : m_expanded_dummy(NULL), m_expanded_panel(NULL)
|
|
{
|
|
}
|
|
|
|
wxRibbonPanel::wxRibbonPanel(wxWindow* parent,
|
|
wxWindowID id,
|
|
const wxString& label,
|
|
const wxBitmap& minimised_icon,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style)
|
|
: wxRibbonControl(parent, id, pos, size, wxBORDER_NONE)
|
|
{
|
|
CommonInit(label, minimised_icon, style);
|
|
}
|
|
|
|
wxRibbonPanel::~wxRibbonPanel()
|
|
{
|
|
if(m_expanded_panel)
|
|
{
|
|
m_expanded_panel->m_expanded_dummy = NULL;
|
|
m_expanded_panel->GetParent()->Destroy();
|
|
}
|
|
}
|
|
|
|
bool wxRibbonPanel::Create(wxWindow* parent,
|
|
wxWindowID id,
|
|
const wxString& label,
|
|
const wxBitmap& icon,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style)
|
|
{
|
|
if(!wxRibbonControl::Create(parent, id, pos, size, wxBORDER_NONE))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CommonInit(label, icon, style);
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxRibbonPanel::SetArtProvider(wxRibbonArtProvider* art)
|
|
{
|
|
m_art = art;
|
|
for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
|
|
node;
|
|
node = node->GetNext() )
|
|
{
|
|
wxWindow* child = node->GetData();
|
|
wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
|
|
if(ribbon_child)
|
|
{
|
|
ribbon_child->SetArtProvider(art);
|
|
}
|
|
}
|
|
if(m_expanded_panel)
|
|
m_expanded_panel->SetArtProvider(art);
|
|
}
|
|
|
|
void wxRibbonPanel::CommonInit(const wxString& label, const wxBitmap& icon, long style)
|
|
{
|
|
SetName(label);
|
|
SetLabel(label);
|
|
|
|
m_minimised_size = wxDefaultSize; // Unknown / none
|
|
m_smallest_unminimised_size = wxDefaultSize;// Unknown / none for IsFullySpecified()
|
|
m_preferred_expand_direction = wxSOUTH;
|
|
m_expanded_dummy = NULL;
|
|
m_expanded_panel = NULL;
|
|
m_flags = style;
|
|
m_minimised_icon = icon;
|
|
m_minimised = false;
|
|
m_hovered = false;
|
|
m_ext_button_hovered = false;
|
|
|
|
if(m_art == NULL)
|
|
{
|
|
wxRibbonControl* parent = wxDynamicCast(GetParent(), wxRibbonControl);
|
|
if(parent != NULL)
|
|
{
|
|
m_art = parent->GetArtProvider();
|
|
}
|
|
}
|
|
|
|
SetAutoLayout(true);
|
|
SetBackgroundStyle(wxBG_STYLE_CUSTOM);
|
|
SetMinSize(wxSize(20, 20));
|
|
}
|
|
|
|
bool wxRibbonPanel::IsMinimised() const
|
|
{
|
|
return m_minimised;
|
|
}
|
|
|
|
bool wxRibbonPanel::IsHovered() const
|
|
{
|
|
return m_hovered;
|
|
}
|
|
|
|
bool wxRibbonPanel::IsExtButtonHovered() const
|
|
{
|
|
return m_ext_button_hovered;
|
|
}
|
|
|
|
void wxRibbonPanel::OnMouseEnter(wxMouseEvent& evt)
|
|
{
|
|
TestPositionForHover(evt.GetPosition());
|
|
}
|
|
|
|
void wxRibbonPanel::OnMouseEnterChild(wxMouseEvent& evt)
|
|
{
|
|
wxPoint pos = evt.GetPosition();
|
|
wxWindow *child = wxDynamicCast(evt.GetEventObject(), wxWindow);
|
|
if(child)
|
|
{
|
|
pos += child->GetPosition();
|
|
TestPositionForHover(pos);
|
|
}
|
|
evt.Skip();
|
|
}
|
|
|
|
void wxRibbonPanel::OnMouseLeave(wxMouseEvent& evt)
|
|
{
|
|
TestPositionForHover(evt.GetPosition());
|
|
}
|
|
|
|
void wxRibbonPanel::OnMouseLeaveChild(wxMouseEvent& evt)
|
|
{
|
|
wxPoint pos = evt.GetPosition();
|
|
wxWindow *child = wxDynamicCast(evt.GetEventObject(), wxWindow);
|
|
if(child)
|
|
{
|
|
pos += child->GetPosition();
|
|
TestPositionForHover(pos);
|
|
}
|
|
evt.Skip();
|
|
}
|
|
|
|
void wxRibbonPanel::OnMotion(wxMouseEvent& evt)
|
|
{
|
|
TestPositionForHover(evt.GetPosition());
|
|
}
|
|
|
|
void wxRibbonPanel::TestPositionForHover(const wxPoint& pos)
|
|
{
|
|
bool hovered = false, ext_button_hovered = false;
|
|
if(pos.x >= 0 && pos.y >= 0)
|
|
{
|
|
wxSize size = GetSize();
|
|
if(pos.x < size.GetWidth() && pos.y < size.GetHeight())
|
|
{
|
|
hovered = true;
|
|
}
|
|
}
|
|
if(hovered)
|
|
{
|
|
if(HasExtButton())
|
|
ext_button_hovered = m_ext_button_rect.Contains(pos);
|
|
else
|
|
ext_button_hovered = false;
|
|
}
|
|
if(hovered != m_hovered || ext_button_hovered != m_ext_button_hovered)
|
|
{
|
|
m_hovered = hovered;
|
|
m_ext_button_hovered = ext_button_hovered;
|
|
Refresh(false);
|
|
}
|
|
}
|
|
|
|
void wxRibbonPanel::AddChild(wxWindowBase *child)
|
|
{
|
|
wxRibbonControl::AddChild(child);
|
|
|
|
// Window enter / leave events count for only the window in question, not
|
|
// for children of the window. The panel wants to be in the hovered state
|
|
// whenever the mouse cursor is within its boundary, so the events need to
|
|
// be attached to children too.
|
|
child->Bind(wxEVT_ENTER_WINDOW, &wxRibbonPanel::OnMouseEnterChild, this);
|
|
child->Bind(wxEVT_LEAVE_WINDOW, &wxRibbonPanel::OnMouseLeaveChild, this);
|
|
}
|
|
|
|
void wxRibbonPanel::RemoveChild(wxWindowBase *child)
|
|
{
|
|
child->Unbind(wxEVT_ENTER_WINDOW, &wxRibbonPanel::OnMouseEnterChild, this);
|
|
child->Unbind(wxEVT_LEAVE_WINDOW, &wxRibbonPanel::OnMouseLeaveChild, this);
|
|
|
|
wxRibbonControl::RemoveChild(child);
|
|
}
|
|
|
|
bool wxRibbonPanel::HasExtButton()const
|
|
{
|
|
wxRibbonBar* bar = GetAncestorRibbonBar();
|
|
if(bar==NULL)
|
|
return false;
|
|
return (m_flags & wxRIBBON_PANEL_EXT_BUTTON) &&
|
|
(bar->GetWindowStyleFlag() & wxRIBBON_BAR_SHOW_PANEL_EXT_BUTTONS);
|
|
}
|
|
|
|
void wxRibbonPanel::OnSize(wxSizeEvent& evt)
|
|
{
|
|
if(GetAutoLayout())
|
|
Layout();
|
|
|
|
evt.Skip();
|
|
}
|
|
|
|
void wxRibbonPanel::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
|
{
|
|
// At least on MSW, changing the size of a window will cause GetSize() to
|
|
// report the new size, but a size event may not be handled immediately.
|
|
// If this minimised check was performed in the OnSize handler, then
|
|
// GetSize() could return a size much larger than the minimised size while
|
|
// IsMinimised() returns true. This would then affect layout, as the panel
|
|
// will refuse to grow any larger while in limbo between minimised and non.
|
|
|
|
bool minimised = (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0 &&
|
|
IsMinimised(wxSize(width, height)); // check if would be at this size
|
|
if(minimised != m_minimised)
|
|
{
|
|
m_minimised = minimised;
|
|
// Note that for sizers, this routine disallows the use of mixed shown
|
|
// and hidden controls
|
|
// TODO ? use some list of user set invisible children to restore status.
|
|
for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
|
|
node;
|
|
node = node->GetNext())
|
|
{
|
|
node->GetData()->Show(!minimised);
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
wxRibbonControl::DoSetSize(x, y, width, height, sizeFlags);
|
|
}
|
|
|
|
// Checks if panel would be minimised at (client size) at_size
|
|
bool wxRibbonPanel::IsMinimised(wxSize at_size) const
|
|
{
|
|
if(GetSizer())
|
|
{
|
|
// we have no information on size change direction
|
|
// so check both
|
|
wxSize size = GetMinNotMinimisedSize();
|
|
if(size.x > at_size.x || size.y > at_size.y)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if(!m_minimised_size.IsFullySpecified())
|
|
return false;
|
|
|
|
return (at_size.GetX() < m_minimised_size.GetX() &&
|
|
at_size.GetY() < m_minimised_size.GetY()) ||
|
|
at_size.GetX() < m_smallest_unminimised_size.GetX() ||
|
|
at_size.GetY() < m_smallest_unminimised_size.GetY();
|
|
}
|
|
|
|
void wxRibbonPanel::OnEraseBackground(wxEraseEvent& WXUNUSED(evt))
|
|
{
|
|
// All painting done in main paint handler to minimise flicker
|
|
}
|
|
|
|
void wxRibbonPanel::OnPaint(wxPaintEvent& WXUNUSED(evt))
|
|
{
|
|
wxAutoBufferedPaintDC dc(this);
|
|
|
|
if(m_art != NULL)
|
|
{
|
|
if(IsMinimised())
|
|
{
|
|
m_art->DrawMinimisedPanel(dc, this, GetSize(), m_minimised_icon_resized);
|
|
}
|
|
else
|
|
{
|
|
m_art->DrawPanelBackground(dc, this, GetSize());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool wxRibbonPanel::IsSizingContinuous() const
|
|
{
|
|
// A panel never sizes continuously, even if all of its children can,
|
|
// as it would appear out of place along side non-continuous panels.
|
|
|
|
// JS 2012-03-09: introducing wxRIBBON_PANEL_STRETCH to allow
|
|
// the panel to fill its parent page. For example we might have
|
|
// a list of styles in one of the pages, which should stretch to
|
|
// fill available space.
|
|
return (m_flags & wxRIBBON_PANEL_STRETCH) != 0;
|
|
}
|
|
|
|
// Finds the best width and height given the parent's width and height
|
|
wxSize wxRibbonPanel::GetBestSizeForParentSize(const wxSize& parentSize) const
|
|
{
|
|
if (GetChildren().GetCount() == 1)
|
|
{
|
|
wxWindow* win = GetChildren().GetFirst()->GetData();
|
|
wxRibbonControl* control = wxDynamicCast(win, wxRibbonControl);
|
|
if (control)
|
|
{
|
|
wxClientDC temp_dc((wxRibbonPanel*) this);
|
|
wxSize clientParentSize = m_art->GetPanelClientSize(temp_dc, this, parentSize, NULL);
|
|
wxSize childSize = control->GetBestSizeForParentSize(clientParentSize);
|
|
wxSize overallSize = m_art->GetPanelSize(temp_dc, this, childSize, NULL);
|
|
return overallSize;
|
|
}
|
|
}
|
|
return GetSize();
|
|
}
|
|
|
|
wxSize wxRibbonPanel::DoGetNextSmallerSize(wxOrientation direction,
|
|
wxSize relative_to) const
|
|
{
|
|
if(m_expanded_panel != NULL)
|
|
{
|
|
// Next size depends upon children, who are currently in the
|
|
// expanded panel
|
|
return m_expanded_panel->DoGetNextSmallerSize(direction, relative_to);
|
|
}
|
|
|
|
if(m_art != NULL)
|
|
{
|
|
wxClientDC dc((wxRibbonPanel*) this);
|
|
wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, NULL);
|
|
wxSize smaller(-1, -1);
|
|
bool minimise = false;
|
|
|
|
if(GetSizer())
|
|
{
|
|
// Get smallest non minimised size
|
|
smaller = GetMinSize();
|
|
// and adjust to child_relative for parent page
|
|
if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
|
|
{
|
|
minimise = (child_relative.y <= smaller.y);
|
|
if(smaller.x < child_relative.x)
|
|
smaller.x = child_relative.x;
|
|
}
|
|
else
|
|
{
|
|
minimise = (child_relative.x <= smaller.x);
|
|
if(smaller.y < child_relative.y)
|
|
smaller.y = child_relative.y;
|
|
}
|
|
}
|
|
else if(GetChildren().GetCount() == 1)
|
|
{
|
|
// Simple (and common) case of single ribbon child or Sizer
|
|
wxWindow* child = GetChildren().Item(0)->GetData();
|
|
wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
|
|
if(ribbon_child != NULL)
|
|
{
|
|
smaller = ribbon_child->GetNextSmallerSize(direction, child_relative);
|
|
minimise = (smaller == child_relative);
|
|
}
|
|
}
|
|
|
|
if(minimise)
|
|
{
|
|
if(CanAutoMinimise())
|
|
{
|
|
wxSize minimised = m_minimised_size;
|
|
switch(direction)
|
|
{
|
|
case wxHORIZONTAL:
|
|
minimised.SetHeight(relative_to.GetHeight());
|
|
break;
|
|
case wxVERTICAL:
|
|
minimised.SetWidth(relative_to.GetWidth());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return minimised;
|
|
}
|
|
else
|
|
{
|
|
return relative_to;
|
|
}
|
|
}
|
|
else if(smaller.IsFullySpecified()) // Use fallback if !(sizer/child = 1)
|
|
{
|
|
return m_art->GetPanelSize(dc, this, smaller, NULL);
|
|
}
|
|
}
|
|
|
|
// Fallback: Decrease by 20% (or minimum size, whichever larger)
|
|
wxSize current(relative_to);
|
|
wxSize minimum(GetMinSize());
|
|
if(direction & wxHORIZONTAL)
|
|
{
|
|
current.x = (current.x * 4) / 5;
|
|
if(current.x < minimum.x)
|
|
{
|
|
current.x = minimum.x;
|
|
}
|
|
}
|
|
if(direction & wxVERTICAL)
|
|
{
|
|
current.y = (current.y * 4) / 5;
|
|
if(current.y < minimum.y)
|
|
{
|
|
current.y = minimum.y;
|
|
}
|
|
}
|
|
return current;
|
|
}
|
|
|
|
wxSize wxRibbonPanel::DoGetNextLargerSize(wxOrientation direction,
|
|
wxSize relative_to) const
|
|
{
|
|
if(m_expanded_panel != NULL)
|
|
{
|
|
// Next size depends upon children, who are currently in the
|
|
// expanded panel
|
|
return m_expanded_panel->DoGetNextLargerSize(direction, relative_to);
|
|
}
|
|
|
|
if(IsMinimised(relative_to))
|
|
{
|
|
wxSize current = relative_to;
|
|
wxSize min_size = GetMinNotMinimisedSize();
|
|
switch(direction)
|
|
{
|
|
case wxHORIZONTAL:
|
|
if(min_size.x > current.x && min_size.y == current.y)
|
|
return min_size;
|
|
break;
|
|
case wxVERTICAL:
|
|
if(min_size.x == current.x && min_size.y > current.y)
|
|
return min_size;
|
|
break;
|
|
case wxBOTH:
|
|
if(min_size.x > current.x && min_size.y > current.y)
|
|
return min_size;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(m_art != NULL)
|
|
{
|
|
wxClientDC dc((wxRibbonPanel*) this);
|
|
wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, NULL);
|
|
wxSize larger(-1, -1);
|
|
|
|
if(GetSizer())
|
|
{
|
|
// We could just let the sizer expand in flow direction but see comment
|
|
// in IsSizingContinuous()
|
|
larger = GetPanelSizerBestSize();
|
|
// and adjust for page in non flow direction
|
|
if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
|
|
{
|
|
if(larger.x != child_relative.x)
|
|
larger.x = child_relative.x;
|
|
}
|
|
else if(larger.y != child_relative.y)
|
|
{
|
|
larger.y = child_relative.y;
|
|
}
|
|
}
|
|
else if(GetChildren().GetCount() == 1)
|
|
{
|
|
// Simple (and common) case of single ribbon child
|
|
wxWindow* child = GetChildren().Item(0)->GetData();
|
|
wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
|
|
if(ribbon_child != NULL)
|
|
{
|
|
larger = ribbon_child->GetNextLargerSize(direction, child_relative);
|
|
}
|
|
}
|
|
|
|
if(larger.IsFullySpecified()) // Use fallback if !(sizer/child = 1)
|
|
{
|
|
if(larger == child_relative)
|
|
{
|
|
return relative_to;
|
|
}
|
|
else
|
|
{
|
|
return m_art->GetPanelSize(dc, this, larger, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: Increase by 25% (equal to a prior or subsequent 20% decrease)
|
|
// Note that due to rounding errors, this increase may not exactly equal a
|
|
// matching decrease - an ideal solution would not have these errors, but
|
|
// avoiding them is non-trivial unless an increase is by 100% rather than
|
|
// a fractional amount. This would then be non-ideal as the resizes happen
|
|
// at very large intervals.
|
|
wxSize current(relative_to);
|
|
if(direction & wxHORIZONTAL)
|
|
{
|
|
current.x = (current.x * 5 + 3) / 4;
|
|
}
|
|
if(direction & wxVERTICAL)
|
|
{
|
|
current.y = (current.y * 5 + 3) / 4;
|
|
}
|
|
return current;
|
|
}
|
|
|
|
bool wxRibbonPanel::CanAutoMinimise() const
|
|
{
|
|
return (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0
|
|
&& m_minimised_size.IsFullySpecified();
|
|
}
|
|
|
|
wxSize wxRibbonPanel::GetMinSize() const
|
|
{
|
|
if(m_expanded_panel != NULL)
|
|
{
|
|
// Minimum size depends upon children, who are currently in the
|
|
// expanded panel
|
|
return m_expanded_panel->GetMinSize();
|
|
}
|
|
|
|
if(CanAutoMinimise())
|
|
{
|
|
return m_minimised_size;
|
|
}
|
|
else
|
|
{
|
|
return GetMinNotMinimisedSize();
|
|
}
|
|
}
|
|
|
|
wxSize wxRibbonPanel::GetMinNotMinimisedSize() const
|
|
{
|
|
// Ask sizer if present
|
|
if(GetSizer())
|
|
{
|
|
wxClientDC dc((wxRibbonPanel*) this);
|
|
return m_art->GetPanelSize(dc, this, GetPanelSizerMinSize(), NULL);
|
|
}
|
|
else if(GetChildren().GetCount() == 1)
|
|
{
|
|
// Common case of single child taking up the entire panel
|
|
wxWindow* child = GetChildren().Item(0)->GetData();
|
|
wxClientDC dc((wxRibbonPanel*) this);
|
|
return m_art->GetPanelSize(dc, this, child->GetMinSize(), NULL);
|
|
}
|
|
|
|
return wxRibbonControl::GetMinSize();
|
|
}
|
|
|
|
wxSize wxRibbonPanel::GetPanelSizerMinSize() const
|
|
{
|
|
// Called from Realize() to set m_smallest_unminimised_size and from other
|
|
// functions to get the minimum size.
|
|
// The panel will be invisible when minimised and sizer calcs will be 0
|
|
// Uses m_smallest_unminimised_size in preference to GetSizer()->CalcMin()
|
|
// to eliminate flicker.
|
|
|
|
// Check if is visible and not previously calculated
|
|
if(IsShown() && !m_smallest_unminimised_size.IsFullySpecified())
|
|
{
|
|
return GetSizer()->CalcMin();
|
|
}
|
|
// else use previously calculated m_smallest_unminimised_size
|
|
wxClientDC dc((wxRibbonPanel*) this);
|
|
return m_art->GetPanelClientSize(dc,
|
|
this,
|
|
m_smallest_unminimised_size,
|
|
NULL);
|
|
}
|
|
|
|
wxSize wxRibbonPanel::GetPanelSizerBestSize() const
|
|
{
|
|
wxSize size = GetPanelSizerMinSize();
|
|
// TODO allow panel to increase its size beyond minimum size
|
|
// by steps similarly to ribbon control panels (preferred for aesthetics)
|
|
// or continuously.
|
|
return size;
|
|
}
|
|
|
|
wxSize wxRibbonPanel::DoGetBestSize() const
|
|
{
|
|
// Ask sizer if present
|
|
if( GetSizer())
|
|
{
|
|
wxClientDC dc((wxRibbonPanel*) this);
|
|
return m_art->GetPanelSize(dc, this, GetPanelSizerBestSize(), NULL);
|
|
}
|
|
else if(GetChildren().GetCount() == 1)
|
|
{
|
|
// Common case of no sizer and single child taking up the entire panel
|
|
wxWindow* child = GetChildren().Item(0)->GetData();
|
|
wxClientDC dc((wxRibbonPanel*) this);
|
|
return m_art->GetPanelSize(dc, this, child->GetBestSize(), NULL);
|
|
}
|
|
|
|
return wxRibbonControl::DoGetBestSize();
|
|
}
|
|
|
|
bool wxRibbonPanel::Realize()
|
|
{
|
|
bool status = true;
|
|
|
|
for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
|
|
node;
|
|
node = node->GetNext())
|
|
{
|
|
wxRibbonControl* child = wxDynamicCast(node->GetData(), wxRibbonControl);
|
|
if(child == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
if(!child->Realize())
|
|
{
|
|
status = false;
|
|
}
|
|
}
|
|
|
|
wxSize minimum_children_size(0, 0);
|
|
|
|
// Reset it before calling GetPanelSizerMinSize() below as it shouldn't use
|
|
// the old value, if we had any.
|
|
m_smallest_unminimised_size = wxDefaultSize;
|
|
|
|
// Ask sizer if there is one present
|
|
if(GetSizer())
|
|
{
|
|
minimum_children_size = GetPanelSizerMinSize();
|
|
}
|
|
else if(GetChildren().GetCount() == 1)
|
|
{
|
|
minimum_children_size = GetChildren().GetFirst()->GetData()->GetMinSize();
|
|
}
|
|
|
|
if(m_art != NULL)
|
|
{
|
|
wxClientDC temp_dc(this);
|
|
|
|
m_smallest_unminimised_size =
|
|
m_art->GetPanelSize(temp_dc, this, minimum_children_size, NULL);
|
|
|
|
wxSize bitmap_size;
|
|
wxSize panel_min_size = GetMinNotMinimisedSize();
|
|
m_minimised_size = m_art->GetMinimisedPanelMinimumSize(temp_dc, this,
|
|
&bitmap_size, &m_preferred_expand_direction);
|
|
if(m_minimised_icon.IsOk() && m_minimised_icon.GetScaledSize() != bitmap_size)
|
|
{
|
|
double scale = m_minimised_icon.GetScaleFactor();
|
|
if (scale > 1.0)
|
|
scale = 2.0;
|
|
|
|
wxImage img(m_minimised_icon.ConvertToImage());
|
|
img.Rescale(scale * bitmap_size.GetWidth(), scale * bitmap_size.GetHeight(), wxIMAGE_QUALITY_HIGH);
|
|
m_minimised_icon_resized = wxBitmap(img, -1, scale);
|
|
}
|
|
else
|
|
{
|
|
m_minimised_icon_resized = m_minimised_icon;
|
|
}
|
|
if(m_minimised_size.x > panel_min_size.x &&
|
|
m_minimised_size.y > panel_min_size.y)
|
|
{
|
|
// No point in having a minimised size which is larger than the
|
|
// minimum size which the children can go to.
|
|
m_minimised_size = wxSize(-1, -1);
|
|
}
|
|
else
|
|
{
|
|
if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
|
|
{
|
|
m_minimised_size.x = panel_min_size.x;
|
|
}
|
|
else
|
|
{
|
|
m_minimised_size.y = panel_min_size.y;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_minimised_size = wxSize(-1, -1);
|
|
}
|
|
|
|
return Layout() && status;
|
|
}
|
|
|
|
bool wxRibbonPanel::Layout()
|
|
{
|
|
if(IsMinimised())
|
|
{
|
|
// Children are all invisible when minimised
|
|
return true;
|
|
}
|
|
|
|
// Get wxRibbonPanel client size
|
|
wxPoint position;
|
|
wxClientDC dc(this);
|
|
wxSize size = m_art->GetPanelClientSize(dc, this, GetSize(), &position);
|
|
|
|
// If there is a sizer, use it
|
|
if(GetSizer())
|
|
{
|
|
GetSizer()->SetDimension(position, size); // SetSize and Layout()
|
|
}
|
|
else if(GetChildren().GetCount() == 1)
|
|
{
|
|
// Common case of no sizer and single child taking up the entire panel
|
|
wxWindow* child = GetChildren().Item(0)->GetData();
|
|
child->SetSize(position.x, position.y, size.GetWidth(), size.GetHeight());
|
|
}
|
|
|
|
if(HasExtButton())
|
|
m_ext_button_rect = m_art->GetPanelExtButtonArea(dc, this, GetSize());
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxRibbonPanel::OnMouseClick(wxMouseEvent& WXUNUSED(evt))
|
|
{
|
|
if(IsMinimised())
|
|
{
|
|
if(m_expanded_panel != NULL)
|
|
{
|
|
HideExpanded();
|
|
}
|
|
else
|
|
{
|
|
ShowExpanded();
|
|
}
|
|
}
|
|
else if(IsExtButtonHovered())
|
|
{
|
|
wxRibbonPanelEvent notification(wxEVT_RIBBONPANEL_EXTBUTTON_ACTIVATED, GetId());
|
|
notification.SetEventObject(this);
|
|
notification.SetPanel(this);
|
|
ProcessEvent(notification);
|
|
}
|
|
}
|
|
|
|
wxRibbonPanel* wxRibbonPanel::GetExpandedDummy()
|
|
{
|
|
return m_expanded_dummy;
|
|
}
|
|
|
|
wxRibbonPanel* wxRibbonPanel::GetExpandedPanel()
|
|
{
|
|
return m_expanded_panel;
|
|
}
|
|
|
|
bool wxRibbonPanel::ShowExpanded()
|
|
{
|
|
if(!IsMinimised())
|
|
{
|
|
return false;
|
|
}
|
|
if(m_expanded_dummy != NULL || m_expanded_panel != NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
wxSize size = GetBestSize();
|
|
|
|
// Special case for flexible panel layout, where GetBestSize doesn't work
|
|
if (GetFlags() & wxRIBBON_PANEL_FLEXIBLE)
|
|
{
|
|
size = GetBestSizeForParentSize(wxSize(400, 1000));
|
|
}
|
|
|
|
wxPoint pos = GetExpandedPosition(wxRect(GetScreenPosition(), GetSize()),
|
|
size, m_preferred_expand_direction).GetTopLeft();
|
|
|
|
// Need a top-level frame to contain the expanded panel
|
|
wxFrame *container = new wxFrame(NULL, wxID_ANY, GetLabel(),
|
|
pos, size, wxFRAME_NO_TASKBAR | wxBORDER_NONE);
|
|
|
|
m_expanded_panel = new wxRibbonPanel(container, wxID_ANY,
|
|
GetLabel(), m_minimised_icon, wxPoint(0, 0), size, (m_flags /* & ~wxRIBBON_PANEL_FLEXIBLE */));
|
|
|
|
m_expanded_panel->SetArtProvider(m_art);
|
|
m_expanded_panel->m_expanded_dummy = this;
|
|
|
|
// Move all children to the new panel.
|
|
// Conceptually it might be simpler to reparent this entire panel to the
|
|
// container and create a new panel to sit in its place while expanded.
|
|
// This approach has a problem though - when the panel is reinserted into
|
|
// its original parent, it'll be at a different position in the child list
|
|
// and thus assume a new position.
|
|
// NB: Children iterators not used as behaviour is not well defined
|
|
// when iterating over a container which is being emptied
|
|
while(!GetChildren().IsEmpty())
|
|
{
|
|
wxWindow *child = GetChildren().GetFirst()->GetData();
|
|
child->Reparent(m_expanded_panel);
|
|
child->Show();
|
|
}
|
|
|
|
// Move sizer to new panel
|
|
if(GetSizer())
|
|
{
|
|
wxSizer* sizer = GetSizer();
|
|
SetSizer(NULL, false);
|
|
m_expanded_panel->SetSizer(sizer);
|
|
}
|
|
|
|
m_expanded_panel->Realize();
|
|
Refresh();
|
|
container->SetMinClientSize(size);
|
|
container->Show();
|
|
m_expanded_panel->SetFocus();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxRibbonPanel::ShouldSendEventToDummy(wxEvent& evt)
|
|
{
|
|
// For an expanded panel, filter events between being sent up to the
|
|
// floating top level window or to the dummy panel sitting in the ribbon
|
|
// bar.
|
|
|
|
// Child focus events should not be redirected, as the child would not be a
|
|
// child of the window the event is redirected to. All other command events
|
|
// seem to be suitable for redirecting.
|
|
return evt.IsCommandEvent() && evt.GetEventType() != wxEVT_CHILD_FOCUS;
|
|
}
|
|
|
|
bool wxRibbonPanel::TryAfter(wxEvent& evt)
|
|
{
|
|
if(m_expanded_dummy && ShouldSendEventToDummy(evt))
|
|
{
|
|
wxPropagateOnce propagateOnce(evt);
|
|
return m_expanded_dummy->GetEventHandler()->ProcessEvent(evt);
|
|
}
|
|
else
|
|
{
|
|
return wxRibbonControl::TryAfter(evt);
|
|
}
|
|
}
|
|
|
|
static bool IsAncestorOf(wxWindow *ancestor, wxWindow *window)
|
|
{
|
|
while(window != NULL)
|
|
{
|
|
wxWindow *parent = window->GetParent();
|
|
if(parent == ancestor)
|
|
return true;
|
|
else
|
|
window = parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void wxRibbonPanel::OnKillFocus(wxFocusEvent& evt)
|
|
{
|
|
if(m_expanded_dummy)
|
|
{
|
|
wxWindow *receiver = evt.GetWindow();
|
|
if(IsAncestorOf(this, receiver))
|
|
{
|
|
m_child_with_focus = receiver;
|
|
receiver->Bind(wxEVT_KILL_FOCUS,
|
|
&wxRibbonPanel::OnChildKillFocus, this);
|
|
}
|
|
else if(receiver == NULL || receiver != m_expanded_dummy)
|
|
{
|
|
HideExpanded();
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxRibbonPanel::OnChildKillFocus(wxFocusEvent& evt)
|
|
{
|
|
if(m_child_with_focus == NULL)
|
|
return; // Should never happen, but a check can't hurt
|
|
|
|
m_child_with_focus->Unbind(wxEVT_KILL_FOCUS,
|
|
&wxRibbonPanel::OnChildKillFocus, this);
|
|
m_child_with_focus = NULL;
|
|
|
|
wxWindow *receiver = evt.GetWindow();
|
|
if(receiver == this || IsAncestorOf(this, receiver))
|
|
{
|
|
m_child_with_focus = receiver;
|
|
receiver->Bind(wxEVT_KILL_FOCUS,
|
|
&wxRibbonPanel::OnChildKillFocus, this);
|
|
evt.Skip();
|
|
}
|
|
else if(receiver == NULL || receiver != m_expanded_dummy)
|
|
{
|
|
HideExpanded();
|
|
// Do not skip event, as the panel has been de-expanded, causing the
|
|
// child with focus to be reparented (and hidden). If the event
|
|
// continues propagation then bad things happen.
|
|
}
|
|
else
|
|
{
|
|
evt.Skip();
|
|
}
|
|
}
|
|
|
|
bool wxRibbonPanel::HideExpanded()
|
|
{
|
|
if(m_expanded_dummy == NULL)
|
|
{
|
|
if(m_expanded_panel)
|
|
{
|
|
return m_expanded_panel->HideExpanded();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Move children back to original panel
|
|
// NB: Children iterators not used as behaviour is not well defined
|
|
// when iterating over a container which is being emptied
|
|
while(!GetChildren().IsEmpty())
|
|
{
|
|
wxWindow *child = GetChildren().GetFirst()->GetData();
|
|
child->Reparent(m_expanded_dummy);
|
|
child->Hide();
|
|
}
|
|
|
|
// Move sizer back
|
|
if(GetSizer())
|
|
{
|
|
wxSizer* sizer = GetSizer();
|
|
SetSizer(NULL, false);
|
|
m_expanded_dummy->SetSizer(sizer);
|
|
}
|
|
|
|
m_expanded_dummy->m_expanded_panel = NULL;
|
|
m_expanded_dummy->Realize();
|
|
m_expanded_dummy->Refresh();
|
|
wxWindow *parent = GetParent();
|
|
Destroy();
|
|
parent->Destroy();
|
|
|
|
return true;
|
|
}
|
|
|
|
wxRect wxRibbonPanel::GetExpandedPosition(wxRect panel,
|
|
wxSize expanded_size,
|
|
wxDirection direction)
|
|
{
|
|
// Strategy:
|
|
// 1) Determine primary position based on requested direction
|
|
// 2) Move the position so that it sits entirely within a display
|
|
// (for single monitor systems, this moves it into the display region,
|
|
// but for multiple monitors, it does so without splitting it over
|
|
// more than one display)
|
|
// 2.1) Move in the primary axis
|
|
// 2.2) Move in the secondary axis
|
|
|
|
wxPoint pos;
|
|
bool primary_x = false;
|
|
int secondary_x = 0;
|
|
int secondary_y = 0;
|
|
switch(direction)
|
|
{
|
|
case wxNORTH:
|
|
pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
|
|
pos.y = panel.GetY() - expanded_size.GetHeight();
|
|
primary_x = true;
|
|
secondary_y = 1;
|
|
break;
|
|
case wxEAST:
|
|
pos.x = panel.GetRight();
|
|
pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
|
|
secondary_x = -1;
|
|
break;
|
|
case wxSOUTH:
|
|
pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
|
|
pos.y = panel.GetBottom();
|
|
primary_x = true;
|
|
secondary_y = -1;
|
|
break;
|
|
case wxWEST:
|
|
default:
|
|
pos.x = panel.GetX() - expanded_size.GetWidth();
|
|
pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
|
|
secondary_x = 1;
|
|
break;
|
|
}
|
|
wxRect expanded(pos, expanded_size);
|
|
|
|
wxRect best(expanded);
|
|
int best_distance = INT_MAX;
|
|
|
|
const unsigned display_n = wxDisplay::GetCount();
|
|
unsigned display_i;
|
|
for(display_i = 0; display_i < display_n; ++display_i)
|
|
{
|
|
wxRect display = wxDisplay(display_i).GetGeometry();
|
|
|
|
if(display.Contains(expanded))
|
|
{
|
|
return expanded;
|
|
}
|
|
else if(display.Intersects(expanded))
|
|
{
|
|
wxRect new_rect(expanded);
|
|
int distance = 0;
|
|
|
|
if(primary_x)
|
|
{
|
|
if(expanded.GetRight() > display.GetRight())
|
|
{
|
|
distance = expanded.GetRight() - display.GetRight();
|
|
new_rect.x -= distance;
|
|
}
|
|
else if(expanded.GetLeft() < display.GetLeft())
|
|
{
|
|
distance = display.GetLeft() - expanded.GetLeft();
|
|
new_rect.x += distance;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(expanded.GetBottom() > display.GetBottom())
|
|
{
|
|
distance = expanded.GetBottom() - display.GetBottom();
|
|
new_rect.y -= distance;
|
|
}
|
|
else if(expanded.GetTop() < display.GetTop())
|
|
{
|
|
distance = display.GetTop() - expanded.GetTop();
|
|
new_rect.y += distance;
|
|
}
|
|
}
|
|
if(!display.Contains(new_rect))
|
|
{
|
|
// Tried moving in primary axis, but failed.
|
|
// Hence try moving in the secondary axis.
|
|
int dx = secondary_x * (panel.GetWidth() + expanded_size.GetWidth());
|
|
int dy = secondary_y * (panel.GetHeight() + expanded_size.GetHeight());
|
|
new_rect.x += dx;
|
|
new_rect.y += dy;
|
|
|
|
// Squaring makes secondary moves more expensive (and also
|
|
// prevents a negative cost)
|
|
distance += dx * dx + dy * dy;
|
|
}
|
|
if(display.Contains(new_rect) && distance < best_distance)
|
|
{
|
|
best = new_rect;
|
|
best_distance = distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
void wxRibbonPanel::HideIfExpanded()
|
|
{
|
|
wxRibbonPage* const containingPage = wxDynamicCast(m_parent, wxRibbonPage);
|
|
if (containingPage)
|
|
containingPage->HideIfExpanded();
|
|
}
|
|
|
|
#endif // wxUSE_RIBBON
|