Enhance the status bar with background progress and notifications

This commit is contained in:
Marek Roszko 2023-07-26 22:03:05 -04:00
parent cfa49b5250
commit 5319316815
22 changed files with 1616 additions and 72 deletions

View File

@ -186,6 +186,7 @@ set( COMMON_WIDGET_SRCS
widgets/grid_text_helpers.cpp
widgets/indicator_icon.cpp
widgets/wx_infobar.cpp
widgets/kistatusbar.cpp
widgets/layer_box_selector.cpp
widgets/lib_tree.cpp
widgets/mathplot.cpp
@ -300,6 +301,7 @@ set( COMMON_SRCS
array_axis.cpp
array_options.cpp
asset_archive.cpp
background_jobs_monitor.cpp
base_screen.cpp
base64.cpp
bin_mod.cpp
@ -363,6 +365,7 @@ set( COMMON_SRCS
marker_base.cpp
markup_parser.cpp
netclass.cpp
notifications_manager.cpp
observable.cpp
origin_transforms.cpp
page_info.cpp
@ -494,6 +497,7 @@ target_link_libraries( common
gal
scripting
threadpool
nlohmann_json
pybind11::embed
compoundfilereader
pcm_settings

View File

@ -0,0 +1,342 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2023 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/gauge.h>
#include <wx/frame.h>
#include <wx/panel.h>
#include <wx/settings.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/string.h>
#include <background_jobs_monitor.h>
#include <widgets/kistatusbar.h>
class BACKGROUND_JOB_PANEL : public wxPanel
{
public:
BACKGROUND_JOB_PANEL( wxWindow* aParent, BACKGROUND_JOB* aJob ) :
wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, 75 ),
wxBORDER_SIMPLE ),
m_job( aJob )
{
SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* mainSizer;
mainSizer = new wxBoxSizer( wxVERTICAL );
SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
m_stName = new wxStaticText( this, wxID_ANY, aJob->m_name );
m_stName->Wrap( -1 );
m_stName->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT,
wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
mainSizer->Add( m_stName, 0, wxALL | wxEXPAND, 1 );
m_stStatus = new wxStaticText( this, wxID_ANY, aJob->m_status, wxDefaultPosition,
wxDefaultSize, 0 );
m_stStatus->Wrap( -1 );
mainSizer->Add( m_stStatus, 0, wxALL | wxEXPAND, 1 );
m_progress = new wxGauge( this, wxID_ANY, aJob->m_maxProgress, wxDefaultPosition, wxDefaultSize,
wxGA_HORIZONTAL );
m_progress->SetValue( 0 );
mainSizer->Add( m_progress, 0, wxALL | wxEXPAND, 1 );
SetSizer( mainSizer );
Layout();
UpdateFromJob();
}
void UpdateFromJob()
{
m_stStatus->SetLabelText( m_job->m_status );
m_progress->SetValue( m_job->m_currentProgress );
m_progress->SetRange( m_job->m_maxProgress );
}
private:
wxGauge* m_progress;
wxStaticText* m_stName;
wxStaticText* m_stStatus;
BACKGROUND_JOB* m_job;
};
class BACKGROUND_JOB_LIST : public wxFrame
{
public:
BACKGROUND_JOB_LIST( wxWindow* parent, const wxPoint& pos ) :
wxFrame( parent, wxID_ANY, _( "Background Jobs" ), pos, wxSize( 300, 150 ), wxFRAME_NO_TASKBAR )
{
SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* bSizer1;
bSizer1 = new wxBoxSizer( wxVERTICAL );
m_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition,
wxSize( -1, -1 ), wxALWAYS_SHOW_SB | wxVSCROLL );
m_scrolledWindow->SetScrollRate( 5, 5 );
m_contentSizer = new wxBoxSizer( wxVERTICAL );
m_scrolledWindow->SetSizer( m_contentSizer );
m_scrolledWindow->Layout();
m_contentSizer->Fit( m_scrolledWindow );
bSizer1->Add( m_scrolledWindow, 1, wxEXPAND | wxALL, 0 );
Bind( wxEVT_KILL_FOCUS, &BACKGROUND_JOB_LIST::onFocusLoss, this );
SetSizer( bSizer1 );
Layout();
SetFocus();
}
void onFocusLoss( wxFocusEvent& aEvent )
{
Close( true );
aEvent.Skip();
}
void Add( BACKGROUND_JOB* aJob )
{
BACKGROUND_JOB_PANEL* panel = new BACKGROUND_JOB_PANEL( m_scrolledWindow, aJob );
m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 );
m_scrolledWindow->Layout();
m_contentSizer->Fit( m_scrolledWindow );
// call this at this window otherwise the child panels dont resize width properly
Layout();
m_jobPanels[aJob] = panel;
}
void Remove( BACKGROUND_JOB* aJob )
{
auto it = m_jobPanels.find( aJob );
if( it != m_jobPanels.end() )
{
BACKGROUND_JOB_PANEL* panel = m_jobPanels[aJob];
m_contentSizer->Detach( panel );
panel->Destroy();
m_jobPanels.erase( it );
}
}
void UpdateJob( BACKGROUND_JOB* aJob )
{
auto it = m_jobPanels.find( aJob );
if( it != m_jobPanels.end() )
{
BACKGROUND_JOB_PANEL* panel = m_jobPanels[aJob];
panel->UpdateFromJob();
}
}
private:
wxScrolledWindow* m_scrolledWindow;
wxBoxSizer* m_contentSizer;
std::unordered_map<BACKGROUND_JOB*, BACKGROUND_JOB_PANEL*> m_jobPanels;
};
BACKGROUND_JOB_REPORTER::BACKGROUND_JOB_REPORTER( BACKGROUND_JOBS_MONITOR* aMonitor,
BACKGROUND_JOB* aJob ) :
PROGRESS_REPORTER_BASE( 1 ),
m_monitor( aMonitor ), m_job( aJob )
{
}
bool BACKGROUND_JOB_REPORTER::updateUI()
{
return true;
}
void BACKGROUND_JOB_REPORTER::Report( const wxString& aMessage )
{
m_job->m_status = aMessage;
m_monitor->jobUpdated( m_job );
}
void BACKGROUND_JOB_REPORTER::SetNumPhases( int aNumPhases )
{
PROGRESS_REPORTER_BASE::SetNumPhases( aNumPhases );
m_job->m_maxProgress = m_numPhases;
m_monitor->jobUpdated( m_job );
}
void BACKGROUND_JOB_REPORTER::AdvancePhase()
{
PROGRESS_REPORTER_BASE::AdvancePhase();
m_job->m_currentProgress = m_phase;
m_monitor->jobUpdated( m_job );
}
BACKGROUND_JOBS_MONITOR::BACKGROUND_JOBS_MONITOR() : m_jobListDialog( nullptr )
{
}
BACKGROUND_JOB* BACKGROUND_JOBS_MONITOR::Create( const wxString& aName )
{
BACKGROUND_JOB* job = new BACKGROUND_JOB();
job->m_name = aName;
job->m_reporter = std::make_shared<BACKGROUND_JOB_REPORTER>( this, job );
m_jobs.push_back( job );
if( m_shownDialogs.size() > 0 )
{
// update dialogs
for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
{
list->Add( job );
}
}
return job;
}
void BACKGROUND_JOBS_MONITOR::Remove( BACKGROUND_JOB* aJob )
{
if( m_shownDialogs.size() > 0 )
{
// update dialogs
for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
{
list->Remove( aJob );
}
}
m_jobs.erase( std::remove_if( m_jobs.begin(), m_jobs.end(),
[&]( BACKGROUND_JOB* job )
{
return job == aJob;
} ) );
if( m_jobs.size() == 0 )
{
for( KISTATUSBAR* statusBar : m_statusBars )
{
statusBar->HideBackgroundProgressBar();
statusBar->SetBackgroundStatusText( wxT( "" ) );
}
}
delete aJob;
}
void BACKGROUND_JOBS_MONITOR::onListWindowClosed( wxCloseEvent& aEvent )
{
BACKGROUND_JOB_LIST* evtWindow = dynamic_cast<BACKGROUND_JOB_LIST*>( aEvent.GetEventObject() );
m_shownDialogs.erase( std::remove_if( m_shownDialogs.begin(), m_shownDialogs.end(),
[&]( BACKGROUND_JOB_LIST* dialog )
{
return dialog == evtWindow;
} ) );
aEvent.Skip();
}
void BACKGROUND_JOBS_MONITOR::ShowList( wxWindow* aParent, wxPoint aPos )
{
BACKGROUND_JOB_LIST* list = new BACKGROUND_JOB_LIST( aParent, aPos );
for( BACKGROUND_JOB* job : m_jobs )
{
list->Add( job );
}
m_shownDialogs.push_back( list );
list->Bind( wxEVT_CLOSE_WINDOW, &BACKGROUND_JOBS_MONITOR::onListWindowClosed, this );
// correct the position
wxSize windowSize = list->GetSize();
list->SetPosition( aPos - windowSize );
list->Show();
}
void BACKGROUND_JOBS_MONITOR::jobUpdated( BACKGROUND_JOB* aJob )
{
//for now, we go and update the status bar if its the first job in the vector
if( m_jobs.size() > 0 )
{
if( m_jobs.front() == aJob )
{
// update all status bar entries
for( KISTATUSBAR* statusBar : m_statusBars )
{
statusBar->ShowBackgroundProgressBar();
statusBar->SetBackgroundProgress( aJob->m_currentProgress );
statusBar->SetBackgroundProgressMax( aJob->m_maxProgress );
statusBar->SetBackgroundStatusText( aJob->m_status );
}
}
}
for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
{
list->UpdateJob( aJob );
}
}
void BACKGROUND_JOBS_MONITOR::RegisterStatusBar( KISTATUSBAR* aStatusBar )
{
m_statusBars.push_back( aStatusBar );
}
void BACKGROUND_JOBS_MONITOR::UnregisterStatusBar( KISTATUSBAR* aStatusBar )
{
m_statusBars.erase( std::remove_if( m_statusBars.begin(), m_statusBars.end(),
[&]( KISTATUSBAR* statusBar )
{
return statusBar == aStatusBar;
} ) );
}

