KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_embedded_files.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 3
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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <eda_item.h>
21#include <confirm.h>
22#include <bitmaps.h>
24#include <embedded_files.h>
25#include <font/outline_font.h>
26#include <kidialog.h>
28#include <widgets/wx_grid.h>
29
30#include <wx/clipbrd.h>
31#include <wx/dirdlg.h>
32#include <wx/filedlg.h>
33#include <wx/filename.h>
34#include <wx/log.h>
35#include <wx/menu.h>
36#include <wx/wfstream.h>
37#include <wx/wupdlock.h>
38#include <kiplatform/ui.h>
39
40/* ---------- GRID_TRICKS for embedded files grid ---------- */
41
47
48
49void EMBEDDED_FILES_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
50{
51 if( const int row = aEvent.GetRow(); row >= 0 && row < m_grid->GetNumberRows() )
52 {
53 m_curRow = row;
54 menu.Append( EMBEDDED_FILES_GRID_TRICKS_COPY_FILENAME, _( "Copy Embedded Reference" ),
55 _( "Copy the reference for this embedded file" ) );
56 menu.AppendSeparator();
57 GRID_TRICKS::showPopupMenu( menu, aEvent );
58 }
59 else
60 {
61 m_curRow = -1;
62 }
63}
64
65
67{
68 if( event.GetId() == EMBEDDED_FILES_GRID_TRICKS_COPY_FILENAME )
69 {
70 if( m_curRow >= 0 )
71 {
72 const wxString cellValue = m_grid->GetCellValue( m_curRow, 1 );
73
74 if( wxTheClipboard->Open() )
75 {
76 wxTheClipboard->SetData( new wxTextDataObject( cellValue ) );
77 wxTheClipboard->Close();
78 }
79 }
80 }
81 else
82 {
84 }
85}
86
87
88/* ---------- End of GRID_TRICKS for embedded files grid ---------- */
89
90
91PANEL_EMBEDDED_FILES::PANEL_EMBEDDED_FILES( wxWindow* aParent, EMBEDDED_FILES* aFiles, int aFlags,
92 std::vector<const EMBEDDED_FILES*> aInheritedFiles ) :
94 m_files( aFiles ),
96 m_inheritedFiles( std::move( aInheritedFiles ) )
97{
98 m_files_grid->SetUseNativeColLabels();
99
100 // Deep-copy entries into m_localFiles so that user edits in the dialog (add/remove) operate
101 // on an isolated working copy. m_localFiles is committed back to m_files on OK.
102 for( auto& [name, file] : m_files->EmbeddedFileMap() )
103 m_localFiles->AddFile( new EMBEDDED_FILES::EMBEDDED_FILE( *file ) );
104
105 for( const EMBEDDED_FILES* inheritedFiles : m_inheritedFiles )
106 {
107 for( auto& [name, file] : inheritedFiles->EmbeddedFileMap() )
108 {
109 if( m_localFiles->HasFile( name ) )
110 continue;
111
112 m_localFiles->AddFile( new EMBEDDED_FILES::EMBEDDED_FILE( *file ) );
113 m_inheritedFileNames.insert( name );
114 }
115 }
116
117 if( aFlags & NO_MARGINS )
118 {
119 m_filesGridSizer->Detach( m_files_grid );
120 m_filesGridSizer->Add( m_files_grid, 5, wxEXPAND, 5 );
121
122 m_buttonsSizer->Detach( m_browse_button );
123 m_buttonsSizer->Prepend( m_browse_button, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
124
125 m_buttonsSizer->Detach( m_export );
126 m_buttonsSizer->Add( m_export, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
127 }
128
129 // Set up the standard buttons
130 m_delete_button->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
131 m_browse_button->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
132 m_files_grid->SetMargins( 0 - wxSYS_VSCROLL_X, 0 );
133 m_files_grid->EnableAlternateRowColors();
134
135 m_files_grid->PushEventHandler( new EMBEDDED_FILES_GRID_TRICKS( m_files_grid ) );
136 m_files_grid->SetupColumnAutosizer( 1 );
137
138 m_localFiles->SetFileAddedCallback(
140 {
141 for( int ii = 0; ii < m_files_grid->GetNumberRows(); ii++ )
142 {
143 if( m_files_grid->GetCellValue( ii, 1 ) == file->GetLink() )
144 {
145 m_files_grid->DeleteRows( ii );
146 break;
147 }
148 }
149
150 m_files_grid->AppendRows( 1 );
151 int ii = m_files_grid->GetNumberRows() - 1;
152 m_files_grid->SetCellValue( ii, 0, file->name );
153 m_files_grid->SetCellValue( ii, 1, file->GetLink() );
154
155 } );
156}
157
158
160{
161 // Remove the GRID_TRICKS handler
162 m_files_grid->PopEventHandler( true );
163 delete m_localFiles;
164}
165
166
168{
169 m_files_grid->ClearGrid();
170 m_files_grid->ClearRows();
171
172 int ii = 0;
173
174 for( auto& [name, file] : m_localFiles->EmbeddedFileMap() )
175 {
176 while( m_files_grid->GetNumberRows() < ii + 1 )
177 m_files_grid->AppendRows( 1 );
178
179 m_files_grid->SetCellValue( ii, 0, name );
180 m_files_grid->SetCellValue( ii, 1, file->GetLink() );
181
182 ii++;
183 }
184
185 m_cbEmbedFonts->SetValue( m_files->GetAreFontsEmbedded() );
186 return true;
187}
188
189
191{
192 std::optional<bool> deleteReferences;
193
194 auto confirmDelete =
195 [&]() -> bool
196 {
197 if( EDA_ITEM* parent = dynamic_cast<EDA_ITEM*>( m_files ) )
198 {
199 if( parent->Type() == PCB_T )
200 {
201 return IsOK( m_parent, _( "Deleted embedded files are also referenced in some footprints.\n"
202 "Delete from footprints as well?" ) );
203 }
204 else if( parent->Type() == SCHEMATIC_T )
205 {
206 return IsOK( m_parent, _( "Deleted embedded files are also referenced in some symbols.\n"
207 "Delete from symbols as well?" ) );
208 }
209 }
210
211 wxFAIL_MSG( wxT( "Unexpected embedded files owner" ) );
212 return false;
213 };
214
215 for( const auto& [name, file] : m_files->EmbeddedFileMap() )
216 {
217 if( !m_localFiles->HasFile( name ) )
218 {
219 m_files->RunOnNestedEmbeddedFiles(
220 [&]( EMBEDDED_FILES* nested_files )
221 {
222 if( nested_files->HasFile( name ) )
223 {
224 if( !deleteReferences.has_value() )
225 deleteReferences = confirmDelete();
226
227 if( deleteReferences.value() )
228 nested_files->RemoveFile( name, true );
229 }
230 } );
231 }
232
233 if( deleteReferences.has_value() && deleteReferences.value() == false )
234 break;
235 }
236
237 m_files->ClearEmbeddedFiles();
238
239 std::vector<std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE>> files;
240
241 for( const auto& [name, file] : m_localFiles->EmbeddedFileMap() )
242 files.push_back( file );
243
244 for( std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE>& file : files )
245 {
246 if( m_inheritedFileNames.count( file->name ) )
247 continue;
248
249 wxString name = file->name;
250 m_files->AddFile( std::move( file ) );
251 m_localFiles->RemoveFile( name, false );
252 }
253
254 m_files->SetAreFontsEmbedded( m_cbEmbedFonts->IsChecked() );
255
256 return true;
257}
258
259
260void PANEL_EMBEDDED_FILES::onFontEmbedClick( wxCommandEvent& event )
261{
262 wxWindowUpdateLocker updateLock( this );
263
264 int row_pos = m_files_grid->GetGridCursorRow();
265 int col_pos = m_files_grid->GetGridCursorCol();
266 wxString row_name;
267
268 if( row_pos >= 0 )
269 row_name = m_files_grid->GetCellValue( row_pos, 0 );
270
271 for( int ii = 0; ii < m_files_grid->GetNumberRows(); ii++ )
272 {
273 wxString name = m_files_grid->GetCellValue( ii, 0 );
274
275 EMBEDDED_FILES::EMBEDDED_FILE* file = m_localFiles->GetEmbeddedFile( name );
276
278 {
279 m_files_grid->DeleteRows( ii );
280 ii--;
281 m_localFiles->RemoveFile( name );
282 }
283 }
284
285 if( m_cbEmbedFonts->IsChecked() )
286 {
287 std::set<KIFONT::OUTLINE_FONT*> fonts = m_files->GetFonts();
288
289 for( KIFONT::OUTLINE_FONT* font : fonts )
290 {
291 EMBEDDED_FILES::EMBEDDED_FILE* result = m_localFiles->AddFile( font->GetFileName(), true );
292
293 if( !result )
294 {
295 wxLogTrace( wxT( "KICAD_EMBED" ), wxString::Format( "Could not embed font %s",
296 font->GetFileName() ) );
297 continue;
298 }
299 }
300 }
301
302 if( row_pos >= 0 )
303 {
304 col_pos = std::max( std::min( col_pos, m_files_grid->GetNumberCols() - 1 ), 0 );
305 row_pos = std::max( std::min( row_pos, m_files_grid->GetNumberRows() - 1 ), 0 );
306 m_files_grid->SetGridCursor( row_pos, col_pos );
307
308 for( int ii = 0; ii < m_files_grid->GetNumberRows(); ++ii )
309 {
310 if( m_files_grid->GetCellValue( ii, 0 ) == row_name )
311 {
312 m_files_grid->SetGridCursor( ii, col_pos );
313 break;
314 }
315 }
316 }
317}
318
319
321{
322 wxFileName fileName( aFile );
323 wxString name = fileName.GetFullName();
324
325 if( m_localFiles->HasFile( name ) )
326 {
327 EMBEDDED_FILES::EMBEDDED_FILE* existingFile = m_localFiles->GetEmbeddedFile( name );
328 std::string newFileHash;
329
331 {
332 wxString msg = wxString::Format( _( "Failed to read file '%s'." ), name );
333 KIDIALOG errorDlg( m_parent, msg, _( "Error" ), wxOK | wxICON_ERROR );
334 errorDlg.ShowModal();
335 return nullptr;
336 }
337
338 if( existingFile && existingFile->data_hash == newFileHash )
339 {
340 return existingFile;
341 }
342
343 wxString msg = wxString::Format(
344 _( "A file named '%s' is already embedded, but the file on disk has different "
345 "content.\n\nDo you want to replace the embedded file with the new version?" ),
346 name );
347
348 KIDIALOG dlg( m_parent, msg, _( "Embedded File Conflict" ),
349 wxYES_NO | wxCANCEL | wxICON_WARNING );
350 dlg.SetYesNoLabels( _( "Replace" ), _( "Reuse Existing" ) );
351
352 int result = dlg.ShowModal();
353
354 if( result == wxID_CANCEL )
355 return nullptr;
356
357 if( result == wxID_NO )
358 {
359 return existingFile;
360 }
361
362 for( int ii = 0; ii < m_files_grid->GetNumberRows(); ii++ )
363 {
364 if( m_files_grid->GetCellValue( ii, 0 ) == name )
365 {
366 m_files_grid->DeleteRows( ii );
367 break;
368 }
369 }
370 }
371
372 EMBEDDED_FILES::EMBEDDED_FILE* result = m_localFiles->AddFile( fileName, true );
373
374 if( !result )
375 {
376 wxString msg = wxString::Format( _( "Failed to add file '%s'." ), name );
377
378 KIDIALOG errorDlg( m_parent, msg, _( "Error" ), wxOK | wxICON_ERROR );
379 errorDlg.ShowModal();
380 return nullptr;
381 }
382
383 return result;
384}
385
386
387void PANEL_EMBEDDED_FILES::onAddEmbeddedFiles( wxCommandEvent& event )
388{
389 // TODO: Update strings to reflect that multiple files can be selected.
390 wxFileDialog fileDialog( this, _( "Select a file to embed" ), wxEmptyString, wxEmptyString,
391 _( "All Files" ) + wxT( " (*.*)|*.*" ),
392 wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
393
395
396 if( fileDialog.ShowModal() == wxID_OK )
397 {
398 wxArrayString paths;
399 fileDialog.GetPaths( paths );
400
401 for( const wxString& path : paths )
403 }
404}
405
406
407bool PANEL_EMBEDDED_FILES::RemoveEmbeddedFile( const wxString& aFileName )
408{
409 wxString name = aFileName;
410
411 if( name.StartsWith( FILEEXT::KiCadUriPrefix ) )
412 name = name.Mid( FILEEXT::KiCadUriPrefix.size() + 3 );
413
414 if( m_inheritedFileNames.count( name ) )
415 {
416 wxString msg = _( "Embedded files inherited from a parent symbol cannot be removed." );
417
418 DisplayErrorMessage( this, msg );
419 return false;
420 }
421
422 int row = std::max( 0, m_files_grid->GetGridCursorRow() );
423
424 for( int ii = 0; ii < m_files_grid->GetNumberRows(); ii++ )
425 {
426 if( m_files_grid->GetCellValue( ii, 0 ) == name )
427 {
428 m_files_grid->DeleteRows( ii );
429 m_localFiles->RemoveFile( name );
430
431 if( row < m_files_grid->GetNumberRows() )
432 m_files_grid->SetGridCursor( row, 0 );
433 else if( m_files_grid->GetNumberRows() > 0 )
434 m_files_grid->SetGridCursor( m_files_grid->GetNumberRows() - 1, 0 );
435
436 return true;
437 }
438 }
439
440 return false;
441}
442
443
445{
446 m_files_grid->OnDeleteRows(
447 [&]( int row )
448 {
449 wxString name = m_files_grid->GetCellValue( row, 0 );
450
452 } );
453}
454
455
456void PANEL_EMBEDDED_FILES::onExportFiles( wxCommandEvent& event )
457{
458 wxDirDialog dirDialog( this, _( "Select a directory to export files" ) );
459
460 if( dirDialog.ShowModal() != wxID_OK )
461 return;
462
463 wxString path = dirDialog.GetPath();
464
465 for( auto& [name, file] : m_localFiles->EmbeddedFileMap() )
466 {
467 wxFileName fileName( path, name );
468
469 if( fileName.FileExists() )
470 {
471 wxString msg = wxString::Format( _( "File '%s' already exists." ), fileName.GetFullName() );
472
473 KIDIALOG errorDlg( m_parent, msg, _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
474 errorDlg.SetOKCancelLabels( _( "Overwrite" ), _( "Skip" ) );
475 errorDlg.DoNotShowCheckbox( __FILE__, __LINE__ );
476
477 if( errorDlg.ShowModal() != wxID_OK )
478 continue;
479 }
480
481 bool skip_file = false;
482
483 while( 1 )
484 {
485 if( !fileName.IsDirWritable() )
486 {
487#ifndef __WXMAC__
488 wxString msg = wxString::Format( _( "Directory '%s' is not writable." ), fileName.GetFullName() );
489#else
490 wxString msg = wxString::Format( _( "Folder '%s' is not writable." ), fileName.GetPath() );
491#endif
492 // Don't set a 'do not show again' checkbox for this dialog
493 KIDIALOG errorDlg( m_parent, msg, _( "Error" ), wxYES_NO | wxCANCEL | wxICON_ERROR );
494 errorDlg.SetYesNoCancelLabels( _( "Retry" ), _( "Skip" ), _( "Cancel" ) );
495
496 int result = errorDlg.ShowModal();
497
498 if( result == wxID_CANCEL )
499 {
500 return;
501 }
502 else if( result == wxID_NO )
503 {
504 skip_file = true;
505 break;
506 }
507 }
508 else
509 {
510 break;
511 }
512 }
513
514 if( skip_file )
515 continue;
516
517
518 wxFFileOutputStream out( fileName.GetFullPath() );
519
520 if( !out.IsOk() )
521 {
522 wxString msg = wxString::Format( _( "Failed to open file '%s'." ), fileName.GetFullName() );
523
524 KIDIALOG errorDlg( m_parent, msg, _( "Error" ), wxOK | wxICON_ERROR );
525 errorDlg.ShowModal();
526 continue;
527 }
528
529 out.Write( file->decompressedData.data(), file->decompressedData.size() );
530
531 if( !out.IsOk() || ( out.LastWrite() != file->decompressedData.size() ) )
532 {
533 wxString msg = wxString::Format( _( "Failed to write file '%s'." ), fileName.GetFullName() );
534
535 KIDIALOG errorDlg( m_parent, msg, _( "Error" ), wxOK | wxICON_ERROR );
536
537 errorDlg.ShowModal();
538 }
539 }
540}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:96
void doPopupSelection(wxCommandEvent &event) override
void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent) override
void RemoveFile(const wxString &name, bool aErase=true)
Remove a file from the collection and frees the memory.
static RETURN_CODE ComputeFileHash(const wxFileName &aFileName, std::string &aHash)
Compute the hash of a file on disk without fully embedding it.
bool HasFile(const wxString &name) const
GRID_TRICKS(WX_GRID *aGrid)
virtual void doPopupSelection(wxCommandEvent &event)
virtual void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent)
WX_GRID * m_grid
I don't own the grid, but he owns me.
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition kidialog.h:38
void DoNotShowCheckbox(wxString file, int line)
Shows the 'do not show again' checkbox.
Definition kidialog.cpp:51
bool SetOKCancelLabels(const ButtonLabel &ok, const ButtonLabel &cancel) override
Definition kidialog.h:48
int ShowModal() override
Definition kidialog.cpp:89
Class OUTLINE_FONT implements outline font drawing.
PANEL_EMBEDDED_FILES_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)
PANEL_EMBEDDED_FILES(wxWindow *aParent, EMBEDDED_FILES *aFiles, int aFlags=0, std::vector< const EMBEDDED_FILES * > aInheritedFiles={})
bool RemoveEmbeddedFile(const wxString &aFileName)
std::set< wxString > m_inheritedFileNames
void onFontEmbedClick(wxCommandEvent &event) override
void onAddEmbeddedFiles(wxCommandEvent &event) override
std::vector< const EMBEDDED_FILES * > m_inheritedFiles
void onDeleteEmbeddedFile(wxCommandEvent &event) override
bool TransferDataToWindow() override
EMBEDDED_FILES * m_localFiles
void onExportFiles(wxCommandEvent &event) override
EMBEDDED_FILES::EMBEDDED_FILE * AddEmbeddedFile(const wxString &aFileName)
bool TransferDataFromWindow() override
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:274
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
#define _(s)
static const std::string KiCadUriPrefix
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:448
STL namespace.
#define NO_MARGINS
std::vector< char > decompressedData
std::string path
wxString result
Test unit parsing edge cases and error handling.
@ PCB_T
Definition typeinfo.h:75
@ SCHEMATIC_T
Definition typeinfo.h:201