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 <unordered_map>
22
23#include <wx/gauge.h>
24#include <wx/frame.h>
25#include <wx/panel.h>
26#include <wx/settings.h>
27#include <wx/scrolwin.h>
28#include <wx/sizer.h>
29#include <wx/stattext.h>
30#include <wx/string.h>
31
33#include <widgets/kistatusbar.h>
34
35
36class BACKGROUND_JOB_PANEL : public wxPanel
37{
38public:
39 BACKGROUND_JOB_PANEL( wxWindow* aParent, std::shared_ptr<BACKGROUND_JOB> aJob ) :
40 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, 75 ),
41 wxBORDER_SIMPLE ),
42 m_job( aJob )
43 {
44 SetSizeHints( wxDefaultSize, wxDefaultSize );
45
46 wxBoxSizer* mainSizer;
47 mainSizer = new wxBoxSizer( wxVERTICAL );
48
49 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
50
51 m_stName = new wxStaticText( this, wxID_ANY, aJob->m_name );
52 m_stName->Wrap( -1 );
53 m_stName->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT,
54 wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false,
55 wxEmptyString ) );
56 mainSizer->Add( m_stName, 0, wxALL | wxEXPAND, 1 );
57
58 m_stStatus = new wxStaticText( this, wxID_ANY, aJob->m_status, wxDefaultPosition,
59 wxDefaultSize, 0 );
60 m_stStatus->Wrap( -1 );
61 mainSizer->Add( m_stStatus, 0, wxALL | wxEXPAND, 1 );
62
63 m_progress = new wxGauge( this, wxID_ANY, aJob->m_maxProgress, wxDefaultPosition,
64 wxDefaultSize, wxGA_HORIZONTAL );
65 m_progress->SetValue( 0 );
66 mainSizer->Add( m_progress, 0, wxALL | wxEXPAND, 1 );
67
68 SetSizer( mainSizer );
69 Layout();
70
72 }
73
74
76 {
77 m_stStatus->SetLabelText( m_job->m_status );
78 m_progress->SetValue( m_job->m_currentProgress );
79 m_progress->SetRange( m_job->m_maxProgress );
80 }
81
82private:
83 wxGauge* m_progress;
84 wxStaticText* m_stName;
85 wxStaticText* m_stStatus;
86 std::shared_ptr<BACKGROUND_JOB> m_job;
87};
88
89
90class BACKGROUND_JOB_LIST : public wxFrame
91{
92public:
93 BACKGROUND_JOB_LIST( wxWindow* parent, const wxPoint& pos ) :
94 wxFrame( parent, wxID_ANY, _( "Background Jobs" ), pos, wxSize( 300, 150 ),
95 wxFRAME_NO_TASKBAR | wxBORDER_SIMPLE )
96 {
97 SetSizeHints( wxDefaultSize, wxDefaultSize );
98
99 wxBoxSizer* bSizer1;
100 bSizer1 = new wxBoxSizer( wxVERTICAL );
101
102 m_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition,
103 wxSize( -1, -1 ), wxVSCROLL );
104 m_scrolledWindow->SetScrollRate( 5, 5 );
105 m_contentSizer = new wxBoxSizer( wxVERTICAL );
106
107 m_scrolledWindow->SetSizer( m_contentSizer );
108 m_scrolledWindow->Layout();
110 bSizer1->Add( m_scrolledWindow, 1, wxEXPAND | wxALL, 0 );
111
112 Bind( wxEVT_KILL_FOCUS, &BACKGROUND_JOB_LIST::onFocusLoss, this );
113
114 SetSizer( bSizer1 );
115 Layout();
116
117 SetFocus();
118 }
119
120 void onFocusLoss( wxFocusEvent& aEvent )
121 {
122 Close( true );
123 aEvent.Skip();
124 }
125
126
127 void Add( std::shared_ptr<BACKGROUND_JOB> aJob )
128 {
130 m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 );
131 m_scrolledWindow->Layout();
133
134 // call this at this window otherwise the child panels don't resize width properly
135 Layout();
136
137 m_jobPanels[aJob] = panel;
138 }
139
140
141 void Remove( std::shared_ptr<BACKGROUND_JOB> aJob )
142 {
143 auto it = m_jobPanels.find( aJob );
144 if( it != m_jobPanels.end() )
145 {
146 BACKGROUND_JOB_PANEL* panel = m_jobPanels[aJob];
147 m_contentSizer->Detach( panel );
148 panel->Destroy();
149
150 m_jobPanels.erase( it );
151 }
152 }
153
154 void UpdateJob( std::shared_ptr<BACKGROUND_JOB> aJob )
155 {
156 auto it = m_jobPanels.find( aJob );
157 if( it != m_jobPanels.end() )
158 {
159 BACKGROUND_JOB_PANEL* panel = m_jobPanels[aJob];
160 panel->UpdateFromJob();
161 }
162 }
163
164private:
165 wxScrolledWindow* m_scrolledWindow;
166 wxBoxSizer* m_contentSizer;
167 std::unordered_map<std::shared_ptr<BACKGROUND_JOB>, BACKGROUND_JOB_PANEL*> m_jobPanels;
168};
169
170
172 const std::shared_ptr<BACKGROUND_JOB>& aJob ) :
174 m_monitor( aMonitor ),
175 m_job( aJob )
176{
177
178}
179
180
182{
183 return !m_cancelled;
184}
185
186
187void BACKGROUND_JOB_REPORTER::Report( const wxString& aMessage )
188{
189 m_job->m_status = aMessage;
190 m_monitor->jobUpdated( m_job );
191}
192
193
195{
197 m_job->m_maxProgress.store( m_numPhases.load() );
198 m_monitor->jobUpdated( m_job );
199}
200
201
203{
205 m_job->m_currentProgress.store( m_phase.load() );
206 m_monitor->jobUpdated( m_job );
207}
208
209
211{
213 m_job->m_maxProgress.store( 1000 );
214 m_job->m_currentProgress.store( static_cast<int>( 1000 * aProgress ) );
215 m_monitor->jobUpdated( m_job );
216}
217
218
223
224
225std::shared_ptr<BACKGROUND_JOB> BACKGROUND_JOBS_MONITOR::Create( const wxString& aName )
226{
227 std::shared_ptr<BACKGROUND_JOB> job = std::make_shared<BACKGROUND_JOB>();
228
229 job->m_name = aName;
230 job->m_reporter = std::make_shared<BACKGROUND_JOB_REPORTER>( this, job );
231
232 std::lock_guard<std::shared_mutex> lock( m_mutex );
233 m_jobs.push_back( job );
234
235 if( m_shownDialogs.size() > 0 )
236 {
237 // update dialogs
239 {
240 list->CallAfter(
241 [=]()
242 {
243 list->Add( job );
244 } );
245 }
246 }
247
248 return job;
249}
250
251
252void BACKGROUND_JOBS_MONITOR::Remove( std::shared_ptr<BACKGROUND_JOB> aJob )
253{
254 if( m_shownDialogs.size() > 0 )
255 {
256 // update dialogs
257
259 {
260 list->CallAfter(
261 [=]()
262 {
263 list->Remove( aJob );
264 } );
265 }
266 }
267
268 std::lock_guard<std::shared_mutex> lock( m_mutex );
269 m_jobs.erase( std::remove_if( m_jobs.begin(), m_jobs.end(),
270 [&]( std::shared_ptr<BACKGROUND_JOB> job )
271 {
272 return job == aJob;
273 } ) );
274
275 if( m_jobs.size() > 0 )
276 {
277 jobUpdated( m_jobs.front() );
278 }
279 else
280 {
281 for( KISTATUSBAR* statusBar : m_statusBars )
282 {
283 statusBar->CallAfter(
284 [=]()
285 {
286 statusBar->HideBackgroundProgressBar();
287 statusBar->SetBackgroundStatusText( wxT( "" ) );
288 } );
289 }
290 }
291}
292
293
295{
296 BACKGROUND_JOB_LIST* evtWindow = dynamic_cast<BACKGROUND_JOB_LIST*>( aEvent.GetEventObject() );
297
298 m_shownDialogs.erase( std::remove_if( m_shownDialogs.begin(), m_shownDialogs.end(),
299 [&]( BACKGROUND_JOB_LIST* dialog )
300 {
301 return dialog == evtWindow;
302 } ) );
303
304 aEvent.Skip();
305}
306
307
308void BACKGROUND_JOBS_MONITOR::ShowList( wxWindow* aParent, wxPoint aPos )
309{
310 BACKGROUND_JOB_LIST* list = new BACKGROUND_JOB_LIST( aParent, aPos );
311
312 std::shared_lock<std::shared_mutex> lock( m_mutex, std::try_to_lock );
313
314 for( const std::shared_ptr<BACKGROUND_JOB>& job : m_jobs )
315 list->Add( job );
316
317 lock.unlock();
318
319 m_shownDialogs.push_back( list );
320
321 list->Bind( wxEVT_CLOSE_WINDOW, &BACKGROUND_JOBS_MONITOR::onListWindowClosed, this );
322
323 // correct the position
324 wxSize windowSize = list->GetSize();
325 list->SetPosition( aPos - windowSize );
326
327 list->Show();
328}
329
330
331void BACKGROUND_JOBS_MONITOR::jobUpdated( std::shared_ptr<BACKGROUND_JOB> aJob )
332{
333 std::shared_lock<std::shared_mutex> lock( m_mutex, std::try_to_lock );
334
335 // this method is called from the reporters from potentially other threads
336 // we have to guard ui calls with CallAfter
337 if( m_jobs.size() > 0 )
338 {
339 //for now, we go and update the status bar if its the first job in the vector
340 if( m_jobs.front() == aJob )
341 {
342 // update all status bar entries
343 for( KISTATUSBAR* statusBar : m_statusBars )
344 {
345 statusBar->CallAfter(
346 [=]()
347 {
348 statusBar->ShowBackgroundProgressBar();
349 statusBar->SetBackgroundProgressMax( aJob->m_maxProgress );
350 statusBar->SetBackgroundProgress( aJob->m_currentProgress );
351 statusBar->SetBackgroundStatusText( aJob->m_status );
352 } );
353 }
354 }
355 }
356
357
359 {
360 list->CallAfter(
361 [=]()
362 {
363 list->UpdateJob( aJob );
364 } );
365 }
366}
367
368
370{
371 std::shared_ptr<BACKGROUND_JOB> frontJob;
372
373 {
374 std::lock_guard<std::shared_mutex> lock( m_mutex );
375 m_statusBars.push_back( aStatusBar );
376
377 // Capture front job while holding lock
378 if( !m_jobs.empty() )
379 frontJob = m_jobs.front();
380 }
381
382 // Make sure the newly-registered bar gets the active job, if any
383 if( frontJob )
384 jobUpdated( frontJob );
385}
386
387
389{
390 std::lock_guard<std::shared_mutex> lock( m_mutex );
391 m_statusBars.erase( std::remove_if( m_statusBars.begin(), m_statusBars.end(),
392 [&]( KISTATUSBAR* statusBar )
393 {
394 return statusBar == aStatusBar;
395 } ),
396 m_statusBars.end() );
397}
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 the m_jobs vector.
std::shared_ptr< BACKGROUND_JOB > Create(const wxString &aName)
Creates a background job with the given name.
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
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)
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)