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