View File

@ -45,6 +45,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
aBitmapInfoCache[BITMAPS::label_align_top].emplace_back( BITMAPS::label_align_top, wxT( "label_align_top_16.png" ), 16, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::label_align_bottom].emplace_back( BITMAPS::label_align_bottom, wxT( "label_align_bottom_16.png" ), 16, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::list_nets_16].emplace_back( BITMAPS::list_nets_16, wxT( "list_nets_16_16.png" ), 16, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::notifications].emplace_back( BITMAPS::notifications, wxT( "notifications_16.png" ), 16, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::options_generic_16].emplace_back( BITMAPS::options_generic_16, wxT( "options_generic_16_16.png" ), 16, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::pinorient_right].emplace_back( BITMAPS::pinorient_right, wxT( "pinorient_right_16.png" ), 16, wxT( "light" ) );
aBitmapInfoCache[BITMAPS::pinorient_left].emplace_back( BITMAPS::pinorient_left, wxT( "pinorient_left_16.png" ), 16, wxT( "light" ) );
@ -115,6 +116,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
aBitmapInfoCache[BITMAPS::label_align_top].emplace_back( BITMAPS::label_align_top, wxT( "label_align_top_dark_16.png" ), 16, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::label_align_bottom].emplace_back( BITMAPS::label_align_bottom, wxT( "label_align_bottom_dark_16.png" ), 16, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::list_nets_16].emplace_back( BITMAPS::list_nets_16, wxT( "list_nets_16_dark_16.png" ), 16, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::notifications].emplace_back( BITMAPS::notifications, wxT( "notifications_dark_16.png" ), 16, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::options_generic_16].emplace_back( BITMAPS::options_generic_16, wxT( "options_generic_16_dark_16.png" ), 16, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::pinorient_right].emplace_back( BITMAPS::pinorient_right, wxT( "pinorient_right_dark_16.png" ), 16, wxT( "dark" ) );
aBitmapInfoCache[BITMAPS::pinorient_left].emplace_back( BITMAPS::pinorient_left, wxT( "pinorient_left_dark_16.png" ), 16, wxT( "dark" ) );

View File

