KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_jobset.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) 2024 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 modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * 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 "panel_jobset.h"
22#include "dialog_destination.h"
25#include <wx/aui/auibook.h>
26#include <jobs/jobset.h>
27#include <jobs/job_registry.h>
28#include <eda_list_dialog.h>
29#include <wx/checkbox.h>
30#include <wx/menu.h>
31#include <bitmaps.h>
32#include <i18n_utility.h>
33#include <jobs_runner.h>
35#include <kicad_manager_frame.h>
36#include <vector>
37#include <wx/dcclient.h>
38
41#include <widgets/wx_grid.h>
43#include <kiplatform/ui.h>
44#include <confirm.h>
45#include <launch_ext.h>
46#include <wx/filename.h>
47
52#include <common.h>
53
54
55extern KICOMMON_API
56std::map<JOBSET_DESTINATION_T, JOBSET_DESTINATION_T_INFO> JobsetDestinationTypeInfos;
57
58
60{
61public:
62 DIALOG_JOBSET_RUN_LOG( wxWindow* aParent, JOBSET* aJobsFile, JOBSET_DESTINATION* aDestination ) :
64 m_jobsFile( aJobsFile ),
65 m_destination( aDestination ),
66 m_lastWidth( -1 ),
67 m_marginsWidth( -1 )
68 {
69 m_staticTextOutputName->SetLabel( wxString::Format( _( "Destination: %s" ),
70 aDestination->GetDescription() ) );
71
72 int jobBmpColId = m_jobList->AppendColumn( wxT( "" ) );
73 int jobNoColId = m_jobList->AppendColumn( _( "No." ) );
74 int jobDescColId = m_jobList->AppendColumn( _( "Job Description" ) );
75 int jobSourceColId = m_jobList->AppendColumn( _( "Source" ) );
76 m_jobList->SetColumnWidth( jobBmpColId, 26 );
77 m_jobList->SetColumnWidth( jobNoColId, GetTextExtent( wxT( "XXXX" ) ).GetWidth() );
78 m_jobList->SetColumnWidth( jobSourceColId, GetTextExtent( wxT( "XXXXXX" ) ).GetWidth() );
79
80 wxImageList* imageList = new wxImageList( 16, 16, true, 3 );
81 imageList->Add( KiBitmapBundle( BITMAPS::ercerr ).GetBitmap( wxSize( 16, 16 ) ) );
82 imageList->Add( KiBitmapBundle( BITMAPS::checked_ok ).GetBitmap( wxSize( 16, 16 ) ) );
83 m_jobList->SetImageList( imageList, wxIMAGE_LIST_SMALL );
84
85 int num = 1;
86
87 for( JOBSET_JOB& job : aJobsFile->GetJobsForDestination( aDestination ) )
88 {
89 int imageIdx = -1;
90
91 if( aDestination->m_lastRunSuccessMap.contains( job.m_id ) )
92 {
93 if( aDestination->m_lastRunSuccessMap[job.m_id].value() )
94 imageIdx = 1;
95 else
96 imageIdx = 0;
97 }
98
99 long itemIndex = m_jobList->InsertItem( m_jobList->GetItemCount(), imageIdx );
100
101 m_jobList->SetItem( itemIndex, jobNoColId, wxString::Format( "%d", num++ ) );
102 m_jobList->SetItem( itemIndex, jobDescColId, job.GetDescription() );
103
104 KIWAY::FACE_T iface = JOB_REGISTRY::GetKifaceType( job.m_type );
105 wxString source = wxEmptyString;
106
107 if( iface < KIWAY::KIWAY_FACE_COUNT )
108 {
109 if( iface == KIWAY::FACE_PCB )
110 source = wxT( "PCB" );
111 else if( iface == KIWAY::FACE_SCH )
112 source = wxT( "SCH" );
113 }
114
115 m_jobList->SetItem( itemIndex, jobSourceColId, source );
116 }
117
118 SetupStandardButtons( { { wxID_OK, _( "Close" ) } } );
120 }
121
122 virtual void OnUpdateUI( wxUpdateUIEvent& event ) override
123 {
124 if( GetSize().GetWidth() != m_lastWidth )
125 {
126 m_lastWidth = GetSize().GetWidth();
127
128 if( m_marginsWidth < 0 )
129 m_marginsWidth = m_lastWidth - ( m_jobList->GetSize().GetWidth() * 2 );
130
131 int width = ( m_lastWidth / 2 );
132 width -= m_marginsWidth;
133 width -= m_jobList->GetColumnWidth( 0 );
134 width -= m_jobList->GetColumnWidth( 1 );
135 width -= m_jobList->GetColumnWidth( 3 );
136
137 m_jobList->SetColumnWidth( 2, width );
138 }
139 }
140
141 void OnJobListItemSelected( wxListEvent& event ) override
142 {
143 int itemIndex = event.GetIndex();
144
145 // The index could be negative (it is default -1)
146 if( itemIndex < 0 )
147 return;
148
149 std::vector<JOBSET_JOB> jobs = m_jobsFile->GetJobsForDestination( m_destination );
150
151 if( static_cast<size_t>( itemIndex ) < jobs.size() )
152 {
153 wxString jobId = jobs[itemIndex].m_id;
154
155 if( m_destination->m_lastRunReporters.contains( jobId ) )
156 m_textCtrlOutput->SetValue( m_destination->m_lastRunReporters.at( jobId )->GetMessages() );
157 else
158 m_textCtrlOutput->SetValue( _( "No output messages" ) );
159 }
160 }
161
162private:
165
168};
169
170
172{
173public:
174 PANEL_DESTINATION( wxWindow* aParent, PANEL_JOBSET* aPanelParent, KICAD_MANAGER_FRAME* aFrame,
175 JOBSET* aFile, JOBSET_DESTINATION* aDestination ) :
176 PANEL_DESTINATION_BASE( aParent ),
177 m_jobsFile( aFile ),
178 m_destinationId( aDestination->m_id ),
179 m_frame( aFrame ),
180 m_panelParent( aPanelParent )
181 {
185
186#if _WIN32
187 // BORDER_RAISED/SUNKEN look pretty on every platform but Windows
188 long style = GetWindowStyleFlag();
189 style &= ~wxBORDER_MASK;
190 style |= wxBORDER_SIMPLE;
191 SetWindowStyleFlag( style );
192#endif // _WIN32
193
194 Connect( wxEVT_MENU, wxCommandEventHandler( PANEL_DESTINATION::onMenu ), nullptr, this );
195
196 if( JobsetDestinationTypeInfos.contains( aDestination->m_type ) )
197 {
198 JOBSET_DESTINATION_T_INFO& jobTypeInfo = JobsetDestinationTypeInfos[aDestination->m_type];
199 m_textOutputType->SetLabel( aDestination->GetDescription() );
200 m_bitmapOutputType->SetBitmap( KiBitmapBundle( jobTypeInfo.bitmap ) );
201 }
202
203 m_pathInfo->SetFont( KIUI::GetSmallInfoFont( this ) );
204 UpdatePathInfo( aDestination->GetPathInfo() );
205 UpdateStatus();
206 }
207
209 {
210 Disconnect( wxEVT_MENU, wxCommandEventHandler( PANEL_DESTINATION::onMenu ), nullptr, this );
211 }
212
214 {
215 JOBSET_DESTINATION* destination = GetDestination();
216 wxCHECK( destination, /*void*/ );
217
218 destination->m_lastRunSuccess = std::nullopt;
219 destination->m_lastResolvedOutputPath = std::nullopt;
220 m_statusBitmap->SetBitmap( wxNullBitmap );
221 }
222
224 {
225 JOBSET_DESTINATION* destination = GetDestination();
226 wxCHECK( destination, /*void*/ );
227
228 if( destination->m_lastRunSuccess.has_value() )
229 {
230 if( destination->m_lastRunSuccess.value() )
231 {
233 m_statusBitmap->Show();
234 m_statusBitmap->SetToolTip( _( "Last run successful" ) );
235 }
236 else
237 {
239 m_statusBitmap->Show();
240 m_statusBitmap->SetToolTip( _( "Last run failed" ) );
241 }
242 }
243 else
244 {
245 m_statusBitmap->SetBitmap( wxNullBitmap );
246 }
247
248 m_buttonGenerate->Enable( !m_jobsFile->GetJobsForDestination( destination ).empty() );
249 }
250
251 void UpdatePathInfo( const wxString& aMsg )
252 {
253 wxClientDC dc( this );
254 int width = GetSize().GetWidth();
255
256 m_pathInfo->SetLabel( wxControl::Ellipsize( aMsg, dc, wxELLIPSIZE_MIDDLE, width ) );
257 }
258
259 virtual void OnGenerate( wxCommandEvent& event ) override
260 {
261 ClearStatus();
262 Refresh();
263
264 CallAfter(
265 [this]()
266 {
267 PROJECT& project = m_frame->Kiway().Prj();
268 m_panelParent->EnsurePcbSchFramesOpen();
269
270 wxFileName fn = project.GetProjectFullName();
271 wxSetWorkingDirectory( fn.GetPath() );
272
273 {
274 JOBS_PROGRESS_REPORTER progressReporter( m_frame, _( "Running Jobs" ) );
275 JOBS_RUNNER jobRunner( &m_frame->Kiway(), m_jobsFile, &project,
276 NULL_REPORTER::GetInstance(), &progressReporter );
277
278 if( JOBSET_DESTINATION* destination = GetDestination() )
279 jobRunner.RunJobsForDestination( destination );
280
281 UpdateStatus();
282 }
283
284 // Bring the Kicad manager frame back to the front
285 m_frame->Raise();
286 } );
287 }
288
289 void OnOpenOutput( wxCommandEvent& aEvent ) override
290 {
291 JOBSET_DESTINATION* destination = GetDestination();
292 wxCHECK( destination, /*void*/ );
293
294 wxString resolvedPath;
295
296 if( destination->m_lastResolvedOutputPath.has_value() )
297 {
298 resolvedPath = destination->m_lastResolvedOutputPath.value();
299 }
300 else
301 {
302 resolvedPath = ExpandTextVars( destination->GetPathInfo(), &m_frame->Prj() );
303 resolvedPath = ExpandEnvVarSubstitutions( resolvedPath, &m_frame->Prj() );
304
305 if( resolvedPath.StartsWith( "~" ) )
306 resolvedPath.Replace( "~", wxGetHomeDir(), false );
307 }
308
309 if( resolvedPath.IsEmpty() )
310 return;
311
312 wxString fullPath = wxFileName( resolvedPath ).GetFullPath();
313
314 if( !LaunchExternal( fullPath ) )
315 DisplayError( this, wxString::Format( _( "Failed to open '%s'." ), fullPath ) );
316 }
317
318 void OnLastStatusClick( wxMouseEvent& aEvent ) override
319 {
320 JOBSET_DESTINATION* destination = GetDestination();
321 wxCHECK( destination, /*void*/ );
322
323 DIALOG_JOBSET_RUN_LOG dialog( m_frame, m_jobsFile, destination );
324 dialog.ShowModal();
325 }
326
327 void OnRightDown( wxMouseEvent& aEvent ) override
328 {
329 JOBSET_DESTINATION* destination = GetDestination();
330 wxCHECK( destination, /*void*/ );
331
332 wxMenu menu;
333 menu.Append( wxID_EDIT, _( "Edit Destination Options..." ) );
334 menu.Append( wxID_DELETE, _( "Delete Destination" ) );
335
336 menu.AppendSeparator();
337 menu.Append( wxID_VIEW_DETAILS, _( "View Last Run Log..." ) );
338 menu.Append( wxID_OPEN, _( "Open Output" ) );
339
340 menu.Enable( wxID_VIEW_DETAILS, destination->m_lastRunSuccess.has_value() );
341 menu.Enable( wxID_OPEN, m_buttonOpenOutput->IsEnabled() );
342
343 PopupMenu( &menu );
344 }
345
346 void OnProperties( wxCommandEvent& aEvent ) override
347 {
348 JOBSET_DESTINATION* destination = GetDestination();
349 wxCHECK( destination, /*void*/ );
350
351 DIALOG_DESTINATION dialog( m_frame, m_jobsFile, destination );
352
353 if( dialog.ShowModal() == wxID_OK )
354 {
355 m_textOutputType->SetLabel( destination->GetDescription() );
356 UpdatePathInfo( destination->GetPathInfo() );
357 m_jobsFile->SetDirty();
358 m_panelParent->UpdateTitle();
359 ClearStatus();
360 }
361 }
362
363 virtual void OnDelete( wxCommandEvent& aEvent ) override
364 {
365 m_panelParent->RemoveDestination( this );
366 }
367
369 {
370 for( JOBSET_DESTINATION& destination : m_jobsFile->GetDestinations() )
371 {
372 if( destination.m_id == m_destinationId )
373 return &destination;
374 }
375
376 return nullptr;
377 }
378
379private:
380 void onMenu( wxCommandEvent& aEvent )
381 {
382 switch( aEvent.GetId() )
383 {
384 case wxID_EDIT:
385 {
386 wxCommandEvent dummy;
388 }
389 break;
390
391 case wxID_DELETE:
392 {
393 wxCommandEvent dummy;
394 OnDelete( dummy );
395 }
396 break;
397
398 case wxID_VIEW_DETAILS:
399 {
400 wxMouseEvent dummy;
402 }
403 break;
404
405 case wxID_OPEN:
406 {
407 wxCommandEvent dummy;
409 }
410 break;
411
412 default: wxFAIL_MSG( wxT( "Unknown ID in context menu event" ) );
413 }
414 }
415
416private:
421};
422
423
425 GRID_TRICKS( aGrid ),
426 m_parent( aParent ),
427 m_doubleClickRow( -1 )
428{
431}
432
433
434void JOBS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
435{
436 // Snapshot the selection, PopupMenu() below can lose it before doPopupSelection() runs
437 m_selectedRows = m_grid->GetSelectedRows();
438 wxArrayInt& selectedRows = m_selectedRows;
439
440 menu.Append( JOB_DESCRIPTION, _( "Edit Job Description" ) );
441 menu.Append( JOB_PROPERTIES, _( "Edit Job Settings..." ) );
442 menu.AppendSeparator();
443 menu.Append( GRIDTRICKS_ID_COPY, _( "Copy" ) + "\tCtrl+C", _( "Copy selected cells to clipboard" ) );
444 menu.Append( GRIDTRICKS_ID_DELETE, _( "Delete" ) + "\tDel", _( "Delete selected jobs" ) );
445 menu.Append( GRIDTRICKS_ID_SELECT, _( "Select All" ) + "\tCtrl+A", _( "Select all jobs" ) );
446
447 menu.Enable( JOB_DESCRIPTION, selectedRows.size() == 1 );
448 menu.Enable( JOB_PROPERTIES, selectedRows.size() == 1 );
449 menu.Enable( GRIDTRICKS_ID_DELETE, selectedRows.size() > 0 );
450
451 m_grid->PopupMenu( &menu );
452}
453
454
455void JOBS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
456{
457 wxArrayInt& selectedRows = m_selectedRows;
458
459 if( event.GetId() == JOB_DESCRIPTION )
460 {
461 if( selectedRows.size() > 0 )
462 {
463 m_grid->SetGridCursor( selectedRows[0], 2 );
464 m_grid->EnableCellEditControl();
465 }
466 }
467 else if( event.GetId() == JOB_PROPERTIES )
468 {
469 if( selectedRows.size() > 0 )
470 m_parent->OpenJobOptionsForListItem( selectedRows[0] );
471 }
472 else if( event.GetId() == GRIDTRICKS_ID_DELETE )
473 {
474 wxCommandEvent dummy;
475 m_parent->OnJobButtonDelete( dummy );
476 }
477 else
478 {
480 }
481}
482
483
484bool JOBS_GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent )
485{
486 m_grid->CancelShowEditorOnMouseUp();
487
488 int curr_col = aEvent.GetCol();
489 int curr_row = aEvent.GetRow();
490
491 if( ( curr_col == COL_NUMBER || curr_col == COL_SOURCE || curr_col == COL_DESCR )
492 && curr_row >= 0 && curr_row < (int) m_parent->GetJobsFile()->GetJobs().size() )
493 {
494 m_doubleClickRow = curr_row;
495 m_grid->CancelPendingChanges();
496
497 CallAfter(
498 [this]()
499 {
500 // Yes, again. CancelShowEditorOnMouseUp() doesn't appear to be 100%
501 // reliable.
502 m_grid->CancelPendingChanges();
503 int row = m_doubleClickRow;
504 m_doubleClickRow = -1;
505
506 if( row >= 0 && row < (int) m_parent->GetJobsFile()->GetJobs().size() )
507 m_parent->OpenJobOptionsForListItem( row );
508 } );
509
510 return true;
511 }
512
513 return false;
514}
515
516
517PANEL_JOBSET::PANEL_JOBSET( wxAuiNotebook* aParent, KICAD_MANAGER_FRAME* aFrame,
518 std::unique_ptr<JOBSET> aJobsFile ) :
519 PANEL_JOBSET_BASE( aParent ),
520 m_parentBook( aParent ),
521 m_frame( aFrame ),
522 m_jobsFile( std::move( aJobsFile ) )
523{
524 m_jobsGrid->PushEventHandler( new JOBS_GRID_TRICKS( this, m_jobsGrid ) );
525
526 m_jobsGrid->OverrideMinSize( 0.6, 0.3 );
527 m_jobsGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
528
529 // 'm' for margins
530 m_jobsGrid->SetColSize( COL_NUMBER, GetTextExtent( wxT( "99m" ) ).x );
531 m_jobsGrid->SetColSize( COL_SOURCE, GetTextExtent( wxT( "PCBm" ) ).x );
532
538
541}
542
543
545{
546 // Delete the GRID_TRICKS.
547 m_jobsGrid->PopEventHandler( true );
548}
549
550
552{
553 JOBSET_DESTINATION* output = aPanel->GetDestination();
554
555 m_destinationListSizer->Detach( aPanel );
556 aPanel->Destroy();
557
558 // ensure the window contents get shifted as needed
559 m_destinationList->Layout();
560 Layout();
561
562 m_jobsFile->RemoveDestination( output );
563}
564
565
567{
568 m_jobsGrid->ClearRows();
569 m_jobsGrid->AppendRows( (int) m_jobsFile->GetJobs().size() );
570
571 int num = 1;
572
573 for( JOBSET_JOB& job : m_jobsFile->GetJobs() )
574 {
575 m_jobsGrid->SetCellValue( num - 1, COL_NUMBER, wxString::Format( "%d", num ) );
576 m_jobsGrid->SetReadOnly( num - 1, COL_NUMBER );
577
578 m_jobsGrid->SetCellValue( num - 1, COL_DESCR, job.GetDescription() );
579
580 m_jobsGrid->SetReadOnly( num - 1, COL_SOURCE );
581
582 KIWAY::FACE_T iface = JOB_REGISTRY::GetKifaceType( job.m_type );
583 wxString source = wxEmptyString;
584
585 if( iface < KIWAY::KIWAY_FACE_COUNT )
586 {
587 if( iface == KIWAY::FACE_PCB )
588 source = wxT( "PCB" );
589 else if( iface == KIWAY::FACE_SCH )
590 source = wxT( "SCH" );
591 }
592
593 m_jobsGrid->SetCellValue( num - 1, COL_SOURCE, source );
594
595 num++;
596 }
597
598 UpdateTitle();
599
600 // Ensure the outputs get their Run-ability status updated
602 panel->UpdateStatus();
603}
604
605
607{
608 wxString tabName = m_jobsFile->GetFullName();
609
610 if( m_jobsFile->GetDirty() )
611 tabName = wxS( "*" ) + tabName;
612
613 int pageIdx = m_parentBook->FindPage( this );
614
615 if( pageIdx >= 0 )
616 m_parentBook->SetPageText( pageIdx, tabName );
617}
618
619
621{
622 PANEL_DESTINATION* destinationPanel = new PANEL_DESTINATION( m_destinationList, this, m_frame,
623 m_jobsFile.get(), aOutput );
624
625#if __OSX__
626 m_outputListSizer->Add( destinationPanel, 0, wxEXPAND, 5 );
627#else
628 m_destinationListSizer->Add( destinationPanel, 0, wxEXPAND|wxLEFT|wxRIGHT|wxBOTTOM, 5 );
629#endif
630
631 m_destinationList->Layout();
632}
633
634
635std::vector<PANEL_DESTINATION*> PANEL_JOBSET::GetDestinationPanels()
636{
637 std::vector<PANEL_DESTINATION*> panels;
638
639 for( const wxSizerItem* item : m_destinationListSizer->GetChildren() )
640 {
641 if( PANEL_DESTINATION* panel = dynamic_cast<PANEL_DESTINATION*>( item->GetWindow() ) )
642 panels.push_back( panel );
643 }
644
645 return panels;
646}
647
648
650{
651 Freeze();
652
653 for( JOBSET_DESTINATION& job : m_jobsFile->GetDestinations() )
654 addDestinationPanel( &job );
655
656 // ensure the window contents get shifted as needed
657 Layout();
658 Thaw();
659}
660
661
663{
664 bool success = false;
665 JOBSET_JOB& job = m_jobsFile->GetJobs()[aItemIndex];
667
668 if( iface < KIWAY::KIWAY_FACE_COUNT )
669 {
671 success = m_frame->Kiway().ProcessJobConfigDialog( iface, job.m_job.get(), m_frame );
672 }
673 else
674 {
675 // special jobs
676 if( job.m_job->GetType() == "special_execute" )
677 {
678 JOB_SPECIAL_EXECUTE* specialJob = static_cast<JOB_SPECIAL_EXECUTE*>( job.m_job.get() );
679
680 DIALOG_EXECUTECOMMAND_JOB_SETTINGS dialog( m_frame, specialJob, &m_frame->Prj() );
681
682 // QuasiModal for Scintilla autocomplete
683 if( dialog.ShowQuasiModal() == wxID_OK )
684 success = true;
685 }
686 else if( job.m_job->GetType() == "special_copyfiles" )
687 {
688 JOB_SPECIAL_COPYFILES* specialJob = static_cast<JOB_SPECIAL_COPYFILES*>( job.m_job.get() );
689 DIALOG_COPYFILES_JOB_SETTINGS dialog( m_frame, specialJob );
690
691 if( dialog.ShowModal() == wxID_OK )
692 success = true;
693 }
694 else if( job.m_job->GetType() == "special_archive" )
695 {
696 JOB_SPECIAL_ARCHIVE* specialJob = static_cast<JOB_SPECIAL_ARCHIVE*>( job.m_job.get() );
697 DIALOG_ARCHIVE_JOB_SETTINGS dialog( m_frame, specialJob );
698
699 if( dialog.ShowModal() == wxID_OK )
700 success = true;
701 }
702 else
703 {
704 DisplayErrorMessage( m_frame, wxString::Format( _( "This version of KiCad does not "
705 "support the '%s' job type." ),
706 job.m_type ) );
707 }
708 }
709
710 if( success )
711 {
712 m_jobsFile->SetDirty();
713 UpdateTitle();
714 }
715
716 // Bring the Kicad manager frame back to the front
717 m_frame->Raise();
718
719 return success;
720}
721
722
723void PANEL_JOBSET::OnGridCellChange( wxGridEvent& aEvent )
724{
725 int row = aEvent.GetRow();
726 int col = aEvent.GetCol();
727
728 if( col == COL_DESCR )
729 m_jobsFile->GetJobs()[row].SetDescription( m_jobsGrid->GetCellValue( row, col ) );
730}
731
732
733void PANEL_JOBSET::OnSaveButtonClick( wxCommandEvent& aEvent )
734{
735 if( !m_jobsGrid->CommitPendingChanges() )
736 return;
737
738 m_jobsFile->SaveToFile( wxEmptyString, true );
739 UpdateTitle();
740}
741
742
743void PANEL_JOBSET::OnAddJobClick( wxCommandEvent& aEvent )
744{
745 if( !m_jobsGrid->CommitPendingChanges() )
746 return;
747
748 wxArrayString headers;
749 std::vector<wxArrayString> items;
750
751 headers.Add( _( "Job Types" ) );
752
754
755 for( const std::pair<const wxString, JOB_REGISTRY_ENTRY>& entry : jobMap )
756 {
757 if( entry.second.deprecated )
758 continue;
759
760 wxArrayString item;
761 item.Add( wxGetTranslation( entry.second.title ) );
762 items.emplace_back( item );
763 }
764
765 EDA_LIST_DIALOG dlg( this, _( "Add New Job" ), headers, items );
766 dlg.SetListLabel( _( "Select job type:" ) );
767
768 if( dlg.ShowModal() == wxID_OK )
769 {
770 wxString selectedString = dlg.GetTextSelection();
771
772 wxString jobKey;
773
774 if( !selectedString.IsEmpty() )
775 {
776 for( const std::pair<const wxString, JOB_REGISTRY_ENTRY>& entry : jobMap )
777 {
778 if( entry.second.deprecated )
779 continue;
780
781 if( wxGetTranslation( entry.second.title ) == selectedString )
782 {
783 jobKey = entry.first;
784 break;
785 }
786 }
787 }
788
789 if( !jobKey.IsEmpty() )
790 {
791 int row = m_jobsFile->GetJobs().size();
792 bool wasDirty = m_jobsFile->GetDirty();
793 JOB* job = JOB_REGISTRY::CreateInstance<JOB>( jobKey );
794
795 m_jobsFile->AddNewJob( jobKey, job );
796
797 if( OpenJobOptionsForListItem( row ) )
798 {
800
801 m_jobsGrid->SetGridCursor( row, 2 );
802 m_jobsGrid->EnableCellEditControl();
803 }
804 else
805 {
806 m_jobsFile->RemoveJob( row );
807 m_jobsFile->SetDirty( wasDirty );
808 }
809 }
810 }
811}
812
813
814void PANEL_JOBSET::OnJobButtonDelete( wxCommandEvent& aEvent )
815{
816 m_jobsGrid->OnDeleteRows(
817 [&]( int row )
818 {
819 m_jobsFile->RemoveJob( row );
820 m_jobsGrid->DeleteRows( row, 1 );
821 } );
822}
823
824
825void PANEL_JOBSET::OnAddDestinationClick( wxCommandEvent& aEvent )
826{
827 wxArrayString headers;
828 std::vector<wxArrayString> items;
829
830 headers.Add( _( "Destination Types" ) );
831
832 for( const auto& [destinationType, destinationTypeInfo] : JobsetDestinationTypeInfos )
833 {
834 wxArrayString item;
835 item.Add( wxGetTranslation( destinationTypeInfo.name ) );
836 items.emplace_back( item );
837 }
838
839 EDA_LIST_DIALOG dlg( this, _( "Add New Destination" ), headers, items );
840 dlg.SetListLabel( _( "Select destination type:" ) );
841 dlg.HideFilter();
842
843 if( dlg.ShowModal() == wxID_OK )
844 {
845 wxString selectedString = dlg.GetTextSelection();
846
847 for( const auto& [destinationType, destinationTypeInfo] : JobsetDestinationTypeInfos )
848 {
849 if( wxGetTranslation( destinationTypeInfo.name ) == selectedString )
850 {
851 JOBSET_DESTINATION* destination = m_jobsFile->AddNewDestination( destinationType );
852
853 DIALOG_DESTINATION dialog( m_frame, m_jobsFile.get(), destination );
854
855 if (dialog.ShowModal() == wxID_OK)
856 {
857 Freeze();
858 addDestinationPanel( destination );
859 Thaw();
860 }
861 else
862 {
863 // canceled
864 m_jobsFile->RemoveDestination( destination );
865 }
866 }
867 }
868 }
869}
870
871
873{
874 if( m_jobsFile->GetDirty() )
875 {
876 wxFileName fileName = m_jobsFile->GetFullFilename();
877 wxString msg = _( "Save changes to '%s' before closing?" );
878
879 if( !HandleUnsavedChanges( this, wxString::Format( msg, fileName.GetFullName() ),
880 [&]() -> bool
881 {
882 return m_jobsFile->SaveToFile( wxEmptyString, true );
883 } ) )
884 {
885 return false;
886 }
887 }
888
889 return true;
890}
891
892
894{
895 PROJECT& project = m_frame->Kiway().Prj();
896 KIWAY_PLAYER* frame = m_frame->Kiway().Player( FRAME_PCB_EDITOR, false );
897
898 if( !frame )
899 {
900 frame = m_frame->Kiway().Player( FRAME_PCB_EDITOR, true );
901
902 // frame can be null if Cvpcb cannot be run. No need to show a warning
903 // Kiway() generates the error messages
904 if( !frame )
905 return;
906
907 wxFileName boardfn = project.GetProjectFullName();
908 boardfn.SetExt( FILEEXT::PcbFileExtension );
909
910 // Prevent our window from being closed during the open process
911 wxEventBlocker blocker( this );
912
913 frame->OpenProjectFiles( std::vector<wxString>( 1, boardfn.GetFullPath() ) );
914
915 if( !frame->IsVisible() )
916 frame->Show( true );
917 }
918
919 frame = m_frame->Kiway().Player( FRAME_SCH, false );
920
921 if( !frame )
922 {
923 frame = m_frame->Kiway().Player( FRAME_SCH, true );
924
925 // frame can be null if Cvpcb cannot be run. No need to show a warning
926 // Kiway() generates the error messages
927 if( !frame )
928 return;
929
930 wxFileName schFn = project.GetProjectFullName();
932
933 wxEventBlocker blocker( this );
934
935 frame->OpenProjectFiles( std::vector<wxString>( 1, schFn.GetFullPath() ) );
936
937 if( !frame->IsVisible() )
938 frame->Show( true );
939 }
940
941 SetFocus();
942}
943
944
946{
947 return m_jobsFile->GetFullFilename();
948}
949
950
951void PANEL_JOBSET::OnJobButtonUp( wxCommandEvent& aEvent )
952{
953 if( !m_jobsGrid->CommitPendingChanges() )
954 return;
955
956 int item = m_jobsGrid->GetGridCursorRow();
957
958 if( item > 0 )
959 {
960 m_jobsFile->MoveJobUp( item );
961
963
964 m_jobsGrid->SelectRow( item - 1 );
965 m_jobsGrid->SetGridCursor( item - 1, m_jobsGrid->GetGridCursorCol() );
966 }
967 else
968 {
969 wxBell();
970 }
971}
972
973
974void PANEL_JOBSET::OnJobButtonDown( wxCommandEvent& aEvent )
975{
976 if( !m_jobsGrid->CommitPendingChanges() )
977 return;
978
979 int item = m_jobsGrid->GetGridCursorRow();
980
981 if( item < m_jobsGrid->GetNumberRows() - 1 )
982 {
983 m_jobsFile->MoveJobDown( item );
984
986
987 m_jobsGrid->SelectRow( item + 1 );
988 m_jobsGrid->SetGridCursor( item + 1, m_jobsGrid->GetGridCursorCol() );
989 }
990 else
991 {
992 wxBell();
993 }
994}
995
996
998{
999 if( !m_jobsGrid->CommitPendingChanges() )
1000 return;
1001
1002 // sanity
1003 if( m_jobsFile->GetDestinations().empty() )
1004 {
1005 DisplayError( this, _( "No destinations defined" ) );
1006 return;
1007 }
1008
1009 for( PANEL_DESTINATION* panel : GetDestinationPanels() )
1010 panel->ClearStatus();
1011
1012 Refresh();
1013
1014 CallAfter(
1015 [this]()
1016 {
1017 PROJECT& project = m_frame->Kiway().Prj();
1019
1020 wxFileName fn = project.GetProjectFullName();
1021 wxSetWorkingDirectory( fn.GetPath() );
1022
1023 {
1024 JOBS_PROGRESS_REPORTER progressReporter( m_frame, _( "Running Jobs" ) );
1025 JOBS_RUNNER jobRunner( &m_frame->Kiway(), m_jobsFile.get(), &project,
1026 NULL_REPORTER::GetInstance(), &progressReporter );
1027
1028 jobRunner.RunJobsAllDestinations();
1029
1030 for( PANEL_DESTINATION* panel : GetDestinationPanels() )
1031 panel->UpdateStatus();
1032 }
1033
1034 // Bring the Kicad manager frame back to the front
1035 m_frame->Raise();
1036 } );
1037}
1038
1039
1040void PANEL_JOBSET::OnSizeGrid( wxSizeEvent& aEvent )
1041{
1042 m_jobsGrid->SetColSize( COL_DESCR, m_jobsGrid->GetSize().x - m_jobsGrid->GetColSize( COL_SOURCE )
1043 - m_jobsGrid->GetColSize( COL_NUMBER ) );
1044
1045 // Always propagate for a grid repaint (needed if the height changes, as well as width)
1046 aEvent.Skip();
1047}
1048
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
@ small_new_window
DIALOG_JOBSET_RUN_LOG_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Jobset Run Log"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void OnJobListItemSelected(wxListEvent &event) override
DIALOG_JOBSET_RUN_LOG(wxWindow *aParent, JOBSET *aJobsFile, JOBSET_DESTINATION *aDestination)
JOBSET_DESTINATION * m_destination
virtual void OnUpdateUI(wxUpdateUIEvent &event) override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
int ShowModal() override
A dialog which shows:
wxString GetTextSelection(int aColumn=0)
Return the selected text from aColumn in the wxListCtrl in the dialog.
void SetListLabel(const wxString &aLabel)
bool m_multiCellEditEnabled
GRID_TRICKS(WX_GRID *aGrid)
bool m_enableSingleClickEdit
virtual void doPopupSelection(wxCommandEvent &event)
WX_GRID * m_grid
I don't own the grid, but he owns me.
std::vector< JOBSET_JOB > GetJobsForDestination(JOBSET_DESTINATION *aDestination)
Definition jobset.cpp:308
PANEL_JOBSET * m_parent
void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent) override
void doPopupSelection(wxCommandEvent &event) override
bool handleDoubleClick(wxGridEvent &aEvent) override
wxArrayInt m_selectedRows
JOBS_GRID_TRICKS(PANEL_JOBSET *aParent, WX_GRID *aGrid)
bool RunJobsAllDestinations(bool aBail=false)
bool RunJobsForDestination(JOBSET_DESTINATION *aDestination, bool aBail=false)
static const REGISTRY_MAP_T & GetRegistry()
std::unordered_map< wxString, JOB_REGISTRY_ENTRY > REGISTRY_MAP_T
static T * CreateInstance(const wxString &aName)
static KIWAY::FACE_T GetKifaceType(const wxString &aName)
An simple container class that lets us dispatch output jobs to kifaces.
Definition job.h:184
const std::string & GetType() const
Definition job.h:195
The main KiCad project manager frame.
A wxFrame capable of the OpenProjectFiles function, meaning it can load a portion of a KiCad project.
virtual bool OpenProjectFiles(const std::vector< wxString > &aFileList, int aCtl=0)
Open a project or set of files given by aFileList.
FACE_T
Known KIFACE implementations.
Definition kiway.h:317
@ KIWAY_FACE_COUNT
Definition kiway.h:326
@ FACE_SCH
eeschema DSO
Definition kiway.h:318
@ FACE_PCB
pcbnew DSO
Definition kiway.h:319
static REPORTER & GetInstance()
Definition reporter.cpp:179
wxStaticBitmap * m_statusBitmap
PANEL_DESTINATION_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxBORDER_SUNKEN|wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
STD_BITMAP_BUTTON * m_buttonProperties
STD_BITMAP_BUTTON * m_buttonDelete
wxStaticBitmap * m_bitmapOutputType
STD_BITMAP_BUTTON * m_buttonOpenOutput
wxStaticText * m_textOutputType
void onMenu(wxCommandEvent &aEvent)
JOBSET_DESTINATION * GetDestination()
void OnLastStatusClick(wxMouseEvent &aEvent) override
void UpdatePathInfo(const wxString &aMsg)
PANEL_DESTINATION(wxWindow *aParent, PANEL_JOBSET *aPanelParent, KICAD_MANAGER_FRAME *aFrame, JOBSET *aFile, JOBSET_DESTINATION *aDestination)
void OnOpenOutput(wxCommandEvent &aEvent) override
virtual void OnDelete(wxCommandEvent &aEvent) override
virtual void OnGenerate(wxCommandEvent &event) override
void OnRightDown(wxMouseEvent &aEvent) override
KICAD_MANAGER_FRAME * m_frame
void OnProperties(wxCommandEvent &aEvent) override
PANEL_JOBSET * m_panelParent
STD_BITMAP_BUTTON * m_buttonAddDestination
STD_BITMAP_BUTTON * m_buttonDown
PANEL_JOBSET_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
wxScrolledWindow * m_destinationList
wxBoxSizer * m_destinationListSizer
STD_BITMAP_BUTTON * m_buttonAddJob
STD_BITMAP_BUTTON * m_buttonDelete
STD_BITMAP_BUTTON * m_buttonUp
wxString GetFilePath() const
virtual void OnSaveButtonClick(wxCommandEvent &aEvent) override
void OnJobButtonDelete(wxCommandEvent &aEvent) override
virtual void OnJobButtonDown(wxCommandEvent &aEvent) override
void EnsurePcbSchFramesOpen()
bool GetCanClose() override
void rebuildJobList()
wxAuiNotebook * m_parentBook
void addDestinationPanel(JOBSET_DESTINATION *aDestination)
virtual void OnGenerateAllDestinationsClick(wxCommandEvent &event) override
bool OpenJobOptionsForListItem(size_t aItemIndex)
std::vector< PANEL_DESTINATION * > GetDestinationPanels()
void buildDestinationList()
KICAD_MANAGER_FRAME * m_frame
PANEL_JOBSET(wxAuiNotebook *aParent, KICAD_MANAGER_FRAME *aFrame, std::unique_ptr< JOBSET > aJobsFile)
virtual void OnAddDestinationClick(wxCommandEvent &aEvent) override
virtual void OnAddJobClick(wxCommandEvent &aEvent) override
std::unique_ptr< JOBSET > m_jobsFile
virtual void OnGridCellChange(wxGridEvent &aEvent) override
void RemoveDestination(PANEL_DESTINATION *aPanel)
virtual void OnJobButtonUp(wxCommandEvent &aEvent) override
virtual void OnSizeGrid(wxSizeEvent &aEvent) override
Container for project specific data.
Definition project.h:63
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:721
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition common.cpp:59
The common library.
bool HandleUnsavedChanges(wxWindow *aParent, const wxString &aMessage, const std::function< bool()> &aSaveFunction)
Display a dialog with Save, Cancel and Discard Changes buttons.
Definition confirm.cpp:146
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:192
This file is part of the common library.
#define _(s)
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_SCH
Definition frame_type.h:30
@ GRIDTRICKS_ID_SELECT
Definition grid_tricks.h:42
@ GRIDTRICKS_ID_COPY
Definition grid_tricks.h:39
@ GRIDTRICKS_ID_DELETE
Definition grid_tricks.h:40
static const std::string KiCadSchematicFileExtension
Some functions to handle hotkeys in KiCad.
KICOMMON_API std::map< JOBSET_DESTINATION_T, JOBSET_DESTINATION_T_INFO > JobsetDestinationTypeInfos
Definition jobset.cpp:42
#define KICOMMON_API
Definition kicommon.h:27
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
KICOMMON_API wxFont GetSmallInfoFont(wxWindow *aWindow)
STL namespace.
KICOMMON_API std::map< JOBSET_DESTINATION_T, JOBSET_DESTINATION_T_INFO > JobsetDestinationTypeInfos
Definition jobset.cpp:42
@ COL_NUMBER
@ COL_DESCR
@ COL_SOURCE
std::vector< FAB_LAYER_COLOR > dummy
JOBSET_DESTINATION_T m_type
Definition jobset.h:134
wxString GetPathInfo() const
Definition jobset.cpp:159
wxString GetDescription() const
Definition jobset.cpp:153
std::optional< wxString > m_lastResolvedOutputPath
Definition jobset.h:143
std::optional< bool > m_lastRunSuccess
Definition jobset.h:140
std::unordered_map< wxString, std::optional< bool > > m_lastRunSuccessMap
Definition jobset.h:141
std::shared_ptr< JOB > m_job
Definition jobset.h:90
wxString m_type
Definition jobset.h:88
Definition of file extensions used in Kicad.