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