@ -0,0 +1,414 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2023 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/filename.h>
#include <wx/frame.h>
#include <wx/hyperlink.h>
#include <wx/panel.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <wx/settings.h>
#include <wx/stattext.h>
#include <wx/string.h>
#include <paths.h>
#include <notifications_manager.h>
#include <widgets/kistatusbar.h>
#include "core/wx_stl_compat.h"
#include <algorithm>
#include <fstream>
#include <map>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <tuple>
#include <vector>
#include <wx/string.h>
// Teaching json en/decoder to understand wxStrings
namespace nlohmann
{
template <>
struct adl_serializer<wxString>
{
static void to_json( json& j, const wxString& s ) { j = s.ToUTF8(); }
static void from_json( const json& j, wxString& s )
{
s = wxString::FromUTF8( j.get<std::string>().c_str() );
}
};
} // namespace nlohmann
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( NOTIFICATION, title, description, href, key, date )
class NOTIFICATION_PANEL : public wxPanel
{
public:
NOTIFICATION_PANEL( wxWindow* aParent, NOTIFICATIONS_MANAGER* aManager, NOTIFICATION* aNoti ) :
wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, 75 ),
wxBORDER_SIMPLE ),
m_hlDetails( nullptr ),
m_notification( aNoti ),
m_manager( aManager )
{
SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* mainSizer;
mainSizer = new wxBoxSizer( wxVERTICAL );
SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
m_stTitle = new wxStaticText( this, wxID_ANY, aNoti->title );
m_stTitle->Wrap( -1 );
m_stTitle->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT,
wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
mainSizer->Add( m_stTitle, 0, wxALL | wxEXPAND, 1 );
m_stDescription = new wxStaticText( this, wxID_ANY, aNoti->description, wxDefaultPosition,
wxDefaultSize, 0 );
m_stDescription->Wrap( -1 );
mainSizer->Add( m_stDescription, 0, wxALL | wxEXPAND, 1 );
wxBoxSizer* tailSizer;
tailSizer = new wxBoxSizer( wxHORIZONTAL );
if( !aNoti->href.IsEmpty() )
{
m_hlDetails =
new wxHyperlinkCtrl( this, wxID_ANY, _( "View Details" ), aNoti->href,
wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE );
tailSizer->Add( m_hlDetails, 0, wxALL, 2 );
}
m_hlDismiss = new wxHyperlinkCtrl( this, wxID_ANY, _( "Dismiss" ), aNoti->href,
wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE );
tailSizer->Add( m_hlDismiss, 0, wxALL, 2 );
mainSizer->Add( tailSizer, 1, wxEXPAND, 5 );
if( m_hlDetails != nullptr )
{
m_hlDetails->Bind( wxEVT_HYPERLINK, &NOTIFICATION_PANEL::onDetails, this );
}
m_hlDismiss->Bind( wxEVT_HYPERLINK, &NOTIFICATION_PANEL::onDismiss, this );
Bind( wxEVT_SET_FOCUS, &NOTIFICATION_PANEL::onFocusSet, this );
SetSizer( mainSizer );
Layout();
}
private:
void onFocusSet( wxFocusEvent& aEvent )
{
// hmmph
}
void onDetails( wxHyperlinkEvent& aEvent )
{
wxString url = aEvent.GetURL();
if( url.StartsWith( wxS( "kicad://" ) ) )
{
url.Replace( wxS( "kicad://" ), wxS( "" ) );
if( url == wxS( "pcm" ) )
{
// TODO
}
}
else
{
wxLaunchDefaultBrowser( aEvent.GetURL(), wxBROWSER_NEW_WINDOW );
}
}
void onDismiss( wxHyperlinkEvent& aEvent )
{
CallAfter(
[=]()
{
// This will cause this panel to get deleted
m_manager->Remove( m_notification->key );
} );
}
wxStaticText* m_stTitle;
wxStaticText* m_stDescription;
wxHyperlinkCtrl* m_hlDetails;
wxHyperlinkCtrl* m_hlDismiss;
NOTIFICATION* m_notification;
NOTIFICATIONS_MANAGER* m_manager;
};
class NOTIFICATIONS_LIST : public wxFrame
{
public:
NOTIFICATIONS_LIST( NOTIFICATIONS_MANAGER* aManager, wxWindow* parent, const wxPoint& pos ) :
wxFrame( parent, wxID_ANY, _( "Notifications" ), pos, wxSize( 300, 150 ), wxFRAME_NO_TASKBAR ),
m_manager( aManager )
{
SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* bSizer1;
bSizer1 = new wxBoxSizer( wxVERTICAL );
m_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition,
wxSize( -1, -1 ), wxALWAYS_SHOW_SB | wxVSCROLL );
m_scrolledWindow->SetScrollRate( 5, 5 );
m_contentSizer = new wxBoxSizer( wxVERTICAL );
m_scrolledWindow->SetSizer( m_contentSizer );
m_scrolledWindow->Layout();
m_contentSizer->Fit( m_scrolledWindow );
bSizer1->Add( m_scrolledWindow, 1, wxEXPAND | wxALL, 0 );
Bind( wxEVT_KILL_FOCUS, &NOTIFICATIONS_LIST::onFocusLoss, this );
SetSizer( bSizer1 );
Layout();
SetFocus();
}
void onFocusLoss( wxFocusEvent& aEvent )
{
// check if a child like say, the hyperlink texts got focus
if( !IsDescendant( aEvent.GetWindow() ) )
Close( true );
aEvent.Skip();
}
void Add( NOTIFICATION* aNoti )
{
NOTIFICATION_PANEL* panel = new NOTIFICATION_PANEL( m_scrolledWindow, m_manager, aNoti );
m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 );
m_scrolledWindow->Layout();
m_contentSizer->Fit( m_scrolledWindow );
// call this at this window otherwise the child panels dont resize width properly
Layout();
m_panelMap[aNoti] = panel;
}
void Remove( NOTIFICATION* aNoti )
{
auto it = m_panelMap.find( aNoti );
if( it != m_panelMap.end() )
{
NOTIFICATION_PANEL* panel = m_panelMap[aNoti];
m_contentSizer->Detach( panel );
panel->Destroy();
m_panelMap.erase( it );
}
}
private:
wxScrolledWindow* m_scrolledWindow;
wxBoxSizer* m_contentSizer;
std::unordered_map<NOTIFICATION*, NOTIFICATION_PANEL*> m_panelMap;
NOTIFICATIONS_MANAGER* m_manager;
};
NOTIFICATIONS_MANAGER::NOTIFICATIONS_MANAGER()
{
m_destFileName = wxFileName( PATHS::GetUserCachePath(), wxT( "notifications.json" ) );
}
void NOTIFICATIONS_MANAGER::Load()
{
nlohmann::json saved_json;
std::ifstream saved_json_stream( m_destFileName.GetFullPath().ToUTF8() );
try
{
saved_json_stream >> saved_json;
m_notifications = saved_json.get<std::vector<NOTIFICATION>>();
}
catch( std::exception& )
{
// failed to load the json
return;
}
}
void NOTIFICATIONS_MANAGER::Save()
{
std::ofstream jsonFileStream( m_destFileName.GetFullPath().ToUTF8() );
nlohmann::json saveJson = nlohmann::json( m_notifications );
jsonFileStream << std::setw( 4 ) << saveJson << std::endl;
jsonFileStream.flush();
jsonFileStream.close();
}
void NOTIFICATIONS_MANAGER::Create( const wxString& aKey,
const wxString& aTitle,
const wxString& aDescription,
const wxString& aHref )
{
auto it = std::find_if( m_notifications.begin(), m_notifications.end(),
[&]( const NOTIFICATION& noti )
{
return noti.key == aKey;
} );
if( it != m_notifications.end() )
{
NOTIFICATION& noti = *it;
noti.title = aTitle;
noti.description = aDescription;
noti.href = aHref;
}
else
{
m_notifications.emplace_back( NOTIFICATION{ aTitle, aDescription, aHref, aKey } );
}
if( m_shownDialogs.size() > 0 )
{
// update dialogs
for( NOTIFICATIONS_LIST* list : m_shownDialogs )
{
list->Add( &m_notifications.back() );
}
}
for( KISTATUSBAR* statusBar : m_statusBars )
{
statusBar->SetNotificationCount( m_notifications.size() );
}
Save();
}
void NOTIFICATIONS_MANAGER::Remove( const wxString& aKey )
{
auto it = std::find_if( m_notifications.begin(), m_notifications.end(),
[&]( const NOTIFICATION& noti )
{
return noti.key == aKey;
} );
if( it == m_notifications.end() )
{
return;
}
if( m_shownDialogs.size() > 0 )
{
// update dialogs
for( NOTIFICATIONS_LIST* list : m_shownDialogs )
{
list->Remove( &(*it) );
}
}
m_notifications.erase( it );
Save();
for( KISTATUSBAR* statusBar : m_statusBars )
{
statusBar->SetNotificationCount( m_notifications.size() );
}
}
void NOTIFICATIONS_MANAGER::onListWindowClosed( wxCloseEvent& aEvent )
{
NOTIFICATIONS_LIST* evtWindow = dynamic_cast<NOTIFICATIONS_LIST*>( aEvent.GetEventObject() );
m_shownDialogs.erase( std::remove_if( m_shownDialogs.begin(), m_shownDialogs.end(),
[&]( NOTIFICATIONS_LIST* dialog )
{
return dialog == evtWindow;
} ) );
aEvent.Skip();
}
void NOTIFICATIONS_MANAGER::ShowList( wxWindow* aParent, wxPoint aPos )
{
NOTIFICATIONS_LIST* list = new NOTIFICATIONS_LIST( this, aParent, aPos );
for( NOTIFICATION& job : m_notifications )
{
list->Add( &job );
}
m_shownDialogs.push_back( list );
list->Bind( wxEVT_CLOSE_WINDOW, &NOTIFICATIONS_MANAGER::onListWindowClosed, this );
// correct the position
wxSize windowSize = list->GetSize();
list->SetPosition( aPos - windowSize );
list->Show();
}
void NOTIFICATIONS_MANAGER::RegisterStatusBar( KISTATUSBAR* aStatusBar )
{
m_statusBars.push_back( aStatusBar );
// notifications should already be loaded so set the initial notification count
aStatusBar->SetNotificationCount( m_notifications.size() );
}
void NOTIFICATIONS_MANAGER::UnregisterStatusBar( KISTATUSBAR* aStatusBar )
{
m_statusBars.erase( std::remove_if( m_statusBars.begin(), m_statusBars.end(),
[&]( KISTATUSBAR* statusBar )
{
return statusBar == aStatusBar;
} ) );
}

