KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_configure_paths.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) 2015 Wayne Stambaugh <[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
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU 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
22
23#include <algorithm>
24#include <set>
25
26#include <bitmaps.h>
27#include <confirm.h>
28#include <kidialog.h>
29#include <validators.h>
32#include <filename_resolver.h>
33#include <env_vars.h>
34#include <grid_tricks.h>
36#include <pgm_base.h>
40#include <widgets/wx_grid.h>
41
42#include <wx/dirdlg.h>
43#include <wx/regex.h>
44
45
52
59
60
63 m_errorGrid( nullptr ),
64 m_errorRow( -1 ),
65 m_errorCol( -1 ),
66 m_helpBox( nullptr )
67{
68 m_heightBeforeHelp = FromDIP ( 400 );
71
72 m_EnvVars->SetMinSize( FromDIP( m_EnvVars->GetMinSize() ) );
73
74 m_EnvVars->ClearRows();
75 m_EnvVars->AppendCols( 1 ); // for the isExternal flags
76 m_EnvVars->HideCol( TV_FLAG_COL );
77 m_EnvVars->UseNativeColHeader( true );
78
79 wxGridCellAttr* attr = new wxGridCellAttr;
80 attr->SetEditor( new GRID_CELL_PATH_EDITOR( this, m_EnvVars, &m_curdir, wxEmptyString ) );
81 m_EnvVars->SetColAttr( TV_VALUE_COL, attr );
82
83 m_EnvVars->PushEventHandler( new GRID_TRICKS( m_EnvVars,
84 [this]( wxCommandEvent& aEvent )
85 {
86 OnAddEnvVar( aEvent );
87 } ) );
88
89 m_EnvVars->SetSelectionMode( wxGrid::wxGridSelectionModes::wxGridSelectRows );
90
93
94 // wxFormBuilder doesn't include this event...
95 m_EnvVars->Connect( wxEVT_GRID_CELL_CHANGING,
97 nullptr, this );
98
99 m_EnvVars->SetupColumnAutosizer( TV_VALUE_COL );
100
101 GetSizer()->SetSizeHints( this );
102 Centre();
103}
104
105
107{
108 // Delete the GRID_TRICKS.
109 m_EnvVars->PopEventHandler( true );
110
111 m_EnvVars->Disconnect( wxEVT_GRID_CELL_CHANGING,
112 wxGridEventHandler( DIALOG_CONFIGURE_PATHS::OnGridCellChanging ),
113 nullptr, this );
114}
115
116
118{
119 if( !wxDialog::TransferDataToWindow() )
120 return false;
121
122 const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
123
124 for( auto it = envVars.begin(); it != envVars.end(); ++it )
125 {
126 const wxString& path = it->second.GetValue();
127 AppendEnvVar( it->first, path, it->second.GetDefinedExternally() );
128
129 if( m_curdir.IsEmpty() && !path.StartsWith( "${" ) && !path.StartsWith( "$(" ) )
130 m_curdir = path;
131 }
132
133 // Scan library tables for environment variables that are used but not in envVars.
134 // This allows users to see and manage obsolete versioned variables (e.g., KICAD8_FOOTPRINT_DIR)
135 // that may still be referenced in library tables from older KiCad versions.
136 wxRegEx envVarRegex( wxS( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?" ), wxRE_ADVANCED );
137 std::set<wxString> foundEnvVars;
138
140
144 {
147 {
148 std::vector<LIBRARY_TABLE_ROW*> rows = libMgr.Rows( tableType, scope, true );
149
150 for( const LIBRARY_TABLE_ROW* row : rows )
151 {
152 wxString uri = row->URI();
153
154 while( envVarRegex.Matches( uri ) )
155 {
156 wxString envvar = envVarRegex.GetMatch( uri, 2 );
157
158 if( envvar.IsEmpty() )
159 envvar = envVarRegex.GetMatch( uri, 4 );
160
161 if( !envvar.IsEmpty() )
162 foundEnvVars.insert( envvar );
163
164 uri.Replace( envVarRegex.GetMatch( uri, 0 ), wxEmptyString );
165 }
166 }
167 }
168 }
169
170 for( const wxString& envvar : foundEnvVars )
171 {
172 if( envVars.count( envvar ) == 0 && envvar != PROJECT_VAR_NAME )
173 {
174 wxString value;
175 bool isExternal = wxGetEnv( envvar, &value );
176
177 AppendEnvVar( envvar, value, isExternal );
178 }
179 }
180
181 return true;
182}
183
184
185void DIALOG_CONFIGURE_PATHS::AppendEnvVar( const wxString& aName, const wxString& aPath,
186 bool isExternal )
187{
188 int i = m_EnvVars->GetNumberRows();
189
190 m_EnvVars->AppendRows( 1 );
191
192 m_EnvVars->SetCellValue( i, TV_NAME_COL, aName );
193
194 wxGridCellAttr* nameCellAttr = m_EnvVars->GetOrCreateCellAttr( i, TV_NAME_COL );
195 wxGridCellTextEditor* nameTextEditor = new GRID_CELL_TEXT_EDITOR();
196 nameTextEditor->SetValidator( ENV_VAR_NAME_VALIDATOR() );
197 nameCellAttr->SetEditor( nameTextEditor );
198 nameCellAttr->SetReadOnly( ENV_VAR::IsEnvVarImmutable( aName ) );
199 nameCellAttr->DecRef();
200
201 m_EnvVars->SetCellValue( i, TV_VALUE_COL, aPath );
202
203 wxGridCellAttr* pathCellAttr = m_EnvVars->GetOrCreateCellAttr( i, TV_VALUE_COL );
204 wxSystemColour c = isExternal ? wxSYS_COLOUR_MENU : wxSYS_COLOUR_LISTBOX;
205 pathCellAttr->SetBackgroundColour( wxSystemSettings::GetColour( c ) );
206 pathCellAttr->DecRef();
207
208 m_EnvVars->SetCellValue( i, TV_FLAG_COL, isExternal ? wxT( "external" ) : wxEmptyString );
209}
210
211
213{
214 if( !m_EnvVars->CommitPendingChanges() )
215 return false;
216
217 if( !wxDialog::TransferDataFromWindow() )
218 return false;
219
220 // Update environment variables
221 ENV_VAR_MAP& envVarMap = Pgm().GetLocalEnvVariables();
222
223 for( int row = 0; row < m_EnvVars->GetNumberRows(); ++row )
224 {
225 wxString name = m_EnvVars->GetCellValue( row, TV_NAME_COL );
226 wxString path = m_EnvVars->GetCellValue( row, TV_VALUE_COL );
227 bool external = !m_EnvVars->GetCellValue( row, TV_FLAG_COL ).IsEmpty();
228
229 if( external )
230 {
231 // Don't check for consistency on external variables, just use them as-is
232 }
233 else if( name.empty() && path.empty() )
234 {
235 // Skip empty rows altogether
236 continue;
237 }
238 else if( name.IsEmpty() )
239 {
241 m_errorRow = row;
243 m_errorMsg = _( "Environment variable name cannot be empty." );
244 return false;
245 }
246 else if( path.IsEmpty() )
247 {
249 m_errorRow = row;
251 m_errorMsg = _( "Environment variable path cannot be empty." );
252 return false;
253 }
254
255 if( envVarMap.count( name ) )
256 envVarMap.at( name ).SetValue( path );
257 else
258 envVarMap[ name ] = ENV_VAR_ITEM( name, path );
259 }
260
261 // Remove deleted env vars
262 for( auto it = envVarMap.begin(); it != envVarMap.end(); )
263 {
264 bool found = false;
265
266 for( int row = 0; row < m_EnvVars->GetNumberRows(); ++row )
267 {
268 wxString name = m_EnvVars->GetCellValue( row, TV_NAME_COL );
269
270 if( it->first == name )
271 {
272 found = true;
273 break;
274 }
275 }
276
277 if( found )
278 ++it;
279 else
280 it = envVarMap.erase( it );
281 }
282
284
285 return true;
286}
287
288
290{
291 wxGrid* grid = dynamic_cast<wxGrid*>( event.GetEventObject() );
292
293 // Mid-edit notifications from text-button editors fire while the user is still typing.
294 // Only validate when the edit is being committed (cell edit control already disabled).
295 if( grid && grid->IsCellEditControlEnabled() )
296 return;
297
298 int row = event.GetRow();
299 int col = event.GetCol();
300 wxString text = event.GetString();
301
302 text.Trim( true ).Trim( false ); // Trim from both sides
303 grid->SetCellValue( row, col, text ); // Update the grid with trimmed value
304
305 if( text.IsEmpty() )
306 {
307 if( grid == m_EnvVars )
308 {
309 if( col == TV_NAME_COL )
310 m_errorMsg = _( "Environment variable name cannot be empty." );
311 else
312 m_errorMsg = _( "Environment variable path cannot be empty." );
313 }
314 else
315 {
316 if( col == SP_ALIAS_COL )
317 m_errorMsg = _( "3D search path alias cannot be empty." );
318 else
319 m_errorMsg = _( "3D search path cannot be empty." );
320 }
321
322 m_errorGrid = dynamic_cast<wxGrid*>( event.GetEventObject() );
323 m_errorRow = row;
324 m_errorCol = col;
325
326 event.Veto();
327 }
328
329 if( grid == m_EnvVars )
330 {
331 if( col == TV_VALUE_COL && m_EnvVars->GetCellValue( row, TV_FLAG_COL ).Length()
332 && !Pgm().GetCommonSettings()->m_DoNotShowAgain.env_var_overwrite_warning )
333 {
334 wxString msg1 = _( "This path was defined externally to the running process and\n"
335 "will only be temporarily overwritten." );
336 wxString msg2 = _( "The next time KiCad is launched, any paths that have already\n"
337 "been defined are honored and any settings defined in the path\n"
338 "configuration dialog are ignored. If you did not intend for\n"
339 "this behavior, either rename any conflicting entries or remove\n"
340 "the external environment variable(s) from your system." );
341 KIDIALOG dlg( this, msg1, KIDIALOG::KD_WARNING );
342 dlg.ShowDetailedText( msg2 );
343 dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
344 dlg.ShowModal();
345
346 if( dlg.DoNotShowAgain() )
348 }
349 else if( col == TV_NAME_COL && m_EnvVars->GetCellValue( row, TV_NAME_COL ) != text )
350 {
351 // This env var name is reserved and cannot be added here.
352 if( text == PROJECT_VAR_NAME )
353 {
354 wxMessageBox( wxString::Format( _( "The name %s is reserved, and cannot be used." ),
356 event.Veto();
357 }
358 else // Changing name; clear external flag
359 {
360 m_EnvVars->SetCellValue( row, TV_FLAG_COL, wxEmptyString );
361 }
362 }
363 }
364}
365
366
367void DIALOG_CONFIGURE_PATHS::OnAddEnvVar( wxCommandEvent& event )
368{
369 m_EnvVars->OnAddRow(
370 [&]() -> std::pair<int, int>
371 {
372 AppendEnvVar( wxEmptyString, wxEmptyString, false );
373 return { m_EnvVars->GetNumberRows() - 1, TV_NAME_COL };
374 } );
375}
376
377
378void DIALOG_CONFIGURE_PATHS::OnRemoveEnvVar( wxCommandEvent& event )
379{
380 m_EnvVars->OnDeleteRows(
381 [&]( int row )
382 {
383 if( ENV_VAR::IsEnvVarImmutable( m_EnvVars->GetCellValue( row, TV_NAME_COL ) ) )
384 {
385 wxBell();
386 return false;
387 }
388
389 return true;
390 },
391 [&]( int row )
392 {
393 m_EnvVars->DeleteRows( row, 1 );
394 } );
395}
396
397
398void DIALOG_CONFIGURE_PATHS::OnUpdateUI( wxUpdateUIEvent& event )
399{
400 // Handle a grid error. This is delayed to OnUpdateUI so that we can change focus even when
401 // the original validation was triggered from a killFocus event.
402 if( m_errorGrid )
403 {
404 // We will re-enter this routine when the error dialog is displayed, so make sure we don't
405 // keep putting up more dialogs.
406 wxGrid* grid = m_errorGrid;
407 m_errorGrid = nullptr;
408
410
411 grid->SetFocus();
412 grid->MakeCellVisible( m_errorRow, m_errorCol );
413 grid->SetGridCursor( m_errorRow, m_errorCol );
414
415 grid->EnableCellEditControl( true );
416 grid->ShowCellEditControl();
417 }
418}
419
420
421void DIALOG_CONFIGURE_PATHS::OnHelp( wxCommandEvent& event )
422{
423 wxSizer* sizerMain = GetSizer();
424
425 if( !m_helpBox )
426 {
427 m_helpBox = new HTML_WINDOW( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
428 wxHW_SCROLLBAR_AUTO );
429
430 wxString msg = _( "Enter the name and value for each environment variable. Grey entries "
431 "are names that have been defined externally at the system or user "
432 "level. Environment variables defined at the system or user level "
433 "take precedence over the ones defined in this table. This means the "
434 "values in this table are ignored." );
435 msg << "<br><br><b>";
436 msg << _( "To ensure environment variable names are valid on all platforms, the name field "
437 "will only accept upper case letters, digits, and the underscore characters." );
438 msg << "</b>";
439
440 for( const wxString& var : ENV_VAR::GetPredefinedEnvVars() )
441 {
442 msg << "<br><br><b>" << var << "</b>";
443
444 const auto desc = ENV_VAR::LookUpEnvVarHelp( var );
445
446 if( desc.size() > 0 )
447 msg << ": " << desc;
448
449 }
450
451 m_helpBox->SetPage( msg );
452 m_helpBox->Show( false );
453
454 sizerMain->Insert( sizerMain->GetItemCount() - 1, m_helpBox, 1, wxALL|wxEXPAND, 10 );
455 }
456
457 if( m_helpBox->IsShown() )
458 {
459 m_helpBox->Show( false );
460 SetClientSize( wxSize( GetClientSize().x, m_heightBeforeHelp ) );
461 }
462 else
463 {
464 m_helpBox->Show( true );
465 m_heightBeforeHelp = GetClientSize().y;
466
467 int minHelpBoxHeight = GetTextExtent( wxT( "T" ) ).y * 20;
468
469 if( GetClientSize().y < minHelpBoxHeight * 2 )
470 SetClientSize( wxSize( GetClientSize().x, GetClientSize().y + minHelpBoxHeight ) );
471 }
472
473 Layout();
474}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
DO_NOT_SHOW_AGAIN m_DoNotShowAgain
DIALOG_CONFIGURE_PATHS_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Configure Paths"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void AppendEnvVar(const wxString &aName, const wxString &aPath, bool isExternal)
void OnGridCellChanging(wxGridEvent &event)
void OnUpdateUI(wxUpdateUIEvent &event) override
void OnHelp(wxCommandEvent &event) override
void OnRemoveEnvVar(wxCommandEvent &event) override
DIALOG_CONFIGURE_PATHS(wxWindow *aParent)
void OnAddEnvVar(wxCommandEvent &event) override
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition dialog_shim.h:79
void SetupStandardButtons(std::map< int, wxString > aLabels={})
KiCad uses environment variables internally for determining the base paths for libraries,...
Provide a custom wxValidator object for limiting the allowable characters when defining an environmen...
Definition validators.h:65
Editor for wxGrid cells that adds a file/folder browser to the grid input field.
This class works around a bug in wxGrid where the first keystroke doesn't get sent through the valida...
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:57
Add dark theme support to wxHtmlWindow.
Definition html_window.h:31
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition kidialog.h:38
@ KD_WARNING
Definition kidialog.h:43
bool DoNotShowAgain() const
Checks the 'do not show again' setting for the dialog.
Definition kidialog.cpp:59
void DoNotShowCheckbox(wxString file, int line)
Shows the 'do not show again' checkbox.
Definition kidialog.cpp:51
int ShowModal() override
Definition kidialog.cpp:89
std::vector< LIBRARY_TABLE_ROW * > Rows(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH, bool aIncludeInvalid=false) const
Returns a flattened list of libraries of the given type.
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:528
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:774
virtual void SetLocalEnvVariables()
Update the local environment with the contents of the current ENV_VAR_MAP stored in the COMMON_SETTIN...
Definition pgm_base.cpp:758
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:126
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)
Functions related to environment variables, including help functions.
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
LIBRARY_TABLE_TYPE
LIBRARY_TABLE_SCOPE
KICOMMON_API wxString LookUpEnvVarHelp(const wxString &aEnvVar)
Look up long-form help text for a given environment variable.
Definition env_vars.cpp:152
KICOMMON_API const std::vector< wxString > & GetPredefinedEnvVars()
Get the list of pre-defined environment variables.
Definition env_vars.cpp:61
KICOMMON_API bool IsEnvVarImmutable(const wxString &aEnvVar)
Determine if an environment variable is "predefined", i.e.
Definition env_vars.cpp:49
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition project.h:37
std::string path
Custom text control validator definitions.