|
|
/*
* 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/wx_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: case WXK_NUMPAD_ENTER: 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 );}
|