View File

@ -43,6 +43,7 @@
#include <wx/tooltip.h>
#include <advanced_config.h>
#include <background_jobs_monitor.h>
#include <bitmaps.h>
#include <cli/cli_names.h> // Needed for the pre wx 3.2 cli workaround
#include <common.h>
@ -57,6 +58,7 @@
#include <kiplatform/policy.h>
#include <macros.h>
#include <menus_helpers.h>
#include <notifications_manager.h>
#include <paths.h>
#include <pgm_base.h>
#include <policy_keys.h>
@ -511,6 +513,8 @@ bool PGM_BASE::InitPgm( bool aHeadless, bool aSkipPyInit, bool aIsUnitTest )
SetDefaultLanguage( tmp );
m_settings_manager = std::make_unique<SETTINGS_MANAGER>( aHeadless );
m_background_jobs_monitor = std::make_unique<BACKGROUND_JOBS_MONITOR>();
m_notifications_manager = std::make_unique<NOTIFICATIONS_MANAGER>();
// Our unit test mocks break if we continue
// A bug caused InitPgm to terminate early in unit tests and the mocks are...simplistic
@ -547,6 +551,8 @@ bool PGM_BASE::InitPgm( bool aHeadless, bool aSkipPyInit, bool aIsUnitTest )
ReadPdfBrowserInfos(); // needs GetCommonSettings()
GetNotificationsManager().Load();
// Create the python scripting stuff
// Skip it fot applications that do not use it
if( !aSkipPyInit )

View File

