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