/* * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif #include 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( 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( 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 ); }