@ -41,7 +41,8 @@ BITMAP_BUTTON::BITMAP_BUTTON( wxWindow* aParent, wxWindowID aId, const wxPoint&
m_badgeTextColor( wxColor( wxT( "white" ) ) ),
m_buttonState( 0 ),
m_padding( 0 ),
m_acceptDraggedInClicks( false )
m_acceptDraggedInClicks( false ),
m_centerBitmap( false )
{
if( aSize == wxDefaultSize )
SetMinSize( wxButton::GetDefaultSize() + wxSize( m_padding * 2, m_padding * 2) );
@ -57,13 +58,18 @@ BITMAP_BUTTON::BITMAP_BUTTON( wxWindow* aParent, wxWindowID aId, const wxBitmap&
wxPanel( aParent, aId, aPos, aSize, aStyles ),
m_isRadioButton( false ),
m_showBadge( false ),
m_badgeColor( wxColor( 210, 0, 0 ) ), // dark red
m_badgeTextColor( wxColor( wxT( "white" ) ) ),
m_buttonState( 0 ),
m_padding( 5 ),
m_acceptDraggedInClicks( false )
m_acceptDraggedInClicks( false ),
m_centerBitmap( false )
{
if( aSize == wxDefaultSize )
SetMinSize( wxButton::GetDefaultSize() + wxSize( m_padding * 2, m_padding * 2) );
m_badgeFont = GetFont().Smaller().MakeBold();
setupEvents();
}
@ -144,9 +150,9 @@ void BITMAP_BUTTON::OnMouseEnter( wxEvent& aEvent )
void BITMAP_BUTTON::OnKillFocus( wxEvent& aEvent )
{
if( hasFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED ) )
if( hasFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED | wxCONTROL_SELECTED ) )
{
clearFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED );
clearFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED | wxCONTROL_SELECTED );
Refresh();
}
@ -279,18 +285,30 @@ void BITMAP_BUTTON::OnPaint( wxPaintEvent& aEvent )
const wxBitmapBundle& bmp = hasFlag( wxCONTROL_DISABLED ) ? m_disabledBitmap : m_normalBitmap;
wxPoint drawBmpPos( m_padding, m_padding );
wxBitmap bmpImg = bmp.GetBitmapFor( this );
if( m_centerBitmap )
{
drawBmpPos = wxPoint( (rect.width - bmpImg.GetWidth()) / 2, (rect.height - bmpImg.GetHeight()) / 2 );
}
// Draw the bitmap with the upper-left corner offset by the padding
if( bmp.IsOk() )
dc.DrawBitmap( bmp.GetBitmapFor( this ), m_padding, m_padding, true );
dc.DrawBitmap( bmpImg, drawBmpPos, true );
// Draw the badge
if( m_showBadge )
{
dc.SetFont( m_badgeFont );
wxSize box_size = dc.GetTextExtent( m_badgeText ) + wxSize( 6, 2 );
wxSize box_offset = box_size + wxSize( m_padding - 2, m_padding );
wxSize text_offset = box_offset - wxSize( 3, 1 );
wxSize box_size = dc.GetTextExtent( m_badgeText );
wxSize box_offset = box_size;
wxSize text_offset = box_offset;
if( m_padding != 0 )
{
box_offset += wxSize( m_padding - 2, m_padding );
text_offset -= wxSize( 3, 1 );
}
dc.SetPen( wxPen( m_badgeColor ) );
dc.SetBrush( wxBrush( m_badgeColor ) );

View File

@ -0,0 +1,197 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2023 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 <array>
#include <widgets/kistatusbar.h>
#include <widgets/bitmap_button.h>
#include <pgm_base.h>
#include <background_jobs_monitor.h>
#include <notifications_manager.h>
#include <bitmaps.h>
KISTATUSBAR::KISTATUSBAR( int aNumberFields, wxWindow* parent, wxWindowID id ) :
wxStatusBar( parent, id ),
m_normalFieldsCount( aNumberFields )
{
const int ExtraFields = 4;
SetFieldsCount( aNumberFields + ExtraFields );
int* widths = new int[aNumberFields + ExtraFields];
for( int i = 0; i < aNumberFields; i++ )
widths[i] = -1;
widths[aNumberFields] = 200;
widths[aNumberFields + 1] = 75;
widths[aNumberFields + 2] = 20;
widths[aNumberFields + 3] = 20;
SetStatusWidths( aNumberFields + ExtraFields, widths );
delete[] widths;
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 );
m_backgroundProgressBar = new wxGauge( this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize,
wxGA_HORIZONTAL | wxGA_SMOOTH );
m_backgroundStopButton =
new wxButton( this, wxID_ANY, "X", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
m_notificationsButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
wxDefaultSize, wxBU_EXACTFIT );
m_notificationsButton->SetPadding( 0 );
m_notificationsButton->SetBitmap( KiBitmap( BITMAPS::notifications ) );
m_notificationsButton->SetShowBadge( true );
m_notificationsButton->SetBitmapCentered( true );
m_notificationsButton->Bind( wxEVT_BUTTON, &KISTATUSBAR::onNotificationsIconClick, this );
Bind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
m_backgroundProgressBar->Bind( wxEVT_LEFT_DOWN, &KISTATUSBAR::onBackgroundProgressClick, this );
HideBackgroundProgressBar();
Layout();
}
void KISTATUSBAR::onNotificationsIconClick( wxCommandEvent& aEvent )
{
wxPoint pos = m_notificationsButton->GetScreenPosition();
wxRect r;
GetFieldRect( m_normalFieldsCount + 3, r );
pos.x += r.GetWidth();
Pgm().GetNotificationsManager().ShowList( this, pos );
}
void KISTATUSBAR::onBackgroundProgressClick( wxMouseEvent& aEvent )
{
wxPoint pos = m_backgroundProgressBar->GetScreenPosition();
wxRect r;
GetFieldRect( m_normalFieldsCount + 1, r );
pos.x += r.GetWidth();
Pgm().GetBackgroundJobMonitor().ShowList( this, pos );
}
void KISTATUSBAR::onSize( wxSizeEvent& aEvent )
{
wxRect r;
GetFieldRect( m_normalFieldsCount, r );
int x = r.GetLeft();
int y = r.GetTop();
m_backgroundTxt->SetPosition( { x, y } );
GetFieldRect( m_normalFieldsCount + 1, r );
x = r.GetLeft();
y = r.GetTop();
int w = r.GetWidth();
int h = r.GetHeight();
constexpr int b = 5;
auto buttonSize = m_backgroundStopButton->GetEffectiveMinSize();
m_backgroundStopButton->SetPosition( { x + w - buttonSize.GetWidth(), y } );
m_backgroundStopButton->SetSize( buttonSize.GetWidth(), h );
m_backgroundProgressBar->SetPosition( { x, y } );
m_backgroundProgressBar->SetSize( w - buttonSize.GetWidth() - b, h );
GetFieldRect( m_normalFieldsCount+3, 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 );
}
void KISTATUSBAR::ShowBackgroundProgressBar( bool aCancellable )
{
m_backgroundProgressBar->Show();
if( aCancellable )
m_backgroundStopButton->Show();
else
m_backgroundStopButton->Hide();
}
void KISTATUSBAR::HideBackgroundProgressBar()
{
m_backgroundProgressBar->Hide();
m_backgroundStopButton->Hide();
}
void KISTATUSBAR::SetBackgroundProgress( int aAmount )
{
m_backgroundProgressBar->SetValue( aAmount );
}
void KISTATUSBAR::SetBackgroundProgressMax( int aAmount )
{
m_backgroundProgressBar->SetRange( aAmount );
}
void KISTATUSBAR::SetBackgroundStatusText( const wxString& aTxt )
{
m_backgroundTxt->SetLabel( aTxt );
}
void KISTATUSBAR::SetNotificationCount(int aCount)
{
wxString cnt = "";
if( aCount > 0 )
{
cnt = wxString::Format( "%d", aCount );
}
m_notificationsButton->SetBadgeText( cnt );
// force a repaint or it wont until it gets activity
Refresh();
}

View File

@ -0,0 +1,136 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2023 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
*/
#ifndef BACKGROUND_JOBS_MONITOR_H
#define BACKGROUND_JOBS_MONITOR_H
#include <widgets/progress_reporter_base.h>
#include <functional>
#include <memory>
#include <vector>
class PROGRESS_REPORTER;
class wxString;
class KISTATUSBAR;
struct BACKGROUND_JOB;
class BACKGROUND_JOB_REPORTER;
class BACKGROUND_JOB_LIST;
class BACKGROUND_JOBS_MONITOR;
class wxWindow;
class wxCloseEvent;
class BACKGROUND_JOB_REPORTER : public PROGRESS_REPORTER_BASE
{
public:
BACKGROUND_JOB_REPORTER( BACKGROUND_JOBS_MONITOR* aMonitor, BACKGROUND_JOB* aJob );
void SetTitle( const wxString& aTitle ) override
{
}
void Report( const wxString& aMessage ) override;
void Cancel() { m_cancelled.store( true ); }
void AdvancePhase() override;
void SetNumPhases( int aNumPhases ) override;
private:
bool updateUI() override;
BACKGROUND_JOBS_MONITOR* m_monitor;
BACKGROUND_JOB* m_job;
wxString m_title;
wxString m_report;
};
struct BACKGROUND_JOB
{
public:
wxString m_name;
wxString m_status;
std::shared_ptr<BACKGROUND_JOB_REPORTER> m_reporter;
int m_maxProgress;
int m_currentProgress;
};
class BACKGROUND_JOBS_MONITOR
{
friend class BACKGROUND_JOB_REPORTER;
friend class BACKGROUND_JOB_LIST;
public:
BACKGROUND_JOBS_MONITOR();
/**
* Creates a background job with the given name
*
* @param aName is the displayed title for the event
*/
BACKGROUND_JOB* Create( const wxString& aName );
/**
* Removes the given background job from any lists and frees it
*/
void Remove( BACKGROUND_JOB* job );
/**
* Shows the background job list
*/
void ShowList( wxWindow* aParent, wxPoint aPos );
/**
* Add a status bar for handling
*/
void RegisterStatusBar( KISTATUSBAR* aStatusBar );
/**
* Removes status bar from handling
*/
void UnregisterStatusBar( KISTATUSBAR* aStatusBar );
private:
/**
* Handles removing the shown list window from our list of shown windows
*/
void onListWindowClosed( wxCloseEvent& aEvent );
/**
* Handles job status updates, intended to be called by BACKGROUND_JOB_REPORTER only
*/
void jobUpdated( BACKGROUND_JOB* aJob );
BACKGROUND_JOB_LIST* m_jobListDialog;
std::vector<BACKGROUND_JOB*> m_jobs;
std::vector<BACKGROUND_JOB_LIST*> m_shownDialogs;
std::vector<KISTATUSBAR*> m_statusBars;
};
#endif

