kicad-source/common/widgets/wx_infobar.cpp
Seth Hillbrand 28b2e656e5 Work around (maybe) wx wrapping issue
wx infobar generic will wrap text, changing the stored label data on
instantiation.  Since we might expand the infobar later, this presents
an issue as the newlines are then baked into the string.

We do two things here: keep a copy of the original string to make sure
that we can reset it on update and do a hard calculation of the minimum
height for the infobar to ensure we are displaying the full text string

Fixes https://gitlab.com/kicad/code/kicad/issues/19799
2025-02-03 13:31:24 -06:00

513 lines
13 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 Ian McInerney <ian.s.mcinerney@ieee.org>
* Copyright The 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 <id.h>
#include <kiplatform/ui.h>
#include <widgets/wx_infobar.h>
#include "wx/artprov.h"
#include <wx/aui/framemanager.h>
#include <wx/bmpbuttn.h>
#include <wx/debug.h>
#include <wx/hyperlink.h>
#include <wx/infobar.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#include <eda_base_frame.h>
#ifdef __WXMSW__
#include <dpi_scaling_common.h>
#endif
wxDEFINE_EVENT( KIEVT_SHOW_INFOBAR, wxCommandEvent );
wxDEFINE_EVENT( KIEVT_DISMISS_INFOBAR, wxCommandEvent );
BEGIN_EVENT_TABLE( WX_INFOBAR, wxInfoBarGeneric )
EVT_COMMAND( wxID_ANY, KIEVT_SHOW_INFOBAR, WX_INFOBAR::onShowInfoBar )
EVT_COMMAND( wxID_ANY, KIEVT_DISMISS_INFOBAR, WX_INFOBAR::onDismissInfoBar )
EVT_SYS_COLOUR_CHANGED( WX_INFOBAR::onThemeChange )
EVT_BUTTON( ID_CLOSE_INFOBAR, WX_INFOBAR::onCloseButton )
EVT_TIMER( ID_CLOSE_INFOBAR, WX_INFOBAR::onTimer )
END_EVENT_TABLE()
WX_INFOBAR::WX_INFOBAR( wxWindow* aParent, wxAuiManager* aMgr, wxWindowID aWinid )
: wxInfoBarGeneric( aParent, aWinid ),
m_showTime( 0 ),
m_updateLock( false ),
m_showTimer( nullptr ),
m_auiManager( aMgr ),
m_type( MESSAGE_TYPE::GENERIC )
{
m_showTimer = new wxTimer( this, ID_CLOSE_INFOBAR );
wxColour fg, bg;
KIPLATFORM::UI::GetInfoBarColours( fg, bg );
SetBackgroundColour( bg );
SetForegroundColour( fg );
#ifdef __WXMAC__
// Infobar is broken on Mac without the effects
SetShowHideEffects( wxSHOW_EFFECT_ROLL_TO_BOTTOM, wxSHOW_EFFECT_ROLL_TO_TOP );
SetEffectDuration( 200 );
#else
// Infobar freezes canvas on Windows with the effect, and GTK looks bad with it
SetShowHideEffects( wxSHOW_EFFECT_NONE, wxSHOW_EFFECT_NONE );
#endif
// The infobar seems to start too small, so increase its height
int sx, sy;
GetSize( &sx, &sy );
sy = 1.5 * sy;
// The bitmap gets cutoff sometimes with the default size, so force it to be the same
// height as the infobar.
wxSizer* sizer = GetSizer();
wxSize iconSize = wxArtProvider::GetSizeHint( wxART_BUTTON );
#ifdef __WXMSW__
DPI_SCALING_COMMON dpi( nullptr, aParent );
iconSize.x *= dpi.GetContentScaleFactor();
sx *= dpi.GetContentScaleFactor();
sy *= dpi.GetContentScaleFactor();
#endif
SetSize( sx, sy );
sizer->SetItemMinSize( (size_t) 0, iconSize.x, sy );
// Forcefully remove all existing buttons added by the wx constructors.
// The default close button doesn't work with the AUI manager update scheme, so this
// ensures any close button displayed is ours.
RemoveAllButtons();
Layout();
m_parent->Bind( wxEVT_SIZE, &WX_INFOBAR::onSize, this );
}
WX_INFOBAR::~WX_INFOBAR()
{
delete m_showTimer;
}
void WX_INFOBAR::SetShowTime( int aTime )
{
m_showTime = aTime;
}
void WX_INFOBAR::QueueShowMessage( const wxString& aMessage, int aFlags )
{
wxCommandEvent* evt = new wxCommandEvent( KIEVT_SHOW_INFOBAR );
evt->SetString( aMessage.c_str() );
evt->SetInt( aFlags );
GetEventHandler()->QueueEvent( evt );
}
void WX_INFOBAR::QueueDismiss()
{
wxCommandEvent* evt = new wxCommandEvent( KIEVT_DISMISS_INFOBAR );
GetEventHandler()->QueueEvent( evt );
}
void WX_INFOBAR::ShowMessageFor( const wxString& aMessage, int aTime, int aFlags,
MESSAGE_TYPE aType )
{
// Don't do anything if we requested the UI update
if( m_updateLock )
return;
m_showTime = aTime;
ShowMessage( aMessage, aFlags );
m_type = aType;
}
void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags )
{
// Don't do anything if we requested the UI update
if( m_updateLock )
return;
m_updateLock = true;
m_message = aMessage;
m_message.Trim();
wxInfoBarGeneric::ShowMessage( m_message, aFlags );
if( m_auiManager )
updateAuiLayout( true );
if( m_showTime > 0 )
m_showTimer->StartOnce( m_showTime );
m_type = MESSAGE_TYPE::GENERIC;
m_updateLock = false;
}
void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags, MESSAGE_TYPE aType )
{
// Don't do anything if we requested the UI update
if( m_updateLock )
return;
ShowMessage( aMessage, aFlags );
m_type = aType;
}
void WX_INFOBAR::Dismiss()
{
if( !IsShownOnScreen() )
return;
// Don't do anything if we requested the UI update
if( m_updateLock )
return;
m_updateLock = true;
wxInfoBarGeneric::Dismiss();
if( m_auiManager )
updateAuiLayout( false );
if( m_callback )
(*m_callback)();
m_updateLock = false;
}
void WX_INFOBAR::onThemeChange( wxSysColourChangedEvent& aEvent )
{
wxColour fg, bg;
KIPLATFORM::UI::GetInfoBarColours( fg, bg );
SetBackgroundColour( bg );
SetForegroundColour( fg );
if( wxBitmapButton* btn = GetCloseButton() )
{
wxString tooltip = btn->GetToolTipText();
RemoveAllButtons();
AddCloseButton( tooltip );
}
}
void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
{
int barWidth = GetSize().GetWidth();
wxSizer* sizer = GetSizer();
if( !sizer )
return;
wxSizerItem* text = sizer->GetItem( 1 );
if( text )
{
if( auto textCtrl = dynamic_cast<wxStaticText*>( text->GetWindow() ) )
textCtrl->SetLabelText( m_message );
}
// Calculate the horizontal size: because the infobar is shown on top of the draw canvas
// it is adjusted to the canvas width.
// On Mac, the canvas is the parent
// On other OS the parent is EDA_BASE_FRAME that contains the canvas
int parentWidth = m_parent->GetClientSize().GetWidth();
EDA_BASE_FRAME* frame = dynamic_cast<EDA_BASE_FRAME*>( m_parent );
if( frame && frame->GetToolCanvas() )
parentWidth = frame->GetToolCanvas()->GetSize().GetWidth();
if( barWidth != parentWidth )
SetSize( parentWidth, GetSize().GetHeight() );
if( text )
{
if( auto textCtrl = dynamic_cast<wxStaticText*>( text->GetWindow() ) )
{
// Re-wrap the text (this is done automatically later but we need it now)
// And count how many lines we need. If we have embedded newlines, then
// multiply the number of lines by the text min height to find the correct
// min height for the control. The min height of the text control will be the size
// of a single line of text. This assumes that two lines of text are larger
// than the height of the icon for the bar.
textCtrl->Wrap( text->GetSize().GetWidth() );
wxString new_text = textCtrl->GetLabel();
int height = ( new_text.Freq( '\n' ) + 1 ) * text->GetMinSize().GetHeight();
SetMinSize( wxSize( GetSize().GetWidth(), height ) );
}
}
aEvent.Skip();
}
void WX_INFOBAR::updateAuiLayout( bool aShow )
{
wxASSERT( m_auiManager );
wxAuiPaneInfo& pane = m_auiManager->GetPane( this );
// If the infobar is in a pane, then show/hide the pane
if( pane.IsOk() )
{
if( aShow )
pane.Show();
else
pane.Hide();
}
// Update the AUI manager regardless
m_auiManager->Update();
}
void WX_INFOBAR::AddButton( wxWindowID aId, const wxString& aLabel )
{
wxButton* button = new wxButton( this, aId, aLabel );
AddButton( button );
}
void WX_INFOBAR::AddButton( wxButton* aButton )
{
wxSizer* sizer = GetSizer();
wxASSERT( aButton );
#ifdef __WXMAC__
// Based on the code in the original class:
// smaller buttons look better in the (narrow) info bar under OS X
aButton->SetWindowVariant( wxWINDOW_VARIANT_SMALL );
#endif // __WXMAC__
auto element = sizer->Add( aButton, wxSizerFlags( 0 ).Centre().Border( wxRIGHT ) );
element->SetFlag( wxSTRETCH_MASK );
if( IsShownOnScreen() )
sizer->Layout();
}
void WX_INFOBAR::AddButton( wxHyperlinkCtrl* aHypertextButton )
{
wxSizer* sizer = GetSizer();
wxASSERT( aHypertextButton );
sizer->Add( aHypertextButton, wxSizerFlags().Centre().Border( wxRIGHT ).Shaped() );
if( IsShownOnScreen() )
sizer->Layout();
}
void WX_INFOBAR::AddCloseButton( const wxString& aTooltip )
{
wxBitmapButton* button = wxBitmapButton::NewCloseButton( this, ID_CLOSE_INFOBAR );
button->SetToolTip( aTooltip );
AddButton( button );
}
void WX_INFOBAR::RemoveAllButtons()
{
wxSizer* sizer = GetSizer();
if( sizer->GetItemCount() == 0 )
return;
// The last item is already the spacer
if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
return;
for( int i = sizer->GetItemCount() - 1; i >= 0; i-- )
{
wxSizerItem* sItem = sizer->GetItem( i );
// The spacer is the end of the custom buttons
if( sItem->IsSpacer() )
break;
delete sItem->GetWindow();
}
}
bool WX_INFOBAR::HasCloseButton() const
{
return GetCloseButton();
}
wxBitmapButton* WX_INFOBAR::GetCloseButton() const
{
wxSizer* sizer = GetSizer();
if( sizer->GetItemCount() == 0 )
return nullptr;
if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
return nullptr;
wxSizerItem* item = sizer->GetItem( sizer->GetItemCount() - 1 );
if( item->GetWindow()->GetId() == ID_CLOSE_INFOBAR )
return static_cast<wxBitmapButton*>( item->GetWindow() );
return nullptr;
}
void WX_INFOBAR::onShowInfoBar( wxCommandEvent& aEvent )
{
RemoveAllButtons();
AddCloseButton();
ShowMessage( aEvent.GetString(), aEvent.GetInt() );
}
void WX_INFOBAR::onDismissInfoBar( wxCommandEvent& aEvent )
{
Dismiss();
}
void WX_INFOBAR::onCloseButton( wxCommandEvent& aEvent )
{
Dismiss();
}
void WX_INFOBAR::onTimer( wxTimerEvent& aEvent )
{
// Reset and clear the timer
m_showTimer->Stop();
m_showTime = 0;
Dismiss();
}
EDA_INFOBAR_PANEL::EDA_INFOBAR_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos,
const wxSize& aSize, long aStyle, const wxString& aName )
: wxPanel( aParent, aId, aPos, aSize, aStyle, aName )
{
m_mainSizer = new wxFlexGridSizer( 1, 0, 0 );
m_mainSizer->SetFlexibleDirection( wxBOTH );
m_mainSizer->AddGrowableCol( 0, 1 );
SetSizer( m_mainSizer );
}
void EDA_INFOBAR_PANEL::AddInfoBar( WX_INFOBAR* aInfoBar )
{
wxASSERT( aInfoBar );
aInfoBar->Reparent( this );
m_mainSizer->Add( aInfoBar, 1, wxEXPAND, 0 );
m_mainSizer->Layout();
}
void EDA_INFOBAR_PANEL::AddOtherItem( wxWindow* aOtherItem )
{
wxASSERT( aOtherItem );
aOtherItem->Reparent( this );
m_mainSizer->Add( aOtherItem, 1, wxEXPAND, 0 );
m_mainSizer->AddGrowableRow( 1, 1 );
m_mainSizer->Layout();
}
REPORTER& INFOBAR_REPORTER::Report( const wxString& aText, SEVERITY aSeverity )
{
m_message.reset( new wxString( aText ) );
m_severity = aSeverity;
m_messageSet = true;
return *this;
}
bool INFOBAR_REPORTER::HasMessage() const
{
return m_message && !m_message->IsEmpty();
}
void INFOBAR_REPORTER::Finalize()
{
// Don't do anything if no message was ever given
if( !m_infoBar || !m_messageSet )
return;
// Short circuit if the message is empty and it is already hidden
if( !HasMessage() && !m_infoBar->IsShownOnScreen() )
return;
int icon = wxICON_NONE;
switch( m_severity )
{
case RPT_SEVERITY_UNDEFINED: icon = wxICON_INFORMATION; break;
case RPT_SEVERITY_INFO: icon = wxICON_INFORMATION; break;
case RPT_SEVERITY_EXCLUSION: icon = wxICON_WARNING; break;
case RPT_SEVERITY_ACTION: icon = wxICON_WARNING; break;
case RPT_SEVERITY_WARNING: icon = wxICON_WARNING; break;
case RPT_SEVERITY_ERROR: icon = wxICON_ERROR; break;
case RPT_SEVERITY_IGNORE: icon = wxICON_INFORMATION; break;
case RPT_SEVERITY_DEBUG: icon = wxICON_INFORMATION; break;
}
if( m_message->EndsWith( wxS( "\n" ) ) )
*m_message = m_message->Left( m_message->Length() - 1 );
if( HasMessage() )
m_infoBar->QueueShowMessage( *m_message, icon );
else
m_infoBar->QueueDismiss();
}