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{
93 _( "Edit Options" ),
94 _( "Edit options for this library entry" ) );
95
96 menu.AppendSeparator();
97
98 bool showActivate = false;
99 bool showDeactivate = false;
100 bool showSetVisible = false;
101 bool showUnsetVisible = false;
102 LIB_TABLE_GRID_DATA_MODEL* tbl = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( m_grid->GetTable() );
103
104 // Ensure selection parameters are up to date
106
107 for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
108 {
109 if( tbl->GetValueAsBool( row, 0 ) )
110 showDeactivate = true;
111 else
112 showActivate = true;
113
114 if( tbl->GetValueAsBool( row, 1 ) )
115 showUnsetVisible = true;
116 else
117 showSetVisible = true;
118
119 if( showActivate && showDeactivate && showSetVisible && showUnsetVisible )
120 break;
121 }
122
123 if( showActivate )
124 menu.Append( LIB_TABLE_GRID_TRICKS_ACTIVATE_SELECTED, _( "Activate Selected" ) );
125
126 if( showDeactivate )
127 menu.Append( LIB_TABLE_GRID_TRICKS_DEACTIVATE_SELECTED, _( "Deactivate Selected" ) );
128
130 {
131 if( showSetVisible )
132 menu.Append( LIB_TABLE_GRID_TRICKS_SET_VISIBLE, _( "Set Visible Flag" ) );
133
134 if( showUnsetVisible )
135 menu.Append( LIB_TABLE_GRID_TRICKS_UNSET_VISIBLE, _( "Unset Visible Flag" ) );
136 }
137
138 bool showSettings = false;
139 bool showOpen = false;
140
141 if( LIBRARY_MANAGER_ADAPTER* adapter = tbl->Adapter() )
142 {
143 wxString nickname = tbl->GetValue( m_sel_row_start, COL_NICKNAME );
144
145 if( m_sel_row_count == 1 && adapter->SupportsConfigurationDialog( nickname ) )
146 {
147 showSettings = true;
148 menu.Append( LIB_TABLE_GRID_TRICKS_OPEN_TABLE, _( "Edit Settings" ) );
149 }
150
151 std::optional<LIBRARY_TABLE_ROW*> row = adapter->GetRow( nickname );
152
153 if( row.has_value() && row.value()->Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
154 {
155 showOpen = true;
156 menu.Append( LIB_TABLE_GRID_TRICKS_LIBRARY_SETTINGS, _( "Open Library Table" ) );
157 }
158 }
159
160 if( showActivate || showDeactivate || showSetVisible || showUnsetVisible || showSettings || showOpen )
161 menu.AppendSeparator();
162
163 GRID_TRICKS::showPopupMenu( menu, aEvent );
164}
165
166
167void LIB_TABLE_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
168{
169 int menu_id = event.GetId();
171
173 {
174 optionsEditor( m_grid->GetGridCursorRow() );
175 }
178 {
179 bool selected_state = menu_id == LIB_TABLE_GRID_TRICKS_ACTIVATE_SELECTED;
180
181 for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
182 tbl->SetValueAsBool( row, 0, selected_state );
183
184 // Ensure the new state (on/off) of the widgets is immediately shown:
185 m_grid->Refresh();
186 }
187 else if( menu_id == LIB_TABLE_GRID_TRICKS_SET_VISIBLE
189 {
190 bool selected_state = menu_id == LIB_TABLE_GRID_TRICKS_SET_VISIBLE;
191
192 for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
193 tbl->SetValueAsBool( row, 1, 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_LIBRARY_SETTINGS )
199 {
200 // TODO(JE) library tables
201#if 0
202 LIB_TABLE_ROW* row = tbl->At( m_sel_row_start );
203 row->Refresh();
204 row->ShowSettingsDialog( m_grid->GetParent() );
205#endif
206 }
207 else if( menu_id == LIB_TABLE_GRID_TRICKS_OPEN_TABLE )
208 {
209 openTable( tbl->At( m_sel_row_start ) );
210 }
211 else
212 {
214 }
215}
216
217
219{
220 if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'V' && m_grid->IsCellEditControlShown() )
221 {
222 wxLogNull doNotLog;
223
224 if( wxTheClipboard->Open() )
225 {
226 if( wxTheClipboard->IsSupported( wxDF_TEXT ) || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
227 {
228 wxTextDataObject data;
229 wxTheClipboard->GetData( data );
230
231 wxString text = data.GetText();
232
233 if( !text.Contains( '\t' ) && text.Contains( ',' ) )
234 text.Replace( ',', '\t' );
235
236 if( text.Contains( '\t' ) || text.Contains( '\n' ) || text.Contains( '\r' ) )
237 {
238 m_grid->CancelPendingChanges();
239 int row = m_grid->GetGridCursorRow();
240
241 // Check if the current row already has data (has a nickname)
242 wxGridTableBase* table = m_grid->GetTable();
243 if( table && row >= 0 && row < table->GetNumberRows() )
244 {
245 // Check if the row has a nickname (indicating it has existing data)
246 wxString nickname = table->GetValue( row, COL_NICKNAME );
247 if( !nickname.IsEmpty() )
248 {
249 // Row already has data, don't allow pasting over it
250 wxTheClipboard->Close();
251 wxBell(); // Provide audio feedback
252 return;
253 }
254 }
255
256 m_grid->ClearSelection();
257 m_grid->SelectRow( row );
258 m_grid->SetGridCursor( row, 0 );
260 paste_text( text );
261 wxTheClipboard->Close();
262 m_grid->ForceRefresh();
263 return;
264 }
265 }
266
267 wxTheClipboard->Close();
268 }
269 }
270
272}
273
274
275/*
276 * Handle specialized clipboard text, either s-expr syntax starting with a lib table preamble
277 * (such as "(fp_lib_table"), or spreadsheet formatted text.
278 */
279void LIB_TABLE_GRID_TRICKS::paste_text( const wxString& cb_text )
280{
281 LIB_TABLE_GRID_DATA_MODEL* tbl = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( m_grid->GetTable() );
282
283 if( size_t ndx = cb_text.find( getTablePreamble() ); ndx != std::string::npos )
284 {
285 // paste the LIB_TABLE_ROWs of s-expr, starting at column 0 regardless of current cursor column.
286
287 if( LIBRARY_TABLE tempTable( cb_text, tbl->Table().Scope() ); tempTable.IsOk() )
288 {
289 std::ranges::copy( tempTable.Rows(),
290 std::inserter( tbl->Table().Rows(), tbl->Table().Rows().begin() ) );
291
292 if( tbl->GetView() )
293 {
294 wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, 0, 0 );
295 tbl->GetView()->ProcessTableMessage( msg );
296 }
297 }
298 else
299 {
300 DisplayError( wxGetTopLevelParent( m_grid ), tempTable.ErrorDescription() );
301 }
302 }
303 else
304 {
305 wxString text = cb_text;
306
307 if( !text.Contains( '\t' ) && text.Contains( ',' ) )
308 text.Replace( ',', '\t' );
309
310 if( text.Contains( '\t' ) )
311 {
312 int row = m_grid->GetGridCursorRow();
313 m_grid->ClearSelection();
314 m_grid->SelectRow( row );
315 m_grid->SetGridCursor( row, 0 );
317 }
318
320
321 m_grid->AutoSizeColumns( false );
322 }
323
324 m_grid->AutoSizeColumns( false );
325}
326
327
329{
330 if( aEvent.GetCol() == COL_OPTIONS )
331 {
332 optionsEditor( aEvent.GetRow() );
333 return true;
334 }
335
336 return false;
337}
338
339
341{
342 aGrid->OnAddRow(
343 [&]() -> std::pair<int, int>
344 {
345 aGrid->AppendRows( 1 );
346 return { aGrid->GetNumberRows() - 1, COL_NICKNAME };
347 } );
348}
349
350
352{
353 if( !aGrid->CommitPendingChanges() )
354 return;
355
356 wxGridUpdateLocker noUpdates( aGrid );
357
358 int curRow = aGrid->GetGridCursorRow();
359 int curCol = aGrid->GetGridCursorCol();
360
361 // In a wxGrid, collect rows that have a selected cell, or are selected
362 // It is not so easy: it depends on the way the selection was made.
363 // Here, we collect rows selected by clicking on a row label, and rows that contain
364 // previously-selected cells.
365 // If no candidate, just delete the row with the grid cursor.
366 wxArrayInt selectedRows = aGrid->GetSelectedRows();
367 wxGridCellCoordsArray cells = aGrid->GetSelectedCells();
368 wxGridCellCoordsArray blockTopLeft = aGrid->GetSelectionBlockTopLeft();
369 wxGridCellCoordsArray blockBotRight = aGrid->GetSelectionBlockBottomRight();
370
371 // Add all row having cell selected to list:
372 for( unsigned ii = 0; ii < cells.GetCount(); ii++ )
373 selectedRows.Add( cells[ii].GetRow() );
374
375 // Handle block selection
376 if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() )
377 {
378 for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i )
379 selectedRows.Add( i );
380 }
381
382 // Use the row having the grid cursor only if we have no candidate:
383 if( selectedRows.size() == 0 && aGrid->GetGridCursorRow() >= 0 )
384 selectedRows.Add( aGrid->GetGridCursorRow() );
385
386 if( selectedRows.size() == 0 )
387 {
388 wxBell();
389 return;
390 }
391
392 std::sort( selectedRows.begin(), selectedRows.end() );
393
394 // Remove selected rows (note: a row can be stored more than once in list)
395 int last_row = -1;
396
397 // Needed to avoid a wxWidgets alert if the row to delete is the last row
398 // at least on wxMSW 3.2
399 aGrid->ClearSelection();
400
401 for( int ii = selectedRows.GetCount()-1; ii >= 0; ii-- )
402 {
403 int row = selectedRows[ii];
404
405 if( row != last_row )
406 {
407 last_row = row;
408 aGrid->DeleteRows( row, 1 );
409 }
410 }
411
412 if( aGrid->GetNumberRows() > 0 && curRow >= 0 )
413 aGrid->SetGridCursor( std::min( curRow, aGrid->GetNumberRows() - 1 ), curCol );
414}
415
416
418{
419 aGrid->OnMoveRowUp(
420 [&]( int row )
421 {
422 LIB_TABLE_GRID_DATA_MODEL* tbl = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
423 int curRow = aGrid->GetGridCursorRow();
424
425 std::vector<LIBRARY_TABLE_ROW>& rows = tbl->Table().Rows();
426
427 auto current = rows.begin() + curRow;
428 auto prev = rows.begin() + curRow - 1;
429
430 std::iter_swap( current, prev );
431
432 // Update the wxGrid
433 wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, row - 1, 0 );
434 tbl->GetView()->ProcessTableMessage( msg );
435 } );
436}
437
438
440{
441 aGrid->OnMoveRowDown(
442 [&]( int row )
443 {
444 LIB_TABLE_GRID_DATA_MODEL* tbl = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
445 int curRow = aGrid->GetGridCursorRow();
446 std::vector<LIBRARY_TABLE_ROW>& rows = tbl->Table().Rows();
447
448 auto current = rows.begin() + curRow;
449 auto next = rows.begin() + curRow + 1;
450
451 std::iter_swap( current, next );
452
453 // Update the wxGrid
454 wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, row, 0 );
455 tbl->GetView()->ProcessTableMessage( msg );
456 } );
457}
458
459
460bool LIB_TABLE_GRID_TRICKS::VerifyTable( WX_GRID* aGrid, std::function<void( int aRow, int aCol )> aErrorHandler )
461{
462 wxWindow* topLevelParent = wxGetTopLevelParent( aGrid );
463 LIB_TABLE_GRID_DATA_MODEL* model = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
464 wxString msg;
465
466 for( int r = 0; r < model->GetNumberRows(); )
467 {
468 wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
469 wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim();
470 unsigned illegalCh = 0;
471
472 if( !nick || !uri )
473 {
474 if( !nick && !uri )
475 msg = _( "A library table row nickname and path cells are empty." );
476 else if( !nick )
477 msg = _( "A library table row nickname cell is empty." );
478 else
479 msg = _( "A library table row path cell is empty." );
480
481 wxMessageDialog badCellDlg( topLevelParent, msg, _( "Invalid Row Definition" ),
482 wxYES_NO | wxCENTER | wxICON_QUESTION | wxYES_DEFAULT );
483 badCellDlg.SetExtendedMessage( _( "Empty cells will result in all rows that are "
484 "invalid to be removed from the table." ) );
485 badCellDlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Remove Invalid Cells" ) ),
486 wxMessageDialog::ButtonLabel( _( "Cancel Table Update" ) ) );
487
488 if( badCellDlg.ShowModal() == wxID_NO )
489 return false;
490
491 // Delete the "empty" row, where empty means missing nick or uri.
492 // This also updates the UI which could be slow, but there should only be a few
493 // rows to delete, unless the user fell asleep on the Add Row
494 // button.
495 model->DeleteRows( r, 1 );
496 }
497 else if( ( illegalCh = LIB_ID::FindIllegalLibraryNameChar( nick ) ) )
498 {
499 msg = wxString::Format( _( "Illegal character '%c' in nickname '%s'." ),
500 illegalCh,
501 nick );
502
503 aErrorHandler( r, 1 );
504
505 wxMessageDialog errdlg( topLevelParent, msg, _( "Library Nickname Error" ) );
506 errdlg.ShowModal();
507 return false;
508 }
509 else
510 {
511 // set the trimmed values back into the table so they get saved to disk.
512 model->SetValue( r, COL_NICKNAME, nick );
513 model->SetValue( r, COL_URI, uri );
514
515 // Make sure to not save a hidden flag
516 model->SetValue( r, COL_VISIBLE, wxS( "1" ) );
517
518 ++r; // this row was OK.
519 }
520 }
521
522 // check for duplicate nickNames
523 for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 )
524 {
525 wxString nick1 = model->GetValue( r1, COL_NICKNAME );
526
527 for( int r2 = r1 + 1; r2 < model->GetNumberRows(); ++r2 )
528 {
529 wxString nick2 = model->GetValue( r2, COL_NICKNAME );
530
531 if( nick1 == nick2 )
532 {
533 msg = wxString::Format( _( "Multiple libraries cannot share the same nickname ('%s')." ), nick1 );
534
535 // go to the lower of the two rows, it is technically the duplicate:
536 aErrorHandler( r2, 1 );
537
538 wxMessageDialog errdlg( topLevelParent, msg, _( "Library Nickname Error" ) );
539 errdlg.ShowModal();
540 return false;
541 }
542 }
543 }
544
545 return true;
546}
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
LIBRARY_TABLE_SCOPE Scope() const
bool IsOk() const
const std::vector< LIBRARY_TABLE_ROW > & Rows() const
static unsigned FindIllegalLibraryNameChar(const UTF8 &aLibraryName)
Looks for characters that are illegal in library nicknames.
Definition lib_id.cpp:241
This abstract base class mixes any object derived from #LIB_TABLE into wxGridTableBase so the result ...
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
bool DeleteRows(size_t aPos, size_t aNumRows) override
LIBRARY_TABLE_ROW & At(size_t aIndex)
void SetValue(int aRow, int aCol, const wxString &aValue) override
void onGridCellLeftClick(wxGridEvent &aEvent) override
virtual void openTable(const LIBRARY_TABLE_ROW &aRow)=0
virtual wxString getTablePreamble()=0
static void MoveUpHandler(WX_GRID *aGrid)
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 bool VerifyTable(WX_GRID *aGrid, std::function< void(int aRow, int aCol)> aErrorHandler)
virtual bool supportsVisibilityColumn()
static void AppendRowHandler(WX_GRID *aGrid)
void doPopupSelection(wxCommandEvent &event) override
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:762
void OnMoveRowDown(const std::function< void(int row)> &aMover)
Definition wx_grid.cpp:795
void OnAddRow(const std::function< std::pair< int, int >()> &aAdder)
Definition wx_grid.cpp:680
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition wx_grid.cpp:628
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:202
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:177
This file is part of the common library.
#define _(s)
CITER next(CITER it)
Definition ptree.cpp:124