View File

@ -370,6 +370,7 @@ enum class BITMAPS : unsigned int
new_project_from_template,
new_python,
noconn,
notifications,
normal,
open_project,
open_project_demo,

View File

@ -0,0 +1,119 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2023 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
*/
#ifndef NOTIFICATIONS_MANAGER_H
#define NOTIFICATIONS_MANAGER_H
#include <functional>
#include <vector>
class wxString;
class KISTATUSBAR;
struct NOTIFICATION;
class NOTIFICATIONS_LIST;
class wxWindow;
class wxCloseEvent;
struct NOTIFICATION
{
public:
wxString title; ///< Title of the notification
wxString description; ///< Additional message displayed under title
wxString href; ///< URL if any to link to for details
wxString key; ///< Unique key to find a notification
wxString date; ///< Date notification will display
};
class NOTIFICATIONS_MANAGER
{
friend class NOTIFICATION_LIST;
public:
NOTIFICATIONS_MANAGER();
/**
* Creates a notification with the given parameters
* @param aKey is a unique key for the notification, this allows removing
* @param aTitle is the displayed title for the event
* @param aDescription is the text that displays underneath the title and has slightly more info
* them later programtically in case a notificaiton is no logner required
* @param aHref is link to external or internal content
*/
void Create( const wxString& aKey, const wxString& aTitle, const wxString& aDescription,
const wxString& aHref = wxEmptyString );
/**
* Remove a notification by key
*
* @param aKey is the unique key to locate
*/
void Remove( const wxString& aKey );
/**
* Loads notifications stored from disk
*/
void Load();
/**
* Saves notifications to disk
*/
void Save();
/**
* Shows the notification list
*/
void ShowList( wxWindow* aParent, wxPoint aPos );
/**
* Add a status bar for handling
*/
void RegisterStatusBar( KISTATUSBAR* aStatusBar );
/**
* Removes status bar from handling
*/
void UnregisterStatusBar( KISTATUSBAR* aStatusBar );
private:
/**
* Handles removing the shown list window from our list of shown windows
*/
void onListWindowClosed( wxCloseEvent& aEvent );
///< Current stack of notifications
std::vector<NOTIFICATION> m_notifications;
///< Currently shown notification lists
std::vector<NOTIFICATIONS_LIST*> m_shownDialogs;
///< Status bars registered for updates
std::vector<KISTATUSBAR*> m_statusBars;
///< The cached file path to read/write notifications on disk
wxFileName m_destFileName;
};
#endif

View File

@ -48,6 +48,8 @@ class wxMenu;
class wxWindow;
class wxSplashScreen;
class BACKGROUND_JOBS_MONITOR;
class NOTIFICATIONS_MANAGER;
class COMMON_SETTINGS;
class SETTINGS_MANAGER;
class SCRIPTING;
@ -136,7 +138,11 @@ public:
virtual SETTINGS_MANAGER& GetSettingsManager() const { return *m_settings_manager; }
virtual COMMON_SETTINGS* GetCommonSettings() const;
virtual COMMON_SETTINGS* GetCommonSettings() const;
virtual BACKGROUND_JOBS_MONITOR& GetBackgroundJobMonitor() const { return *m_background_jobs_monitor; }
virtual NOTIFICATIONS_MANAGER& GetNotificationsManager() const { return *m_notifications_manager; }
virtual void SetTextEditor( const wxString& aFileName );
@ -396,6 +402,8 @@ protected:
protected:
std::unique_ptr<SETTINGS_MANAGER> m_settings_manager;
std::unique_ptr<BACKGROUND_JOBS_MONITOR> m_background_jobs_monitor;
std::unique_ptr<NOTIFICATIONS_MANAGER> m_notifications_manager;
std::unique_ptr<SCRIPTING> m_python_scripting;

View File

@ -115,6 +115,11 @@ public:
m_badgeTextColor = aBadgeTextColor;
}
void SetBitmapCentered( bool aCentered )
{
m_centerBitmap = aCentered;
}
protected:
void setupEvents();
@ -157,6 +162,9 @@ private:
///< Accept mouse-up as click even if mouse-down happened outside of the control
bool m_acceptDraggedInClicks;
///< Draws bitmap centered in the control
bool m_centerBitmap;
};
#endif /*BITMAP_BUTTON_H_*/

View File

@ -0,0 +1,83 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2023 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
*/
#ifndef KISTATUSBAR_H
#define KISTATUSBAR_H
class wxGauge;
class wxButton;
class wxStaticText;
class BITMAP_BUTTON;
class KISTATUSBAR : public wxStatusBar
{
public:
KISTATUSBAR( int aNumberFields, wxWindow* parent, wxWindowID id );
public:
/**
* Shows the background progress bar
*/
void ShowBackgroundProgressBar( bool aCancellable = false );
/**
* Hides the background progress bar
*/
void HideBackgroundProgressBar();
/**
* Sets the current progress of the progress bar
*/
void SetBackgroundProgress( int aAmount );
/**
* Sets the maX progress of the progress bar
*/
void SetBackgroundProgressMax( int aAmount );
/**
* Sets the status text that displays next to the progress bar
*/
void SetBackgroundStatusText( const wxString& aTxt );
/**
* Sets the notification count on the notifications button
* A value of 0 will hide the count
*/
void SetNotificationCount( int aCount );
private:
void OnSize( wxSizeEvent& aEvent );
void onBackgroundProgressClick( wxMouseEvent& aEvent );
void onNotificationsIconClick( wxCommandEvent& aEvent );
private:
wxGauge* m_backgroundProgressBar;
wxButton* m_backgroundStopButton;
wxStaticText* m_backgroundTxt;
BITMAP_BUTTON* m_notificationsButton;
int m_normalFieldsCount;
};
#endif

View File

