KiCad PCB EDA Suite
Loading...
Searching...
No Matches
lib_table_grid_tricks.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21#include "confirm.h"
24#include <wx/clipbrd.h>
25#include <wx/log.h>
26#include <wx/msgdlg.h>
27#include <lib_id.h>
28
29
30static bool isGridReadOnly( WX_GRID* aGrid )
31{
32 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
33 return model && model->Table().IsReadOnly();
34}
35
36
38 GRID_TRICKS( aGrid )
39{
40 m_grid->Disconnect( wxEVT_CHAR_HOOK );
41 m_grid->Connect( wxEVT_CHAR_HOOK, wxCharEventHandler( LIB_TABLE_GRID_TRICKS::onCharHook ), nullptr, this );
42}
43
44
46 std::function<void( wxCommandEvent& )> aAddHandler ) :
47 GRID_TRICKS( aGrid, aAddHandler )
48{
49 m_grid->Disconnect( wxEVT_CHAR_HOOK );
50 m_grid->Connect( wxEVT_CHAR_HOOK, wxCharEventHandler( LIB_TABLE_GRID_TRICKS::onCharHook ), nullptr, this );
51}
52
53
55{
56 if( aEvent.GetCol() == COL_STATUS )
57 {
58 // Status column button action depends on row:
59 // Normal rows should have no button, so they are a no-op
60 // Errored rows should have the warning button, so we show their error
61 // Configurable libraries will have the options button, so we launch the config
62 // Chained tables will have the open button, so we request the table be opened
64 const LIBRARY_TABLE_ROW& row = model->at( aEvent.GetRow() );
65 LIBRARY_MANAGER_ADAPTER* adapter = model->Adapter();
66
67 wxString title = row.Type() == "Table"
68 ? wxString::Format( _( "Error loading library table '%s'" ), row.Nickname() )
69 : wxString::Format( _( "Error loading library '%s'" ), row.Nickname() );
70
71 // The settings dialog reports load errors itself and is where they get fixed
72 if( adapter->SupportsConfigurationDialog( row ) && !isGridReadOnly( m_grid ) )
73 {
74 if( adapter->ShowConfigurationDialog( row, model->Table().Scope(), wxGetTopLevelParent( m_grid ) )
75 != wxID_CANCEL )
76 {
77 model->OnModify();
78 }
79 }
80 else if( !row.IsOk() )
81 {
83 }
84 else if( std::optional<LIBRARY_ERROR> e = adapter->LibraryError( row.Nickname() ) )
85 {
86 DisplayErrorMessage( m_grid, title, e->message );
87 }
89 {
90 openTable( row );
91 }
92
93 aEvent.Skip();
94 }
95 else
96 {
98 }
99}
100
101
102void LIB_TABLE_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
103{
104 // Ensure selection parameters are up to date
106
107 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( m_grid->GetTable() );
108 const LIBRARY_TABLE_ROW& firstRow = model->at( m_sel_row_start );
109 bool readOnly = model->Table().IsReadOnly();
110
111 bool showSettings = false;
112 bool showOpen = false;
113
114 if( !readOnly )
115 {
116 if( LIBRARY_MANAGER_ADAPTER* adapter = model->Adapter() )
117 {
118 if( m_sel_row_count == 1 && adapter->SupportsConfigurationDialog( firstRow ) )
119 {
120 showSettings = true;
121 menu.Append( LIB_TABLE_GRID_TRICKS_LIBRARY_SETTINGS, _( "Edit Settings..." ) );
122 }
123 }
124 }
125
126 if( firstRow.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
127 {
128 showOpen = true;
129 menu.Append( LIB_TABLE_GRID_TRICKS_OPEN_TABLE, _( "Open Library Table" ) );
130 }
131
132 if( showSettings || showOpen )
133 menu.AppendSeparator();
134
135 bool showActivate = false;
136 bool showDeactivate = false;
137 bool showSetVisible = false;
138 bool showUnsetVisible = false;
139 bool showOptions = false;
140
141 for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
142 {
143 if( model->GetValueAsBool( row, COL_ENABLED ) )
144 showDeactivate = true;
145 else
146 showActivate = true;
147
148 if( model->GetValueAsBool( row, COL_VISIBLE ) )
149 showUnsetVisible = true;
150 else
151 showSetVisible = true;
152
153 if( showActivate && showDeactivate && showSetVisible && showUnsetVisible )
154 break;
155 }
156
157 if( showActivate )
158 menu.Append( LIB_TABLE_GRID_TRICKS_ACTIVATE_SELECTED, _( "Activate Selected" ) );
159
160 if( showDeactivate )
161 menu.Append( LIB_TABLE_GRID_TRICKS_DEACTIVATE_SELECTED, _( "Deactivate Selected" ) );
162
164 {
165 if( showSetVisible )
166 menu.Append( LIB_TABLE_GRID_TRICKS_SET_VISIBLE, _( "Set Visible Flag" ) );
167
168 if( showUnsetVisible )
169 menu.Append( LIB_TABLE_GRID_TRICKS_UNSET_VISIBLE, _( "Unset Visible Flag" ) );
170 }
171
172 if( !readOnly && m_sel_row_count == 1 && firstRow.Type() != LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
173 {
174 showOptions = true;
175 menu.Append( LIB_TABLE_GRID_TRICKS_OPTIONS_EDITOR, _( "Edit Options..." ),
176 _( "Edit options for this library entry" ) );
177 }
178
179 if( showActivate || showDeactivate || showSetVisible || showUnsetVisible || showOptions )
180 menu.AppendSeparator();
181
182 // For read-only tables, only show activate/deactivate and visible options (no cut/paste/delete)
183 if( !readOnly )
184 GRID_TRICKS::showPopupMenu( menu, aEvent );
185}
186
187
188void LIB_TABLE_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
189{
190 int menu_id = event.GetId();
191 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( m_grid->GetTable() );
192
194 {
195 optionsEditor( m_grid->GetGridCursorRow() );
196 }
199 {
200 bool selected_state = menu_id == LIB_TABLE_GRID_TRICKS_ACTIVATE_SELECTED;
201
202 for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
203 model->SetValueAsBool( row, COL_ENABLED, selected_state );
204
205 // Ensure the new state (on/off) of the widgets is immediately shown:
206 m_grid->Refresh();
207 }
208 else if( menu_id == LIB_TABLE_GRID_TRICKS_SET_VISIBLE
210 {
211 bool selected_state = menu_id == LIB_TABLE_GRID_TRICKS_SET_VISIBLE;
212
213 for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
214 model->SetValueAsBool( row, COL_VISIBLE, selected_state );
215
216 // Ensure the new state (on/off) of the widgets is immediately shown:
217 m_grid->Refresh();
218 }
219 else if( menu_id == LIB_TABLE_GRID_TRICKS_LIBRARY_SETTINGS )
220 {
221 LIBRARY_MANAGER_ADAPTER* adapter = model->Adapter();
223
224 if( adapter->ShowConfigurationDialog( row, model->Table().Scope(), wxGetTopLevelParent( m_grid ) )
225 != wxID_CANCEL )
226 {
227 model->OnModify();
228 }
229 }
230 else if( menu_id == LIB_TABLE_GRID_TRICKS_OPEN_TABLE )
231 {
233 }
234 else
235 {
237 }
238}
239
240
242{
243 if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'V' && m_grid->IsCellEditControlShown() )
244 {
245 wxLogNull doNotLog;
246
247 if( wxTheClipboard->Open() )
248 {
249 if( wxTheClipboard->IsSupported( wxDF_TEXT ) || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
250 {
251 wxTextDataObject data;
252 wxTheClipboard->GetData( data );
253
254 wxString text = data.GetText();
255
256 if( !text.Contains( '\t' ) && text.Contains( ',' ) )
257 text.Replace( ',', '\t' );
258
259 if( text.Contains( '\t' ) || text.Contains( '\n' ) || text.Contains( '\r' ) )
260 {
261 m_grid->CancelPendingChanges();
262 int row = m_grid->GetGridCursorRow();
263
264 // Check if the current row already has data (has a nickname)
265 wxGridTableBase* table = m_grid->GetTable();
266
267 if( table && row >= 0 && row < table->GetNumberRows() )
268 {
269 // Check if the row has a nickname (indicating it has existing data)
270 wxString nickname = table->GetValue( row, COL_NICKNAME );
271
272 if( !nickname.IsEmpty() )
273 {
274 // Row already has data, don't allow pasting over it
275 wxTheClipboard->Close();
276 wxBell(); // Provide audio feedback
277 return;
278 }
279 }
280
281 m_grid->ClearSelection();
282 m_grid->SelectRow( row );
283 m_grid->SetGridCursor( row, 0 );
285 paste_text( text );
286 wxTheClipboard->Close();
287 m_grid->ForceRefresh();
288 return;
289 }
290 }
291
292 wxTheClipboard->Close();
293 }
294 }
295
297}
298
299
300/*
301 * Handle specialized clipboard text, either s-expr syntax starting with a lib table preamble
302 * (such as "(fp_lib_table"), or spreadsheet formatted text.
303 */
304void LIB_TABLE_GRID_TRICKS::paste_text( const wxString& cb_text )
305{
306 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( m_grid->GetTable() );
307
308 if( model->Table().IsReadOnly() )
309 {
310 wxBell();
311 return;
312 }
313
314 if( size_t ndx = cb_text.find( getTablePreamble() ); ndx != std::string::npos )
315 {
316 // paste the LIB_TABLE_ROWs of s-expr, starting at column 0 regardless of current cursor column.
317
318 if( LIBRARY_TABLE tempTable( true, cb_text, model->Table().Scope() ); tempTable.IsOk() )
319 {
320 std::ranges::copy( tempTable.Rows(),
321 std::inserter( model->Table().Rows(), model->Table().Rows().begin() ) );
322
323 if( model->GetView() )
324 {
325 wxGridTableMessage msg( model, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, 0, 0 );
326 model->GetView()->ProcessTableMessage( msg );
327 model->OnModify();
328 }
329 }
330 else
331 {
332 DisplayError( wxGetTopLevelParent( m_grid ), tempTable.ErrorDescription() );
333 }
334 }
335 else
336 {
337 wxString text = cb_text;
338
339 if( !text.Contains( '\t' ) && text.Contains( ',' ) )
340 text.Replace( ',', '\t' );
341
342 if( text.Contains( '\t' ) )
343 {
344 int row = m_grid->GetGridCursorRow();
345 m_grid->ClearSelection();
346 m_grid->SelectRow( row );
347 m_grid->SetGridCursor( row, 0 );
349 }
350
352 model->OnModify();
353
354 m_grid->AutoSizeColumns( false );
355 }
356
357 m_grid->AutoSizeColumns( false );
358}
359
360
362{
363 if( aEvent.GetCol() == COL_OPTIONS )
364 {
365 if( isGridReadOnly( m_grid ) )
366 {
367 wxBell();
368 return true;
369 }
370
371 optionsEditor( aEvent.GetRow() );
372 return true;
373 }
374
375 return false;
376}
377
378
379void LIB_TABLE_GRID_TRICKS::AppendRowHandler( WX_GRID* aGrid, const wxString& aType )
380{
381 if( isGridReadOnly( aGrid ) )
382 {
383 wxBell();
384 return;
385 }
386
387 aGrid->OnAddRow(
388 [&]() -> std::pair<int, int>
389 {
390 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
391
392 aGrid->AppendRows( 1 );
393 aGrid->SetCellValue( aGrid->GetNumberRows() - 1, COL_TYPE, aType );
394 model->OnModify();
395 return { aGrid->GetNumberRows() - 1, COL_NICKNAME };
396 } );
397}
398
399
401{
402 if( isGridReadOnly( aGrid ) )
403 {
404 wxBell();
405 return;
406 }
407
408 if( !aGrid->CommitPendingChanges() )
409 return;
410
411 wxGridUpdateLocker noUpdates( aGrid );
412
413 int curRow = aGrid->GetGridCursorRow();
414 int curCol = aGrid->GetGridCursorCol();
415
416 // In a wxGrid, collect rows that have a selected cell, or are selected
417 // It is not so easy: it depends on the way the selection was made.
418 // Here, we collect rows selected by clicking on a row label, and rows that contain
419 // previously-selected cells.
420 // If no candidate, just delete the row with the grid cursor.
421 wxArrayInt selectedRows = aGrid->GetSelectedRows();
422 wxGridCellCoordsArray cells = aGrid->GetSelectedCells();
423 wxGridCellCoordsArray blockTopLeft = aGrid->GetSelectionBlockTopLeft();
424 wxGridCellCoordsArray blockBotRight = aGrid->GetSelectionBlockBottomRight();
425
426 // Add all row having cell selected to list:
427 for( unsigned ii = 0; ii < cells.GetCount(); ii++ )
428 selectedRows.Add( cells[ii].GetRow() );
429
430 // Handle block selection
431 if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() )
432 {
433 for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i )
434 selectedRows.Add( i );
435 }
436
437 // Use the row having the grid cursor only if we have no candidate:
438 if( selectedRows.size() == 0 && aGrid->GetGridCursorRow() >= 0 )
439 selectedRows.Add( aGrid->GetGridCursorRow() );
440
441 if( selectedRows.size() == 0 )
442 {
443 wxBell();
444 return;
445 }
446
447 std::sort( selectedRows.begin(), selectedRows.end() );
448
449 // Remove selected rows (note: a row can be stored more than once in list)
450 int last_row = -1;
451
452 // Needed to avoid a wxWidgets alert if the row to delete is the last row
453 // at least on wxMSW 3.2
454 aGrid->ClearSelection();
455
456 for( int ii = selectedRows.GetCount()-1; ii >= 0; ii-- )
457 {
458 int row = selectedRows[ii];
459
460 if( row != last_row )
461 {
462 last_row = row;
463 aGrid->DeleteRows( row, 1 );
464 static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() )->OnModify();
465 }
466 }
467
468 if( aGrid->GetNumberRows() > 0 && curRow >= 0 )
469 aGrid->SetGridCursor( std::min( curRow, aGrid->GetNumberRows() - 1 ), curCol );
470}
471
472
474{
475 if( isGridReadOnly( aGrid ) )
476 {
477 wxBell();
478 return;
479 }
480
481 aGrid->OnMoveRowUp(
482 [&]( int row )
483 {
484 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
485 int curRow = aGrid->GetGridCursorRow();
486 std::deque<LIBRARY_TABLE_ROW>& rows = model->Table().Rows();
487
488 auto current = rows.begin() + curRow;
489 auto prev = rows.begin() + curRow - 1;
490
491 std::iter_swap( current, prev );
492
493 // Update the wxGrid
494 wxGridTableMessage msg( model, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, row - 1, 0 );
495 model->GetView()->ProcessTableMessage( msg );
496 model->OnModify();
497 } );
498}
499
500
502{
503 if( isGridReadOnly( aGrid ) )
504 {
505 wxBell();
506 return;
507 }
508
509 aGrid->OnMoveRowDown(
510 [&]( int row )
511 {
512 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
513 int curRow = aGrid->GetGridCursorRow();
514 std::deque<LIBRARY_TABLE_ROW>& rows = model->Table().Rows();
515
516 auto current = rows.begin() + curRow;
517 auto next = rows.begin() + curRow + 1;
518
519 std::iter_swap( current, next );
520
521 // Update the wxGrid
522 wxGridTableMessage msg( model, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, row, 0 );
523 model->GetView()->ProcessTableMessage( msg );
524 model->OnModify();
525 } );
526}
527
528
529bool LIB_TABLE_GRID_TRICKS::VerifyTable( WX_GRID* aGrid, bool aSupportsVisibilityColumn,
530 std::function<void( int aRow, int aCol )> aErrorHandler )
531{
532 wxWindow* topLevelParent = wxGetTopLevelParent( aGrid );
533 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
534 wxString msg;
535
536 for( int r = 0; r < model->GetNumberRows(); )
537 {
538 wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
539 wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim();
540 unsigned illegalCh = 0;
541
542 if( !uri )
543 {
544 // Silently nuke rows that have no libraray URI
545 model->DeleteRows( r, 1 );
546 }
547 else if( !nick || ( illegalCh = LIB_ID::FindIllegalLibraryNameChar( nick ) ) )
548 {
549 if( !nick )
550 msg = _( "Library must have a nickname." );
551 else
552 msg = wxString::Format( _( "Illegal character '%c' in nickname '%s'." ), illegalCh, nick );
553
554 aErrorHandler( r, COL_NICKNAME );
555
556 KICAD_MESSAGE_DIALOG errdlg( topLevelParent, msg, _( "Library Nickname Error" ) );
557 errdlg.ShowModal();
558 return false;
559 }
560 else
561 {
562 // set the trimmed values back into the table so they get saved to disk.
563 model->SetValue( r, COL_NICKNAME, nick );
564 model->SetValue( r, COL_URI, uri );
565
566 if( !aSupportsVisibilityColumn )
567 {
568 // Make sure to not save an inappropriate hidden flag
569 model->SetValue( r, COL_VISIBLE, wxS( "1" ) );
570 }
571
572 ++r; // this row was OK.
573 }
574 }
575
576 // check for duplicate nickNames
577 for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 )
578 {
579 wxString nick1 = model->GetValue( r1, COL_NICKNAME );
580
581 for( int r2 = r1 + 1; r2 < model->GetNumberRows(); ++r2 )
582 {
583 wxString nick2 = model->GetValue( r2, COL_NICKNAME );
584
585 if( nick1 == nick2 )
586 {
587 msg = wxString::Format( _( "Multiple libraries cannot share the same nickname ('%s')." ), nick1 );
588
589 // go to the lower of the two rows, it is technically the duplicate:
590 aErrorHandler( r2, 1 );
591
592 KICAD_MESSAGE_DIALOG errdlg( topLevelParent, msg, _( "Library Nickname Error" ) );
593 errdlg.ShowModal();
594 return false;
595 }
596 }
597 }
598
599 return true;
600}
virtual void paste_text(const wxString &cb_text)
void getSelectedArea()
Puts the selected area into a sensible rectangle of m_sel_{row,col}_{start,count} above.
GRID_TRICKS(WX_GRID *aGrid)
virtual void doPopupSelection(wxCommandEvent &event)
virtual void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent)
int m_sel_row_start
WX_GRID * m_grid
I don't own the grid, but he owns me.
int m_sel_row_count
virtual void onGridCellLeftClick(wxGridEvent &event)
void onCharHook(wxKeyEvent &event)
The interface used by the classes that actually can load IO plugins for the different parts of KiCad ...
virtual std::optional< LIBRARY_ERROR > LibraryError(const wxString &aNickname) const
virtual bool SupportsConfigurationDialog(const LIBRARY_TABLE_ROW &aRow) const
Configuration works from the row itself, so libraries that failed to load or are not yet committed to...
virtual int ShowConfigurationDialog(const LIBRARY_TABLE_ROW &aRow, LIBRARY_TABLE_SCOPE aScope, wxWindow *aParent)
Shows the plugin's configuration dialog for a library table row.
const wxString & ErrorDescription() const
const wxString & Type() const
static const wxString TABLE_TYPE_NAME
bool IsOk() const
const wxString & Nickname() const
bool IsOk() const
static unsigned FindIllegalLibraryNameChar(const UTF8 &aLibraryName)
Looks for characters that are illegal in library nicknames.
Definition lib_id.cpp:228
This abstract base class mixes any object derived from LIBRARY_TABLE into wxGridTableBase so the resu...
void onGridCellLeftClick(wxGridEvent &aEvent) override
virtual void openTable(const LIBRARY_TABLE_ROW &aRow)=0
virtual wxString getTablePreamble()=0
static void MoveUpHandler(WX_GRID *aGrid)
virtual bool supportsVisibilityColumn()=0
void paste_text(const wxString &cb_text) override
bool handleDoubleClick(wxGridEvent &aEvent) override
LIB_TABLE_GRID_TRICKS(WX_GRID *aGrid)
static void DeleteRowHandler(WX_GRID *aGrid)
virtual void optionsEditor(int aRow)=0
static void AppendRowHandler(WX_GRID *aGrid, const wxString &aType)
void doPopupSelection(wxCommandEvent &event) override
static bool VerifyTable(WX_GRID *aGrid, bool aSupportsVisibilityColumn, std::function< void(int aRow, int aCol)> aErrorHandler)
void onCharHook(wxKeyEvent &ev)
static void MoveDownHandler(WX_GRID *aGrid)
void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent) override
void OnMoveRowUp(const std::function< void(int row)> &aMover)
Definition wx_grid.cpp:926
void OnMoveRowDown(const std::function< void(int row)> &aMover)
Definition wx_grid.cpp:959
void OnAddRow(const std::function< std::pair< int, int >()> &aAdder)
Definition wx_grid.cpp:819
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition wx_grid.cpp:767
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
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.
#define KICAD_MESSAGE_DIALOG
Definition confirm.h:48
#define _(s)
static bool isGridReadOnly(WX_GRID *aGrid)
CITER next(CITER it)
Definition ptree.cpp:120
KIBIS_MODEL * model