Alignment and padding were off, and layout was not getting done properly on draw frames until the window was resized.
591 lines
17 KiB
C++
591 lines
17 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2023 Mark Roszko <[email protected]>
|
|
* 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 2
|
|
* 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, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <wx/button.h>
|
|
#include <wx/statusbr.h>
|
|
#include <wx/gauge.h>
|
|
#include <wx/stattext.h>
|
|
#include <wx/tokenzr.h>
|
|
#include <fmt/format.h>
|
|
#include <array>
|
|
#include <widgets/kistatusbar.h>
|
|
#include <widgets/wx_html_report_box.h>
|
|
#include <widgets/bitmap_button.h>
|
|
#include <widgets/ui_common.h>
|
|
#include <pgm_base.h>
|
|
#include <background_jobs_monitor.h>
|
|
#include <notifications_manager.h>
|
|
#include <bitmaps.h>
|
|
#include <reporter.h>
|
|
#include <dialog_HTML_reporter_base.h>
|
|
#include <trace_helpers.h>
|
|
#include <wx/dcclient.h>
|
|
|
|
|
|
KISTATUSBAR::KISTATUSBAR( int aNumberFields, wxWindow* parent, wxWindowID id, STYLE_FLAGS aFlags ) :
|
|
wxStatusBar( parent, id ),
|
|
m_backgroundStopButton( nullptr ),
|
|
m_notificationsButton( nullptr ),
|
|
m_warningButton( nullptr ),
|
|
m_normalFieldsCount( aNumberFields ),
|
|
m_styleFlags( aFlags )
|
|
{
|
|
#ifdef __WXOSX__
|
|
// we need +1 extra field on OSX to offset from the rounded corner on the right
|
|
// OSX doesn't use resize grippers like the other platforms and the statusbar field
|
|
// includes the rounded part
|
|
int extraFields = 3;
|
|
#else
|
|
int extraFields = 2;
|
|
#endif
|
|
|
|
bool showNotification = ( m_styleFlags & NOTIFICATION_ICON );
|
|
bool showCancel = ( m_styleFlags & CANCEL_BUTTON );
|
|
bool showWarning = ( m_styleFlags & WARNING_ICON );
|
|
|
|
if( showCancel )
|
|
extraFields++;
|
|
|
|
if( showWarning )
|
|
extraFields++;
|
|
|
|
if( showNotification )
|
|
extraFields++;
|
|
|
|
SetFieldsCount( aNumberFields + extraFields );
|
|
|
|
m_fieldWidths.assign( aNumberFields + extraFields, -1 );
|
|
|
|
#ifdef __WXOSX__
|
|
// offset from the right edge
|
|
m_fieldWidths[aNumberFields + extraFields - 1] = 10;
|
|
#endif
|
|
|
|
SetStatusWidths( aNumberFields + extraFields, m_fieldWidths.data() );
|
|
|
|
int* styles = new int[aNumberFields + extraFields];
|
|
|
|
for( int i = 0; i < aNumberFields + extraFields; i++ )
|
|
styles[i] = wxSB_FLAT;
|
|
|
|
SetStatusStyles( aNumberFields + extraFields, styles );
|
|
delete[] styles;
|
|
|
|
m_backgroundTxt = new wxStaticText( this, wxID_ANY, wxT( "" ), wxDefaultPosition,
|
|
wxDefaultSize, wxALIGN_RIGHT | wxST_NO_AUTORESIZE );
|
|
|
|
m_backgroundProgressBar = new wxGauge( this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize,
|
|
wxGA_HORIZONTAL | wxGA_SMOOTH );
|
|
|
|
if( showCancel )
|
|
{
|
|
m_backgroundStopButton = new wxButton( this, wxID_ANY, "X", wxDefaultPosition,
|
|
wxDefaultSize, wxBU_EXACTFIT );
|
|
}
|
|
|
|
if( showNotification )
|
|
{
|
|
m_notificationsButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
|
|
wxDefaultSize, wxBU_EXACTFIT );
|
|
|
|
m_notificationsButton->SetPadding( 0 );
|
|
m_notificationsButton->SetBitmap( KiBitmapBundle( BITMAPS::notifications ) );
|
|
m_notificationsButton->SetShowBadge( true );
|
|
m_notificationsButton->SetBitmapCentered( true );
|
|
|
|
m_notificationsButton->Bind( wxEVT_BUTTON, &KISTATUSBAR::onNotificationsIconClick, this );
|
|
}
|
|
|
|
if( showWarning )
|
|
{
|
|
m_warningButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
|
|
wxDefaultSize, wxBU_EXACTFIT );
|
|
|
|
m_warningButton->SetPadding( 0 );
|
|
m_warningButton->SetBitmap( KiBitmapBundle( BITMAPS::small_warning ) );
|
|
m_warningButton->SetBitmapCentered( true );
|
|
m_warningButton->SetToolTip( _( "View load messages" ) );
|
|
m_warningButton->Hide();
|
|
|
|
m_warningButton->Bind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
|
|
}
|
|
|
|
Bind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
|
|
m_backgroundProgressBar->Bind( wxEVT_LEFT_DOWN, &KISTATUSBAR::onBackgroundProgressClick, this );
|
|
|
|
HideBackgroundProgressBar();
|
|
Layout();
|
|
}
|
|
|
|
|
|
KISTATUSBAR::~KISTATUSBAR()
|
|
{
|
|
if( m_notificationsButton )
|
|
m_notificationsButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onNotificationsIconClick, this );
|
|
|
|
if( m_warningButton )
|
|
m_warningButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
|
|
|
|
Unbind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
|
|
m_backgroundProgressBar->Unbind( wxEVT_LEFT_DOWN, &KISTATUSBAR::onBackgroundProgressClick,
|
|
this );
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::onNotificationsIconClick( wxCommandEvent& aEvent )
|
|
{
|
|
wxCHECK( m_notificationsButton, /* void */ );
|
|
wxPoint pos = m_notificationsButton->GetScreenPosition();
|
|
|
|
wxRect r;
|
|
if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
|
|
{
|
|
GetFieldRect( m_normalFieldsCount + *idx, r );
|
|
pos.x += r.GetWidth();
|
|
}
|
|
|
|
Pgm().GetNotificationsManager().ShowList( this, pos );
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::onBackgroundProgressClick( wxMouseEvent& aEvent )
|
|
{
|
|
wxPoint pos = m_backgroundProgressBar->GetScreenPosition();
|
|
|
|
wxRect r;
|
|
if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
|
|
{
|
|
GetFieldRect( m_normalFieldsCount + *idx, r );
|
|
pos.x += r.GetWidth();
|
|
}
|
|
|
|
Pgm().GetBackgroundJobMonitor().ShowList( this, pos );
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::onSize( wxSizeEvent& aEvent )
|
|
{
|
|
layoutControls();
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::layoutControls()
|
|
{
|
|
constexpr int padding = 5;
|
|
|
|
wxRect r;
|
|
GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r );
|
|
int x = r.GetLeft();
|
|
int y = r.GetTop();
|
|
int textHeight = KIUI::GetTextSize( wxT( "bp" ), this ).y;
|
|
|
|
if( r.GetHeight() > textHeight )
|
|
y += ( r.GetHeight() - textHeight ) / 2;
|
|
|
|
m_backgroundTxt->SetPosition( { x, y } );
|
|
m_backgroundTxt->SetSize( r.GetWidth(), textHeight );
|
|
updateBackgroundText();
|
|
|
|
GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_GAUGE ), r );
|
|
x = r.GetLeft();
|
|
y = r.GetTop();
|
|
int w = r.GetWidth();
|
|
int h = r.GetHeight();
|
|
wxSize buttonSize( 0, 0 );
|
|
|
|
if( m_backgroundStopButton )
|
|
{
|
|
buttonSize = m_backgroundStopButton->GetEffectiveMinSize();
|
|
m_backgroundStopButton->SetPosition( { x + w - buttonSize.GetWidth(), y } );
|
|
m_backgroundStopButton->SetSize( buttonSize.GetWidth(), h );
|
|
buttonSize.x += padding;
|
|
}
|
|
|
|
m_backgroundProgressBar->SetPosition( { x + padding, y } );
|
|
m_backgroundProgressBar->SetSize( w - buttonSize.GetWidth() - padding, h );
|
|
|
|
if( m_notificationsButton )
|
|
{
|
|
GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::NOTIFICATION ), r );
|
|
x = r.GetLeft();
|
|
y = r.GetTop();
|
|
h = r.GetHeight();
|
|
buttonSize = m_notificationsButton->GetEffectiveMinSize();
|
|
m_notificationsButton->SetPosition( { x, y } );
|
|
m_notificationsButton->SetSize( buttonSize.GetWidth() + 6, h );
|
|
}
|
|
|
|
if( m_warningButton )
|
|
{
|
|
GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::WARNING ), r );
|
|
x = r.GetLeft();
|
|
y = r.GetTop();
|
|
h = r.GetHeight();
|
|
buttonSize = m_warningButton->GetEffectiveMinSize();
|
|
m_warningButton->SetPosition( { x, y } );
|
|
m_warningButton->SetSize( buttonSize.GetWidth() + 6, h );
|
|
}
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::ShowBackgroundProgressBar( bool aCancellable )
|
|
{
|
|
m_backgroundProgressBar->Show();
|
|
|
|
if( m_backgroundStopButton )
|
|
m_backgroundStopButton->Show( aCancellable );
|
|
|
|
updateAuxFieldWidths();
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::HideBackgroundProgressBar()
|
|
{
|
|
m_backgroundProgressBar->Hide();
|
|
|
|
if( m_backgroundStopButton )
|
|
m_backgroundStopButton->Hide();
|
|
|
|
updateAuxFieldWidths();
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::SetBackgroundProgress( int aAmount )
|
|
{
|
|
int range = m_backgroundProgressBar->GetRange();
|
|
|
|
if( aAmount > range )
|
|
aAmount = range;
|
|
|
|
m_backgroundProgressBar->SetValue( aAmount );
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::SetBackgroundProgressMax( int aAmount )
|
|
{
|
|
m_backgroundProgressBar->SetRange( aAmount );
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::SetBackgroundStatusText( const wxString& aTxt )
|
|
{
|
|
m_backgroundRawText = aTxt;
|
|
updateBackgroundText();
|
|
|
|
// When there are multiple normal fields, the last normal field (typically used for
|
|
// file watcher status on Windows) can visually overlap with the background job label
|
|
// since both have variable width. Save and clear that field when showing background
|
|
// text, and restore it when the background text is cleared.
|
|
if( m_normalFieldsCount > 1 )
|
|
{
|
|
int adjacentField = m_normalFieldsCount - 1;
|
|
|
|
if( !aTxt.empty() )
|
|
{
|
|
m_savedStatusText = GetStatusText( adjacentField );
|
|
SetStatusText( wxEmptyString, adjacentField );
|
|
}
|
|
else if( !m_savedStatusText.empty() )
|
|
{
|
|
SetStatusText( m_savedStatusText, adjacentField );
|
|
m_savedStatusText.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::updateAuxFieldWidths()
|
|
{
|
|
if( m_fieldWidths.empty() )
|
|
return;
|
|
|
|
if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_LABEL ) )
|
|
m_fieldWidths[m_normalFieldsCount + *idx] = -2;
|
|
|
|
if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
|
|
m_fieldWidths[m_normalFieldsCount + *idx] = 75;
|
|
|
|
if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_CANCEL ) )
|
|
{
|
|
m_fieldWidths[m_normalFieldsCount + *idx] =
|
|
m_backgroundStopButton && m_backgroundStopButton->IsShown() ? 20 : 0;
|
|
}
|
|
|
|
if( std::optional<int> idx = fieldIndex( FIELD::WARNING ) )
|
|
{
|
|
m_fieldWidths[m_normalFieldsCount + *idx] =
|
|
m_warningButton && m_warningButton->IsShown() ? 20 : 0;
|
|
}
|
|
|
|
if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
|
|
{
|
|
m_fieldWidths[m_normalFieldsCount + *idx] =
|
|
m_notificationsButton && m_notificationsButton->IsShown() ? 20 : 0;
|
|
}
|
|
|
|
SetStatusWidths( static_cast<int>( m_fieldWidths.size() ), m_fieldWidths.data() );
|
|
layoutControls();
|
|
updateBackgroundText();
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::updateBackgroundText()
|
|
{
|
|
wxRect r;
|
|
|
|
if( !GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r ) )
|
|
return;
|
|
|
|
wxString text = m_backgroundRawText;
|
|
|
|
if( !text.empty() && r.GetWidth() > 4 )
|
|
{
|
|
wxClientDC dc( this );
|
|
int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
|
|
text = wxControl::Ellipsize( text, dc, wxELLIPSIZE_END, std::max( 0, r.GetWidth() - margin ) );
|
|
}
|
|
|
|
m_backgroundTxt->SetLabel( text );
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::SetNotificationCount( int aCount )
|
|
{
|
|
wxCHECK( m_notificationsButton, /* void */ );
|
|
wxString cnt = "";
|
|
|
|
if( aCount > 0 )
|
|
cnt = fmt::format( "{}", aCount );
|
|
|
|
m_notificationsButton->SetBadgeText( cnt );
|
|
|
|
// force a repaint or it wont until it gets activity
|
|
Refresh();
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::SetLoadWarningMessages( const wxString& aMessages )
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_loadWarningMutex );
|
|
m_loadWarningMessages.clear();
|
|
|
|
wxStringTokenizer tokenizer( aMessages, wxS( "\n" ), wxTOKEN_STRTOK );
|
|
|
|
while( tokenizer.HasMoreTokens() )
|
|
{
|
|
LOAD_MESSAGE msg;
|
|
msg.message = tokenizer.GetNextToken();
|
|
msg.severity = RPT_SEVERITY_WARNING; // Default to warning for font substitutions
|
|
m_loadWarningMessages.push_back( msg );
|
|
}
|
|
}
|
|
|
|
updateWarningUI();
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::AddLoadWarningMessages( const std::vector<LOAD_MESSAGE>& aMessages )
|
|
{
|
|
wxLogTrace( traceLibraries, "KISTATUSBAR::AddLoadWarningMessages: this=%p, count=%zu",
|
|
this, aMessages.size() );
|
|
|
|
if( aMessages.empty() )
|
|
return;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_loadWarningMutex );
|
|
m_loadWarningMessages.insert( m_loadWarningMessages.end(), aMessages.begin(), aMessages.end() );
|
|
wxLogTrace( traceLibraries, " -> total messages now=%zu", m_loadWarningMessages.size() );
|
|
}
|
|
|
|
// Update UI on main thread
|
|
wxLogTrace( traceLibraries, " -> calling CallAfter for updateWarningUI" );
|
|
CallAfter( [this]() { updateWarningUI(); } );
|
|
}
|
|
|
|
|
|
size_t KISTATUSBAR::GetLoadWarningCount() const
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_loadWarningMutex );
|
|
return m_loadWarningMessages.size();
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::updateWarningUI()
|
|
{
|
|
wxLogTrace( traceLibraries, "KISTATUSBAR::updateWarningUI: this=%p, m_warningButton=%p",
|
|
this, m_warningButton );
|
|
|
|
if( !m_warningButton )
|
|
{
|
|
wxLogTrace( traceLibraries, " -> no warning button, returning early" );
|
|
return;
|
|
}
|
|
|
|
size_t messageCount;
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_loadWarningMutex );
|
|
messageCount = m_loadWarningMessages.size();
|
|
}
|
|
|
|
wxLogTrace( traceLibraries, " -> message count=%zu, showing button=%s",
|
|
messageCount, messageCount > 0 ? "true" : "false" );
|
|
|
|
m_warningButton->Show( messageCount > 0 );
|
|
updateAuxFieldWidths();
|
|
|
|
if( messageCount > 0 )
|
|
{
|
|
m_warningButton->SetToolTip( wxString::Format( _( "View %zu load message(s)" ), messageCount ) );
|
|
|
|
// Show count badge on the warning button
|
|
m_warningButton->SetShowBadge( true );
|
|
wxString badgeText = messageCount > 99
|
|
? wxString( "99+" )
|
|
: wxString::Format( wxS( "%zu" ), messageCount );
|
|
m_warningButton->SetBadgeText( badgeText );
|
|
|
|
wxLogTrace( traceLibraries, " -> badge set to '%s'", badgeText );
|
|
}
|
|
|
|
Layout();
|
|
Refresh();
|
|
wxLogTrace( traceLibraries, " -> Layout and Refresh complete" );
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::ClearLoadWarningMessages()
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_loadWarningMutex );
|
|
m_loadWarningMessages.clear();
|
|
}
|
|
|
|
if( m_warningButton )
|
|
{
|
|
m_warningButton->Hide();
|
|
m_warningButton->SetShowBadge( false );
|
|
m_warningButton->SetBadgeText( wxEmptyString );
|
|
updateAuxFieldWidths();
|
|
Layout();
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
|
|
void KISTATUSBAR::onLoadWarningsIconClick( wxCommandEvent& aEvent )
|
|
{
|
|
// Copy messages under lock to avoid holding lock during modal dialog
|
|
std::vector<LOAD_MESSAGE> messages;
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_loadWarningMutex );
|
|
messages = m_loadWarningMessages;
|
|
}
|
|
|
|
if( messages.empty() )
|
|
return;
|
|
|
|
DIALOG_HTML_REPORTER dlg( GetParent(), wxID_ANY, _( "Load Messages" ) );
|
|
|
|
for( const LOAD_MESSAGE& msg : messages )
|
|
dlg.m_Reporter->Report( msg.message, msg.severity );
|
|
|
|
dlg.m_Reporter->Flush();
|
|
dlg.ShowModal();
|
|
}
|
|
|
|
void KISTATUSBAR::SetEllipsedTextField( const wxString& aText, int aFieldId )
|
|
{
|
|
wxRect fieldRect;
|
|
int width = -1;
|
|
wxString etext = aText;
|
|
|
|
// Only GetFieldRect() returns the current size for variable size fields
|
|
// Other methods return -1 for the width of these fields.
|
|
if( GetFieldRect( aFieldId, fieldRect ) )
|
|
width = fieldRect.GetWidth();
|
|
|
|
if( width > 20 )
|
|
{
|
|
wxClientDC dc( this );
|
|
|
|
// Gives a margin to the text to be sure it is not clamped at its end
|
|
int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
|
|
etext = wxControl::Ellipsize( etext, dc, wxELLIPSIZE_MIDDLE, width - margin );
|
|
}
|
|
|
|
SetStatusText( etext, aFieldId );
|
|
}
|
|
|
|
|
|
std::optional<int> KISTATUSBAR::fieldIndex( FIELD aField ) const
|
|
{
|
|
switch( aField )
|
|
{
|
|
case FIELD::BGJOB_LABEL: return 0;
|
|
case FIELD::BGJOB_GAUGE: return 1;
|
|
case FIELD::BGJOB_CANCEL:
|
|
{
|
|
if( m_styleFlags & CANCEL_BUTTON )
|
|
return 2;
|
|
|
|
break;
|
|
}
|
|
case FIELD::WARNING:
|
|
{
|
|
if( m_styleFlags & WARNING_ICON )
|
|
{
|
|
int offset = 2;
|
|
|
|
if( m_styleFlags & CANCEL_BUTTON )
|
|
offset++;
|
|
|
|
return offset;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case FIELD::NOTIFICATION:
|
|
{
|
|
if( m_styleFlags & NOTIFICATION_ICON )
|
|
{
|
|
int offset = 2;
|
|
|
|
if( m_styleFlags & CANCEL_BUTTON )
|
|
offset++;
|
|
|
|
if( m_styleFlags & WARNING_ICON )
|
|
offset++;
|
|
|
|
return offset;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|