@ -30,6 +30,7 @@
#include "widgets/bitmap_button.h"
#include <advanced_config.h>
#include <background_jobs_monitor.h>
#include <bitmaps.h>
#include <build_version.h>
#include <dialogs/panel_kicad_launcher.h>
@ -44,6 +45,7 @@
#include <kiway.h>
#include <kiway_express.h>
#include <launch_ext.h>
#include <notifications_manager.h>
#include <reporter.h>
#include <project/project_local_settings.h>
#include <sch_file_versions.h>
@ -57,6 +59,7 @@
#include <tools/kicad_manager_control.h>
#include <wildcards_and_files_ext.h>
#include <widgets/app_progress_dialog.h>
#include <widgets/kistatusbar.h>
#include <wx/ffile.h>
#include <wx/filedlg.h>
#include <wx/dcclient.h>
@ -134,7 +137,9 @@ KICAD_MANAGER_FRAME::KICAD_MANAGER_FRAME( wxWindow* parent, const wxString& titl
// Create the status line (bottom of the frame). Left half is for project name; right half
// is for Reporter (currently used by archiver/unarchiver and PCM).
CreateStatusBar( 2 );
CreateStatusBar( 3 );
Pgm().GetBackgroundJobMonitor().RegisterStatusBar( (KISTATUSBAR*) GetStatusBar() );
Pgm().GetNotificationsManager().RegisterStatusBar( (KISTATUSBAR*) GetStatusBar() );
GetStatusBar()->SetFont( KIUI::GetStatusFont( this ) );
// Give an icon
@ -246,6 +251,9 @@ KICAD_MANAGER_FRAME::KICAD_MANAGER_FRAME( wxWindow* parent, const wxString& titl
KICAD_MANAGER_FRAME::~KICAD_MANAGER_FRAME()
{
Pgm().GetBackgroundJobMonitor().UnregisterStatusBar( (KISTATUSBAR*) GetStatusBar() );
Pgm().GetNotificationsManager().UnregisterStatusBar( (KISTATUSBAR*) GetStatusBar() );
// Shutdown all running tools
if( m_toolManager )
m_toolManager->ShutdownAllTools();
@ -261,6 +269,13 @@ KICAD_MANAGER_FRAME::~KICAD_MANAGER_FRAME()
}
wxStatusBar* KICAD_MANAGER_FRAME::OnCreateStatusBar( int number, long style, wxWindowID id,
const wxString& name )
{
return new KISTATUSBAR( number, this, id );
}
void KICAD_MANAGER_FRAME::CreatePCM()
{
// creates the PLUGIN_CONTENT_MANAGER, if not exists
@ -271,20 +286,26 @@ void KICAD_MANAGER_FRAME::CreatePCM()
[this]( int aUpdateCount )
{
m_pcmUpdateCount = aUpdateCount;
if( aUpdateCount > 0 )
{
Pgm().GetNotificationsManager().Create(
wxS( "pcm" ),
_( "PCM Updates Available" ),
wxString::Format( _( "%d package update(s) avaliable" ), aUpdateCount ),
wxT( "" ) );
}
else
{
Pgm().GetNotificationsManager().Remove( wxS( "pcm" ) );
}
CallAfter(
[this]()
{
updatePcmButtonBadge();
} );
},
[this]( const wxString aText )
{
CallAfter(
[aText, this]()
{
SetStatusText( aText, 1 );
} );
} );
});
m_pcm->SetRepositoryList( kicadSettings()->m_PcmRepositories );
}

View File

@ -62,6 +62,8 @@ public:
void OnFileHistory( wxCommandEvent& event );
void OnClearFileHistory( wxCommandEvent& aEvent );
void OnExit( wxCommandEvent& event );
wxStatusBar* OnCreateStatusBar( int number, long style, wxWindowID id,
const wxString& name ) override;
void RecreateBaseHToolbar();

View File

@ -24,6 +24,7 @@
#include <kicad_curl/kicad_curl.h>
#include "core/wx_stl_compat.h"
#include <background_jobs_monitor.h>
#include "build_version.h"
#include "paths.h"
#include "pcm.h"
@ -59,47 +60,10 @@ class THROWING_ERROR_HANDLER : public nlohmann::json_schema::error_handler
};
class STATUS_TEXT_REPORTER : public PROGRESS_REPORTER_BASE
{
public:
STATUS_TEXT_REPORTER( std::function<void( const wxString )> aStatusCallback ) :
PROGRESS_REPORTER_BASE( 1 ), m_statusCallback( aStatusCallback )
{
}
void SetTitle( const wxString& aTitle ) override
{
m_title = aTitle;
m_report = wxT( "" );
}
void Report( const wxString& aMessage ) override
{
m_report = wxString::Format( wxT( ": %s" ), aMessage );
}
void Cancel() { m_cancelled.store( true ); }
private:
bool updateUI() override
{
m_statusCallback( wxString::Format( wxT( "%s%s" ), m_title, m_report ) );
return true;
}
const std::function<void( const wxString )> m_statusCallback;
wxString m_title;
wxString m_report;
};
PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER(
std::function<void( int )> aAvailableUpdateCallback,
std::function<void( const wxString )> aStatusCallback ) :
std::function<void( int )> aAvailableUpdateCallback ) :
m_dialog( nullptr ),
m_availableUpdateCallback( aAvailableUpdateCallback ),
m_statusCallback( aStatusCallback )
m_availableUpdateCallback( aAvailableUpdateCallback )
{
ReadEnvVar();
@ -437,7 +401,7 @@ bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryId )
if( m_dialog )
reporter = std::make_shared<WX_PROGRESS_REPORTER>( m_dialog, wxEmptyString, 1 );
else
reporter = m_statusReporter;
reporter = m_updateBackgroundJob->m_reporter;
if( !FetchRepository( url, current_repo, reporter.get() ) )
return false;
@ -1136,7 +1100,8 @@ void PLUGIN_CONTENT_MANAGER::RunBackgroundUpdate()
if( m_updateThread.joinable() )
return;
m_statusReporter = std::make_shared<STATUS_TEXT_REPORTER>( m_statusCallback );
m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "PCM Update" ) );
m_updateThread = std::thread(
[this]()
@ -1144,6 +1109,10 @@ void PLUGIN_CONTENT_MANAGER::RunBackgroundUpdate()
if( m_installed.size() == 0 )
return;
int maxProgress = m_repository_list.size() + m_installed.size();
m_updateBackgroundJob->m_reporter->SetNumPhases( maxProgress );
m_updateBackgroundJob->m_reporter->Report( _( "Preparing to fetch repositories" ) );
// Only fetch repositories that have installed not pinned packages
std::unordered_set<wxString> repo_ids;
@ -1155,25 +1124,31 @@ void PLUGIN_CONTENT_MANAGER::RunBackgroundUpdate()
for( const auto& [ repository_id, name, url ] : m_repository_list )
{
m_updateBackgroundJob->m_reporter->AdvancePhase();
if( repo_ids.count( repository_id ) == 0 )
continue;
m_updateBackgroundJob->m_reporter->Report(
_( "Fetching repository..." ) );
CacheRepository( repository_id );
if( m_statusReporter->IsCancelled() )
if( m_updateBackgroundJob->m_reporter->IsCancelled() )
break;
}
if( m_statusReporter->IsCancelled() )
if( m_updateBackgroundJob->m_reporter->IsCancelled() )
return;
// Count packages with updates
int availableUpdateCount = 0;
m_updateBackgroundJob->m_reporter->Report( _( "Reviewing packages..." ) );
for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
{
PCM_INSTALLATION_ENTRY& entry = pair.second;
m_updateBackgroundJob->m_reporter->AdvancePhase();
if( m_repository_cache.find( entry.repository_id ) != m_repository_cache.end() )
{
PCM_PACKAGE_STATE state = GetPackageState( entry.repository_id,
@ -1183,15 +1158,15 @@ void PLUGIN_CONTENT_MANAGER::RunBackgroundUpdate()
availableUpdateCount++;
}
if( m_statusReporter->IsCancelled() )
if( m_updateBackgroundJob->m_reporter->IsCancelled() )
return;
}
Pgm().GetBackgroundJobMonitor().Remove( m_updateBackgroundJob );
m_updateBackgroundJob = nullptr;
// Update the badge on PCM button
m_availableUpdateCallback( availableUpdateCount );
m_statusCallback( availableUpdateCount > 0 ? _( "Package updates are available" )
: _( "No package updates available" ) );
} );
}
@ -1200,7 +1175,8 @@ void PLUGIN_CONTENT_MANAGER::StopBackgroundUpdate()
{
if( m_updateThread.joinable() )
{
m_statusReporter->Cancel();
if( m_updateBackgroundJob )
m_updateBackgroundJob->m_reporter->Cancel();
m_updateThread.join();
}
}

