kicad/common/widgets/collapsible_pane.cpp

436 lines
11 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <bitmaps.h>
#include <widgets/collapsible_pane.h>
#include <wx/collpane.h>
#include <wx/dc.h>
#include <wx/dcclient.h>
#include <wx/panel.h>
#include <wx/renderer.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/toplevel.h>
#include <wx/window.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include <algorithm>
wxDEFINE_EVENT( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, wxCommandEvent );
wxDEFINE_EVENT( WX_COLLAPSIBLE_PANE_CHANGED, wxCommandEvent );
bool WX_COLLAPSIBLE_PANE:: Create( wxWindow* aParent, wxWindowID aId, const wxString& aLabel,
const wxPoint& aPos, const wxSize& aSize, long aStyle,
const wxValidator& aValidator, const wxString& aName )
{
if( !wxControl::Create( aParent, aId, aPos, aSize, aStyle, aValidator, aName ) )
return false;
m_sizer = new wxBoxSizer( wxVERTICAL );
m_header = new WX_COLLAPSIBLE_PANE_HEADER( this, wxID_ANY, aLabel, wxPoint( 0, 0 ),
wxDefaultSize );
m_sizer->Add( m_header, wxSizerFlags().Border( wxBOTTOM, getBorder() ) );
m_pane = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxTAB_TRAVERSAL | wxNO_BORDER, wxT( "COLLAPSIBLE_PANE_PANE" ) );
m_pane->Hide();
Bind( wxEVT_SIZE, &WX_COLLAPSIBLE_PANE::onSize, this );
Bind( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, &WX_COLLAPSIBLE_PANE::onHeaderClicked, this );
return true;
}
void WX_COLLAPSIBLE_PANE::init()
{
m_pane = nullptr;
m_sizer = nullptr;
m_header = nullptr;
}
WX_COLLAPSIBLE_PANE::~WX_COLLAPSIBLE_PANE()
{
if( m_sizer )
m_sizer->SetContainingWindow( nullptr );
// Not owned by wxWindow
delete m_sizer;
}
void WX_COLLAPSIBLE_PANE::Collapse( bool aCollapse )
{
if( IsCollapsed() == aCollapse )
return;
InvalidateBestSize();
m_pane->Show( !aCollapse );
m_header->SetCollapsed( aCollapse );
SetSize( GetBestSize() );
}
bool WX_COLLAPSIBLE_PANE::IsCollapsed() const
{
return !m_pane || !m_pane->IsShown();
}
void WX_COLLAPSIBLE_PANE::SetLabel( const wxString& aLabel )
{
m_header->SetLabel( aLabel );
m_header->SetInitialSize();
Layout();
}
bool WX_COLLAPSIBLE_PANE::SetBackgroundColour( const wxColour& aColor )
{
m_header->SetBackgroundColour( aColor );
return wxWindow::SetBackgroundColour( aColor );
}
bool WX_COLLAPSIBLE_PANE::InformFirstDirection( int aDirection, int aSize, int aAvailableOtherDir )
{
wxWindow* const pane = GetPane();
if( !pane )
return false;
if( !pane->InformFirstDirection( aDirection, aSize, aAvailableOtherDir ) )
return false;
InvalidateBestSize();
return true;
}
wxSize WX_COLLAPSIBLE_PANE::DoGetBestClientSize() const
{
wxSize size = m_sizer->GetMinSize();
if( IsExpanded() )
{
wxSize paneSize = m_pane->GetBestSize();
size.SetWidth( std::max( size.GetWidth(), paneSize.x ) );
size.SetHeight( size.y + getBorder() + paneSize.y );
}
return size;
}
bool WX_COLLAPSIBLE_PANE::Layout()
{
if( !m_sizer || !m_pane || !m_header )
return false;
wxSize size( GetSize() );
m_sizer->SetDimension( 0, 0, size.x, m_sizer->GetMinSize().y );
m_sizer->Layout();
if( IsExpanded() )
{
int yoffset = m_sizer->GetSize().y + getBorder();
m_pane->SetSize( 0, yoffset, size.x, size.y - yoffset );
m_pane->Layout();
}
return true;
}
int WX_COLLAPSIBLE_PANE::getBorder() const
{
#if defined( __WXMSW__ )
wxASSERT( m_header );
return m_header->ConvertDialogToPixels( wxSize( 2, 0 ) ).x;
#else
return 3;
#endif
}
void WX_COLLAPSIBLE_PANE::onSize( wxSizeEvent& aEvent )
{
Layout();
aEvent.Skip();
}
void WX_COLLAPSIBLE_PANE::onHeaderClicked( wxCommandEvent& aEvent )
{
if( aEvent.GetEventObject() != m_header )
{
aEvent.Skip();
return;
}
Collapse( !IsCollapsed() );
wxCommandEvent evt( WX_COLLAPSIBLE_PANE_CHANGED, GetId() );
evt.SetEventObject( this );
ProcessEvent( evt );
}
// WX_COLLAPSIBLE_PANE_HEADER implementation
void WX_COLLAPSIBLE_PANE_HEADER::init()
{
m_collapsed = true;
m_inWindow = false;
}
bool WX_COLLAPSIBLE_PANE_HEADER::Create( wxWindow* aParent, wxWindowID aId, const wxString& aLabel,
const wxPoint& aPos, const wxSize& aSize, long aStyle,
const wxValidator& aValidator, const wxString& aName )
{
if ( !wxControl::Create( aParent, aId, aPos, aSize, aStyle, aValidator, aName ) )
return false;
SetLabel( aLabel );
Bind( wxEVT_PAINT, &WX_COLLAPSIBLE_PANE_HEADER::onPaint, this );
Bind( wxEVT_SET_FOCUS, &WX_COLLAPSIBLE_PANE_HEADER::onFocus, this );
Bind( wxEVT_KILL_FOCUS, &WX_COLLAPSIBLE_PANE_HEADER::onFocus, this );
Bind( wxEVT_ENTER_WINDOW, &WX_COLLAPSIBLE_PANE_HEADER::onEnterWindow, this);
Bind( wxEVT_LEAVE_WINDOW, &WX_COLLAPSIBLE_PANE_HEADER::onLeaveWindow, this);
Bind( wxEVT_LEFT_UP, &WX_COLLAPSIBLE_PANE_HEADER::onLeftUp, this );
Bind( wxEVT_CHAR, &WX_COLLAPSIBLE_PANE_HEADER::onChar, this );
return true;
}
void WX_COLLAPSIBLE_PANE_HEADER::SetCollapsed( bool aCollapsed )
{
m_collapsed = aCollapsed;
Refresh();
}
void WX_COLLAPSIBLE_PANE_HEADER::doSetCollapsed( bool aCollapsed )
{
SetCollapsed( aCollapsed );
wxCommandEvent evt( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, GetId() );
evt.SetEventObject( this );
ProcessEvent( evt );
}
wxSize WX_COLLAPSIBLE_PANE_HEADER::DoGetBestClientSize() const
{
WX_COLLAPSIBLE_PANE_HEADER* self = const_cast<WX_COLLAPSIBLE_PANE_HEADER*>( this );
// The code here parallels that of OnPaint() -- except without drawing.
wxClientDC dc( self );
wxString text;
wxControl::FindAccelIndex( GetLabel(), &text );
wxSize size = dc.GetTextExtent( text );
// Reserve space for arrow (which is a square the height of the text)
size.x += size.GetHeight();
#ifdef __WXMSW__
size.IncBy( GetSystemMetrics( SM_CXFOCUSBORDER ),
GetSystemMetrics( SM_CYFOCUSBORDER ) );
#endif // __WXMSW__
return size;
}
void WX_COLLAPSIBLE_PANE_HEADER::onPaint( wxPaintEvent& aEvent )
{
wxPaintDC dc( this );
wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
#ifdef __WXMSW__
wxBrush brush = dc.GetBrush();
brush.SetColour( GetParent()->GetBackgroundColour() );
dc.SetBrush( brush );
dc.SetPen( *wxTRANSPARENT_PEN );
dc.DrawRectangle( rect );
#endif
// Make the background look like a button when the pointer is over it
if( m_inWindow )
{
dc.SetBrush( wxBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNHIGHLIGHT ) ) );
dc.SetPen( *wxTRANSPARENT_PEN );
dc.DrawRectangle( rect );
}
wxString text;
int indexAccel = wxControl::FindAccelIndex( GetLabel(), &text );
wxSize textSize = dc.GetTextExtent( text );
// Compute all the sizes
wxRect arrowRect( 0, 0, textSize.GetHeight(), textSize.GetHeight() );
wxRect textRect( arrowRect.GetTopRight(), textSize );
textRect = textRect.CenterIn( rect, wxVERTICAL );
// Find out if the window we are in is active or not
bool isActive = true;
wxTopLevelWindow* tlw = dynamic_cast<wxTopLevelWindow*>( wxGetTopLevelParent( this ) );
if( tlw && !tlw->IsActive() )
isActive = false;
// Draw the arrow
drawArrow( dc, arrowRect, isActive );
// We are responsible for showing the text as disabled when the window isn't active
wxColour clr;
if( isActive )
clr = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
else
clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
dc.SetTextForeground( clr );
dc.DrawLabel( text, textRect, wxALIGN_CENTER_VERTICAL, indexAccel );
#ifdef __WXMSW__
int flags = 0;
if( m_inWindow )
flags |= wxCONTROL_CURRENT;
int focusSize = GetSystemMetrics( SM_CXFOCUSBORDER );
if( HasFocus() )
wxRendererNative::Get().DrawFocusRect( this, dc, textRect.Inflate( focusSize ), flags );
#endif
}
void WX_COLLAPSIBLE_PANE_HEADER::onFocus( wxFocusEvent& aEvent )
{
Refresh();
aEvent.Skip();
}
void WX_COLLAPSIBLE_PANE_HEADER::onEnterWindow( wxMouseEvent& aEvent )
{
m_inWindow = true;
Refresh();
aEvent.Skip();
}
void WX_COLLAPSIBLE_PANE_HEADER::onLeaveWindow( wxMouseEvent& aEvent )
{
m_inWindow = false;
Refresh();
aEvent.Skip();
}
void WX_COLLAPSIBLE_PANE_HEADER::onLeftUp( wxMouseEvent& aEvent )
{
doSetCollapsed( !m_collapsed );
aEvent.Skip();
}
void WX_COLLAPSIBLE_PANE_HEADER::onChar( wxKeyEvent& aEvent )
{
switch( aEvent.GetKeyCode() )
{
case WXK_SPACE:
case WXK_RETURN:
doSetCollapsed( !m_collapsed );
break;
default:
aEvent.Skip();
break;
}
}
void WX_COLLAPSIBLE_PANE_HEADER::drawArrow( wxDC& aDC, wxRect aRect, bool aIsActive )
{
// The bottom corner of the triangle is located halfway across the area and 3/4 down from the top
wxPoint btmCorner( aRect.GetWidth() / 2, 3 * aRect.GetHeight() / 4 );
// The right corner of the triangle is located halfway down from the top and 3/4 across the area
wxPoint rtCorner( 3 * aRect.GetWidth() / 4, aRect.GetHeight() / 2 );
// Choose the other corner depending on if the panel is expanded or collapsed
wxPoint otherCorner( 0, 0 );
if( m_collapsed )
otherCorner = wxPoint( aRect.GetWidth() / 2, aRect.GetHeight() / 4 );
else
otherCorner = wxPoint( aRect.GetWidth() / 4, aRect.GetHeight() / 2 );
// Choose the color to draw the triangle
wxColour clr;
// Highlight the arrow when the pointer is inside the header, otherwise use text color
if( m_inWindow )
clr = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
else
clr = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
// If the window isn't active, then use the disabled text color
if( !aIsActive )
clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
// Must set both the pen (for the outline) and the brush (for the polygon fill)
aDC.SetPen( wxPen( clr ) );
aDC.SetBrush( wxBrush( clr ) );
// Draw the triangle
wxPointList points;
points.Append( &btmCorner );
points.Append( &rtCorner );
points.Append( &otherCorner );
aDC.DrawPolygon( &points );
}