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