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