View File

@ -76,7 +76,7 @@ typedef std::vector<std::pair<wxString, wxString>> STRING_PAIR_LIST;
typedef std::vector<std::tuple<wxString, wxString, wxString>> STRING_TUPLE_LIST;
class STATUS_TEXT_REPORTER;
struct BACKGROUND_JOB;
/**
@ -101,8 +101,7 @@ class STATUS_TEXT_REPORTER;
class PLUGIN_CONTENT_MANAGER
{
public:
PLUGIN_CONTENT_MANAGER( std::function<void( int )> aAvailableUpdateCallback,
std::function<void( const wxString )> aStatusCallback );
PLUGIN_CONTENT_MANAGER( std::function<void( int )> aAvailableUpdateCallbac );
~PLUGIN_CONTENT_MANAGER();
/**
@ -400,10 +399,9 @@ private:
std::map<wxString, PCM_INSTALLATION_ENTRY> m_installed;
const static std::tuple<int, int, int> m_kicad_version;
std::function<void( int )> m_availableUpdateCallback;
std::function<void( const wxString )> m_statusCallback;
std::thread m_updateThread;
std::shared_ptr<STATUS_TEXT_REPORTER> m_statusReporter;
BACKGROUND_JOB* m_updateBackgroundJob;
};
#endif // PCM_H_

View File

@ -87,6 +87,7 @@ set( BMAPS_SMALL
label_align_top
label_align_bottom
list_nets_16
notifications
options_generic_16
pinorient_right
pinorient_left

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Слой_1"
data-name="Слой 1"
viewBox="0 0 16 16"
version="1.1"
sodipodi:docname="notifications.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
inkscape:export-filename="..\..\png\notifications_dark_16.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
width="16"
height="16"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1027"
id="namedview30"
showgrid="true"
inkscape:zoom="45.254834"
inkscape:cx="7.7892231"
inkscape:cy="7.8334173"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:document-rotation="0"
inkscape:current-layer="Слой_1"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid_kicad"
spacingx="0.5"
spacingy="0.5"
color="#9999ff"
opacity="0.13"
empspacing="2"
originx="-2.0583608"
originy="-0.53240496"
units="px"
visible="true" />
</sodipodi:namedview>
<metadata
id="metadata43">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>drc</dc:title>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<defs
id="defs116686">
<style
id="style116684">.cls-1{fill:#b9b9b9;}.cls-2{fill:#fff;}.cls-2,.cls-3{stroke:#545454;}.cls-2,.cls-3,.cls-4{stroke-linecap:round;stroke-linejoin:round;}.cls-3,.cls-4{fill:none;}.cls-4{stroke:#1a81c4;}.cls-5{fill:#545454;}</style>
</defs>
<title
id="title116688">drc</title>
<path
style="fill:#545454;stroke:#ded3dd;stroke-width:1.47184;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 2.5086208,11.299302 10.6831142,0.02238 -1.22801,-1.35504 V 5.8373277 c 0,0 0.266307,-4.2714869 -4.2889251,-4.2669535 -4.5880423,0.00455 -4.2889256,4.2669535 -4.2889256,4.2669535 V 9.966639 Z"
id="path5"
sodipodi:nodetypes="ccccsccc" />
<path
style="fill:#545454;stroke:#ded3dd;stroke-width:0.826064;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 6.1082082,11.739465 3.7817331,0.03146 c 0,0 -0.0059,1.308909 -1.8831818,1.312612 -1.8676305,0.0037 -1.8985513,-1.34406 -1.8985513,-1.34406 z"
id="path7"
sodipodi:nodetypes="ccsc" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Слой_1"
data-name="Слой 1"
viewBox="0 0 16 16"
version="1.1"
sodipodi:docname="notifications.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
inkscape:export-filename="..\..\png\notifications_16.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
width="16"
height="16"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1027"
id="namedview30"
showgrid="false"
inkscape:zoom="32"
inkscape:cx="5.734375"
inkscape:cy="9.953125"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:document-rotation="0"
inkscape:current-layer="Слой_1"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid_kicad"
spacingx="0.5"
spacingy="0.5"
color="#9999ff"
opacity="0.13"
empspacing="2"
originx="-2.0583608"
originy="-0.53240496"
units="px"
visible="false" />
</sodipodi:namedview>
<metadata
id="metadata43">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>drc</dc:title>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<defs
id="defs116686">
<style
id="style116684">.cls-1{fill:#b9b9b9;}.cls-2{fill:#fff;}.cls-2,.cls-3{stroke:#545454;}.cls-2,.cls-3,.cls-4{stroke-linecap:round;stroke-linejoin:round;}.cls-3,.cls-4{fill:none;}.cls-4{stroke:#1a81c4;}.cls-5{fill:#545454;}</style>
</defs>
<title
id="title116688">drc</title>
<path
style="fill:none;stroke:#545454;stroke-width:1.47184;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 2.5086208,11.299302 10.6831142,0.02238 -1.22801,-1.35504 V 5.8373277 c 0,0 0.266307,-4.2714869 -4.2889251,-4.2669535 -4.5880423,0.00455 -4.2889256,4.2669535 -4.2889256,4.2669535 V 9.966639 Z"
id="path5"
sodipodi:nodetypes="ccccsccc" />
<path
style="fill:none;stroke:#545454;stroke-width:0.826064;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 6.1082082,11.739465 3.7817331,0.03146 c 0,0 -0.0059,1.308909 -1.8831818,1.312612 -1.8676305,0.0037 -1.8985513,-1.34406 -1.8985513,-1.34406 z"
id="path7"
sodipodi:nodetypes="ccsc" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB