KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_design_block_lib_table.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24
25/* TODO:
26
27*) After any change to uri, reparse the environment variables.
28
29*/
30
31
32#include <set>
33#include <wx/dir.h>
34#include <wx/log.h>
35#include <wx/regex.h>
36#include <wx/grid.h>
37#include <wx/dirdlg.h>
38#include <wx/filedlg.h>
39#include <wx/msgdlg.h>
40
41#include <common.h>
42#include <project.h>
43#include <env_vars.h>
44#include <lib_id.h>
46#include <lib_table_lexer.h>
47#include <bitmaps.h>
49#include <widgets/wx_grid.h>
51#include <confirm.h>
52#include <lib_table_grid.h>
54#include <pgm_base.h>
55#include <env_paths.h>
59#include <kiway.h>
60#include <kiway_express.h>
65#include <paths.h>
66#include <macros.h>
67
68
73{
76
77protected:
78 LIB_TABLE_ROW* at( size_t aIndex ) override { return &m_rows.at( aIndex ); }
79
80 size_t size() const override { return m_rows.size(); }
81
83 {
84 return dynamic_cast<LIB_TABLE_ROW*>( new DESIGN_BLOCK_LIB_TABLE_ROW );
85 }
86
87 LIB_TABLE_ROWS_ITER begin() override { return m_rows.begin(); }
88
90 {
91 return m_rows.insert( aIterator, aRow );
92 }
93
94 void push_back( LIB_TABLE_ROW* aRow ) override { m_rows.push_back( aRow ); }
95
97 {
98 return m_rows.erase( aFirst, aLast );
99 }
100
101public:
103 {
104 m_rows = aTableToEdit.m_rows;
105 }
106
107 void SetValue( int aRow, int aCol, const wxString& aValue ) override
108 {
109 wxCHECK( aRow < (int) size(), /* void */ );
110
111 LIB_TABLE_GRID::SetValue( aRow, aCol, aValue );
112
113 // If setting a filepath, attempt to auto-detect the format
114 if( aCol == COL_URI )
115 {
116 LIB_TABLE_ROW* row = at( (size_t) aRow );
117 wxString fullURI = row->GetFullURI( true );
118
121
122 if( pluginType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
124
125 SetValue( aRow, COL_TYPE, DESIGN_BLOCK_IO_MGR::ShowType( pluginType ) );
126 }
127 }
128};
129
130
132{
133public:
135 LIB_TABLE_GRID_TRICKS( aGrid ), m_dialog( aParent )
136 {
137 }
138
139protected:
141
142 void optionsEditor( int aRow ) override
143 {
145
146 if( tbl->GetNumberRows() > aRow )
147 {
148 LIB_TABLE_ROW* row = tbl->at( (size_t) aRow );
149 const wxString& options = row->GetOptions();
150 wxString result = options;
151 std::map<std::string, UTF8> choices;
152
156 pi->GetLibraryOptions( &choices );
157
158 DIALOG_PLUGIN_OPTIONS dlg( m_dialog, row->GetNickName(), choices, options, &result );
159 dlg.ShowModal();
160
161 if( options != result )
162 {
163 row->SetOptions( result );
164 m_grid->Refresh();
165 }
166 }
167 }
168
171 void paste_text( const wxString& cb_text ) override
172 {
174 size_t ndx = cb_text.find( "(design_block_lib_table" );
175
176 if( ndx != std::string::npos )
177 {
178 // paste the DESIGN_BLOCK_LIB_TABLE_ROWs of s-expression (design_block_lib_table),
179 // starting at column 0 regardless of current cursor column.
180
181 STRING_LINE_READER slr( TO_UTF8( cb_text ), wxT( "Clipboard" ) );
182 LIB_TABLE_LEXER lexer( &slr );
184 bool parsed = true;
185
186 try
187 {
188 tmp_tbl.Parse( &lexer );
189 }
190 catch( PARSE_ERROR& pe )
191 {
192 DisplayError( m_dialog, pe.What() );
193 parsed = false;
194 }
195
196 if( parsed )
197 {
198 // make sure the table is big enough...
199 if( tmp_tbl.GetCount() > (unsigned) tbl->GetNumberRows() )
200 tbl->AppendRows( tmp_tbl.GetCount() - tbl->GetNumberRows() );
201
202 for( unsigned i = 0; i < tmp_tbl.GetCount(); ++i )
203 tbl->m_rows.replace( i, tmp_tbl.At( i ).clone() );
204 }
205
206 m_grid->AutoSizeColumns( false );
207 }
208 else
209 {
210 // paste spreadsheet formatted text.
211 GRID_TRICKS::paste_text( cb_text );
212
213 m_grid->AutoSizeColumns( false );
214 }
215 }
216};
217
218
220 PROJECT* aProject,
221 DESIGN_BLOCK_LIB_TABLE* aGlobalTable,
222 const wxString& aGlobalTblPath,
223 DESIGN_BLOCK_LIB_TABLE* aProjectTable,
224 const wxString& aProjectTblPath,
225 const wxString& aProjectBasePath ) :
227 m_globalTable( aGlobalTable ),
228 m_projectTable( aProjectTable ),
229 m_project( aProject ),
230 m_projectBasePath( aProjectBasePath ),
231 m_parent( aParent )
232{
233 m_global_grid->SetTable( new DESIGN_BLOCK_LIB_TABLE_GRID( *aGlobalTable ), true );
234
235 // add Cut, Copy, and Paste to wxGrids
236 m_path_subs_grid->PushEventHandler( new GRID_TRICKS( m_path_subs_grid ) );
237
239
240 wxArrayString choices;
241
242 // There aren't (yet) any legacy DesignBlock libraries to migrate
243 m_migrate_libs_button->Hide();
244
245 for( auto& [fileType, desc] : m_supportedDesignBlockFiles )
246 choices.Add( DESIGN_BLOCK_IO_MGR::ShowType( fileType ) );
247
249
250 if( cfg->m_lastDesignBlockLibDir.IsEmpty() )
252
254
255 auto autoSizeCol =
256 [&]( WX_GRID* aGrid, int aCol )
257 {
258 int prevWidth = aGrid->GetColSize( aCol );
259
260 aGrid->AutoSizeColumn( aCol, false );
261 aGrid->SetColSize( aCol, std::max( prevWidth, aGrid->GetColSize( aCol ) ) );
262 };
263
264 auto setupGrid =
265 [&]( WX_GRID* aGrid )
266 {
267 // add Cut, Copy, and Paste to wxGrids
268 aGrid->PushEventHandler( new DESIGN_BLOCK_GRID_TRICKS( m_parent, aGrid ) );
269
270 aGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
271
272 wxGridCellAttr* attr = new wxGridCellAttr;
273
274 if( cfg )
275 {
276 attr->SetEditor( new GRID_CELL_PATH_EDITOR(
278 [this]( WX_GRID* grid, int row ) -> wxString
279 {
280 auto* libTable = static_cast<DESIGN_BLOCK_LIB_TABLE_GRID*>( grid->GetTable() );
281 auto* tableRow = static_cast<DESIGN_BLOCK_LIB_TABLE_ROW*>( libTable->at( row ) );
282 DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T fileType = tableRow->GetFileType();
283 const IO_BASE::IO_FILE_DESC& pluginDesc = m_supportedDesignBlockFiles.at( fileType );
284
285 if( pluginDesc.m_IsFile )
286 return pluginDesc.FileFilter();
287 else
288 return wxEmptyString;
289 } ) );
290 }
291
292 aGrid->SetColAttr( COL_URI, attr );
293
294 attr = new wxGridCellAttr;
295 attr->SetEditor( new wxGridCellChoiceEditor( choices ) );
296 aGrid->SetColAttr( COL_TYPE, attr );
297
298 attr = new wxGridCellAttr;
299 attr->SetRenderer( new wxGridCellBoolRenderer() );
300 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
301 aGrid->SetColAttr( COL_ENABLED, attr );
302
303 // No visibility control for design block libraries yet; this feature is primarily
304 // useful for database libraries and it's only implemented for schematic symbols
305 // at the moment.
306 aGrid->HideCol( COL_VISIBLE );
307
308 // all but COL_OPTIONS, which is edited with Option Editor anyways.
309 autoSizeCol( aGrid, COL_NICKNAME );
310 autoSizeCol( aGrid, COL_TYPE );
311 autoSizeCol( aGrid, COL_URI );
312 autoSizeCol( aGrid, COL_DESCR );
313
314 // Gives a selection to each grid, mainly for delete button. wxGrid's wake up with
315 // a currentCell which is sometimes not highlighted.
316 if( aGrid->GetNumberRows() > 0 )
317 aGrid->SelectRow( 0 );
318 };
319
320 setupGrid( m_global_grid );
321
323
324 if( aProjectTable )
325 {
326 m_project_grid->SetTable( new DESIGN_BLOCK_LIB_TABLE_GRID( *aProjectTable ), true );
327 setupGrid( m_project_grid );
328 }
329 else
330 {
331 m_pageNdx = 0;
332 m_notebook->DeletePage( 1 );
333 m_project_grid = nullptr;
334 }
335
336 m_path_subs_grid->SetColLabelValue( 0, _( "Name" ) );
337 m_path_subs_grid->SetColLabelValue( 1, _( "Value" ) );
338
339 // select the last selected page
340 m_notebook->SetSelection( m_pageNdx );
342
343 // for ALT+A handling, we want the initial focus to be on the first selected grid.
344 m_parent->SetInitialFocus( m_cur_grid );
345
346 // Configure button logos
352
353 // For aesthetic reasons, we must set the size of m_browseButton to match the other bitmaps
354 // manually (for instance m_append_button)
355 Layout(); // Needed at least on MSW to compute the actual buttons sizes, after initializing
356 // their bitmaps
357 wxSize buttonSize = m_append_button->GetSize();
358
359 m_browseButton->SetWidthPadding( 4 );
360 m_browseButton->SetMinSize( buttonSize );
361
362 // Populate the browse library options
363 wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
364
365 auto joinExts = []( const std::vector<std::string>& aExts )
366 {
367 wxString joined;
368
369 for( const std::string& ext : aExts )
370 {
371 if( !joined.empty() )
372 joined << wxS( ", " );
373
374 joined << wxS( "*." ) << ext;
375 }
376
377 return joined;
378 };
379
380 for( auto& [type, desc] : m_supportedDesignBlockFiles )
381 {
382 wxString entryStr = DESIGN_BLOCK_IO_MGR::ShowType( type );
383
384 if( desc.m_IsFile && !desc.m_FileExtensions.empty() )
385 {
386 entryStr << wxString::Format( wxS( " (%s)" ), joinExts( desc.m_FileExtensions ) );
387 }
388 else if( !desc.m_IsFile && !desc.m_ExtensionsInDir.empty() )
389 {
390 wxString midPart = wxString::Format( _( "folder with %s files" ), joinExts( desc.m_ExtensionsInDir ) );
391
392 entryStr << wxString::Format( wxS( " (%s)" ), midPart );
393 }
394
395 browseMenu->Append( type, entryStr );
396
397 browseMenu->Bind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler,
398 this, type );
399 }
400
401 Layout();
402
403 // This is the button only press for the browse button instead of the menu
405 this );
406}
407
408
410{
411 wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
412
413 for( auto& [type, desc] : m_supportedDesignBlockFiles )
414 {
415 browseMenu->Unbind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler,
416 this, type );
417 }
418
420
421 // Delete the GRID_TRICKS.
422 // Any additional event handlers should be popped before the window is deleted.
423 m_global_grid->PopEventHandler( true );
424
425 if( m_project_grid )
426 m_project_grid->PopEventHandler( true );
427
428 m_path_subs_grid->PopEventHandler( true );
429}
430
431
433{
436 {
438
439 if( !pi )
440 continue;
441
442 if( const IO_BASE::IO_FILE_DESC& desc = pi->GetLibraryDesc() )
443 m_supportedDesignBlockFiles.emplace( type, desc );
444 }
445}
446
447
449{
450 wxString msg;
451
453 {
454 if( !model )
455 continue;
456
457 for( int r = 0; r < model->GetNumberRows(); )
458 {
459 wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
460 wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim();
461 unsigned illegalCh = 0;
462
463 if( !nick || !uri )
464 {
465 if( !nick && !uri )
466 msg = _( "A library table row nickname and path cells are empty." );
467 else if( !nick )
468 msg = _( "A library table row nickname cell is empty." );
469 else
470 msg = _( "A library table row path cell is empty." );
471
472 wxWindow* topLevelParent = wxGetTopLevelParent( this );
473
474 wxMessageDialog badCellDlg( topLevelParent, msg, _( "Invalid Row Definition" ),
475 wxYES_NO | wxCENTER | wxICON_QUESTION | wxYES_DEFAULT );
476 badCellDlg.SetExtendedMessage( _( "Empty cells will result in all rows that are "
477 "invalid to be removed from the table." ) );
478 badCellDlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Remove Invalid Cells" ) ),
479 wxMessageDialog::ButtonLabel( _( "Cancel Table Update" ) ) );
480
481 if( badCellDlg.ShowModal() == wxID_NO )
482 return false;
483
484 // Delete the "empty" row, where empty means missing nick or uri.
485 // This also updates the UI which could be slow, but there should only be a few
486 // rows to delete, unless the user fell asleep on the Add Row
487 // button.
488 model->GetView()->ClearSelection();
489 model->DeleteRows( r, 1 );
490 }
491 else if( ( illegalCh = LIB_ID::FindIllegalLibraryNameChar( nick ) ) )
492 {
493 msg = wxString::Format( _( "Illegal character '%c' in nickname '%s'." ), illegalCh, nick );
494
495 // show the tabbed panel holding the grid we have flunked:
496 if( model != cur_model() )
497 m_notebook->SetSelection( model == global_model() ? 0 : 1 );
498
499 model->GetView()->MakeCellVisible( r, 0 );
500 model->GetView()->SetGridCursor( r, 1 );
501
502 wxWindow* topLevelParent = wxGetTopLevelParent( this );
503
504 wxMessageDialog errdlg( topLevelParent, msg, _( "Library Nickname Error" ) );
505 errdlg.ShowModal();
506 return false;
507 }
508 else
509 {
510 // set the trimmed values back into the table so they get saved to disk.
511 model->SetValue( r, COL_NICKNAME, nick );
512 model->SetValue( r, COL_URI, uri );
513
514 // Make sure to not save a hidden flag
515 model->SetValue( r, COL_VISIBLE, wxS( "1" ) );
516
517 ++r; // this row was OK.
518 }
519 }
520 }
521
522 // check for duplicate nickNames, separately in each table.
524 {
525 if( !model )
526 continue;
527
528 for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 )
529 {
530 wxString nick1 = model->GetValue( r1, COL_NICKNAME );
531
532 for( int r2 = r1 + 1; r2 < model->GetNumberRows(); ++r2 )
533 {
534 wxString nick2 = model->GetValue( r2, COL_NICKNAME );
535
536 if( nick1 == nick2 )
537 {
538 msg = wxString::Format( _( "Multiple libraries cannot share the same nickname ('%s')." ),
539 nick1 );
540
541 // show the tabbed panel holding the grid we have flunked:
542 if( model != cur_model() )
543 m_notebook->SetSelection( model == global_model() ? 0 : 1 );
544
545 // go to the lower of the two rows, it is technically the duplicate:
546 m_cur_grid->MakeCellVisible( r2, 0 );
547 m_cur_grid->SetGridCursor( r2, 1 );
548
549 wxWindow* topLevelParent = wxGetTopLevelParent( this );
550
551 wxMessageDialog errdlg( topLevelParent, msg, _( "Library Nickname Error" ) );
552 errdlg.ShowModal();
553 return false;
554 }
555 }
556 }
557 }
558
560 {
561 if( !table )
562 continue;
563
564 for( unsigned int r = 0; r < table->GetCount(); ++r )
565 {
566 DESIGN_BLOCK_LIB_TABLE_ROW& row = dynamic_cast<DESIGN_BLOCK_LIB_TABLE_ROW&>( table->At( r ) );
567
568 // Newly-added rows won't have set this yet
569 row.SetParent( table );
570
571 if( !row.GetIsEnabled() )
572 continue;
573
574 try
575 {
576 if( row.Refresh() )
577 {
578 if( table == global_model() )
579 m_parent->m_GlobalTableChanged = true;
580 else
581 m_parent->m_ProjectTableChanged = true;
582 }
583 }
584 catch( const IO_ERROR& ioe )
585 {
586 msg.Printf( _( "Design block library '%s' failed to load." ), row.GetNickName() );
587
588 wxWindow* topLevelParent = wxGetTopLevelParent( this );
589 wxMessageDialog errdlg( topLevelParent, msg + wxS( "\n" ) + ioe.What(), _( "Error Loading Library" ) );
590 errdlg.ShowModal();
591
592 return true;
593 }
594 }
595 }
596
597 return true;
598}
599
600
601void PANEL_DESIGN_BLOCK_LIB_TABLE::OnUpdateUI( wxUpdateUIEvent& event )
602{
603 m_pageNdx = (unsigned) std::max( 0, m_notebook->GetSelection() );
605}
606
607
609{
610 m_cur_grid->OnAddRow(
611 [&]() -> std::pair<int, int>
612 {
613 m_cur_grid->AppendRows( 1 );
614 return { m_cur_grid->GetNumberRows() - 1, COL_NICKNAME };
615 } );
616}
617
618
620{
621 if( !m_cur_grid->CommitPendingChanges() )
622 return;
623
624 wxGridUpdateLocker noUpdates( m_cur_grid );
625
626 int curRow = m_cur_grid->GetGridCursorRow();
627 int curCol = m_cur_grid->GetGridCursorCol();
628
629 // In a wxGrid, collect rows that have a selected cell, or are selected
630 // It is not so easy: it depends on the way the selection was made.
631 // Here, we collect rows selected by clicking on a row label, and rows that contain any
632 // previously-selected cells.
633 // If no candidate, just delete the row with the grid cursor.
634 wxArrayInt selectedRows = m_cur_grid->GetSelectedRows();
635 wxGridCellCoordsArray cells = m_cur_grid->GetSelectedCells();
636 wxGridCellCoordsArray blockTopLeft = m_cur_grid->GetSelectionBlockTopLeft();
637 wxGridCellCoordsArray blockBotRight = m_cur_grid->GetSelectionBlockBottomRight();
638
639 // Add all row having cell selected to list:
640 for( unsigned ii = 0; ii < cells.GetCount(); ii++ )
641 selectedRows.Add( cells[ii].GetRow() );
642
643 // Handle block selection
644 if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() )
645 {
646 for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i )
647 selectedRows.Add( i );
648 }
649
650 // Use the row having the grid cursor only if we have no candidate:
651 if( selectedRows.size() == 0 && m_cur_grid->GetGridCursorRow() >= 0 )
652 selectedRows.Add( m_cur_grid->GetGridCursorRow() );
653
654 if( selectedRows.size() == 0 )
655 {
656 wxBell();
657 return;
658 }
659
660 std::sort( selectedRows.begin(), selectedRows.end() );
661
662 // Remove selected rows (note: a row can be stored more than once in list)
663 int last_row = -1;
664
665 // Needed to avoid a wxWidgets alert if the row to delete is the last row
666 // at least on wxMSW 3.2
667 m_cur_grid->ClearSelection();
668
669 for( int ii = selectedRows.GetCount() - 1; ii >= 0; ii-- )
670 {
671 int row = selectedRows[ii];
672
673 if( row != last_row )
674 {
675 last_row = row;
676 m_cur_grid->DeleteRows( row, 1 );
677 }
678 }
679
680 if( m_cur_grid->GetNumberRows() > 0 && curRow >= 0 )
681 m_cur_grid->SetGridCursor( std::min( curRow, m_cur_grid->GetNumberRows() - 1 ), curCol );
682}
683
684
686{
687 m_cur_grid->OnMoveRowUp(
688 [&]( int row )
689 {
691 boost::ptr_vector<LIB_TABLE_ROW>::auto_type move_me = tbl->m_rows.release( tbl->m_rows.begin() + row );
692
693 tbl->m_rows.insert( tbl->m_rows.begin() + row - 1, move_me.release() );
694
695 // Update the wxGrid
696 wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, row - 1, 0 );
697 tbl->GetView()->ProcessTableMessage( msg );
698 } );
699}
700
701
703{
704 m_cur_grid->OnMoveRowDown(
705 [&]( int row )
706 {
708 boost::ptr_vector<LIB_TABLE_ROW>::auto_type move_me = tbl->m_rows.release( tbl->m_rows.begin() + row );
709
710 tbl->m_rows.insert( tbl->m_rows.begin() + row + 1, move_me.release() );
711
712 // Update the wxGrid
713 wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, row, 0 );
714 tbl->GetView()->ProcessTableMessage( msg );
715 } );
716}
717
718
719// @todo refactor this function into single location shared with PANEL_SYM_LIB_TABLE
721{
722 if( !m_cur_grid->CommitPendingChanges() )
723 return;
724
725 wxArrayInt selectedRows = m_cur_grid->GetSelectedRows();
726
727 if( selectedRows.empty() && m_cur_grid->GetGridCursorRow() >= 0 )
728 selectedRows.push_back( m_cur_grid->GetGridCursorRow() );
729
730 wxArrayInt rowsToMigrate;
732 wxString msg;
733
734 for( int row : selectedRows )
735 {
736 if( m_cur_grid->GetCellValue( row, COL_TYPE ) != kicadType )
737 rowsToMigrate.push_back( row );
738 }
739
740 if( rowsToMigrate.size() <= 0 )
741 {
742 wxMessageBox( wxString::Format( _( "Select one or more rows containing libraries "
743 "to save as current KiCad format." ) ) );
744 return;
745 }
746 else
747 {
748 if( rowsToMigrate.size() == 1 )
749 {
750 msg.Printf( _( "Save '%s' as current KiCad format and replace entry in table?" ),
751 m_cur_grid->GetCellValue( rowsToMigrate[0], COL_NICKNAME ) );
752 }
753 else
754 {
755 msg.Printf( _( "Save %d libraries as current KiCad format and replace entries in table?" ),
756 (int) rowsToMigrate.size() );
757 }
758
759 if( !IsOK( m_parent, msg ) )
760 return;
761 }
762
763 for( int row : rowsToMigrate )
764 {
765 wxString libName = m_cur_grid->GetCellValue( row, COL_NICKNAME );
766 wxString relPath = m_cur_grid->GetCellValue( row, COL_URI );
767 wxString resolvedPath = ExpandEnvVarSubstitutions( relPath, m_project );
768 wxFileName legacyLib( resolvedPath );
769
770 if( !legacyLib.Exists() )
771 {
772 msg.Printf( _( "Library '%s' not found." ), relPath );
773 DisplayErrorMessage( wxGetTopLevelParent( this ), msg );
774 continue;
775 }
776
777 wxFileName newLib( resolvedPath );
778 newLib.AppendDir( newLib.GetName() + "." + FILEEXT::KiCadDesignBlockLibPathExtension );
779 newLib.SetName( "" );
780 newLib.ClearExt();
781
782 if( newLib.DirExists() )
783 {
784 msg.Printf( _( "Folder '%s' already exists. Do you want overwrite any existing design "
785 "blocks?" ),
786 newLib.GetFullPath() );
787
788 switch( wxMessageBox( msg, _( "Migrate Library" ), wxYES_NO | wxCANCEL | wxICON_QUESTION, m_parent ) )
789 {
790 case wxYES: break;
791 case wxNO: continue;
792 case wxCANCEL: return;
793 }
794 }
795
796 wxString options = m_cur_grid->GetCellValue( row, COL_OPTIONS );
797 std::unique_ptr<std::map<std::string, UTF8>> props( LIB_TABLE::ParseOptions( options.ToStdString() ) );
798
799 if( DESIGN_BLOCK_IO_MGR::ConvertLibrary( props.get(), legacyLib.GetFullPath(),
800 newLib.GetFullPath() ) )
801 {
802 relPath =
803 NormalizePath( newLib.GetFullPath(), &Pgm().GetLocalEnvVariables(), m_project );
804
805 // Do not use the project path in the global library table. This will almost
806 // assuredly be wrong for a different project.
807 if( m_cur_grid == m_global_grid && relPath.Contains( "${KIPRJMOD}" ) )
808 relPath = newLib.GetFullPath();
809
810 m_cur_grid->SetCellValue( row, COL_URI, relPath );
811 m_cur_grid->SetCellValue( row, COL_TYPE, kicadType );
812 }
813 else
814 {
815 msg.Printf( _( "Failed to save design block library file '%s'." ), newLib.GetFullPath() );
816 DisplayErrorMessage( wxGetTopLevelParent( this ), msg );
817 }
818 }
819}
820
821
823{
824 if( !m_cur_grid->CommitPendingChanges() )
825 return;
826
828
829 // We are bound both to the menu and button with this one handler
830 // So we must set the file type based on it
831 if( event.GetEventType() == wxEVT_BUTTON )
832 {
833 // Let's default to adding a kicad design block file for just the design block
835 }
836 else
837 {
838 fileType = static_cast<DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T>( event.GetId() );
839 }
840
842 {
843 wxLogWarning( wxT( "File type selection event received but could not find the file type in the table" ) );
844 return;
845 }
846
849
850 wxString title = wxString::Format( _( "Select %s Library" ), DESIGN_BLOCK_IO_MGR::ShowType( fileType ) );
851 wxString dummy;
852 wxString* lastDir;
853
855 lastDir = &m_lastProjectLibDir;
856 else
857 lastDir = cfg ? &cfg->m_lastDesignBlockLibDir : &dummy;
858
859 wxArrayString files;
860 wxWindow* topLevelParent = wxGetTopLevelParent( this );
861
862 if( fileDesc.m_IsFile )
863 {
864 wxFileDialog dlg( topLevelParent, title, *lastDir, wxEmptyString, fileDesc.FileFilter(),
865 wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
866
867 if( dlg.ShowModal() == wxID_CANCEL )
868 return;
869
870 dlg.GetPaths( files );
871 *lastDir = dlg.GetDirectory();
872 }
873 else
874 {
875 wxDirDialog dlg( topLevelParent, title, *lastDir,
876 wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST | wxDD_MULTIPLE );
877
878 if( dlg.ShowModal() == wxID_CANCEL )
879 return;
880
881 dlg.GetPaths( files );
882
883 if( !files.IsEmpty() )
884 {
885 wxFileName first( files.front() );
886 *lastDir = first.GetPath();
887 }
888 }
889
890 // Drop the last directory if the path is a .pretty folder
892 cfg->m_lastDesignBlockLibDir = cfg->m_lastDesignBlockLibDir.BeforeLast( wxFileName::GetPathSeparator() );
893
894 const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
895 bool addDuplicates = false;
896 bool applyToAll = false;
897 wxString warning = _( "Warning: Duplicate Nicknames" );
898 wxString msg = _( "A library nicknamed '%s' already exists." );
899 wxString detailedMsg = _( "One of the nicknames will need to be changed after "
900 "adding this library." );
901
902 for( const wxString& filePath : files )
903 {
904 wxFileName fn( filePath );
905 wxString nickname = LIB_ID::FixIllegalChars( fn.GetName(), true );
906 bool doAdd = true;
907
909 nickname = LIB_ID::FixIllegalChars( fn.GetFullName(), true ).wx_str();
910
911 if( cur_model()->ContainsNickname( nickname ) )
912 {
913 if( !applyToAll )
914 {
915 // The cancel button adds the library to the table anyway
916 addDuplicates = OKOrCancelDialog( wxGetTopLevelParent( this ), warning,
917 wxString::Format( msg, nickname ), detailedMsg,
918 _( "Skip" ), _( "Add Anyway" ), &applyToAll )
919 == wxID_CANCEL;
920 }
921
922 doAdd = addDuplicates;
923 }
924
925 if( doAdd && m_cur_grid->AppendRows( 1 ) )
926 {
927 int last_row = m_cur_grid->GetNumberRows() - 1;
928
929 m_cur_grid->SetCellValue( last_row, COL_NICKNAME, nickname );
930
931 m_cur_grid->SetCellValue( last_row, COL_TYPE,
933
934 // try to use path normalized to an environmental variable or project path
935 wxString path = NormalizePath( filePath, &envVars, m_projectBasePath );
936
937 // Do not use the project path in the global library table. This will almost
938 // assuredly be wrong for a different project.
939 if( m_pageNdx == 0 && path.Contains( wxT( "${KIPRJMOD}" ) ) )
940 path = fn.GetFullPath();
941
942 m_cur_grid->SetCellValue( last_row, COL_URI, path );
943 }
944 }
945
946 if( !files.IsEmpty() )
947 {
948 int new_row = m_cur_grid->GetNumberRows() - 1;
949 m_cur_grid->MakeCellVisible( new_row, m_cur_grid->GetGridCursorCol() );
950 m_cur_grid->SetGridCursor( new_row, m_cur_grid->GetGridCursorCol() );
951 }
952}
953
954
956{
957 // Account for scroll bars
958 aWidth -= ( m_path_subs_grid->GetSize().x - m_path_subs_grid->GetClientSize().x );
959
960 m_path_subs_grid->AutoSizeColumn( 0 );
961 m_path_subs_grid->SetColSize( 0, std::max( 72, m_path_subs_grid->GetColSize( 0 ) ) );
962 m_path_subs_grid->SetColSize( 1, std::max( 120, aWidth - m_path_subs_grid->GetColSize( 0 ) ) );
963}
964
965
967{
968 adjustPathSubsGridColumns( event.GetSize().GetX() );
969
970 event.Skip();
971}
972
973
975{
976 if( !m_cur_grid->CommitPendingChanges() )
977 return false;
978
979 if( verifyTables() )
980 {
981 if( *global_model() != *m_globalTable )
982 {
983 m_parent->m_GlobalTableChanged = true;
984
985 m_globalTable->TransferRows( global_model()->m_rows );
986 }
987
989 {
990 m_parent->m_ProjectTableChanged = true;
991
992 m_projectTable->TransferRows( project_model()->m_rows );
993 }
994
995 return true;
996 }
997
998 return false;
999}
1000
1001
1005{
1006 wxRegEx re( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?", wxRE_ADVANCED );
1007 wxASSERT( re.IsValid() ); // wxRE_ADVANCED is required.
1008
1009 std::set<wxString> unique;
1010
1011 // clear the table
1012 m_path_subs_grid->ClearRows();
1013
1015 {
1016 if( !tbl )
1017 continue;
1018
1019 for( int row = 0; row < tbl->GetNumberRows(); ++row )
1020 {
1021 wxString uri = tbl->GetValue( row, COL_URI );
1022
1023 while( re.Matches( uri ) )
1024 {
1025 wxString envvar = re.GetMatch( uri, 2 );
1026
1027 // if not ${...} form then must be $(...)
1028 if( envvar.IsEmpty() )
1029 envvar = re.GetMatch( uri, 4 );
1030
1031 // ignore duplicates
1032 unique.insert( envvar );
1033
1034 // delete the last match and search again
1035 uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
1036 }
1037 }
1038 }
1039
1040 // Make sure this special environment variable shows up even if it was
1041 // not used yet. It is automatically set by KiCad to the directory holding
1042 // the current project.
1043 unique.insert( PROJECT_VAR_NAME );
1045
1046 // This special environment variable is used to locate 3d shapes
1047 unique.insert( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ) );
1048
1049 for( const wxString& evName : unique )
1050 {
1051 int row = m_path_subs_grid->GetNumberRows();
1052 m_path_subs_grid->AppendRows( 1 );
1053
1054 m_path_subs_grid->SetCellValue( row, 0, wxT( "${" ) + evName + wxT( "}" ) );
1055 m_path_subs_grid->SetCellEditor( row, 0, new GRID_CELL_READONLY_TEXT_EDITOR() );
1056
1057 wxString evValue;
1058 wxGetEnv( evName, &evValue );
1059 m_path_subs_grid->SetCellValue( row, 1, evValue );
1060 m_path_subs_grid->SetCellEditor( row, 1, new GRID_CELL_READONLY_TEXT_EDITOR() );
1061 }
1062
1063 adjustPathSubsGridColumns( m_path_subs_grid->GetRect().GetWidth() );
1064}
1065
1066//-----</event handlers>---------------------------------
1067
1068
1070
1071
1072void InvokeEditDesignBlockLibTable( KIWAY* aKiway, wxWindow *aParent )
1073{
1075 wxString globalTablePath = DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName();
1076 DESIGN_BLOCK_LIB_TABLE* projectTable = aKiway->Prj().DesignBlockLibs();
1077 wxString projectTablePath = aKiway->Prj().DesignBlockLibTblName();
1078 wxString msg;
1079
1080 DIALOG_EDIT_LIBRARY_TABLES dlg( aParent, _( "Design Block Libraries" ) );
1081
1082 if( aKiway->Prj().IsNullProject() )
1083 projectTable = nullptr;
1084
1085 dlg.InstallPanel( new PANEL_DESIGN_BLOCK_LIB_TABLE( &dlg, &aKiway->Prj(), globalTable,
1086 globalTablePath,
1087 projectTable, projectTablePath,
1088 aKiway->Prj().GetProjectPath() ) );
1089
1090 if( dlg.ShowModal() == wxID_CANCEL )
1091 return;
1092
1093 if( dlg.m_GlobalTableChanged )
1094 {
1095 try
1096 {
1097 globalTable->Save( globalTablePath );
1098 }
1099 catch( const IO_ERROR& ioe )
1100 {
1101 msg.Printf( _( "Error saving global library table:\n\n%s" ), ioe.What() );
1102 wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
1103 }
1104 }
1105
1106 if( projectTable && dlg.m_ProjectTableChanged )
1107 {
1108 try
1109 {
1110 projectTable->Save( projectTablePath );
1111 }
1112 catch( const IO_ERROR& ioe )
1113 {
1114 msg.Printf( _( "Error saving project-specific library table:\n\n%s" ), ioe.What() );
1115 wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
1116 }
1117 }
1118
1119 std::string payload = "";
1120 aKiway->ExpressMail( FRAME_SCH, MAIL_RELOAD_LIB, payload );
1121 aKiway->ExpressMail( FRAME_PCB_EDITOR, MAIL_RELOAD_LIB, payload );
1122
1123 return;
1124}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
void paste_text(const wxString &cb_text) override
handle specialized clipboard text, with leading "(design_block_lib_table", OR spreadsheet formatted t...
DIALOG_EDIT_LIBRARY_TABLES * m_dialog
DESIGN_BLOCK_GRID_TRICKS(DIALOG_EDIT_LIBRARY_TABLES *aParent, WX_GRID *aGrid)
@ KICAD_SEXP
S-expression KiCad file format.
static const wxString ShowType(DESIGN_BLOCK_FILE_T aFileType)
static DESIGN_BLOCK_FILE_T GuessPluginTypeFromLibPath(const wxString &aLibPath, int aCtl=0)
static DESIGN_BLOCK_FILE_T EnumFromStr(const wxString &aFileType)
static bool ConvertLibrary(std::map< std::string, UTF8 > *aOldFileProps, const wxString &aOldFilePath, const wxString &aNewFilePath)
Convert a design block library to the latest KiCad format.
static DESIGN_BLOCK_IO * FindPlugin(DESIGN_BLOCK_FILE_T aFileType)
This class builds a wxGridTableBase by wrapping an DESIGN_BLOCK_LIB_TABLE object.
LIB_TABLE_ROW * at(size_t aIndex) override
void push_back(LIB_TABLE_ROW *aRow) override
DESIGN_BLOCK_LIB_TABLE_GRID(const DESIGN_BLOCK_LIB_TABLE &aTableToEdit)
LIB_TABLE_ROWS_ITER erase(LIB_TABLE_ROWS_ITER aFirst, LIB_TABLE_ROWS_ITER aLast) override
void SetValue(int aRow, int aCol, const wxString &aValue) override
LIB_TABLE_ROWS_ITER insert(LIB_TABLE_ROWS_ITER aIterator, LIB_TABLE_ROW *aRow) override
LIB_TABLE_ROWS_ITER begin() override
Hold a record identifying a library accessed by the appropriate design block library #PLUGIN object i...
bool Refresh() override
Attempt to reload the library.
static wxString GetGlobalTableFileName()
virtual void Parse(LIB_TABLE_LEXER *aLexer) override
Parse the #LIB_TABLE_LEXER s-expression library table format into the appropriate LIB_TABLE_ROW objec...
DESIGN_BLOCK_LIB_TABLE(DESIGN_BLOCK_LIB_TABLE *aFallBackTable=nullptr)
Build a design block library table by pre-pending this table fragment in front of aFallBackTable.
static const wxString GlobalPathEnvVariableName()
Return the name of the environment variable used to hold the directory of locally installed "KiCad sp...
static DESIGN_BLOCK_LIB_TABLE & GetGlobalLibTable()
An options editor in the form of a two column name/value spreadsheet like (table) UI.
int ShowModal() override
Editor for wxGrid cells that adds a file/folder browser to the grid input field.
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:61
virtual void paste_text(const wxString &cb_text)
WX_GRID * m_grid
I don't own the grid, but he owns me.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
wxString m_lastDesignBlockLibDir
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:286
virtual void ExpressMail(FRAME_T aDestination, MAIL_T aCommand, std::string &aPayload, wxWindow *aSource=nullptr)
Send aPayload to aDestination from aSource.
Definition kiway.cpp:499
virtual PROJECT & Prj() const
Return the PROJECT associated with this KIWAY.
Definition kiway.cpp:192
static unsigned FindIllegalLibraryNameChar(const UTF8 &aLibraryName)
Looks for characters that are illegal in library nicknames.
Definition lib_id.cpp:241
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, bool aLib)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition lib_id.cpp:192
LIB_TABLE_GRID_TRICKS(WX_GRID *aGrid)
This abstract base class mixes any object derived from LIB_TABLE into wxGridTableBase so the result c...
void SetValue(int aRow, int aCol, const wxString &aValue) override
bool AppendRows(size_t aNumRows=1) override
int GetNumberRows() override
Hold a record identifying a library accessed by the appropriate plug in object in the LIB_TABLE.
const wxString & GetOptions() const
Return the options string, which may hold a password or anything else needed to instantiate the under...
virtual const wxString GetType() const =0
Return the type of library represented by this row.
void SetParent(LIB_TABLE *aParent)
const wxString & GetNickName() const
const wxString GetFullURI(bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
LIB_TABLE_ROW * clone() const
bool GetIsEnabled() const
void SetOptions(const wxString &aOptions)
Change the library options strings.
static std::map< std::string, UTF8 > * ParseOptions(const std::string &aOptionsList)
Parses aOptionsList and places the result into a #PROPERTIES object which is returned.
LIB_TABLE_ROW & At(unsigned aIndex)
Get the 'n'th LIB_TABLE_ROW object.
LIB_TABLE_ROWS m_rows
Owning set of rows.
unsigned GetCount() const
Get the number of rows contained in the table.
void Save(const wxString &aFileName) const
Write this library table to aFileName in s-expression form.
PANEL_DESIGN_BLOCK_LIB_TABLE_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)
Dialog to show and edit symbol library tables.
void moveUpHandler(wxCommandEvent &event) override
void onMigrateLibraries(wxCommandEvent &event) override
DESIGN_BLOCK_LIB_TABLE_GRID * project_model() const
void browseLibrariesHandler(wxCommandEvent &event)
void populateEnvironReadOnlyTable()
Populate the readonly environment variable table with names and values by examining all the full_uri ...
DESIGN_BLOCK_LIB_TABLE_GRID * cur_model() const
std::map< DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T, IO_BASE::IO_FILE_DESC > m_supportedDesignBlockFiles
void moveDownHandler(wxCommandEvent &event) override
PANEL_DESIGN_BLOCK_LIB_TABLE(DIALOG_EDIT_LIBRARY_TABLES *aParent, PROJECT *aProject, DESIGN_BLOCK_LIB_TABLE *aGlobalTable, const wxString &aGlobalTblPath, DESIGN_BLOCK_LIB_TABLE *aProjectTable, const wxString &aProjectTblPath, const wxString &aProjectBasePath)
DIALOG_EDIT_LIBRARY_TABLES * m_parent
void OnUpdateUI(wxUpdateUIEvent &event) override
void appendRowHandler(wxCommandEvent &event) override
DESIGN_BLOCK_LIB_TABLE_GRID * global_model() const
void onSizeGrid(wxSizeEvent &event) override
void deleteRowHandler(wxCommandEvent &event) override
bool verifyTables()
Trim important fields, removes blank row entries, and checks for duplicates.
static wxString GetDefaultUserDesignBlocksPath()
Gets the default path we point users to create projects.
Definition paths.cpp:103
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:822
Container for project specific data.
Definition project.h:65
virtual const wxString DesignBlockLibTblName() const
Return the path and file name of this projects design block library table.
Definition project.cpp:198
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:162
virtual DESIGN_BLOCK_LIB_TABLE * DesignBlockLibs()
Return the table of design block libraries.
Definition project.cpp:445
virtual bool IsNullProject() const
Check if this project is a null project (i.e.
Definition project.cpp:180
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:253
wxString wx_str() const
Definition utf8.cpp:45
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:355
The common library.
int OKOrCancelDialog(wxWindow *aParent, const wxString &aWarning, const wxString &aMessage, const wxString &aDetailedMessage, const wxString &aOKLabel, const wxString &aCancelLabel, bool *aApplyToAll)
Display a warning dialog with aMessage and returns the user response.
Definition confirm.cpp:142
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:251
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:194
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:169
This file is part of the common library.
#define _(s)
wxString NormalizePath(const wxFileName &aFilePath, const ENV_VAR_MAP *aEnvVars, const wxString &aProjectPath)
Normalize a file path to an environmental variable, if possible.
Definition env_paths.cpp:73
Helper functions to substitute paths with environmental variables.
Functions related to environment variables, including help functions.
@ FRAME_PCB_EDITOR
Definition frame_type.h:42
@ FRAME_SCH
Definition frame_type.h:34
static const std::string KiCadDesignBlockLibPathExtension
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
LIB_TABLE_ROWS::iterator LIB_TABLE_ROWS_ITER
@ COL_DESCR
@ COL_NICKNAME
@ COL_OPTIONS
@ COL_ENABLED
@ COL_URI
This file contains miscellaneous commonly used macros and functions.
@ MAIL_RELOAD_LIB
Definition mail_type.h:57
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:77
void InvokeEditDesignBlockLibTable(KIWAY *aKiway, wxWindow *aParent)
PGM_BASE & Pgm()
The global program "get" accessor.
Definition pgm_base.cpp:913
see class PGM_BASE
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition project.h:40
T * GetAppSettings(const char *aFilename)
std::vector< FAB_LAYER_COLOR > dummy
MODEL3D_FORMAT_TYPE fileType(const char *aFileName)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Container that describes file type info.
Definition io_base.h:43
bool m_IsFile
Whether the library is a folder or a file.
Definition io_base.h:51
wxString FileFilter() const
Definition io_base.cpp:40
A filename or source description, a problem input line, a line number, a byte offset,...
wxString result
Test unit parsing edge cases and error handling.
Definition of file extensions used in Kicad.