KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_database_lib_settings.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) 2023 Jon Evans <[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 <algorithm>
22#include <set>
23
24#include <wx/app.h>
25#include <wx/display.h>
26#include <wx/filedlg.h>
27#include <wx/filename.h>
28#include <wx/menu.h>
29#include <wx/regex.h>
30#include <wx/settings.h>
31#include <wx/stdpaths.h>
32#include <wx/utils.h>
33
34#include <bitmaps.h>
35#include <confirm.h>
36#include <core/kicad_algo.h>
40#include <eda_draw_frame.h>
41#include <eeschema_settings.h>
42#include <grid_tricks.h>
43#include <i18n_utility.h>
44#include <kiplatform/ui.h>
45#include <lib_symbol.h>
46#include <pgm_base.h>
47#include <reporter.h>
49#include <thread_pool.h>
50#include <scoped_set_reset.h>
54#include <string_utils.h>
58#include <widgets/wx_grid.h>
59#include <widgets/wx_infobar.h>
61
62
64static constexpr size_t PREVIEW_ROW_LIMIT = 200;
65
67static constexpr int PREVIEW_MIN_WIDTH_DIP = 450;
68
70static constexpr int PREVIEW_DEBOUNCE_MS = 750;
71
73static void relayoutPage( wxScrolledWindow* aPage )
74{
75 aPage->Layout();
76 aPage->FitInside();
77}
78
79
81template <typename MAP>
82static bool isAutofilled( const MAP& aAutofilled, const typename MAP::key_type& aKey, const std::string& aValue )
83{
84 auto it = aAutofilled.find( aKey );
85
86 return it != aAutofilled.end() && it->second == aValue;
87}
88
89
99
100
101static wxString displayName( const DATABASE_LIB_TABLE& aTable )
102{
103 return aTable.name.empty() ? _( "(no nickname)" ) : From_UTF8( aTable.name );
104}
105
106
107static wxString describeFailure( const DATABASE_CONNECTION& aConn, const DATABASE_SOURCE& aSource )
108{
109 using FAILURE = DATABASE_CONNECTION::FAILURE;
110
112 {
113 case FAILURE::LOGIN:
114 return _( "The server rejected the user name or password." );
115
116 case FAILURE::UNREACHABLE:
117 return _( "Could not reach the database server; check that it is running." );
118
119 case FAILURE::TIMEOUT:
120 return wxString::Format( _( "The server did not answer within %d seconds." ), aSource.timeout );
121
122 case FAILURE::NO_DSN:
123 if( !aSource.connection_string.empty() )
124 return _( "The connection string names a data source or driver that is not configured." );
125
126 return wxString::Format( _( "There is no ODBC data source named '%s'." ), From_UTF8( aSource.dsn ) );
127
128 case FAILURE::NO_DRIVER:
129 return _( "The ODBC driver for this data source is not installed." );
130
131 case FAILURE::UNKNOWN:
132 break;
133 }
134
135 return _( "Could not connect to the database." );
136}
137
138
141{
142 const char* name;
143 std::vector<wxString> terms;
145};
146
147
148static const std::vector<AUTOFILL_FIELD>& autofillFields()
149{
150 static const std::vector<AUTOFILL_FIELD> fields = {
151 { "Value", { _HKI( "Value" ) }, true },
152 { "Datasheet", { _HKI( "Datasheet" ), _HKI( "data sheet" ) }, false }
153 };
154
155 return fields;
156}
157
158
159static std::string fieldChoiceId( const char* aFieldName )
160{
161 return std::string( "field." ) + aFieldName;
162}
163
164
165static DB_AUTOFILL::ROLE autofillRole( const std::string& aChoiceId, const std::vector<wxString>& aTerms )
166{
168
169 for( const wxString& term : aTerms )
170 {
171 role.terms.push_back( term );
172
173 if( wxString translated = wxGetTranslation( term ); translated != term )
174 role.terms.push_back( translated );
175 }
176
178 {
179 if( auto it = cfg->m_DatabaseColumnChoices.find( aChoiceId ); it != cfg->m_DatabaseColumnChoices.end() )
180 role.remembered = it->second;
181 }
182
183 return role;
184}
185
186
187static DATABASE_FIELD_MAPPING* findField( DATABASE_LIB_TABLE& aLib, const char* aName )
188{
189 for( DATABASE_FIELD_MAPPING& field : aLib.fields )
190 {
191 if( wxString::FromUTF8( field.name ).IsSameAs( aName, false ) )
192 return &field;
193 }
194
195 return nullptr;
196}
197
198
199DIALOG_DATABASE_LIB_SETTINGS::DIALOG_DATABASE_LIB_SETTINGS( wxWindow* aParent, const wxString& aDblPath,
201 const wxString& aDefaultDir ) :
203 m_defaultDir( aDefaultDir ),
204 m_libs( aLibs ),
205 m_preview( nullptr ),
206 m_currentLibrary( -1 ),
207 m_loading( false ),
208 m_unnamed( aDblPath.IsEmpty() ),
209 m_flagMissing( false ),
210 m_canSave( true ),
211 m_previewDebounce( [this]()
212 {
214 },
216{
217 // Search terms are English; autofill also tries their translations
218 m_roles = {
219 { m_cbKeyCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.key_col; }, false, _( "key" ),
220 "key", { _HKI( "id" ), _HKI( "key" ) } },
221 { m_cbSymbolsCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.symbols_col; }, false,
222 _( "symbols" ), "symbols", { _HKI( "symbol" ), _HKI( "sym" ) } },
223 { m_cbFootprintsCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.footprints_col; }, false,
224 _( "footprints" ), "footprints", { _HKI( "footprint" ), _HKI( "fp" ) } },
225 { m_cbPinsCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.pins_col; }, true, _( "pin map" ),
226 "pins", { _HKI( "pin map" ), _HKI( "pins" ) } },
227 { m_cbDescriptionCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.properties.description; },
228 true, _( "description" ), "description", { _HKI( "description" ), _HKI( "desc" ) } },
229 { m_cbKeywordsCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.properties.keywords; }, true,
230 _( "keywords" ), "keywords", { _HKI( "keywords" ), _HKI( "tags" ) } },
231 { m_cbFpFiltersCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.properties.footprint_filters; },
232 true, _( "footprint filters" ), "footprint_filters",
233 { _HKI( "footprint filters" ), _HKI( "fp filters" ) } },
234 { m_cbExclSimCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.properties.exclude_from_sim; },
235 true, _( "exclude from simulation" ), "exclude_from_sim",
236 { _HKI( "exclude from simulation" ), _HKI( "exclude from sim" ) } },
237 { m_cbExclBomCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.properties.exclude_from_bom; },
238 true, _( "exclude from BOM" ), "exclude_from_bom", { _HKI( "exclude from BOM" ) } },
239 { m_cbExclBoardCol, []( DATABASE_LIB_TABLE& t ) -> std::string& { return t.properties.exclude_from_board; },
240 true, _( "exclude from board" ), "exclude_from_board", { _HKI( "exclude from board" ) } }
241 };
242
243 wxFileName fn;
244 bool isNew = true;
245 bool loaded = true;
246
247 if( m_unnamed )
248 {
249 // The file is chosen on OK, so there is nothing to load yet
250 m_settings = std::make_unique<DATABASE_LIB_SETTINGS>( std::string() );
251 m_settings->SetReadOnly( true );
252 m_settings->ResetToDefaults();
253 }
254 else
255 {
256 // The settings file always carries the extension, whatever the library table row says
257 fn.Assign( aDblPath );
259 m_path = fn.GetFullPath();
260
261 isNew = !wxFileExists( m_path );
262
263 m_settings = LoadDatabaseLibSettingsReadOnly( m_path, &loaded );
264 m_canSave = loaded && !m_settings->IsFutureFormat();
265 }
266
267 // Matches the documented template so hand editors recognise the source block
268 if( isNew )
269 m_settings->Set( "source.type", std::string( "odbc" ) );
270
271 EDA_BASE_FRAME* frame = nullptr;
272
273 for( wxWindow* window = aParent; window && !frame; window = window->GetParent() )
274 frame = dynamic_cast<EDA_BASE_FRAME*>( window );
275
277 EDA_DRAW_FRAME* drawFrame = dynamic_cast<EDA_DRAW_FRAME*>( frame );
278
279 if( drawFrame && drawFrame->GetCanvas() )
280 canvasType = drawFrame->GetCanvas()->GetBackend();
281 else if( COMMON_SETTINGS* cfg = Pgm().GetCommonSettings() )
282 canvasType = EDA_DRAW_PANEL_GAL::ResolveStoredCanvasType( cfg->m_Graphics.canvas_type );
283
284 m_preview = new DB_LIB_PREVIEW_PANEL( m_previewPanel, frame, HasKiway() ? &Kiway() : nullptr, canvasType );
285 m_previewSizer->Add( m_preview, 1, wxEXPAND | wxALL, 5 );
286
287 m_btnAddLibrary->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
288 m_btnDuplicateLibrary->SetBitmap( KiBitmapBundle( BITMAPS::copy ) );
289 m_btnMoveLibraryUp->SetBitmap( KiBitmapBundle( BITMAPS::small_up ) );
290 m_btnMoveLibraryDown->SetBitmap( KiBitmapBundle( BITMAPS::small_down ) );
291 m_btnRemoveLibrary->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
292 m_btnAddField->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
293 m_btnMoveFieldUp->SetBitmap( KiBitmapBundle( BITMAPS::small_up ) );
294 m_btnMoveFieldDown->SetBitmap( KiBitmapBundle( BITMAPS::small_down ) );
295 m_btnRemoveField->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
296
298 {
299 wxGridCellAttr* attr = new wxGridCellAttr;
300 attr->SetRenderer( new wxGridCellBoolRenderer() );
301 attr->SetEditor( new wxGridCellBoolEditor() );
302 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
303 m_fieldsGrid->SetColAttr( col, attr );
304 }
305
306 m_fieldsGrid->PushEventHandler( new GRID_TRICKS( m_fieldsGrid ) );
307 m_dataGrid->PushEventHandler( new GRID_TRICKS( m_dataGrid ) );
308
309 // The button turns into Stop while connecting and must not shift the status beside it
310 m_btnTest->SetMinSize( m_btnTest->GetBestSize() );
311
312 // Lets the label shrink below its text so long messages ellipsize instead of widening the page
313 m_stConnectionStatus->SetMinSize( wxSize( FromDIP( 40 ), -1 ) );
314 m_stDsnDriver->SetMinSize( wxSize( FromDIP( 40 ), -1 ) );
315 m_stSymbolExample->SetMinSize( wxSize( FromDIP( 40 ), -1 ) );
316
317 if( m_unnamed && m_defaultDir.IsEmpty() )
318 m_defaultDir = wxStandardPaths::Get().GetDocumentsDir();
319
320 m_stCwdHelp->SetLabel( m_unnamed ? wxString::Format( _( "${CWD} is replaced by %s (the folder the file will "
321 "be saved in)" ),
322 m_defaultDir )
323 : wxString::Format( _( "${CWD} is replaced by %s" ), fn.GetPath() ) );
324
325 // The hint sits in the narrow library list column
326 m_stNoLibraries->Wrap( FromDIP( 160 ) );
327
328 if( !loaded )
329 {
330 m_infoBar->ShowMessage( _( "This file could not be read and is shown read-only." ), wxICON_ERROR );
331 }
332 else if( m_settings->IsFutureFormat() )
333 {
334 m_infoBar->ShowMessage( _( "This file was written by a newer version of KiCad and is shown read-only." ),
335 wxICON_WARNING );
336 }
337 else if( isNew )
338 {
339 m_infoBar->ShowMessage( _( "Choose a data source and click Test Connection, then add a library on the "
340 "Libraries page." ),
341 wxICON_INFORMATION );
342 }
343
344 // Every control mirrors the file being edited, so restoring values from a previous session
345 // would silently overwrite it
346 OptOut( this );
347
348 // Child windows of the combo boxes' parents, which destroy them
349 m_tableIcon = new FIELD_WARNING_ICON( m_cbTable );
350
351 for( const COLUMN_ROLE& role : m_roles )
352 {
353 m_roleIcons.push_back( new FIELD_WARNING_ICON( role.combo ) );
354
355 role.combo->Bind( wxEVT_KILL_FOCUS,
356 [this]( wxFocusEvent& aEvent )
357 {
358 aEvent.Skip();
360 } );
361 }
362
363 // Bound after GRID_TRICKS so the mapping menu runs first; a left click opens it as well
364 m_dataGrid->Bind( wxEVT_GRID_LABEL_RIGHT_CLICK, &DIALOG_DATABASE_LIB_SETTINGS::OnDataLabelRightClick, this );
365 m_dataGrid->Bind( wxEVT_GRID_LABEL_LEFT_CLICK, &DIALOG_DATABASE_LIB_SETTINGS::OnDataLabelRightClick, this );
366
367 m_fieldsGrid->GetGridColLabelWindow()->Bind( wxEVT_MOTION,
368 [this]( wxMouseEvent& aEvent )
369 {
370 const wxString tips[] = {
371 _( "Database column the field reads its value from" ),
372 _( "Name of the symbol field" ),
373 _( "Show the field on the schematic when the symbol is placed" ),
374 _( "Show the field as a column in the symbol chooser" ),
375 _( "Show the field name before its value" ),
376 _( "Take position and style from the symbol's own field of the same name" )
377 };
378
379 int x = m_fieldsGrid->CalcUnscrolledPosition( aEvent.GetPosition() ).x;
380 int col = m_fieldsGrid->XToCol( x );
381
382 bool validCol = col >= 0 && col < static_cast<int>( std::size( tips ) );
383
384 m_fieldsGrid->GetGridColLabelWindow()->SetToolTip( validCol ? tips[col] : wxString() );
385 aEvent.Skip();
386 } );
387
388 // Escape arrives as the Cancel button too
389 Bind( wxEVT_BUTTON, &DIALOG_DATABASE_LIB_SETTINGS::onCancel, this, wxID_CANCEL );
390 Bind( wxEVT_CLOSE_WINDOW, &DIALOG_DATABASE_LIB_SETTINGS::onCloseWindow, this );
391
392 SetTitle( m_unnamed ? _( "New Database Library" )
393 : wxString::Format( _( "Database Library Settings (%s)" ), fn.GetFullName() ) );
394
395 SetupStandardButtons();
396
397 if( !m_canSave )
398 m_sdbSizerOK->Disable();
399
400 // The pages scroll, so their full height must not make the dialog taller than the screen
401 wxSize fullSize = GetBestSize();
402
403 // wxFormBuilder's Fit() leaves each page as tall as its content, which GTK will not shrink
404 for( wxScrolledWindow* page : { m_connectionPage, m_librariesPage } )
405 {
406 page->SetMinSize( wxSize( -1, FromDIP( 200 ) ) );
407 page->SetSize( wxSize( page->GetSize().x, FromDIP( 200 ) ) );
408 }
409
410 finishDialogSettings();
411
412 // DIALOG_SHIM opens at this size, clamped to the monitor the dialog lands on
413 m_initialSize.IncTo( fullSize );
414 m_connectionPage->FitInside();
415 m_librariesPage->FitInside();
416
417 // The base class restores a fixed sash position on the first idle event, which cuts the pages off wherever
418 // the platform's fonts and controls need more room
419 m_splitter->Disconnect( wxEVT_IDLE, wxIdleEventHandler( DIALOG_DATABASE_LIB_SETTINGS_BASE::m_splitterOnIdle ),
420 nullptr, this );
421 m_splitter->Bind( wxEVT_IDLE, &DIALOG_DATABASE_LIB_SETTINGS::onSplitterIdle, this );
422}
423
424
426{
428 m_previewDebounce.Cancel();
429
430 m_fieldsGrid->PopEventHandler( true );
431 m_dataGrid->PopEventHandler( true );
432}
433
434
436{
437 SCOPED_SET_RESET<bool> loading( m_loading, true );
438
439 DATABASE_SOURCE& source = m_settings->m_Source;
440 bool useConnectionString = !source.connection_string.empty();
441 bool passwordInFile = !source.password.empty();
442
444 m_keyringLogin.password.clear();
445
446 if( !passwordInFile && GetDatabasePassword( source, m_keyringLogin.password ) )
447 source.password = m_keyringLogin.password;
448
449 m_txtName->ChangeValue( From_UTF8( m_settings->m_Name ) );
450 m_txtDescription->ChangeValue( From_UTF8( m_settings->m_Description ) );
451
452 m_rbDSN->SetValue( !useConnectionString );
453 m_rbConnectionString->SetValue( useConnectionString );
454
455 for( const std::string& driver : DATABASE_CONNECTION::ListDrivers() )
456 m_choiceDriver->Append( From_UTF8( driver ) );
457
458 m_cbDSN->ChangeValue( From_UTF8( source.dsn ) );
460 m_txtUser->ChangeValue( From_UTF8( source.username ) );
461 m_txtPassword->ChangeValue( From_UTF8( source.password ) );
462 m_cbPasswordInFile->SetValue( passwordInFile );
463 m_txtConnectionString->ChangeValue( From_UTF8( source.connection_string ) );
464 m_spinTimeout->SetValue( source.timeout );
465
466 m_spinCacheSize->SetValue( m_settings->m_Cache.max_size );
467 m_spinCacheAge->SetValue( m_settings->m_Cache.max_age );
468 m_cbGloballyUniqueKeys->SetValue( m_settings->m_GloballyUniqueKeys );
469
471
472 m_currentLibrary = m_settings->m_Tables.empty() ? -1 : 0;
474 loadLibrary();
475
477
478 // Connect once the dialog is visible; a slow server should not delay it appearing
479 CallAfter( [this]()
480 {
481 connect();
482 } );
483
484 return true;
485}
486
487
489{
490 // Ctrl+Enter reaches here even with OK disabled
491 if( !m_canSave || !m_fieldsGrid->CommitPendingChanges() )
492 return false;
493
494 storeSource();
495 storeLibrary();
496
497 if( !validateLibraries() )
498 return false;
499
500 // Asked last so that cancelling it leaves the keyring and the dialog as they were
501 if( m_unnamed && m_path.IsEmpty() && !chooseSavePath() )
502 return false;
503
504 DATABASE_SOURCE& source = m_settings->m_Source;
505 std::string password = source.password;
506 bool useKeyring = source.connection_string.empty() && !m_cbPasswordInFile->GetValue();
507
508 if( useKeyring )
509 {
510 bool sameLogin = source.dsn == m_keyringLogin.dsn && source.username == m_keyringLogin.username;
511
512 if( !password.empty() && ( !sameLogin || password != m_keyringLogin.password ) )
513 {
514 if( !StoreDatabasePassword( source, password ) )
515 {
516 DisplayError( this, _( "The password could not be saved in the system keyring, which needs a "
517 "user name and a running keyring service.\n\n"
518 "Check 'Save password in the library file' to keep it there instead." ) );
519 return false;
520 }
521 }
522 else if( password.empty() && sameLogin && !m_keyringLogin.password.empty() )
523 {
524 // A failure here is usually an entry that is already gone
525 StoreDatabasePassword( source, password );
526 }
527
529 source.password.clear();
530 }
531
532 // A file chosen in the save dialog is replaced, however much it already holds
533 m_settings->SetReadOnly( false );
534 bool saved = m_settings->SaveToFile( wxEmptyString, m_unnamed || !wxFileExists( m_path ) );
535 m_settings->SetReadOnly( true );
536
537 // The dialog stays open on failure and keeps connecting with the entered password
538 source.password = password;
539
540 // An unchanged file is not rewritten, which is not a failure
541 if( !saved && m_settings->LastSaveFailed() )
542 {
543 DisplayError( this, wxString::Format( _( "Could not save '%s'." ), m_path ) );
544
545 // The next OK asks for a location again, starting in the same folder
546 if( m_unnamed )
547 {
549 m_path.Clear();
550 }
551
552 return false;
553 }
554
555 m_unnamed = false;
556
558 {
559 for( const auto& [id, column] : m_newChoices )
560 DB_AUTOFILL::RememberChoice( cfg->m_DatabaseColumnChoices[id], column );
561 }
562
563 return true;
564}
565
566
568{
569 std::set<std::string> nicknames;
570
571 for( size_t ii = 0; ii < m_settings->m_Tables.size(); ++ii )
572 {
573 const DATABASE_LIB_TABLE& table = m_settings->m_Tables[ii];
574 wxString msg;
575 wxWindow* focus = nullptr;
576 wxString name = From_UTF8( table.name );
577
578 if( table.table.empty() && table.key_col.empty() )
579 {
580 msg = wxString::Format( _( "Library '%s' needs a table and a key column." ), name );
581 focus = m_cbTable;
582 }
583 else if( table.table.empty() )
584 {
585 msg = wxString::Format( _( "Library '%s' needs a table." ), name );
586 focus = m_cbTable;
587 }
588 else if( table.key_col.empty() )
589 {
590 msg = wxString::Format( _( "Library '%s' needs a key column." ), name );
591 focus = m_cbKeyCol;
592 }
593 else if( !nicknames.insert( table.name ).second )
594 {
595 msg = wxString::Format( _( "More than one library is named '%s'." ), name );
596 focus = m_txtLibName;
597 }
598
599 if( !focus )
600 continue;
601
602 m_flagMissing = true;
603 m_notebook->SetSelection( 1 );
604
605 // Reloading would drop the autofill state of a library the user is already looking at
606 if( m_currentLibrary != static_cast<int>( ii ) )
607 {
608 m_currentLibrary = static_cast<int>( ii );
610 loadLibrary();
611 }
612 else
613 {
615 loadDataGrid();
616 }
617
618 showInfo( msg, wxICON_ERROR );
619 focus->SetFocus();
620 return false;
621 }
622
623 return true;
624}
625
626
628{
629 return m_path.IsEmpty() ? m_defaultDir : wxFileName( m_path ).GetPath();
630}
631
632
634{
635 wxString ext = From_UTF8( FILEEXT::DatabaseLibraryFileExtension.c_str() );
636 wxString filter = _( "KiCad database library files" )
638
639 wxString dir = m_defaultDir;
640 wxString name = DefaultDatabaseLibFileName( From_UTF8( m_settings->m_Name ) );
641
642 wxFileDialog dlg( this, _( "Save Database Library" ), dir, name + wxS( "." ) + ext, filter,
643 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
644
646
647 if( dlg.ShowModal() != wxID_OK )
648 return false;
649
650 wxFileName fn( dlg.GetPath() );
652
653 // The file dialog only confirmed the name as typed, not the one with the extension added
654 if( fn.GetFullPath() != dlg.GetPath() && fn.FileExists()
655 && !IsOK( this, wxString::Format( _( "Overwrite '%s'?" ), fn.GetFullPath() ) ) )
656 {
657 return false;
658 }
659
660 wxString oldBase = cwdDir();
661 m_path = fn.GetFullPath();
662
663 m_settings->SetFilename( m_path );
664 m_settings->Internals()->SetFromString( "meta.filename", std::string( m_settings->GetFullFilename().ToUTF8() ) );
665 m_stCwdHelp->SetLabel( wxString::Format( _( "${CWD} is replaced by %s" ), cwdDir() ) );
666
667 // The connection was tested against another folder than the one it will resolve in once saved
668 if( oldBase != cwdDir() && m_settings->m_Source.connection_string.find( "${CWD}" ) != std::string::npos )
669 {
670 connect();
671 showInfo( _( "The data source uses ${CWD} and was tested again in the folder you chose. "
672 "Check the result, then click OK to save." ),
673 wxICON_INFORMATION );
674 return false;
675 }
676
677 return true;
678}
679
680
682{
683 storeSource();
684 storeLibrary();
685
686 return m_settings->FormatAsString() + ( m_cbPasswordInFile->GetValue() ? "1" : "0" );
687}
688
689
691{
692 // A grid edit in progress only reaches the settings once committed
693 if( !m_fieldsGrid->CommitPendingChanges() )
694 return CLOSE_ACTION::STAY;
695
698
699 bool saved = false;
700 wxString prompt = m_unnamed ? _( "Save the new database library before closing?" )
701 : wxString::Format( _( "Save changes to '%s' before closing?" ),
702 wxFileName( m_path ).GetFullName() );
703
704 if( !HandleUnsavedChanges( this, prompt,
705 [&]() -> bool
706 {
707 saved = TransferDataFromWindow();
708 return saved;
709 } ) )
710 {
711 return CLOSE_ACTION::STAY;
712 }
713
714 if( saved )
715 {
716 EndDialogShim( wxID_OK );
717 return CLOSE_ACTION::SAVED;
718 }
719
721}
722
723
724void DIALOG_DATABASE_LIB_SETTINGS::onCancel( wxCommandEvent& aEvent )
725{
727 aEvent.Skip();
728}
729
730
732{
733 if( !aEvent.CanVeto() )
734 {
735 aEvent.Skip();
736 return;
737 }
738
739 // The default close handler sends a Cancel, which would ask a second time
741 EndDialogShim( wxID_CANCEL );
742 else
743 aEvent.Veto();
744}
745
746
748{
749 if( m_loading )
750 return;
751
752 m_settings->m_Name = TO_UTF8( m_txtName->GetValue() );
753 m_settings->m_Description = TO_UTF8( m_txtDescription->GetValue() );
754 m_settings->m_Cache.max_size = m_spinCacheSize->GetValue();
755 m_settings->m_Cache.max_age = m_spinCacheAge->GetValue();
756 m_settings->m_GloballyUniqueKeys = m_cbGloballyUniqueKeys->GetValue();
757
759 markDirty();
760}
761
762
764{
765 OnSettingChanged( aEvent );
766}
767
768
770{
771 // Clearing the list raises text events that would store an empty data source
772 SCOPED_SET_RESET<bool> loading( m_loading, true );
773
774 wxString current = m_cbDSN->GetValue();
775
777 m_cbDSN->Clear();
778
780 m_cbDSN->Append( From_UTF8( source.name ) );
781
782 m_cbDSN->ChangeValue( current );
784}
785
786
788{
789 std::string name = TO_UTF8( m_cbDSN->GetValue() );
790 wxString text;
791
792 if( !name.empty() )
793 {
794 text = _( "Not configured on this computer" );
795
797 {
798 if( source.name == name )
799 text = wxString::Format( _( "Driver: %s" ), From_UTF8( source.driver ) );
800 }
801 }
802
803 m_stDsnDriver->SetLabel( text );
804 m_stDsnDriver->SetToolTip( text );
805
806 // SetLabel() resizes the control to its text; layout shrinks it back so the end ellipsizes
808}
809
810
812{
813#ifdef __WXMSW__
814 // Waits so that the list can show what was added
815 wxExecute( wxS( "odbcad32.exe" ), wxEXEC_SYNC );
817#endif
818}
819
820
822{
823 bool useDSN = m_rbDSN->GetValue();
824 wxSizer* sbSource = m_rbDSN->GetContainingSizer();
825
826 sbSource->Show( m_cbDSN->GetContainingSizer(), useDSN );
827 sbSource->Show( m_cbPasswordInFile, useDSN );
828 sbSource->Show( m_txtConnectionString, !useDSN );
829 sbSource->Show( m_choiceDriver->GetContainingSizer(), !useDSN );
830 sbSource->Show( m_stCwdHelp, !useDSN );
831
832 // Showing the data source rows shows every control in them
833#ifdef __WXMSW__
834 m_btnManageDsn->Show( useDSN );
835#else
836 m_btnManageDsn->Hide();
837#endif
838
840}
841
842
844{
846 OnSourceChanged( aEvent );
847}
848
849
851{
852 if( m_loading )
853 return;
854
856 storeSource();
858 markDirty();
859
860 // Connecting on every keystroke would hammer the server, so it waits for an explicit test
861 setConnectionStatus( _( "Data source changed; click Test Connection to apply it." ), false );
862}
863
864
866{
867 OnSourceChanged( aEvent );
868}
869
870
872{
873 wxString driver = wxString::Format( wxS( "Driver={%s};" ), m_choiceDriver->GetStringSelection() );
874 wxString text = m_txtConnectionString->GetValue();
875 wxRegEx existing( wxS( "Driver=\\{[^}]*\\};?" ), wxRE_ICASE | wxRE_ADVANCED );
876
877 if( !existing.Replace( &text, driver, 1 ) )
878 text = driver + text;
879
880 m_txtConnectionString->SetValue( text );
881}
882
883
885{
886 if( m_connectJob )
887 {
889 setConnectionStatus( _( "Connection attempt stopped." ), false );
891 markDirty();
892 return;
893 }
894
895 storeSource();
896 connect();
897}
898
899
901{
902 DATABASE_SOURCE& source = m_settings->m_Source;
903
904 if( m_rbConnectionString->GetValue() )
905 {
906 source.connection_string = TO_UTF8( m_txtConnectionString->GetValue() );
907 source.dsn.clear();
908 source.username.clear();
909 source.password.clear();
910 }
911 else
912 {
913 source.connection_string.clear();
914 source.dsn = TO_UTF8( m_cbDSN->GetValue() );
915 source.username = TO_UTF8( m_txtUser->GetValue() );
916 source.password = TO_UTF8( m_txtPassword->GetValue() );
917 }
918
919 source.timeout = m_spinTimeout->GetValue();
920}
921
922
924{
926
927 m_conn.reset();
928 m_tables.clear();
929 m_columns.clear();
930 m_sampleTable.clear();
931 m_sampleColumns.clear();
932 m_sampleRows.clear();
933 m_sampleRepeats.clear();
934 loadDataGrid();
935 markDirty();
936
937 const DATABASE_SOURCE& source = m_settings->m_Source;
938
939 if( source.dsn.empty() && source.connection_string.empty() )
940 {
941 setConnectionStatus( _( "No data source configured." ), false );
943 return;
944 }
945
946 auto job = std::make_shared<CONNECT_JOB>();
947 job->owner = this;
948 job->conn = SCH_IO_DATABASE::CreateConnection( *m_settings, false, cwdDir() );
949 m_connectJob = job;
950
951 setConnectionStatus( source.dsn.empty() ? _( "Connecting..." )
952 : wxString::Format( _( "Connecting to %s..." ), From_UTF8( source.dsn ) ),
953 false );
954 m_btnTest->SetLabel( _( "Stop" ) );
956
957 // A slow or unreachable server would otherwise freeze the dialog for the whole login timeout
958 GetKiCadThreadPool().detach_task(
959 [job]()
960 {
961 job->connected = job->conn->Connect();
962
963 if( job->connected )
964 job->conn->GetTables( job->tables );
965
966 if( wxTheApp )
967 {
968 wxTheApp->CallAfter(
969 [job]()
970 {
971 if( job->owner )
972 job->owner->onConnectFinished( job );
973 } );
974 }
975 } );
976}
977
978
980{
981 if( !m_connectJob )
982 return;
983
984 m_connectJob->owner = nullptr;
985 m_connectJob.reset();
986 m_btnTest->SetLabel( _( "Test Connection" ) );
987}
988
989
990void DIALOG_DATABASE_LIB_SETTINGS::onConnectFinished( const std::shared_ptr<CONNECT_JOB>& aJob )
991{
992 if( aJob != m_connectJob )
993 return;
994
995 m_connectJob.reset();
996 m_btnTest->SetLabel( _( "Test Connection" ) );
997
998 if( aJob->connected )
999 {
1000 m_conn = std::move( aJob->conn );
1001 m_tables = std::move( aJob->tables );
1002 setConnectionStatus( wxString::Format( _( "Connected; %zu tables and views found." ), m_tables.size() ),
1003 false );
1004 }
1005 else
1006 {
1007 setConnectionStatus( describeFailure( *aJob->conn, m_settings->m_Source ), true,
1008 From_UTF8( aJob->conn->GetLastError() ) );
1009 }
1010
1011 refreshTables();
1012 autofillTable();
1013 loadDataGrid();
1014 markDirty();
1015}
1016
1017
1019{
1020 SCOPED_SET_RESET<bool> loading( m_loading, true );
1021
1022 wxArrayString tables;
1023
1024 for( const std::string& name : m_tables )
1025 tables.Add( From_UTF8( name ) );
1026
1027 wxString table = m_cbTable->GetValue();
1028 m_cbTable->Set( tables );
1029 m_cbTable->ChangeValue( table );
1031}
1032
1033
1034void DIALOG_DATABASE_LIB_SETTINGS::setConnectionStatus( const wxString& aText, bool aError,
1035 const wxString& aDetails )
1036{
1037 m_stConnectionStatus->SetLabel( aText );
1038 m_stConnectionStatus->SetToolTip( aDetails.IsEmpty() ? aText : aText + wxS( "\n\n" ) + aDetails );
1039 m_stConnectionStatus->SetForegroundColour( aError ? *wxRED : wxNullColour );
1040
1041 // SetLabel() resizes the control to its text; layout shrinks it back so the end ellipsizes
1043}
1044
1045
1047{
1048 if( m_currentLibrary < 0 || m_currentLibrary >= static_cast<int>( m_settings->m_Tables.size() ) )
1049 return nullptr;
1050
1051 return &m_settings->m_Tables[m_currentLibrary];
1052}
1053
1054
1055const std::vector<std::string>& DIALOG_DATABASE_LIB_SETTINGS::columnsFor( const std::string& aTable )
1056{
1057 static const std::vector<std::string> empty;
1058
1059 if( !m_conn || aTable.empty() )
1060 return empty;
1061
1062 auto it = m_columns.find( aTable );
1063
1064 if( it == m_columns.end() )
1065 {
1066 it = m_columns.emplace( aTable, std::vector<std::string>() ).first;
1067 m_conn->GetColumns( aTable, it->second );
1068 }
1069
1070 return it->second;
1071}
1072
1073
1075{
1076 wxArrayString names;
1077
1078 for( const DATABASE_LIB_TABLE& table : m_settings->m_Tables )
1079 names.Add( displayName( table ) );
1080
1081 m_libList->Set( names );
1082
1083 if( m_currentLibrary >= 0 )
1084 m_libList->SetSelection( m_currentLibrary );
1085
1087}
1088
1089
1091{
1092 bool empty = m_settings->m_Tables.empty();
1093
1094 if( m_stNoLibraries->IsShown() == empty && m_btnAddFirstLibrary->IsShown() == empty )
1095 return;
1096
1097 m_stNoLibraries->Show( empty );
1098 m_btnAddFirstLibrary->Show( empty );
1100}
1101
1102
1104{
1106
1107 m_autoTable.clear();
1108 m_autoRoles.clear();
1109 m_autoFields.clear();
1110 m_userRoles.clear();
1111
1112 {
1113 SCOPED_SET_RESET<bool> loading( m_loading, true );
1114
1115 for( wxWindow* control : std::initializer_list<wxWindow*>{ m_txtLibName, m_cbTable, m_fieldsGrid,
1121 {
1122 control->Enable( lib != nullptr );
1123 }
1124
1125 for( const COLUMN_ROLE& role : m_roles )
1126 role.combo->Enable( lib != nullptr );
1127
1128 m_txtLibName->ChangeValue( lib ? From_UTF8( lib->name ) : wxString() );
1129 m_cbTable->ChangeValue( lib ? From_UTF8( lib->table ) : wxString() );
1130
1132 loadFields();
1133 }
1134
1136
1137 // Another library may sample the same table with different column roles
1138 loadDataGrid();
1139 markDirty();
1140}
1141
1142
1144{
1147 DATABASE_LIB_TABLE& shown = lib ? *lib : empty;
1148
1149 wxArrayString columns;
1150
1151 for( const std::string& column : columnsFor( shown.table ) )
1152 columns.Add( From_UTF8( column ) );
1153
1154 // Optional mappings offer a blank entry to clear them
1155 wxArrayString optionalColumns = columns;
1156 optionalColumns.Insert( wxEmptyString, 0 );
1157
1158 SCOPED_SET_RESET<bool> loading( m_loading, true );
1159
1160 for( const COLUMN_ROLE& role : m_roles )
1161 {
1162 role.combo->Set( role.optional ? optionalColumns : columns );
1163 role.combo->ChangeValue( From_UTF8( role.value( shown ) ) );
1164 }
1165
1166 wxGridCellAttr* attr = new wxGridCellAttr;
1167 attr->SetEditor( new GRID_CELL_COMBOBOX( columns ) );
1168 m_fieldsGrid->SetColAttr( FIELD_COL_COLUMN, attr );
1169
1173}
1174
1175
1176void DIALOG_DATABASE_LIB_SETTINGS::markUserRole( const COLUMN_ROLE& aRole, const std::string& aValue )
1177{
1178 m_userRoles.insert( aRole.combo );
1179 m_autoRoles.erase( aRole.combo );
1180 m_newChoices[aRole.autofillId] = aValue;
1181}
1182
1183
1185{
1186 for( const COLUMN_ROLE& role : m_roles )
1187 {
1188 std::string value = TO_UTF8( role.combo->GetValue() );
1189 bool autofilled = !value.empty() && isAutofilled( m_autoRoles, role.combo, value );
1190
1191 wxFont font = role.combo->GetFont();
1192 wxFontStyle style = autofilled ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL;
1193
1194 // A new font on a combo that is being typed in makes Windows expand the typed character to a matching entry
1195 // and select it, so the next key replaces it. The style follows when focus leaves.
1196 wxWindow* focus = wxWindow::FindFocus();
1197 bool typing = focus && ( focus == role.combo || focus->GetParent() == role.combo );
1198
1199 if( font.GetStyle() != style && !typing )
1200 {
1201 font.SetStyle( style );
1202 role.combo->SetFont( font );
1203 }
1204 }
1205}
1206
1207
1209{
1211
1212 if( !lib )
1213 return;
1214
1215 lib->name = TO_UTF8( m_txtLibName->GetValue() );
1216
1217 // The loader strips '/' because it separates the nickname from the symbol name
1218 std::erase( lib->name, '/' );
1219
1220 lib->table = TO_UTF8( m_cbTable->GetValue() );
1221
1222 for( const COLUMN_ROLE& role : m_roles )
1223 role.value( *lib ) = TO_UTF8( role.combo->GetValue() );
1224}
1225
1226
1228{
1229 if( !m_fieldsGrid->CommitPendingChanges() )
1230 return;
1231
1232 storeLibrary();
1233 m_currentLibrary = m_libList->GetSelection();
1234 loadLibrary();
1235}
1236
1237
1239{
1240 storeLibrary();
1241 m_flagMissing = false;
1242 m_settings->m_Tables.emplace_back();
1243 m_currentLibrary = static_cast<int>( m_settings->m_Tables.size() ) - 1;
1245 loadLibrary();
1246 m_txtLibName->SetFocus();
1247}
1248
1249
1251{
1252 storeLibrary();
1253
1254 if( DATABASE_LIB_TABLE* lib = currentLibrary() )
1255 {
1256 DATABASE_LIB_TABLE copy = *lib;
1257 copy.name += "-copy";
1258 m_settings->m_Tables.insert( m_settings->m_Tables.begin() + m_currentLibrary + 1, std::move( copy ) );
1261 loadLibrary();
1262 }
1263}
1264
1265
1267{
1268 storeLibrary();
1269
1270 int target = m_currentLibrary + aDelta;
1271
1272 if( m_currentLibrary < 0 || target < 0 || target >= static_cast<int>( m_settings->m_Tables.size() ) )
1273 {
1274 wxBell();
1275 return;
1276 }
1277
1278 std::swap( m_settings->m_Tables[m_currentLibrary], m_settings->m_Tables[target] );
1279 m_currentLibrary = target;
1281}
1282
1283
1285{
1286 moveLibrary( -1 );
1287}
1288
1289
1291{
1292 moveLibrary( 1 );
1293}
1294
1295
1297{
1298 if( !currentLibrary() )
1299 return;
1300
1301 m_settings->m_Tables.erase( m_settings->m_Tables.begin() + m_currentLibrary );
1302 m_currentLibrary = std::min( m_currentLibrary, static_cast<int>( m_settings->m_Tables.size() ) - 1 );
1304 loadLibrary();
1305}
1306
1307
1309{
1310 if( m_loading )
1311 return;
1312
1313 storeLibrary();
1314
1315 if( DATABASE_LIB_TABLE* lib = currentLibrary() )
1316 m_libList->SetString( m_currentLibrary, displayName( *lib ) );
1317
1318 for( const COLUMN_ROLE& role : m_roles )
1319 {
1320 if( aEvent.GetEventObject() == role.combo )
1321 {
1322 markUserRole( role, TO_UTF8( role.combo->GetValue() ) );
1323 }
1324 }
1325
1326 if( aEvent.GetEventObject() == m_txtLibName )
1327 autofillTable();
1328
1333 markDirty();
1334}
1335
1336
1338{
1339 if( m_loading )
1340 return;
1341
1342 storeLibrary();
1343 m_autoTable.clear();
1344
1346
1347 // Typing a partial name would otherwise query the catalog for every keystroke
1348 if( lib && ( m_tables.empty() || alg::contains( m_tables, lib->table ) ) )
1349 {
1352 }
1353
1356 markDirty();
1357}
1358
1359
1361{
1363
1364 if( !lib || m_tables.empty() || ( !lib->table.empty() && lib->table != m_autoTable ) )
1365 return;
1366
1367 std::string table = DB_AUTOFILL::MatchTable( m_tables, lib->name );
1368
1369 if( table.empty() || table == lib->table )
1370 return;
1371
1372 lib->table = table;
1374
1375 {
1376 SCOPED_SET_RESET<bool> loading( m_loading, true );
1377 m_cbTable->ChangeValue( From_UTF8( table ) );
1378 }
1379
1380 // Reloads the column choices and, by sampling the table, the data grid
1382}
1383
1384
1386{
1388
1389 if( !lib || !alg::contains( m_tables, lib->table ) )
1390 return;
1391
1392 // The sample shows which columns repeat, and a repeating column cannot be the key
1393 ensureSample( lib->table );
1394
1395 // Mappings the user made keep their columns, which are then not offered to other roles
1396 std::set<std::string> taken;
1397 std::vector<const COLUMN_ROLE*> roles;
1398 std::vector<const AUTOFILL_FIELD*> fields;
1399 std::vector<DB_AUTOFILL::ROLE> requests;
1400
1401 for( const COLUMN_ROLE& role : m_roles )
1402 {
1403 const std::string& value = role.value( *lib );
1404
1405 if( m_userRoles.contains( role.combo )
1406 || ( !value.empty() && !isAutofilled( m_autoRoles, role.combo, value ) ) )
1407 {
1408 taken.insert( value );
1409 continue;
1410 }
1411
1412 roles.push_back( &role );
1413 requests.push_back( autofillRole( role.autofillId, role.terms ) );
1414
1415 if( role.combo == m_cbKeyCol && m_sampleTable == lib->table )
1416 {
1417 for( const auto& [column, repeats] : m_sampleRepeats )
1418 {
1419 if( repeats > 0 )
1420 requests.back().excluded.insert( column );
1421 }
1422 }
1423 }
1424
1425 for( const AUTOFILL_FIELD& def : autofillFields() )
1426 {
1427 const DATABASE_FIELD_MAPPING* field = findField( *lib, def.name );
1428
1429 if( field && !isAutofilled( m_autoFields, def.name, field->column ) )
1430 continue;
1431
1432 fields.push_back( &def );
1433 requests.push_back( autofillRole( fieldChoiceId( def.name ), def.terms ) );
1434 }
1435
1436 for( const DATABASE_FIELD_MAPPING& field : lib->fields )
1437 {
1438 if( !isAutofilled( m_autoFields, field.name, field.column ) )
1439 taken.insert( field.column );
1440 }
1441
1442 std::vector<std::string> columns;
1443
1444 for( const std::string& column : columnsFor( lib->table ) )
1445 {
1446 if( !taken.contains( column ) )
1447 columns.push_back( column );
1448 }
1449
1450 std::vector<std::string> chosen = DB_AUTOFILL::AssignColumns( columns, requests );
1451
1452 for( size_t ii = 0; ii < roles.size(); ++ii )
1453 {
1454 roles[ii]->value( *lib ) = chosen[ii];
1455 m_autoRoles[roles[ii]->combo] = chosen[ii];
1456 }
1457
1458 for( size_t ii = 0; ii < fields.size(); ++ii )
1459 {
1460 const AUTOFILL_FIELD& def = *fields[ii];
1461 const std::string& column = chosen[roles.size() + ii];
1462 DATABASE_FIELD_MAPPING* field = findField( *lib, def.name );
1463
1464 if( column.empty() )
1465 {
1466 if( field )
1467 lib->fields.erase( lib->fields.begin() + ( field - lib->fields.data() ) );
1468
1469 m_autoFields.erase( def.name );
1470 }
1471 else
1472 {
1473 if( field )
1474 field->column = column;
1475 else
1476 lib->fields.emplace_back( column, def.name, def.visibleOnAdd, def.visibleOnAdd, false, false );
1477
1478 m_autoFields[def.name] = column;
1479 }
1480 }
1481
1483 loadFields();
1485}
1486
1487
1489{
1491
1492 m_fieldsGrid->ClearRows();
1493
1494 if( !lib )
1495 return;
1496
1497 m_fieldsGrid->AppendRows( static_cast<int>( lib->fields.size() ) );
1498
1499 auto boolCell =
1500 []( bool aValue )
1501 {
1502 return aValue ? wxS( "1" ) : wxS( "" );
1503 };
1504
1505 for( int row = 0; row < static_cast<int>( lib->fields.size() ); ++row )
1506 {
1507 const DATABASE_FIELD_MAPPING& field = lib->fields[row];
1508
1509 m_fieldsGrid->SetCellValue( row, FIELD_COL_COLUMN, From_UTF8( field.column ) );
1510 m_fieldsGrid->SetCellValue( row, FIELD_COL_NAME, From_UTF8( field.name ) );
1511 m_fieldsGrid->SetCellValue( row, FIELD_COL_VISIBLE_ON_ADD, boolCell( field.visible_on_add ) );
1512 m_fieldsGrid->SetCellValue( row, FIELD_COL_VISIBLE_IN_CHOOSER, boolCell( field.visible_in_chooser ) );
1513 m_fieldsGrid->SetCellValue( row, FIELD_COL_SHOW_NAME, boolCell( field.show_name ) );
1514 m_fieldsGrid->SetCellValue( row, FIELD_COL_INHERIT, boolCell( field.inherit_properties ) );
1515 }
1516
1517 // Counts the header too, since empty rows give no width and the labels would overlap
1518 for( int col = 0; col < m_fieldsGrid->GetNumberCols(); ++col )
1519 m_fieldsGrid->SetColSize( col, m_fieldsGrid->GetVisibleWidth( col ) );
1520}
1521
1522
1524{
1526 int row = aEvent.GetRow();
1527
1528 if( !lib || row < 0 || row >= static_cast<int>( lib->fields.size() ) )
1529 return;
1530
1531 // Edit in place so each mapping keeps the raw JSON it was loaded from
1532 DATABASE_FIELD_MAPPING& field = lib->fields[row];
1533 wxString value = m_fieldsGrid->GetCellValue( row, aEvent.GetCol() );
1534 bool checked = value == wxS( "1" );
1535
1536 switch( aEvent.GetCol() )
1537 {
1538 case FIELD_COL_COLUMN: field.column = TO_UTF8( value ); break;
1539 case FIELD_COL_NAME: field.SetName( TO_UTF8( value ) ); break;
1540 case FIELD_COL_VISIBLE_ON_ADD: field.visible_on_add = checked; break;
1541 case FIELD_COL_VISIBLE_IN_CHOOSER: field.visible_in_chooser = checked; break;
1542 case FIELD_COL_SHOW_NAME: field.show_name = checked; break;
1543 case FIELD_COL_INHERIT: field.inherit_properties = checked; break;
1544 default: break;
1545 }
1546
1547 if( aEvent.GetCol() == FIELD_COL_COLUMN || aEvent.GetCol() == FIELD_COL_NAME )
1548 {
1549 for( const AUTOFILL_FIELD& def : autofillFields() )
1550 {
1551 if( wxString::FromUTF8( field.name ).IsSameAs( def.name, false ) )
1552 {
1553 m_autoFields.erase( def.name );
1554 m_newChoices[fieldChoiceId( def.name )] = field.column;
1555 }
1556 }
1557
1559 }
1560
1561 markDirty();
1562}
1563
1564
1565void DIALOG_DATABASE_LIB_SETTINGS::OnAddField( wxCommandEvent& aEvent )
1566{
1568
1569 if( !lib )
1570 return;
1571
1572 m_fieldsGrid->OnAddRow(
1573 [&]() -> std::pair<int, int>
1574 {
1575 lib->fields.emplace_back();
1576 m_fieldsGrid->AppendRows( 1 );
1577 return { m_fieldsGrid->GetNumberRows() - 1, FIELD_COL_COLUMN };
1578 } );
1579}
1580
1581
1583{
1584 if( DATABASE_LIB_TABLE* lib = currentLibrary() )
1585 m_fieldsGrid->OnMoveRowUp( [&]( int row ) { swapFields( *lib, row, row - 1 ); } );
1586}
1587
1588
1590{
1591 if( DATABASE_LIB_TABLE* lib = currentLibrary() )
1592 m_fieldsGrid->OnMoveRowDown( [&]( int row ) { swapFields( *lib, row, row + 1 ); } );
1593}
1594
1595
1597{
1598 std::swap( aLib.fields[aRow], aLib.fields[aOther] );
1599 m_fieldsGrid->SwapRows( aRow, aOther );
1600 markDirty();
1601}
1602
1603
1605{
1606 if( DATABASE_LIB_TABLE* lib = currentLibrary() )
1607 {
1608 m_fieldsGrid->OnDeleteRows(
1609 [&]( int row )
1610 {
1611 lib->fields.erase( lib->fields.begin() + row );
1612 m_fieldsGrid->DeleteRows( row, 1 );
1613 } );
1614
1616 markDirty();
1617 }
1618}
1619
1620
1622{
1624
1625 if( !lib || !m_fieldsGrid->CommitPendingChanges() )
1626 return;
1627
1628 std::set<std::string> mapped;
1629
1630 for( const COLUMN_ROLE& role : m_roles )
1631 mapped.insert( role.value( *lib ) );
1632
1633 for( const DATABASE_FIELD_MAPPING& field : lib->fields )
1634 mapped.insert( field.column );
1635
1636 for( const std::string& column : columnsFor( lib->table ) )
1637 {
1638 if( !mapped.contains( column ) )
1639 lib->fields.emplace_back( column, column, false, false, false, false );
1640 }
1641
1642 loadFields();
1644 markDirty();
1645}
1646
1647
1649{
1651
1652 if( !lib )
1653 return nullptr;
1654
1655 for( const DATABASE_FIELD_MAPPING& field : lib->fields )
1656 {
1657 if( field.column == aColumn )
1658 return &field;
1659 }
1660
1661 return nullptr;
1662}
1663
1664
1666{
1668
1669 // Rebuilding moves the grid cursor, which must not select a symbol in the preview
1670 SCOPED_SET_RESET<bool> loading( m_loading, true );
1671
1672 m_dataGrid->BeginBatch();
1673 m_dataGrid->ClearRows();
1674
1675 if( m_dataGrid->GetNumberCols() > 0 )
1676 m_dataGrid->DeleteCols( 0, m_dataGrid->GetNumberCols() );
1677
1678 if( lib && !m_sampleTable.empty() && m_sampleTable == lib->table )
1679 {
1680 m_dataGrid->AppendCols( static_cast<int>( m_sampleColumns.size() ) );
1681 m_dataGrid->AppendRows( static_cast<int>( m_sampleRows.size() ) );
1682
1683 for( int col = 0; col < static_cast<int>( m_sampleColumns.size() ); ++col )
1684 {
1685 for( int row = 0; row < static_cast<int>( m_sampleRows.size() ); ++row )
1686 {
1687 auto it = m_sampleRows[row].find( m_sampleColumns[col] );
1688
1689 if( it == m_sampleRows[row].end() )
1690 continue;
1691
1692 if( const std::string* value = std::any_cast<std::string>( &it->second ) )
1693 m_dataGrid->SetCellValue( row, col, From_UTF8( *value ) );
1694 }
1695 }
1696
1697 // Labels first, so the sizing accounts for the role lines under each column name
1699 m_dataGrid->AutoSizeColumns( false );
1700 }
1701 else
1702 {
1704 }
1705
1706 m_dataGrid->EndBatch();
1707}
1708
1709
1711{
1713 wxString text;
1714
1715 if( lib )
1716 {
1717 wxString symbol;
1718
1719 if( m_sampleTable == lib->table && !m_sampleRows.empty() )
1720 symbol = SCH_IO_DATABASE::SymbolNameForRow( *lib, m_settings->m_GloballyUniqueKeys, m_sampleRows[0] );
1721
1722 if( symbol.IsEmpty() )
1723 {
1724 wxString key = _( "<key>" );
1725
1726 if( m_settings->m_GloballyUniqueKeys || lib->name.empty() )
1727 symbol = key;
1728 else
1729 symbol = From_UTF8( lib->name ) + wxS( "/" ) + key;
1730 }
1731
1732 text = wxString::Format( _( "Symbols appear as %s" ), symbol );
1733 }
1734
1735 m_stSymbolExample->SetLabel( text );
1736 m_stSymbolExample->SetToolTip( text );
1737
1738 // SetLabel() resizes the control to its text; layout shrinks it back so the end ellipsizes
1740}
1741
1742
1744{
1746
1748
1749 if( !lib )
1750 {
1751 m_stDataStatus->SetLabel( _( "Choose a library on the Libraries page to see its table." ) );
1752 return;
1753 }
1754
1755 if( m_sampleTable.empty() || m_sampleTable != lib->table )
1756 {
1757 m_stDataStatus->SetLabel( m_conn ? _( "No rows loaded." ) : _( "Not connected." ) );
1758 return;
1759 }
1760
1761 std::map<std::string, wxString> roles;
1762
1763 for( const COLUMN_ROLE& role : m_roles )
1764 {
1765 const std::string& column = role.value( *lib );
1766
1767 if( !column.empty() )
1768 roles.emplace( column, role.label );
1769 }
1770
1771 for( int col = 0; col < m_dataGrid->GetNumberCols(); ++col )
1772 {
1773 const std::string& column = m_sampleColumns[col];
1774 wxString label = From_UTF8( column );
1775
1776 if( auto role = roles.find( column ); role != roles.end() )
1777 label += wxS( "\n" ) + role->second;
1778 else if( const DATABASE_FIELD_MAPPING* field = fieldForColumn( column ) )
1779 label += wxS( "\n" ) + wxString::Format( _( "field '%s'" ), field->name_wx );
1780
1781 m_dataGrid->SetColLabelValue( col, label );
1782 }
1783
1784 wxString rows = m_sampleRows.size() < PREVIEW_ROW_LIMIT
1785 ? wxString::Format( _( "%zu rows" ), m_sampleRows.size() )
1786 : wxString::Format( _( "first %zu rows" ), m_sampleRows.size() );
1787
1788 m_stDataStatus->SetLabel( wxString::Format( _( "Library '%s', table '%s', %s. Click a column header to map it." ),
1789 displayName( *lib ), From_UTF8( lib->table ), rows ) );
1790}
1791
1792
1794{
1796 int col = aEvent.GetCol();
1797
1798 if( !lib || m_sampleTable != lib->table || col < 0 || col >= static_cast<int>( m_sampleColumns.size() )
1799 || !m_fieldsGrid->CommitPendingChanges() )
1800 {
1801 return;
1802 }
1803
1804 // A copy, since the preview timer can replace the sample while the popup menu runs its event loop
1805 const std::string column = m_sampleColumns[col];
1806 bool mapped = fieldForColumn( column ) != nullptr;
1807 wxMenu menu;
1808 int fieldId = wxWindow::NewControlId();
1809
1810 menu.Append( fieldId, mapped ? _( "Remove Field Mapping" ) : _( "Map as Field" ) );
1811 menu.AppendSeparator();
1812
1813 std::map<int, const COLUMN_ROLE*> roleIds;
1814
1815 for( const COLUMN_ROLE& role : m_roles )
1816 {
1817 int id = wxWindow::NewControlId();
1818 roleIds[id] = &role;
1819 menu.AppendCheckItem( id, wxString::Format( _( "Use for %s" ), role.label ) )
1820 ->Check( role.value( *lib ) == column );
1821 }
1822
1823 int choice = GetPopupMenuSelectionFromUser( menu );
1824
1825 if( auto it = roleIds.find( choice ); it != roleIds.end() )
1826 {
1827 const COLUMN_ROLE& role = *it->second;
1828
1829 role.value( *lib ) = role.value( *lib ) == column ? std::string() : column;
1830 markUserRole( role, role.value( *lib ) );
1833 markDirty();
1834 return;
1835 }
1836
1837 if( choice != fieldId )
1838 return;
1839
1840 if( mapped )
1841 {
1842 std::erase_if( lib->fields,
1843 [&]( const DATABASE_FIELD_MAPPING& aField )
1844 {
1845 return aField.column == column;
1846 } );
1847 }
1848 else
1849 {
1850 lib->fields.emplace_back( column, column, false, false, false, false );
1851 }
1852
1853 loadFields();
1855 markDirty();
1856}
1857
1858
1860{
1862 int row = aEvent.GetRow();
1863
1864 if( !m_loading && lib && row >= 0 && row < static_cast<int>( m_sampleRows.size() ) )
1865 {
1866 m_preview->SelectSymbol( SCH_IO_DATABASE::SymbolNameForRow( *lib, m_settings->m_GloballyUniqueKeys,
1867 m_sampleRows[row] ) );
1868 }
1869
1870 aEvent.Skip();
1871}
1872
1873
1874void DIALOG_DATABASE_LIB_SETTINGS::showInfo( const wxString& aMessage, int aIcon )
1875{
1876 m_infoBar->RemoveAllButtons();
1877 m_infoBar->AddCloseButton();
1878 m_infoBar->ShowMessage( aMessage, aIcon );
1879}
1880
1881
1883{
1884 m_splitter->Unbind( wxEVT_IDLE, &DIALOG_DATABASE_LIB_SETTINGS::onSplitterIdle, this );
1885
1886 // What the pages need in the pane around them. A scrolled window accepts being narrower than its content, so
1887 // its own size says nothing, and the sizer's minimum does.
1888 int needed = 0;
1889
1890 for( wxScrolledWindow* page : { m_connectionPage, m_librariesPage } )
1891 {
1892 int overhead = m_editPanel->GetSize().x - page->GetClientSize().x;
1893
1894#ifndef __WXOSX__
1895 // Classic scrollbars take room from the page once the page grows taller than its pane
1896 if( !page->HasScrollbar( wxVERTICAL ) )
1897 overhead += wxSystemSettings::GetMetric( wxSYS_VSCROLL_X, page );
1898#endif
1899
1900 needed = std::max( needed, page->GetSizer()->CalcMin().x + overhead );
1901 }
1902
1903 // The preview is only useful with room for its tree columns and symbol, so the dialog grows to give it that
1904 const int previewWidth = FromDIP( PREVIEW_MIN_WIDTH_DIP );
1905 const int deficit = needed + m_splitter->GetSashSize() + previewWidth - m_splitter->GetClientSize().x;
1906
1907 if( deficit > 0 )
1908 {
1909 wxRect display = wxDisplay( this ).GetClientArea();
1910 wxSize size = GetSize();
1911
1912 SetSize( std::min( size.x + deficit, display.width ), size.y );
1913 }
1914
1915 // Resizing then only changes the preview, so the pages keep the width they need
1916 m_splitter->SetSashGravity( 0.0 );
1917 m_splitter->SetSashPosition( std::clamp( needed, m_splitter->GetMinimumPaneSize(),
1918 std::max( m_splitter->GetClientSize().x - previewWidth, 0 ) ) );
1919
1920 // Narrower than this the pages are cut off, and where scrollbars are overlays nothing says they scroll
1921 wxSize minSize = GetMinSize();
1922
1923 minSize.x = std::max( minSize.x, GetSize().x - m_splitter->GetClientSize().x + needed + m_splitter->GetSashSize()
1924 + m_splitter->GetMinimumPaneSize() );
1925 SetMinSize( minSize );
1926}
1927
1928
1933
1934
1935bool DIALOG_DATABASE_LIB_SETTINGS::ensureSample( const std::string& aTable )
1936{
1937 if( !m_conn || aTable.empty() )
1938 return false;
1939
1940 if( m_sampleTable == aTable )
1941 return true;
1942
1943 wxBusyCursor busy;
1944
1945 // Only a successful sample is cached, so an error stays visible until the table reads
1946 m_sampleTable.clear();
1947 m_sampleRepeats.clear();
1948
1949 bool ok = m_conn->SelectSample( aTable, PREVIEW_ROW_LIMIT, m_sampleColumns, m_sampleRows );
1950
1951 if( ok )
1952 {
1953 m_sampleTable = aTable;
1955 }
1956
1957 loadDataGrid();
1958 return ok;
1959}
1960
1961
1963{
1965
1966 wxArrayString warnings;
1967
1968 if( m_flagMissing && lib && lib->table.empty() )
1969 warnings.Add( _( "Choose a table." ) );
1970 else if( !m_tableError.IsEmpty() )
1971 warnings.Add( m_tableError );
1972
1973 m_tableIcon->SetWarnings( warnings );
1974}
1975
1976
1978{
1980
1982
1983 // A table name still being typed must not query the catalog for its columns
1984 const std::vector<std::string>* columns =
1985 lib && alg::contains( m_tables, lib->table ) ? &columnsFor( lib->table ) : nullptr;
1986
1987 // The sample counts rows with the same key, which is only meaningful for the table it was read from
1988 size_t repeats = 0;
1989
1990 if( lib && m_sampleTable == lib->table )
1991 {
1992 if( auto it = m_sampleRepeats.find( lib->key_col ); it != m_sampleRepeats.end() )
1993 repeats = it->second;
1994 }
1995
1996 for( size_t ii = 0; ii < m_roles.size(); ++ii )
1997 {
1998 const COLUMN_ROLE& role = m_roles[ii];
1999 const bool isKey = role.combo == m_cbKeyCol;
2000 wxArrayString warnings;
2001
2002 if( lib )
2003 {
2004 const std::string& value = role.value( *lib );
2005
2006 if( value.empty() )
2007 {
2008 if( isKey && m_flagMissing )
2009 warnings.Add( _( "Choose a key column." ) );
2010 }
2011 else if( columns && !columns->empty() && !alg::contains( *columns, value ) )
2012 {
2013 warnings.Add( wxString::Format( _( "Column '%s' is not in table '%s'." ), From_UTF8( value ),
2014 From_UTF8( lib->table ) ) );
2015 }
2016
2017 if( isKey && repeats > 0 )
2018 {
2019 warnings.Add( wxString::Format( _( "'%s' repeats in %zu of the first %zu rows, and parts that share "
2020 "a key hide each other. Choose a column with a unique value per "
2021 "part." ),
2022 From_UTF8( lib->key_col ), repeats, m_sampleRows.size() ) );
2023 }
2024 }
2025
2026 // The repeat warning already covers every key the preview found repeated
2027 if( isKey )
2028 {
2029 for( const wxString& message : previewIssues( FIELD::KEY ) )
2030 warnings.Add( message );
2031
2032 if( repeats == 0 )
2033 {
2034 for( const wxString& message : previewIssues( FIELD::REPEATED_KEY ) )
2035 warnings.Add( message );
2036 }
2037 }
2038 else if( role.combo == m_cbSymbolsCol )
2039 {
2040 for( const wxString& message : previewIssues( FIELD::SYMBOLS ) )
2041 warnings.Add( message );
2042 }
2043 else if( role.combo == m_cbPinsCol )
2044 {
2045 for( const wxString& message : previewIssues( FIELD::PIN_MAP ) )
2046 warnings.Add( message );
2047 }
2048
2049 m_roleIcons[ii]->SetWarnings( warnings );
2050 }
2051}
2052
2053
2055{
2056 wxArrayString messages;
2057
2059 {
2060 if( issue.field == aField )
2061 messages.Add( issue.message );
2062 }
2063
2064 return messages;
2065}
2066
2067
2069{
2071
2072 auto clear =
2073 [&]( const wxString& aStatus, const wxString& aDetails = wxEmptyString )
2074 {
2075 m_tableError = aDetails.IsEmpty() ? wxString() : aStatus + wxS( "\n" ) + aDetails;
2076 m_previewIssues.clear();
2079 m_preview->SetSymbols( wxEmptyString, {} );
2080 m_preview->SetStatus( aStatus );
2081 };
2082
2083 if( !lib )
2084 return clear( _( "Add a library on the Libraries page to preview it." ) );
2085
2086 if( m_connectJob )
2087 return clear( _( "Connecting..." ) );
2088
2089 if( !m_conn )
2090 return clear( _( "Not connected to a database." ) );
2091
2092 if( lib->table.empty() )
2093 return clear( _( "Choose a table." ) );
2094
2095 if( !ensureSample( lib->table ) )
2096 {
2097 return clear( wxString::Format( _( "Could not read table '%s'." ), From_UTF8( lib->table ) ),
2098 From_UTF8( m_conn->GetLastError() ) );
2099 }
2100
2101 m_tableError.Clear();
2103
2104 m_previewIssues.clear();
2105 std::vector<std::unique_ptr<LIB_SYMBOL>> symbols = SCH_IO_DATABASE::BuildPreviewSymbols(
2106 m_libs, *lib, m_settings->m_GloballyUniqueKeys, m_sampleRows, &m_previewIssues );
2107
2108 size_t count = symbols.size();
2109 wxString status;
2110
2111 if( lib->key_col.empty() )
2112 status = _( "Choose a key column." );
2113 else if( !m_sampleRows.empty() && count == 0 && !alg::contains( m_sampleColumns, lib->key_col ) )
2114 status = wxString::Format( _( "Key column '%s' is not in table '%s'." ), From_UTF8( lib->key_col ),
2115 From_UTF8( lib->table ) );
2116 else if( m_sampleRows.size() < PREVIEW_ROW_LIMIT )
2117 status = wxString::Format( _( "%zu symbols." ), count );
2118 else
2119 status = wxString::Format( _( "%zu symbols from the first %zu rows." ), count, m_sampleRows.size() );
2120
2122
2123 m_preview->SetSymbols( displayName( *lib ), std::move( symbols ) );
2124 m_preview->SetStatus( status );
2125}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
FAILURE
Why a connection attempt failed, as far as the driver's report tells.
static FAILURE ClassifyFailure(const std::string &aState, const std::string &aMessage)
Classifies a driver error from its SQLSTATE, falling back to its text since drivers disagree on state...
static std::vector< std::string > ListDrivers()
std::string GetLastState() const
std::string GetLastError() const
static std::vector< DATA_SOURCE > ListDataSourceDetails()
Shows symbols that are not in any library table the way the symbol chooser does: a searchable library...
DIALOG_DATABASE_LIB_SETTINGS_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Database Library Settings"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(1200, 760), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void updateTableWarning()
Marks the table field when it is required and missing or could not be read.
void onConnectFinished(const std::shared_ptr< CONNECT_JOB > &aJob)
bool m_loading
Set while controls are filled from the settings, so their change events are ignored.
int m_currentLibrary
Index into m_settings->m_Tables of the library shown on the Libraries page, or -1.
void updateLibraryListHints()
Shows the empty state of the library list only while it is empty.
std::set< wxComboBox * > m_userRoles
Role combos the user set for the current library, which autofill leaves alone.
std::map< std::string, std::string > m_newChoices
Columns the user chose this session, keyed like EESCHEMA_SETTINGS::m_DatabaseColumnChoices.
std::map< std::string, size_t > m_sampleRepeats
void OnSourceSpinChanged(wxSpinEvent &aEvent) override
wxArrayString previewIssues(SCH_IO_DATABASE::PREVIEW_ISSUE::FIELD aField) const
void markUserRole(const COLUMN_ROLE &aRole, const std::string &aValue)
Records that the user chose aValue for aRole, so autofill leaves it alone.
void autofillColumns()
Maps columns of the chosen table to roles and fields the user has not set.
bool m_canSave
False when the file is unreadable or from a newer KiCad, since saving would lose its contents.
void markDirty()
Restarts the debounce before the preview is rebuilt.
void updateDsnDriver()
Shows which driver the chosen data source uses.
void loadDataSources()
Fills the data source list from the ODBC driver manager, keeping what is typed.
void autofillTable()
Picks the table named like the library while the user has not chosen one.
bool ensureSample(const std::string &aTable)
Reads aTable's first rows unless they are already loaded; false if they could not be read.
std::vector< DATABASE_CONNECTION::ROW > m_sampleRows
DATABASE_SOURCE m_keyringLogin
DSN login and keyring password at load, so an untouched login never rewrites the keyring.
void updateAutofillStyle()
Italicizes the role combos that still hold an autofilled value.
@ SAVED
The user chose to save, which ended the dialog with wxID_OK.
@ DISCARD
Nothing to keep, or the user chose to discard it.
bool m_unnamed
True until a new library has been given a file.
void OnSourceChanged(wxCommandEvent &aEvent) override
void OnDataLabelRightClick(wxGridEvent &aEvent) override
void OnMoveFieldUp(wxCommandEvent &aEvent) override
void OnAddField(wxCommandEvent &aEvent) override
DIALOG_DATABASE_LIB_SETTINGS(wxWindow *aParent, const wxString &aDblPath, SYMBOL_LIBRARY_ADAPTER *aLibs, const wxString &aDefaultDir=wxEmptyString)
WX_DEBOUNCED_ACTION m_previewDebounce
Coalesces edits into one preview refresh.
std::vector< std::string > m_sampleColumns
void refreshTables()
Refreshes the table list and everything built on it after the connection changes.
void OnAddLibrary(wxCommandEvent &aEvent) override
void OnAddUnmappedColumns(wxCommandEvent &aEvent) override
void updateSymbolExample()
Shows how the current library's symbols will be named.
void updateDataLabels()
Refreshes only the Data page column labels, which follow mapping edits.
void OnMoveLibraryDown(wxCommandEvent &aEvent) override
bool m_flagMissing
Set once OK found a missing table or key, which is then flagged inline.
void connect()
Starts connecting with the current settings; the table list refreshes when it finishes.
std::map< std::string, std::vector< std::string > > m_columns
void OnInsertDriver(wxCommandEvent &aEvent) override
void OnLibraryChanged(wxCommandEvent &aEvent) override
std::string m_cleanSnapshot
settingsSnapshot() when the dialog opened, to tell whether closing loses anything
void swapFields(DATABASE_LIB_TABLE &aLib, int aRow, int aOther)
wxString m_tableError
Why the table could not be read, shown on the table warning icon.
void OnMoveLibraryUp(wxCommandEvent &aEvent) override
bool chooseSavePath()
Asks where to save a library that has no file yet; false if the user cancelled.
const DATABASE_FIELD_MAPPING * fieldForColumn(const std::string &aColumn)
std::map< std::string, std::string > m_autoFields
void OnTestConnection(wxCommandEvent &aEvent) override
std::vector< FIELD_WARNING_ICON * > m_roleIcons
One per entry of m_roles.
SCH_IO_DATABASE::PREVIEW_ISSUES m_previewIssues
Problems found while building the preview symbols, shown on the icons of the fields they came from.
void updateColumnWarnings()
Marks each column field for a missing key, a column the table does not have, keys that repeat in the ...
std::vector< DATABASE_CONNECTION::DATA_SOURCE > m_dataSources
void OnMoveFieldDown(wxCommandEvent &aEvent) override
std::map< wxComboBox *, std::string > m_autoRoles
CLOSE_ACTION confirmClose()
Asks what to do with unsaved edits before the dialog closes without OK.
void OnSourceTypeChanged(wxCommandEvent &aEvent) override
std::unique_ptr< DATABASE_CONNECTION > m_conn
void OnRemoveLibrary(wxCommandEvent &aEvent) override
void showInfo(const wxString &aMessage, int aIcon)
Replaces the info bar message, which can be dismissed.
void OnTableChanged(wxCommandEvent &aEvent) override
void OnManageDataSources(wxCommandEvent &aEvent) override
std::string m_autoTable
Autofilled values for the current library, which a later autofill may replace.
void OnSettingChanged(wxCommandEvent &aEvent) override
std::shared_ptr< CONNECT_JOB > m_connectJob
const std::vector< std::string > & columnsFor(const std::string &aTable)
bool validateLibraries()
Shows the first problem that stops the settings from being saved.
void OnRemoveField(wxCommandEvent &aEvent) override
std::string m_sampleTable
Rows of m_sampleTable shared by the Data page and the preview.
void OnDuplicateLibrary(wxCommandEvent &aEvent) override
void OnLibrarySelected(wxCommandEvent &aEvent) override
void OnFieldCellChanged(wxGridEvent &aEvent) override
void cancelConnect()
Abandons a connection attempt in progress, whose result is then discarded.
std::unique_ptr< DATABASE_LIB_SETTINGS > m_settings
void setConnectionStatus(const wxString &aText, bool aError, const wxString &aDetails=wxEmptyString)
Shows a one-line status; the full driver report goes in the tooltip.
void OnDataCellSelected(wxGridEvent &aEvent) override
void OnSettingSpinChanged(wxSpinEvent &aEvent) override
std::string settingsSnapshot()
Every setting as the file would hold it, plus where the password is kept.
void EndDialogShim(int aReturnCode)
A mode agnostic way to close a dialog.
The base frame for deriving all KiCad main window classes.
The base class for create windows for drawing purpose.
virtual EDA_DRAW_PANEL_GAL * GetCanvas() const
Return a pointer to GAL-based canvas of given EDA draw frame.
GAL_TYPE GetBackend() const
Return the type of backend currently used by GAL canvas.
@ GAL_TYPE_OPENGL
OpenGL implementation.
static GAL_TYPE ResolveStoredCanvasType(int aStoredCanvasType)
Convert a stored canvas type setting into a backend that can actually be used.
A warning icon drawn inside the right edge of a field, left of its dropdown button when it has one,...
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:57
static std::unique_ptr< DATABASE_CONNECTION > CreateConnection(const DATABASE_LIB_SETTINGS &aSettings, bool aConnectNow=true, const wxString &aBaseDir=wxEmptyString)
Opens a connection described by aSettings, expanding ${CWD} in a connection string to the directory h...
static wxString SymbolNameForRow(const DATABASE_LIB_TABLE &aTable, bool aGloballyUniqueKeys, const DATABASE_CONNECTION::ROW &aRow, std::string *aRawKey=nullptr)
static std::vector< std::unique_ptr< LIB_SYMBOL > > BuildPreviewSymbols(SYMBOL_LIBRARY_ADAPTER *aLibs, const DATABASE_LIB_TABLE &aTable, bool aGloballyUniqueKeys, const std::vector< DATABASE_CONNECTION::ROW > &aRows, PREVIEW_ISSUES *aIssues)
Builds symbols from already-fetched rows exactly as the library would name and load them,...
RAII class that sets an value at construction and resets it to the original value at destruction.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:274
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 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.
wxString DefaultDatabaseLibFileName(const wxString &aLibName)
std::unique_ptr< DATABASE_LIB_SETTINGS > LoadDatabaseLibSettingsReadOnly(const wxString &aPath, bool *aLoaded)
Loads a database library settings file without allowing the load itself (e.g.
bool StoreDatabasePassword(const DATABASE_SOURCE &aSource, const std::string &aPassword)
Stores the password for the source's DSN login in the system keyring, or removes the entry when the p...
bool GetDatabasePassword(const DATABASE_SOURCE &aSource, std::string &aPassword, const DATABASE_SECRET_LOOKUP &aLookup)
DSN passwords that are not saved in the (often shared) library file live in the system keyring,...
static DB_AUTOFILL::ROLE autofillRole(const std::string &aChoiceId, const std::vector< wxString > &aTerms)
static DATABASE_FIELD_MAPPING * findField(DATABASE_LIB_TABLE &aLib, const char *aName)
static bool isAutofilled(const MAP &aAutofilled, const typename MAP::key_type &aKey, const std::string &aValue)
True if autofill wrote aValue for aKey and the user has not changed it since.
static constexpr int PREVIEW_DEBOUNCE_MS
Delay after the last edit before the preview is rebuilt.
static void relayoutPage(wxScrolledWindow *aPage)
Re-lays out a scrolled page and refits its scroll area after its contents change size.
static constexpr int PREVIEW_MIN_WIDTH_DIP
Narrowest the preview next to the settings can be before its tree columns are cut off.
static wxString displayName(const DATABASE_LIB_TABLE &aTable)
static constexpr size_t PREVIEW_ROW_LIMIT
Rows read for the Data page and preview; enough to judge a mapping without loading a whole table.
static std::string fieldChoiceId(const char *aFieldName)
static wxString describeFailure(const DATABASE_CONNECTION &aConn, const DATABASE_SOURCE &aSource)
static const std::vector< AUTOFILL_FIELD > & autofillFields()
static bool empty(const wxTextEntryBase *aCtrl)
const int minSize
Push and Shove router track width and via size dialog.
#define _(s)
static const std::string DatabaseLibraryFileExtension
Some functions to handle hotkeys in KiCad.
std::string source
std::string message
static wxString From_UTF8(const char *cstring)
Convert a UTF8 encoded C string to a wxString for all wxWidgets build modes.
Definition idf_helpers.h:44
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
std::vector< std::string > AssignColumns(const std::vector< std::string > &aColumns, const std::vector< ROLE > &aRoles)
Chooses a column for each role, best scores first, so no column serves two roles.
void RememberChoice(std::vector< std::string > &aHistory, const std::string &aColumn)
Moves aColumn to the front of a role's history, dropping repeats and the oldest entries.
std::string MatchTable(const std::vector< std::string > &aTables, const std::string &aLibraryName)
std::map< std::string, size_t > RepeatCounts(const std::vector< std::map< std::string, std::any > > &aRows)
Counts, per column, the rows whose value already appeared in an earlier row, with NULL counted as emp...
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:640
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
#define _HKI(x)
Definition page_info.cpp:40
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
T * GetAppSettings(const char *aFilename)
KIWAY Kiway(KFCTL_STANDALONE)
A symbol field that autofill maps from a like-named column.
std::vector< wxString > terms
A data source configured in the ODBC driver manager.
bool visible_in_chooser
Whether the column is shown by default in the chooser.
std::string column
Database column name.
std::string name
KiCad field name.
void SetName(const std::string &aName)
bool inherit_properties
Whether or not to inherit properties from symbol field.
bool visible_on_add
Whether to show the field when placing the symbol.
bool show_name
Whether or not to show the field name as well as its value.
A database library table will be mapped to a sub-library provided by the database library entry in th...
std::string key_col
Unique key column name (will form part of the LIB_ID)
std::string name
KiCad library nickname (will form part of the LIB_ID)
std::vector< DATABASE_FIELD_MAPPING > fields
std::string table
Database table to pull content from.
std::string connection_string
One mapping to fill, such as the key column or the Value field.
std::vector< std::string > remembered
Columns the user chose before, most recent first.
std::vector< wxString > terms
Words to look for, in English and translated.
One column mapping on the Libraries page: its combo, its setting and its Data page label.
std::string &(* value)(DATABASE_LIB_TABLE &)
const char * autofillId
Key of the remembered column choices for this role.
A problem found while previewing, tagged with the setting that caused it.
netlist clear()
VECTOR2I end
PCB_VIA::VIA_PARAMETER_ERROR::FIELD FIELD
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
wxString AddFileExtListToFilter(const std::vector< std::string > &aExts)
Build the wildcard extension file dialog wildcard filter to add to the base message dialog.
Definition of file extensions used in Kicad.