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