You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

372 lines
11 KiB

2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2023 Mark Roszko <mark.roszko@gmail.com>
  5. * Copyright (C) 2023 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, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <unordered_map>
  25. #include <wx/gauge.h>
  26. #include <wx/frame.h>
  27. #include <wx/panel.h>
  28. #include <wx/settings.h>
  29. #include <wx/scrolwin.h>
  30. #include <wx/sizer.h>
  31. #include <wx/stattext.h>
  32. #include <wx/string.h>
  33. #include <background_jobs_monitor.h>
  34. #include <widgets/kistatusbar.h>
  35. class BACKGROUND_JOB_PANEL : public wxPanel
  36. {
  37. public:
  38. BACKGROUND_JOB_PANEL( wxWindow* aParent, std::shared_ptr<BACKGROUND_JOB> aJob ) :
  39. wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, 75 ),
  40. wxBORDER_SIMPLE ),
  41. m_job( aJob )
  42. {
  43. SetSizeHints( wxDefaultSize, wxDefaultSize );
  44. wxBoxSizer* mainSizer;
  45. mainSizer = new wxBoxSizer( wxVERTICAL );
  46. SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
  47. m_stName = new wxStaticText( this, wxID_ANY, aJob->m_name );
  48. m_stName->Wrap( -1 );
  49. m_stName->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT,
  50. wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
  51. mainSizer->Add( m_stName, 0, wxALL | wxEXPAND, 1 );
  52. m_stStatus = new wxStaticText( this, wxID_ANY, aJob->m_status, wxDefaultPosition,
  53. wxDefaultSize, 0 );
  54. m_stStatus->Wrap( -1 );
  55. mainSizer->Add( m_stStatus, 0, wxALL | wxEXPAND, 1 );
  56. m_progress = new wxGauge( this, wxID_ANY, aJob->m_maxProgress, wxDefaultPosition, wxDefaultSize,
  57. wxGA_HORIZONTAL );
  58. m_progress->SetValue( 0 );
  59. mainSizer->Add( m_progress, 0, wxALL | wxEXPAND, 1 );
  60. SetSizer( mainSizer );
  61. Layout();
  62. UpdateFromJob();
  63. }
  64. void UpdateFromJob()
  65. {
  66. m_stStatus->SetLabelText( m_job->m_status );
  67. m_progress->SetValue( m_job->m_currentProgress );
  68. m_progress->SetRange( m_job->m_maxProgress );
  69. }
  70. private:
  71. wxGauge* m_progress;
  72. wxStaticText* m_stName;
  73. wxStaticText* m_stStatus;
  74. std::shared_ptr<BACKGROUND_JOB> m_job;
  75. };
  76. class BACKGROUND_JOB_LIST : public wxFrame
  77. {
  78. public:
  79. BACKGROUND_JOB_LIST( wxWindow* parent, const wxPoint& pos ) :
  80. wxFrame( parent, wxID_ANY, _( "Background Jobs" ), pos, wxSize( 300, 150 ),
  81. wxFRAME_NO_TASKBAR | wxBORDER_SIMPLE )
  82. {
  83. SetSizeHints( wxDefaultSize, wxDefaultSize );
  84. wxBoxSizer* bSizer1;
  85. bSizer1 = new wxBoxSizer( wxVERTICAL );
  86. m_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition,
  87. wxSize( -1, -1 ), wxVSCROLL );
  88. m_scrolledWindow->SetScrollRate( 5, 5 );
  89. m_contentSizer = new wxBoxSizer( wxVERTICAL );
  90. m_scrolledWindow->SetSizer( m_contentSizer );
  91. m_scrolledWindow->Layout();
  92. m_contentSizer->Fit( m_scrolledWindow );
  93. bSizer1->Add( m_scrolledWindow, 1, wxEXPAND | wxALL, 0 );
  94. Bind( wxEVT_KILL_FOCUS, &BACKGROUND_JOB_LIST::onFocusLoss, this );
  95. SetSizer( bSizer1 );
  96. Layout();
  97. SetFocus();
  98. }
  99. void onFocusLoss( wxFocusEvent& aEvent )
  100. {
  101. Close( true );
  102. aEvent.Skip();
  103. }
  104. void Add( std::shared_ptr<BACKGROUND_JOB> aJob )
  105. {
  106. BACKGROUND_JOB_PANEL* panel = new BACKGROUND_JOB_PANEL( m_scrolledWindow, aJob );
  107. m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 );
  108. m_scrolledWindow->Layout();
  109. m_contentSizer->Fit( m_scrolledWindow );
  110. // call this at this window otherwise the child panels dont resize width properly
  111. Layout();
  112. m_jobPanels[aJob] = panel;
  113. }
  114. void Remove( std::shared_ptr<BACKGROUND_JOB> aJob )
  115. {
  116. auto it = m_jobPanels.find( aJob );
  117. if( it != m_jobPanels.end() )
  118. {
  119. BACKGROUND_JOB_PANEL* panel = m_jobPanels[aJob];
  120. m_contentSizer->Detach( panel );
  121. panel->Destroy();
  122. m_jobPanels.erase( it );
  123. }
  124. }
  125. void UpdateJob( std::shared_ptr<BACKGROUND_JOB> aJob )
  126. {
  127. auto it = m_jobPanels.find( aJob );
  128. if( it != m_jobPanels.end() )
  129. {
  130. BACKGROUND_JOB_PANEL* panel = m_jobPanels[aJob];
  131. panel->UpdateFromJob();
  132. }
  133. }
  134. private:
  135. wxScrolledWindow* m_scrolledWindow;
  136. wxBoxSizer* m_contentSizer;
  137. std::unordered_map<std::shared_ptr<BACKGROUND_JOB>, BACKGROUND_JOB_PANEL*> m_jobPanels;
  138. };
  139. BACKGROUND_JOB_REPORTER::BACKGROUND_JOB_REPORTER( BACKGROUND_JOBS_MONITOR* aMonitor,
  140. std::shared_ptr<BACKGROUND_JOB> aJob ) :
  141. PROGRESS_REPORTER_BASE( 1 ),
  142. m_monitor( aMonitor ), m_job( aJob )
  143. {
  144. }
  145. bool BACKGROUND_JOB_REPORTER::updateUI()
  146. {
  147. return true;
  148. }
  149. void BACKGROUND_JOB_REPORTER::Report( const wxString& aMessage )
  150. {
  151. m_job->m_status = aMessage;
  152. m_monitor->jobUpdated( m_job );
  153. }
  154. void BACKGROUND_JOB_REPORTER::SetNumPhases( int aNumPhases )
  155. {
  156. PROGRESS_REPORTER_BASE::SetNumPhases( aNumPhases );
  157. m_job->m_maxProgress = m_numPhases;
  158. m_monitor->jobUpdated( m_job );
  159. }
  160. void BACKGROUND_JOB_REPORTER::AdvancePhase()
  161. {
  162. PROGRESS_REPORTER_BASE::AdvancePhase();
  163. m_job->m_currentProgress = m_phase;
  164. m_monitor->jobUpdated( m_job );
  165. }
  166. BACKGROUND_JOBS_MONITOR::BACKGROUND_JOBS_MONITOR() : m_jobListDialog( nullptr )
  167. {
  168. }
  169. std::shared_ptr<BACKGROUND_JOB> BACKGROUND_JOBS_MONITOR::Create( const wxString& aName )
  170. {
  171. std::shared_ptr<BACKGROUND_JOB> job = std::make_shared<BACKGROUND_JOB>();
  172. job->m_name = aName;
  173. job->m_reporter = std::make_shared<BACKGROUND_JOB_REPORTER>( this, job );
  174. std::lock_guard<std::shared_mutex> lock( m_mutex );
  175. m_jobs.push_back( job );
  176. if( m_shownDialogs.size() > 0 )
  177. {
  178. // update dialogs
  179. for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
  180. {
  181. list->CallAfter(
  182. [=]()
  183. {
  184. list->Add( job );
  185. } );
  186. }
  187. }
  188. return job;
  189. }
  190. void BACKGROUND_JOBS_MONITOR::Remove( std::shared_ptr<BACKGROUND_JOB> aJob )
  191. {
  192. if( m_shownDialogs.size() > 0 )
  193. {
  194. // update dialogs
  195. for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
  196. {
  197. list->CallAfter(
  198. [=]()
  199. {
  200. list->Remove( aJob );
  201. } );
  202. }
  203. }
  204. std::lock_guard<std::shared_mutex> lock( m_mutex );
  205. m_jobs.erase( std::remove_if( m_jobs.begin(), m_jobs.end(),
  206. [&]( std::shared_ptr<BACKGROUND_JOB> job )
  207. {
  208. return job == aJob;
  209. } ) );
  210. if( m_jobs.size() == 0 )
  211. {
  212. for( KISTATUSBAR* statusBar : m_statusBars )
  213. {
  214. statusBar->CallAfter(
  215. [=]()
  216. {
  217. statusBar->HideBackgroundProgressBar();
  218. statusBar->SetBackgroundStatusText( wxT( "" ) );
  219. } );
  220. }
  221. }
  222. }
  223. void BACKGROUND_JOBS_MONITOR::onListWindowClosed( wxCloseEvent& aEvent )
  224. {
  225. BACKGROUND_JOB_LIST* evtWindow = dynamic_cast<BACKGROUND_JOB_LIST*>( aEvent.GetEventObject() );
  226. m_shownDialogs.erase( std::remove_if( m_shownDialogs.begin(), m_shownDialogs.end(),
  227. [&]( BACKGROUND_JOB_LIST* dialog )
  228. {
  229. return dialog == evtWindow;
  230. } ) );
  231. aEvent.Skip();
  232. }
  233. void BACKGROUND_JOBS_MONITOR::ShowList( wxWindow* aParent, wxPoint aPos )
  234. {
  235. BACKGROUND_JOB_LIST* list = new BACKGROUND_JOB_LIST( aParent, aPos );
  236. std::shared_lock<std::shared_mutex> lock( m_mutex, std::try_to_lock );
  237. for( std::shared_ptr<BACKGROUND_JOB> job : m_jobs )
  238. {
  239. list->Add( job );
  240. }
  241. lock.unlock();
  242. m_shownDialogs.push_back( list );
  243. list->Bind( wxEVT_CLOSE_WINDOW, &BACKGROUND_JOBS_MONITOR::onListWindowClosed, this );
  244. // correct the position
  245. wxSize windowSize = list->GetSize();
  246. list->SetPosition( aPos - windowSize );
  247. list->Show();
  248. }
  249. void BACKGROUND_JOBS_MONITOR::jobUpdated( std::shared_ptr<BACKGROUND_JOB> aJob )
  250. {
  251. std::shared_lock<std::shared_mutex> lock( m_mutex, std::try_to_lock );
  252. // this method is called from the reporters from potentially other threads
  253. // we have to guard ui calls with CallAfter
  254. if( m_jobs.size() > 0 )
  255. {
  256. //for now, we go and update the status bar if its the first job in the vector
  257. if( m_jobs.front() == aJob )
  258. {
  259. // update all status bar entries
  260. for( KISTATUSBAR* statusBar : m_statusBars )
  261. {
  262. statusBar->CallAfter(
  263. [=]()
  264. {
  265. statusBar->ShowBackgroundProgressBar();
  266. statusBar->SetBackgroundProgress( aJob->m_currentProgress );
  267. statusBar->SetBackgroundProgressMax( aJob->m_maxProgress );
  268. statusBar->SetBackgroundStatusText( aJob->m_status );
  269. } );
  270. }
  271. }
  272. }
  273. for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
  274. {
  275. list->CallAfter(
  276. [=]()
  277. {
  278. list->UpdateJob( aJob );
  279. } );
  280. }
  281. }
  282. void BACKGROUND_JOBS_MONITOR::RegisterStatusBar( KISTATUSBAR* aStatusBar )
  283. {
  284. m_statusBars.push_back( aStatusBar );
  285. }
  286. void BACKGROUND_JOBS_MONITOR::UnregisterStatusBar( KISTATUSBAR* aStatusBar )
  287. {
  288. m_statusBars.erase( std::remove_if( m_statusBars.begin(), m_statusBars.end(),
  289. [&]( KISTATUSBAR* statusBar )
  290. {
  291. return statusBar == aStatusBar;
  292. } ) );
  293. }