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