KiCad PCB EDA Suite
Loading...
Searching...
No Matches
background_jobs_monitor.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2023 Mark Roszko <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <algorithm>
22#include <unordered_map>
23
24#include <wx/gauge.h>
25#include <wx/frame.h>
26#include <wx/panel.h>
27#include <wx/settings.h>
28#include <wx/scrolwin.h>
29#include <wx/sizer.h>
30#include <wx/stattext.h>
31#include <wx/string.h>
32
34#include <widgets/kistatusbar.h>
35
36
37class BACKGROUND_JOB_PANEL : public wxPanel
38{
39public:
40 BACKGROUND_JOB_PANEL( wxWindow* aParent, std::shared_ptr<BACKGROUND_JOB> aJob ) :
41 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, 75 ),
42 wxBORDER_SIMPLE ),
43 m_job( aJob )
44 {
45 SetSizeHints( wxDefaultSize, wxDefaultSize );
46
47 wxBoxSizer* mainSizer;
48 mainSizer = new wxBoxSizer( wxVERTICAL );
49
50 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
51
52 m_stName = new wxStaticText( this, wxID_ANY, aJob->m_name );
53 m_stName->Wrap( -1 );
54 m_stName->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT,
55 wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false,
56 wxEmptyString ) );
57 mainSizer->Add( m_stName, 0, wxALL | wxEXPAND, 1 );
58
59 m_stStatus = new wxStaticText( this, wxID_ANY, aJob->GetStatus(), wxDefaultPosition,
60 wxDefaultSize, 0 );
61 m_stStatus->Wrap( -1 );
62 mainSizer->Add( m_stStatus, 0, wxALL | wxEXPAND, 1 );
63
64 m_progress = new wxGauge( this, wxID_ANY, aJob->m_maxProgress, wxDefaultPosition,
65 wxDefaultSize, wxGA_HORIZONTAL );
66 m_progress->SetValue( 0 );
67 mainSizer->Add( m_progress, 0, wxALL | wxEXPAND, 1 );
68
69 SetSizer( mainSizer );
70 Layout();
71
73 }
74
75
77 {
78 m_stStatus->SetLabelText( m_job->GetStatus() );
79 m_progress->SetValue( m_job->m_currentProgress );
80 m_progress->SetRange( m_job->m_maxProgress );
81 }
82
83private:
84 wxGauge* m_progress;
85 wxStaticText* m_stName;
86 wxStaticText* m_stStatus;
87 std::shared_ptr<BACKGROUND_JOB> m_job;
88};
89
90
91class BACKGROUND_JOB_LIST : public wxFrame
92{
93public:
94 BACKGROUND_JOB_LIST( wxWindow* parent, const wxPoint& pos ) :
95 wxFrame( parent, wxID_ANY, _( "Background Jobs" ), pos, wxSize( 300, 150 ),
96 wxFRAME_NO_TASKBAR | wxBORDER_SIMPLE )
97 {
98 SetSizeHints( wxDefaultSize, wxDefaultSize );
99
100 wxBoxSizer* bSizer1;
101 bSizer1 = new wxBoxSizer( wxVERTICAL );
102
103 m_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition,
104 wxSize( -1, -1 ), wxVSCROLL );
105 m_scrolledWindow->SetScrollRate( 5, 5 );
106 m_contentSizer = new wxBoxSizer( wxVERTICAL );
107
108 m_scrolledWindow->SetSizer( m_contentSizer );
109 m_scrolledWindow->Layout();
111 bSizer1->Add( m_scrolledWindow, 1, wxEXPAND | wxALL, 0 );
112
113 Bind( wxEVT_KILL_FOCUS, &BACKGROUND_JOB_LIST::onFocusLoss, this );
114
115 SetSizer( bSizer1 );
116 Layout();
117
118 SetFocus();
119 }
120
121 void onFocusLoss( wxFocusEvent& aEvent )
122 {
123 Close( true );
124 aEvent.Skip();
125 }
126
127
128 void Add( std::shared_ptr<BACKGROUND_JOB> aJob )
129 {
131 m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 );
132 m_scrolledWindow->Layout();
134
135 // call this at this window otherwise the child panels don't resize width properly
136 Layout();
137
138 m_jobPanels[aJob] = panel;
139 }
140
141
142 void Remove( std::shared_ptr<BACKGROUND_JOB> aJob )
143 {
144 auto it = m_jobPanels.find( aJob );
145 if( it != m_jobPanels.end() )
146 {
147 BACKGROUND_JOB_PANEL* panel = m_jobPanels[aJob];
148 m_contentSizer->Detach( panel );
149 panel->Destroy();
150
151 m_jobPanels.erase( it );
152 }
153 }
154
155 void UpdateJob( std::shared_ptr<BACKGROUND_JOB> aJob )
156 {
157 auto it = m_jobPanels.find( aJob );
158 if( it != m_jobPanels.end() )
159 {
160 BACKGROUND_JOB_PANEL* panel = m_jobPanels[aJob];
161 panel->UpdateFromJob();
162 }
163 }
164
165private:
166 wxScrolledWindow* m_scrolledWindow;
167 wxBoxSizer* m_contentSizer;
168 std::unordered_map<std::shared_ptr<BACKGROUND_JOB>, BACKGROUND_JOB_PANEL*> m_jobPanels;
169};
170
171
173 const std::shared_ptr<BACKGROUND_JOB>& aJob ) :
175 m_monitor( aMonitor ),
176 m_job( aJob )
177{
178
179}
180
181
183{
184 return !m_cancelled;
185}
186
187
188void BACKGROUND_JOB_REPORTER::Report( const wxString& aMessage )
189{
190 m_job->SetStatus( aMessage );
191 m_monitor->jobUpdated( m_job );
192}
193
194
196{
198 m_job->m_maxProgress.store( m_numPhases.load() );
199 m_monitor->jobUpdated( m_job );
200}
201
202
204{
206 m_job->m_currentProgress.store( m_phase.load() );
207 m_monitor->jobUpdated( m_job );
208}
209
210
212{
214 m_job->m_maxProgress.store( 1000 );
215 m_job->m_currentProgress.store( static_cast<int>( 1000 * aProgress ) );
216 m_monitor->jobUpdated( m_job );
217}
218
219
224
225
226std::shared_ptr<BACKGROUND_JOB> BACKGROUND_JOBS_MONITOR::Create( const wxString& aName )
227{
228 std::shared_ptr<BACKGROUND_JOB> job = std::make_shared<BACKGROUND_JOB>();
229
230 job->m_name = aName;
231 job->m_reporter = std::make_shared<BACKGROUND_JOB_REPORTER>( this, job );
232
233 std::lock_guard<std::shared_mutex> lock( m_mutex );
234 m_jobs.push_back( job );
235
236 // Keep the lock during CallAfter or a dialog can close and free the pointer
238 {
239 list->CallAfter(
240 [=]()
241 {
242 list->Add( job );
243 } );
244 }
245
246 return job;
247}
248
249
250void BACKGROUND_JOBS_MONITOR::Remove( std::shared_ptr<BACKGROUND_JOB> aJob )
251{
252 std::shared_ptr<BACKGROUND_JOB> frontJob;
253
254 {
255 std::lock_guard<std::shared_mutex> lock( m_mutex );
256
257 m_jobs.erase( std::remove( m_jobs.begin(), m_jobs.end(), aJob ), m_jobs.end() );
258
260 {
261 list->CallAfter(
262 [=]()
263 {
264 list->Remove( aJob );
265 } );
266 }
267
268 if( m_jobs.empty() )
269 {
270 for( KISTATUSBAR* statusBar : m_statusBars )
271 {
272 statusBar->CallAfter(
273 [=]()
274 {
275 statusBar->HideBackgroundProgressBar();
276 statusBar->SetBackgroundStatusText( wxT( "" ) );
277 } );
278 }
279 }
280 else
281 {
282 frontJob = m_jobs.front();
283 }
284 }
285
286 // jobUpdated takes the mutex so call it after we release ours
287 if( frontJob )
288 jobUpdated( frontJob );
289}
290
291
293{
294 removeShownDialog( aEvent.GetEventObject() );
295
296 aEvent.Skip();
297}
298
299
300void BACKGROUND_JOBS_MONITOR::onListWindowDestroyed( wxWindowDestroyEvent& aEvent )
301{
302 removeShownDialog( aEvent.GetEventObject() );
303
304 aEvent.Skip();
305}
306
307
309{
310 std::lock_guard<std::shared_mutex> lock( m_mutex );
311
312 // dynamic_cast fails during base destruction so compare the upcast address
313 m_shownDialogs.erase( std::remove_if( m_shownDialogs.begin(), m_shownDialogs.end(),
314 [&]( BACKGROUND_JOB_LIST* dialog )
315 {
316 return static_cast<wxObject*>( dialog ) == aWindow;
317 } ),
318 m_shownDialogs.end() );
319}
320
321
322void BACKGROUND_JOBS_MONITOR::ShowList( wxWindow* aParent, wxPoint aPos )
323{
324 BACKGROUND_JOB_LIST* list = new BACKGROUND_JOB_LIST( aParent, aPos );
325
326 {
327 std::lock_guard<std::shared_mutex> lock( m_mutex );
328
329 for( const std::shared_ptr<BACKGROUND_JOB>& job : m_jobs )
330 list->Add( job );
331
332 m_shownDialogs.push_back( list );
333 }
334
335 list->Bind( wxEVT_CLOSE_WINDOW, &BACKGROUND_JOBS_MONITOR::onListWindowClosed, this );
336 list->Bind( wxEVT_DESTROY, &BACKGROUND_JOBS_MONITOR::onListWindowDestroyed, this );
337
338 // correct the position
339 wxSize windowSize = list->GetSize();
340 list->SetPosition( aPos - windowSize );
341
342 list->Show();
343}
344
345
346void BACKGROUND_JOBS_MONITOR::jobUpdated( std::shared_ptr<BACKGROUND_JOB> aJob )
347{
348 // Reporters call this from other threads so all UI work goes through CallAfter
349 // Keep the lock during those calls or a status bar can close and free the pointer
350 std::shared_lock<std::shared_mutex> lock( m_mutex );
351
352 //for now, we go and update the status bar if its the first job in the vector
353 if( !m_jobs.empty() && m_jobs.front() == aJob )
354 {
355 // update all status bar entries
356 for( KISTATUSBAR* statusBar : m_statusBars )
357 {
358 statusBar->CallAfter(
359 [=]()
360 {
361 statusBar->ShowBackgroundProgressBar();
362 statusBar->SetBackgroundProgressMax( aJob->m_maxProgress );
363 statusBar->SetBackgroundProgress( aJob->m_currentProgress );
364 statusBar->SetBackgroundStatusText( aJob->GetStatus() );
365 } );
366 }
367 }
368
370 {
371 list->CallAfter(
372 [=]()
373 {
374 list->UpdateJob( aJob );
375 } );
376 }
377}
378
379
381{
382 std::shared_ptr<BACKGROUND_JOB> frontJob;
383
384 {
385 std::lock_guard<std::shared_mutex> lock( m_mutex );
386 m_statusBars.push_back( aStatusBar );
387
388 // Capture front job while holding lock
389 if( !m_jobs.empty() )
390 frontJob = m_jobs.front();
391 }
392
393 // Make sure the newly-registered bar gets the active job, if any
394 if( frontJob )
395 jobUpdated( frontJob );
396}
397
398
400{
401 std::lock_guard<std::shared_mutex> lock( m_mutex );
402 m_statusBars.erase( std::remove_if( m_statusBars.begin(), m_statusBars.end(),
403 [&]( KISTATUSBAR* statusBar )
404 {
405 return statusBar == aStatusBar;
406 } ),
407 m_statusBars.end() );
408}
void UnregisterStatusBar(KISTATUSBAR *aStatusBar)
Removes status bar from handling.
std::vector< std::shared_ptr< BACKGROUND_JOB > > m_jobs
Holds a reference to all active background jobs Access to this vector should be protected by locks si...
void ShowList(wxWindow *aParent, wxPoint aPos)
Shows the background job list.
std::shared_mutex m_mutex
Mutex to protect access to m_jobs, m_shownDialogs and m_statusBars.
std::shared_ptr< BACKGROUND_JOB > Create(const wxString &aName)
Creates a background job with the given name.
void onListWindowDestroyed(wxWindowDestroyEvent &aEvent)
Handles a list window that its parent destroys without a close event.
void jobUpdated(std::shared_ptr< BACKGROUND_JOB > aJob)
Handles job status updates, intended to be called by BACKGROUND_JOB_REPORTER only.
void Remove(std::shared_ptr< BACKGROUND_JOB > job)
Removes the given background job from any lists and frees it.
void onListWindowClosed(wxCloseEvent &aEvent)
Handles removing the shown list window from our list of shown windows.
std::vector< KISTATUSBAR * > m_statusBars
void removeShownDialog(wxObject *aWindow)
Removes a list window from m_shownDialogs.
std::vector< BACKGROUND_JOB_LIST * > m_shownDialogs
void RegisterStatusBar(KISTATUSBAR *aStatusBar)
Add a status bar for handling.
void Remove(std::shared_ptr< BACKGROUND_JOB > aJob)
void onFocusLoss(wxFocusEvent &aEvent)
void Add(std::shared_ptr< BACKGROUND_JOB > aJob)
BACKGROUND_JOB_LIST(wxWindow *parent, const wxPoint &pos)
void UpdateJob(std::shared_ptr< BACKGROUND_JOB > aJob)
std::unordered_map< std::shared_ptr< BACKGROUND_JOB >, BACKGROUND_JOB_PANEL * > m_jobPanels
wxScrolledWindow * m_scrolledWindow
BACKGROUND_JOB_PANEL(wxWindow *aParent, std::shared_ptr< BACKGROUND_JOB > aJob)
std::shared_ptr< BACKGROUND_JOB > m_job
void SetNumPhases(int aNumPhases) override
Set the number of phases.
void AdvancePhase() override
Use the next available virtual zone of the dialog progress bar.
std::shared_ptr< BACKGROUND_JOB > m_job
void Report(const wxString &aMessage) override
Display aMessage in the progress bar dialog.
void SetCurrentProgress(double aProgress) override
Set the progress value to aProgress (0..1).
BACKGROUND_JOBS_MONITOR * m_monitor
BACKGROUND_JOB_REPORTER(BACKGROUND_JOBS_MONITOR *aMonitor, const std::shared_ptr< BACKGROUND_JOB > &aJob)
KISTATUSBAR is a wxStatusBar suitable for Kicad manager.
Definition kistatusbar.h:50
virtual void AdvancePhase() override
Use the next available virtual zone of the dialog progress bar.
virtual void SetCurrentProgress(double aProgress) override
Set the progress value to aProgress (0..1).
void SetNumPhases(int aNumPhases) override
Set the number of phases.
#define _(s)