KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 (C) 2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
5 * Copyright (C) 2012-2022 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <grid_tricks.h>
26#include <wx/defs.h>
27#include <wx/event.h>
28#include <wx/tokenzr.h>
29#include <wx/clipbrd.h>
30#include <wx/log.h>
31#include <wx/stc/stc.h>
33
34
35// It works for table data on clipboard for an Excel spreadsheet,
36// why not us too for now.
37#define COL_SEP wxT( '\t' )
38#define ROW_SEP wxT( '\n' )
39#define ROW_SEP_R wxT( '\r' )
40
41
43 m_grid( aGrid ),
44 m_addHandler( []( wxCommandEvent& ) {} )
45{
46 init();
47}
48
49
50GRID_TRICKS::GRID_TRICKS( WX_GRID* aGrid, std::function<void( wxCommandEvent& )> aAddHandler ) :
51 m_grid( aGrid ),
52 m_addHandler( aAddHandler )
53{
54 init();
55}
56
57
59{
64
65 m_grid->Connect( wxEVT_GRID_CELL_LEFT_CLICK,
66 wxGridEventHandler( GRID_TRICKS::onGridCellLeftClick ), nullptr, this );
67 m_grid->Connect( wxEVT_GRID_CELL_LEFT_DCLICK,
68 wxGridEventHandler( GRID_TRICKS::onGridCellLeftDClick ), nullptr, this );
69 m_grid->Connect( wxEVT_GRID_CELL_RIGHT_CLICK,
70 wxGridEventHandler( GRID_TRICKS::onGridCellRightClick ), nullptr, this );
71 m_grid->Connect( wxEVT_GRID_LABEL_RIGHT_CLICK,
72 wxGridEventHandler( GRID_TRICKS::onGridLabelRightClick ), nullptr, this );
73 m_grid->Connect( wxEVT_GRID_LABEL_LEFT_CLICK,
74 wxGridEventHandler( GRID_TRICKS::onGridLabelLeftClick ), nullptr, this );
75 m_grid->Connect( GRIDTRICKS_FIRST_ID, GRIDTRICKS_LAST_ID, wxEVT_COMMAND_MENU_SELECTED,
76 wxCommandEventHandler( GRID_TRICKS::onPopupSelection ), nullptr, this );
77 m_grid->Connect( wxEVT_CHAR_HOOK,
78 wxCharEventHandler( GRID_TRICKS::onCharHook ), nullptr, this );
79 m_grid->Connect( wxEVT_KEY_DOWN,
80 wxKeyEventHandler( GRID_TRICKS::onKeyDown ), nullptr, this );
81 m_grid->Connect( wxEVT_UPDATE_UI,
82 wxUpdateUIEventHandler( GRID_TRICKS::onUpdateUI ), nullptr, this );
83
84 // The handlers that control the tooltips must be on the actual grid window, not the grid
85 m_grid->GetGridWindow()->Connect( wxEVT_MOTION,
86 wxMouseEventHandler( GRID_TRICKS::onGridMotion ), nullptr,
87 this );
88}
89
90
91bool GRID_TRICKS::isTextEntry( int aRow, int aCol )
92{
93 wxGridCellEditor* editor = m_grid->GetCellEditor( aRow, aCol );
94 bool retval = ( dynamic_cast<wxTextEntry*>( editor )
95 || dynamic_cast<GRID_CELL_STC_EDITOR*>( editor ) );
96
97 editor->DecRef();
98 return retval;
99}
100
101
102bool GRID_TRICKS::isCheckbox( int aRow, int aCol )
103{
104 wxGridCellRenderer* renderer = m_grid->GetCellRenderer( aRow, aCol );
105 bool retval = ( dynamic_cast<wxGridCellBoolRenderer*>( renderer ) );
106
107 renderer->DecRef();
108 return retval;
109}
110
111
112bool GRID_TRICKS::isReadOnly( int aRow, int aCol )
113{
114 return !m_grid->IsEditable() || m_grid->IsReadOnly( aRow, aCol );
115}
116
117
118bool GRID_TRICKS::toggleCell( int aRow, int aCol, bool aPreserveSelection )
119{
120 if( isCheckbox( aRow, aCol ) )
121 {
122 if( !aPreserveSelection )
123 {
124 m_grid->ClearSelection();
125 m_grid->SetGridCursor( aRow, aCol );
126 }
127
128 wxGridTableBase* model = m_grid->GetTable();
129
130 if( model->CanGetValueAs( aRow, aCol, wxGRID_VALUE_BOOL )
131 && model->CanSetValueAs( aRow, aCol, wxGRID_VALUE_BOOL ) )
132 {
133 model->SetValueAsBool( aRow, aCol, !model->GetValueAsBool( aRow, aCol ) );
134 }
135 else // fall back to string processing
136 {
137 if( model->GetValue( aRow, aCol ) == wxT( "1" ) )
138 model->SetValue( aRow, aCol, wxT( "0" ) );
139 else
140 model->SetValue( aRow, aCol, wxT( "1" ) );
141 }
142
143 // Mac needs this for the keyboard events; Linux appears to always need it.
144 m_grid->ForceRefresh();
145
146 // Let any clients know
147 wxGridEvent event( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, aRow, aCol );
148 event.SetString( model->GetValue( aRow, aCol ) );
149 m_grid->GetEventHandler()->ProcessEvent( event );
150
151 return true;
152 }
153
154 return false;
155}
156
157
158bool GRID_TRICKS::showEditor( int aRow, int aCol )
159{
160 if( m_grid->GetGridCursorRow() != aRow || m_grid->GetGridCursorCol() != aCol )
161 m_grid->SetGridCursor( aRow, aCol );
162
163 if( !isReadOnly( aRow, aCol ) )
164 {
165 m_grid->ClearSelection();
166
167 m_sel_row_start = aRow;
168 m_sel_col_start = aCol;
169 m_sel_row_count = 1;
170 m_sel_col_count = 1;
171
172 if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
173 {
174 wxArrayInt rows = m_grid->GetSelectedRows();
175
176 if( rows.size() != 1 || rows.Item( 0 ) != aRow )
177 m_grid->SelectRow( aRow );
178 }
179
180 // For several reasons we can't enable the control here. There's the whole
181 // SetInSetFocus() issue/hack in wxWidgets, and there's also wxGrid's MouseUp
182 // handler which doesn't notice it's processing a MouseUp until after it has
183 // disabled the editor yet again. So we re-use wxWidgets' slow-click hack,
184 // which is processed later in the MouseUp handler.
185 //
186 // It should be pointed out that the fact that it's wxWidgets' hack doesn't
187 // make it any less of a hack. Be extra careful with any modifications here.
188 // See, in particular, https://bugs.launchpad.net/kicad/+bug/1817965.
190
191 return true;
192 }
193
194 return false;
195}
196
197
198void GRID_TRICKS::onGridCellLeftClick( wxGridEvent& aEvent )
199{
200 int row = aEvent.GetRow();
201 int col = aEvent.GetCol();
202
203 // Don't make users click twice to toggle a checkbox or edit a text cell
204 if( !aEvent.GetModifiers() )
205 {
206 bool toggled = false;
207
208 if( toggleCell( row, col, true ) )
209 toggled = true;
210 else if( showEditor( row, col ) )
211 return;
212
213 // Apply checkbox changes to multi-selection.
214 // Non-checkbox changes handled elsewhere
215 if( toggled )
216 {
218
219 // We only want to apply this to whole rows. If the grid allows selecting individual
220 // cells, and the selection contains dijoint cells, skip this logic.
221 if( !m_grid->GetSelectedCells().IsEmpty() || m_sel_row_count < 2 )
222 {
223 // We preserved the selection in toggleCell above; so clear it now that we know
224 // we aren't doing a multi-select edit
225 m_grid->ClearSelection();
226 return;
227 }
228
229 wxString newVal = m_grid->GetCellValue( row, col );
230
231 for( int affectedRow = m_sel_row_start; affectedRow < m_sel_row_count; ++affectedRow )
232 {
233 if( affectedRow == row )
234 continue;
235
236 m_grid->SetCellValue( affectedRow, col, newVal );
237 }
238 }
239 }
240
241 aEvent.Skip();
242}
243
244
245void GRID_TRICKS::onGridCellLeftDClick( wxGridEvent& aEvent )
246{
247 if( !handleDoubleClick( aEvent ) )
248 onGridCellLeftClick( aEvent );
249}
250
251
252void GRID_TRICKS::onGridMotion( wxMouseEvent& aEvent )
253{
254 // Always skip the event
255 aEvent.Skip();
256
257 wxPoint pt = aEvent.GetPosition();
258 wxPoint pos = m_grid->CalcScrolledPosition( wxPoint( pt.x, pt.y ) );
259
260 int col = m_grid->XToCol( pos.x );
261 int row = m_grid->YToRow( pos.y );
262
263 // Empty tooltip if the cell doesn't exist or the column doesn't have tooltips
264 if( ( col == wxNOT_FOUND ) || ( row == wxNOT_FOUND ) || !m_tooltipEnabled[col] )
265 {
266 m_grid->GetGridWindow()->SetToolTip( wxS( "" ) );
267 return;
268 }
269
270 // Set the tooltip to the string contained in the cell
271 m_grid->GetGridWindow()->SetToolTip( m_grid->GetCellValue( row, col ) );
272}
273
274
275bool GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent )
276{
277 // Double-click processing must be handled by specific sub-classes
278 return false;
279}
280
281
283{
284 wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
285 wxGridCellCoordsArray botRight = m_grid->GetSelectionBlockBottomRight();
286
287 wxArrayInt cols = m_grid->GetSelectedCols();
288 wxArrayInt rows = m_grid->GetSelectedRows();
289
290 if( topLeft.Count() && botRight.Count() )
291 {
292 m_sel_row_start = topLeft[0].GetRow();
293 m_sel_col_start = topLeft[0].GetCol();
294
295 m_sel_row_count = botRight[0].GetRow() - m_sel_row_start + 1;
296 m_sel_col_count = botRight[0].GetCol() - m_sel_col_start + 1;
297 }
298 else if( cols.Count() )
299 {
300 m_sel_col_start = cols[0];
301 m_sel_col_count = cols.Count();
302 m_sel_row_start = 0;
303 m_sel_row_count = m_grid->GetNumberRows();
304 }
305 else if( rows.Count() )
306 {
307 m_sel_col_start = 0;
308 m_sel_col_count = m_grid->GetNumberCols();
309 m_sel_row_start = rows[0];
310 m_sel_row_count = rows.Count();
311 }
312 else
313 {
314 m_sel_row_start = m_grid->GetGridCursorRow();
315 m_sel_col_start = m_grid->GetGridCursorCol();
316 m_sel_row_count = m_sel_row_start >= 0 ? 1 : 0;
317 m_sel_col_count = m_sel_col_start >= 0 ? 1 : 0;
318 }
319}
320
321
322void GRID_TRICKS::onGridCellRightClick( wxGridEvent& aEvent )
323{
324 wxMenu menu;
325
326 showPopupMenu( menu, aEvent );
327}
328
329
330void GRID_TRICKS::onGridLabelLeftClick( wxGridEvent& aEvent )
331{
333
334 aEvent.Skip();
335}
336
337
339{
340 wxMenu menu;
341
342 for( int i = 0; i < m_grid->GetNumberCols(); ++i )
343 {
344 int id = GRIDTRICKS_FIRST_SHOWHIDE + i;
345 menu.AppendCheckItem( id, m_grid->GetColLabelValue( i ) );
346 menu.Check( id, m_grid->IsColShown( i ) );
347 }
348
349 m_grid->PopupMenu( &menu );
350}
351
352
353void GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
354{
355 menu.Append( GRIDTRICKS_ID_CUT, _( "Cut" ) + "\tCtrl+X",
356 _( "Clear selected cells placing original contents on clipboard" ) );
357 menu.Append( GRIDTRICKS_ID_COPY, _( "Copy" ) + "\tCtrl+C",
358 _( "Copy selected cells to clipboard" ) );
359 menu.Append( GRIDTRICKS_ID_PASTE, _( "Paste" ) + "\tCtrl+V",
360 _( "Paste clipboard cells to matrix at current cell" ) );
361 menu.Append( GRIDTRICKS_ID_DELETE, _( "Delete" ) + "\tDel",
362 _( "Clear contents of selected cells" ) );
363 menu.Append( GRIDTRICKS_ID_SELECT, _( "Select All" ) + "\tCtrl+A",
364 _( "Select all cells" ) );
365
366 menu.Enable( GRIDTRICKS_ID_CUT, false );
367 menu.Enable( GRIDTRICKS_ID_DELETE, false );
368 menu.Enable( GRIDTRICKS_ID_PASTE, false );
369
371
372 auto anyCellsWritable =
373 [&]()
374 {
375 for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
376 {
377 for( int col = m_sel_col_start; col < m_sel_col_start + m_sel_col_count; ++col )
378 {
379 if( !isReadOnly( row, col ) && isTextEntry( row, col ) )
380 return true;
381 }
382 }
383
384 return false;
385 };
386
387 if( anyCellsWritable() )
388 {
389 menu.Enable( GRIDTRICKS_ID_CUT, true );
390 menu.Enable( GRIDTRICKS_ID_DELETE, true );
391 }
392
393 // Paste can overflow the selection, so don't depend on the particular cell being writeable.
394
395 wxLogNull doNotLog; // disable logging of failed clipboard actions
396
397 if( wxTheClipboard->Open() )
398 {
399 if( wxTheClipboard->IsSupported( wxDF_TEXT )
400 || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
401 {
402 menu.Enable( GRIDTRICKS_ID_PASTE, true );
403 }
404
405 wxTheClipboard->Close();
406 }
407
408 m_grid->PopupMenu( &menu );
409}
410
411
412void GRID_TRICKS::onPopupSelection( wxCommandEvent& event )
413{
414 doPopupSelection( event );
415}
416
417
418void GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
419{
420 int menu_id = event.GetId();
421
422 // assume getSelectedArea() was called by rightClickPopupMenu() and there's
423 // no way to have gotten here without that having been called.
424
425 switch( menu_id )
426 {
428 cutcopy( true, true );
429 break;
430
432 cutcopy( true, false );
433 break;
434
436 cutcopy( false, true );
437 break;
438
441 break;
442
444 m_grid->SelectAll();
445 break;
446
447 default:
448 if( menu_id >= GRIDTRICKS_FIRST_SHOWHIDE && m_grid->CommitPendingChanges( false ) )
449 {
450 int col = menu_id - GRIDTRICKS_FIRST_SHOWHIDE;
451
452 if( m_grid->IsColShown( col ) )
453 m_grid->HideCol( col );
454 else
455 m_grid->ShowCol( col );
456 }
457 }
458}
459
460
461void GRID_TRICKS::onCharHook( wxKeyEvent& ev )
462{
463 bool handled = false;
464
465 if( ( ev.GetKeyCode() == WXK_RETURN || ev.GetKeyCode() == WXK_NUMPAD_ENTER )
466 && ev.GetModifiers() == wxMOD_NONE
467 && m_grid->GetGridCursorRow() == m_grid->GetNumberRows() - 1 )
468 {
469 if( m_grid->IsCellEditControlShown() )
470 {
472 handled = true;
473 }
474 else
475 {
476 wxCommandEvent dummy;
478 handled = true;
479 }
480 }
481 else if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'V' )
482 {
483 if( m_grid->IsCellEditControlShown() && wxTheClipboard->Open() )
484 {
485 if( wxTheClipboard->IsSupported( wxDF_TEXT )
486 || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
487 {
488 wxTextDataObject data;
489 wxTheClipboard->GetData( data );
490
491 if( data.GetText().Contains( COL_SEP ) || data.GetText().Contains( ROW_SEP ) )
492 {
493 wxString stripped( data.GetText() );
494 stripped.Replace( ROW_SEP, " " );
495 stripped.Replace( ROW_SEP_R, " " );
496 stripped.Replace( COL_SEP, " " );
497
498 // Write to the CellEditControl if we can
499 if( wxTextEntry* te = dynamic_cast<wxTextEntry*>( ev.GetEventObject() ) )
500 te->WriteText( stripped );
501 else
502 paste_text( stripped );
503
504 handled = true;
505 }
506 }
507
508 wxTheClipboard->Close();
509 m_grid->ForceRefresh();
510 }
511 }
512 else if( ev.GetKeyCode() == WXK_ESCAPE )
513 {
514 if( m_grid->IsCellEditControlShown() )
515 {
517 handled = true;
518 }
519 }
520
521 if( !handled )
522 ev.Skip( true );
523}
524
525
526void GRID_TRICKS::onKeyDown( wxKeyEvent& ev )
527{
528 if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'A' )
529 {
530 m_grid->SelectAll();
531 return;
532 }
533 else if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'C' )
534 {
536 cutcopy( true, false );
537 return;
538 }
539 else if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'V' )
540 {
543 return;
544 }
545 else if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'X' )
546 {
548 cutcopy( true, true );
549 return;
550 }
551 else if( !ev.GetModifiers() && ev.GetKeyCode() == WXK_DELETE )
552 {
554 cutcopy( false, true );
555 return;
556 }
557
558 // space-bar toggling of checkboxes
559 if( m_grid->IsEditable() && ev.GetKeyCode() == ' ' )
560 {
561 bool retVal = false;
562
563 // If only rows can be selected, only toggle the first cell in a row
564 if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
565 {
566 wxArrayInt rowSel = m_grid->GetSelectedRows();
567
568 for( unsigned int rowInd = 0; rowInd < rowSel.GetCount(); rowInd++ )
569 retVal |= toggleCell( rowSel[rowInd], 0, true );
570 }
571
572 // If only columns can be selected, only toggle the first cell in a column
573 else if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectColumns )
574 {
575 wxArrayInt colSel = m_grid->GetSelectedCols();
576
577 for( unsigned int colInd = 0; colInd < colSel.GetCount(); colInd++ )
578 retVal |= toggleCell( 0, colSel[colInd], true );
579 }
580
581 // If the user can select the individual cells, toggle each cell selected
582 else if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectCells )
583 {
584 wxArrayInt rowSel = m_grid->GetSelectedRows();
585 wxArrayInt colSel = m_grid->GetSelectedCols();
586 wxGridCellCoordsArray cellSel = m_grid->GetSelectedCells();
587 wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
588 wxGridCellCoordsArray botRight = m_grid->GetSelectionBlockBottomRight();
589
590 // Iterate over every individually selected cell and try to toggle it
591 for( unsigned int cellInd = 0; cellInd < cellSel.GetCount(); cellInd++ )
592 {
593 retVal |= toggleCell( cellSel[cellInd].GetRow(), cellSel[cellInd].GetCol(), true );
594 }
595
596 // Iterate over every column and try to toggle each cell in it
597 for( unsigned int colInd = 0; colInd < colSel.GetCount(); colInd++ )
598 {
599 for( int row = 0; row < m_grid->GetNumberRows(); row++ )
600 retVal |= toggleCell( row, colSel[colInd], true );
601 }
602
603 // Iterate over every row and try to toggle each cell in it
604 for( unsigned int rowInd = 0; rowInd < rowSel.GetCount(); rowInd++ )
605 {
606 for( int col = 0; col < m_grid->GetNumberCols(); col++ )
607 retVal |= toggleCell( rowSel[rowInd], col, true );
608 }
609
610 // Iterate over the selection blocks
611 for( unsigned int blockInd = 0; blockInd < topLeft.GetCount(); blockInd++ )
612 {
613 wxGridCellCoords start = topLeft[blockInd];
614 wxGridCellCoords end = botRight[blockInd];
615
616 for( int row = start.GetRow(); row <= end.GetRow(); row++ )
617 {
618 for( int col = start.GetCol(); col <= end.GetCol(); col++ )
619 retVal |= toggleCell( row, col, true );
620 }
621 }
622 }
623
624 // Return if there were any cells toggled
625 if( retVal )
626 return;
627 }
628
629 // ctrl-tab for exit grid
630#ifdef __APPLE__
631 bool ctrl = ev.RawControlDown();
632#else
633 bool ctrl = ev.ControlDown();
634#endif
635
636 if( ctrl && ev.GetKeyCode() == WXK_TAB )
637 {
638 wxWindow* test = m_grid->GetNextSibling();
639
640 if( !test )
641 test = m_grid->GetParent()->GetNextSibling();
642
643 while( test && !test->IsTopLevel() )
644 {
645 test->SetFocus();
646
647 if( test->HasFocus() )
648 break;
649
650 if( !test->GetChildren().empty() )
651 {
652 test = test->GetChildren().front();
653 }
654 else if( test->GetNextSibling() )
655 {
656 test = test->GetNextSibling();
657 }
658 else
659 {
660 while( test )
661 {
662 test = test->GetParent();
663
664 if( test && test->IsTopLevel() )
665 {
666 break;
667 }
668 else if( test && test->GetNextSibling() )
669 {
670 test = test->GetNextSibling();
671 break;
672 }
673 }
674 }
675 }
676
677 return;
678 }
679
680 ev.Skip( true );
681}
682
683
685{
686 wxLogNull doNotLog; // disable logging of failed clipboard actions
687
688 if( m_grid->IsEditable() && wxTheClipboard->Open() )
689 {
690 if( wxTheClipboard->IsSupported( wxDF_TEXT )
691 || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
692 {
693 wxTextDataObject data;
694
695 wxTheClipboard->GetData( data );
696
697 paste_text( data.GetText() );
698 }
699
700 wxTheClipboard->Close();
701 m_grid->ForceRefresh();
702 }
703}
704
705
706void GRID_TRICKS::paste_text( const wxString& cb_text )
707{
708 wxGridTableBase* tbl = m_grid->GetTable();
709
710 const int cur_row = m_grid->GetGridCursorRow();
711 const int cur_col = m_grid->GetGridCursorCol();
712 int start_row;
713 int end_row;
714 int start_col;
715 int end_col;
716 bool is_selection = false;
717
718 if( cur_row < 0 || cur_col < 0 )
719 {
720 wxBell();
721 return;
722 }
723
724 if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
725 {
726 if( m_sel_row_count > 1 )
727 is_selection = true;
728 }
729 else if( m_sel_col_count > 1 || m_sel_row_count > 1 )
730 {
731 is_selection = true;
732 }
733
734 wxStringTokenizer rows( cb_text, ROW_SEP, wxTOKEN_RET_EMPTY );
735
736 // If selection of cells is present
737 // then a clipboard pastes to selected cells only.
738 if( is_selection )
739 {
740 start_row = m_sel_row_start;
742 start_col = m_sel_col_start;
744 }
745 // Otherwise, paste whole clipboard
746 // starting from cell with cursor.
747 else
748 {
749 start_row = cur_row;
750 end_row = cur_row + rows.CountTokens();
751
752 if( end_row > tbl->GetNumberRows() )
753 {
754 if( m_addHandler )
755 {
756 for( int ii = end_row - tbl->GetNumberRows(); ii > 0; --ii )
757 {
758 wxCommandEvent dummy;
760 }
761 }
762
763 end_row = tbl->GetNumberRows();
764 }
765
766 start_col = cur_col;
767 end_col = start_col; // end_col actual value calculates later
768 }
769
770 for( int row = start_row; row < end_row; ++row )
771 {
772 // If number of selected rows is larger than the count of rows on the clipboard, paste
773 // again and again until the end of the selection is reached.
774 if( !rows.HasMoreTokens() )
775 rows.SetString( cb_text, ROW_SEP, wxTOKEN_RET_EMPTY );
776
777 wxString rowTxt = rows.GetNextToken();
778
779 wxStringTokenizer cols( rowTxt, COL_SEP, wxTOKEN_RET_EMPTY );
780
781 if( !is_selection )
782 end_col = cur_col + cols.CountTokens();
783
784 for( int col = start_col; col < end_col && col < tbl->GetNumberCols(); ++col )
785 {
786 // Skip hidden columns
787 if( !m_grid->IsColShown( col ) )
788 {
789 end_col++;
790 continue;
791 }
792
793 // If number of selected cols is larger than the count of cols on the clipboard,
794 // paste again and again until the end of the selection is reached.
795 if( !cols.HasMoreTokens() )
796 cols.SetString( rowTxt, COL_SEP, wxTOKEN_RET_EMPTY );
797
798 wxString cellTxt = cols.GetNextToken();
799
800 // Allow paste to anything that can take a string, including things like color
801 // swatches and checkboxes
802 if( tbl->CanSetValueAs( row, col, wxGRID_VALUE_STRING ) && !isReadOnly( row, col ) )
803 {
804 tbl->SetValue( row, col, cellTxt );
805
806 wxGridEvent evt( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, row, col );
807 m_grid->GetEventHandler()->ProcessEvent( evt );
808 }
809 }
810 }
811}
812
813
814void GRID_TRICKS::cutcopy( bool doCopy, bool doDelete )
815{
816 wxLogNull doNotLog; // disable logging of failed clipboard actions
817
818 if( doCopy && !wxTheClipboard->Open() )
819 return;
820
821 wxGridTableBase* tbl = m_grid->GetTable();
822 wxString txt;
823
824 // fill txt with a format that is compatible with most spreadsheets
825 for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
826 {
827 for( int col = m_sel_col_start; col < m_sel_col_start + m_sel_col_count; ++col )
828 {
829 if( !m_grid->IsColShown( col ) )
830 continue;
831
832 txt += tbl->GetValue( row, col );
833
834 if( col < m_sel_col_start + m_sel_col_count - 1 ) // that was not last column
835 txt += COL_SEP;
836
837 if( doDelete )
838 {
839 // Do NOT allow clear of things that can take strings but aren't textEntries
840 // (ie: color swatches, textboxes, etc.).
841 if( isTextEntry( row, col ) && !isReadOnly( row, col ) )
842 tbl->SetValue( row, col, wxEmptyString );
843 }
844 }
845
846 txt += ROW_SEP;
847 }
848
849 if( doCopy )
850 {
851 wxTheClipboard->SetData( new wxTextDataObject( txt ) );
852 wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
853 wxTheClipboard->Close();
854 }
855
856 if( doDelete )
857 m_grid->ForceRefresh();
858}
859
860
861void GRID_TRICKS::onUpdateUI( wxUpdateUIEvent& event )
862{
863 // Respect ROW selectionMode when moving cursor
864
865 if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
866 {
867 int cursorRow = m_grid->GetGridCursorRow();
868 bool cursorInSelectedRow = false;
869
870 for( int row : m_grid->GetSelectedRows() )
871 {
872 if( row == cursorRow )
873 {
874 cursorInSelectedRow = true;
875 break;
876 }
877 }
878
879 if( !cursorInSelectedRow && cursorRow >= 0 )
880 m_grid->SelectRow( cursorRow );
881 }
882}
bool isReadOnly(int aRow, int aCol)
void onGridMotion(wxMouseEvent &event)
void onGridLabelLeftClick(wxGridEvent &event)
virtual void paste_text(const wxString &cb_text)
void init()
Shared initialization for various ctors.
Definition: grid_tricks.cpp:58
void getSelectedArea()
Puts the selected area into a sensible rectangle of m_sel_{row,col}_{start,count} above.
GRID_TRICKS(WX_GRID *aGrid)
Definition: grid_tricks.cpp:42
void onKeyDown(wxKeyEvent &event)
void onPopupSelection(wxCommandEvent &event)
virtual void cutcopy(bool doCopy, bool doDelete)
virtual void doPopupSelection(wxCommandEvent &event)
bool isCheckbox(int aRow, int aCol)
std::function< void(wxCommandEvent &)> m_addHandler
Definition: grid_tricks.h:134
virtual void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent)
int m_sel_row_start
Definition: grid_tricks.h:129
std::bitset< GRIDTRICKS_MAX_COL > m_tooltipEnabled
Definition: grid_tricks.h:136
void onUpdateUI(wxUpdateUIEvent &event)
void onGridCellRightClick(wxGridEvent &event)
WX_GRID * m_grid
I don't own the grid, but he owns me.
Definition: grid_tricks.h:125
int m_sel_row_count
Definition: grid_tricks.h:131
bool isTextEntry(int aRow, int aCol)
Definition: grid_tricks.cpp:91
void onGridCellLeftDClick(wxGridEvent &event)
void onGridCellLeftClick(wxGridEvent &event)
virtual bool handleDoubleClick(wxGridEvent &aEvent)
int m_sel_col_count
Definition: grid_tricks.h:132
void onCharHook(wxKeyEvent &event)
virtual void paste_clipboard()
bool showEditor(int aRow, int aCol)
int m_sel_col_start
Definition: grid_tricks.h:130
void onGridLabelRightClick(wxGridEvent &event)
bool toggleCell(int aRow, int aCol, bool aPreserveSelection=false)
bool CancelPendingChanges()
Definition: wx_grid.cpp:530
void ShowEditorOnMouseUp()
WxWidgets has a bunch of bugs in its handling of wxGrid mouse events which close cell editors right a...
Definition: wx_grid.h:150
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:558
#define _(s)
#define ROW_SEP_R
Definition: grid_tricks.cpp:39
#define COL_SEP
Definition: grid_tricks.cpp:37
#define ROW_SEP
Definition: grid_tricks.cpp:38
@ GRIDTRICKS_ID_PASTE
Definition: grid_tricks.h:45
@ GRIDTRICKS_FIRST_ID
Definition: grid_tricks.h:41
@ GRIDTRICKS_FIRST_SHOWHIDE
Definition: grid_tricks.h:51
@ GRIDTRICKS_ID_SELECT
Definition: grid_tricks.h:46
@ GRIDTRICKS_ID_CUT
Definition: grid_tricks.h:42
@ GRIDTRICKS_LAST_ID
Definition: grid_tricks.h:53
@ GRIDTRICKS_ID_COPY
Definition: grid_tricks.h:43
@ GRIDTRICKS_ID_DELETE
Definition: grid_tricks.h:44
std::vector< FAB_LAYER_COLOR > dummy