KiCad PCB EDA Suite
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 <dick@softplc.com>
5  * Copyright (C) 2012-18 KiCad Developers, see change_log.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 
26 #include <fctsys.h>
27 #include <grid_tricks.h>
28 #include <wx/tokenzr.h>
29 #include <wx/clipbrd.h>
31 
32 
33 // It works for table data on clipboard for an Excell spreadsheet,
34 // why not us too for now.
35 #define COL_SEP wxT( '\t' )
36 #define ROW_SEP wxT( '\n' )
37 
38 
40  m_grid( aGrid )
41 {
42  m_sel_row_start = 0;
43  m_sel_col_start = 0;
44  m_sel_row_count = 0;
45  m_sel_col_count = 0;
46 
47  aGrid->Connect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridCellLeftClick ), NULL, this );
48  aGrid->Connect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( GRID_TRICKS::onGridCellLeftDClick ), NULL, this );
49  aGrid->Connect( wxEVT_GRID_CELL_RIGHT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridCellRightClick ), NULL, this );
50  aGrid->Connect( wxEVT_GRID_LABEL_RIGHT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridLabelRightClick ), NULL, this );
51  aGrid->Connect( wxEVT_GRID_LABEL_LEFT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridLabelLeftClick ), NULL, this );
52  aGrid->Connect( GRIDTRICKS_FIRST_ID, GRIDTRICKS_LAST_ID, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( GRID_TRICKS::onPopupSelection ), NULL, this );
53  aGrid->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( GRID_TRICKS::onKeyDown ), NULL, this );
54  aGrid->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( GRID_TRICKS::onUpdateUI ), NULL, this );
55 }
56 
57 
58 bool GRID_TRICKS::toggleCell( int aRow, int aCol )
59 {
60  auto renderer = m_grid->GetCellRenderer( aRow, aCol );
61  bool isCheckbox = ( dynamic_cast<wxGridCellBoolRenderer*>( renderer ) != nullptr );
62  renderer->DecRef();
63 
64  if( isCheckbox )
65  {
66  m_grid->ClearSelection();
67  m_grid->SetGridCursor( aRow, aCol );
68 
69  wxGridTableBase* model = m_grid->GetTable();
70 
71  if( model->CanGetValueAs( aRow, aCol, wxGRID_VALUE_BOOL )
72  && model->CanSetValueAs( aRow, aCol, wxGRID_VALUE_BOOL ))
73  {
74  model->SetValueAsBool( aRow, aCol, !model->GetValueAsBool( aRow, aCol ));
75  }
76  else // fall back to string processing
77  {
78  if( model->GetValue( aRow, aCol ) == wxT( "1" ) )
79  model->SetValue( aRow, aCol, wxT( "0" ) );
80  else
81  model->SetValue( aRow, aCol, wxT( "1" ) );
82  }
83 
84  // Mac needs this for the keyboard events; Linux appears to always need it.
85  m_grid->ForceRefresh();
86 
87  // Let any clients know
88  wxGridEvent event( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, aRow, aCol );
89  event.SetString( model->GetValue( aRow, aCol ) );
90  m_grid->GetEventHandler()->ProcessEvent( event );
91 
92  return true;
93  }
94 
95  return false;
96 }
97 
98 
99 bool GRID_TRICKS::showEditor( int aRow, int aCol )
100 {
101  if( m_grid->GetGridCursorRow() != aRow || m_grid->GetGridCursorCol() != aCol )
102  m_grid->SetGridCursor( aRow, aCol );
103 
104  if( m_grid->IsEditable() && !m_grid->IsReadOnly( aRow, aCol ) )
105  {
106  m_grid->ClearSelection();
107 
108  if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
109  {
110  wxArrayInt rows = m_grid->GetSelectedRows();
111 
112  if( rows.size() != 1 || rows.Item( 0 ) != aRow )
113  m_grid->SelectRow( aRow );
114  }
115 
116  // For several reasons we can't enable the control here. There's the whole
117  // SetInSetFocus() issue/hack in wxWidgets, and there's also wxGrid's MouseUp
118  // handler which doesn't notice it's processing a MouseUp until after it has
119  // disabled the editor yet again. So we re-use wxWidgets' slow-click hack,
120  // which is processed later in the MouseUp handler.
121  //
122  // It should be pointed out that the fact that it's wxWidgets' hack doesn't
123  // make it any less of a hack. Be extra careful with any modifications here.
124  // See, in particular, https://bugs.launchpad.net/kicad/+bug/1817965.
126 
127  return true;
128  }
129 
130  return false;
131 }
132 
133 
134 void GRID_TRICKS::onGridCellLeftClick( wxGridEvent& aEvent )
135 {
136  int row = aEvent.GetRow();
137  int col = aEvent.GetCol();
138 
139  // Don't make users click twice to toggle a checkbox or edit a text cell
140  if( !aEvent.GetModifiers() )
141  {
142  if( toggleCell( row, col ) )
143  return;
144 
145  if( showEditor( row, col ) )
146  return;
147  }
148 
149  aEvent.Skip();
150 }
151 
152 
153 void GRID_TRICKS::onGridCellLeftDClick( wxGridEvent& aEvent )
154 {
155  if( !handleDoubleClick( aEvent ) )
156  onGridCellLeftClick( aEvent );
157 }
158 
159 
160 bool GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent )
161 {
162  // Double-click processing must be handled by specific sub-classes
163  return false;
164 }
165 
166 
168 {
169  wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
170  wxGridCellCoordsArray botRight = m_grid->GetSelectionBlockBottomRight();
171 
172  wxArrayInt cols = m_grid->GetSelectedCols();
173  wxArrayInt rows = m_grid->GetSelectedRows();
174 
175  if( topLeft.Count() && botRight.Count() )
176  {
177  m_sel_row_start = topLeft[0].GetRow();
178  m_sel_col_start = topLeft[0].GetCol();
179 
180  m_sel_row_count = botRight[0].GetRow() - m_sel_row_start + 1;
181  m_sel_col_count = botRight[0].GetCol() - m_sel_col_start + 1;
182  }
183  else if( cols.Count() )
184  {
185  m_sel_col_start = cols[0];
186  m_sel_col_count = cols.Count();
187  m_sel_row_start = 0;
188  m_sel_row_count = m_grid->GetNumberRows();
189  }
190  else if( rows.Count() )
191  {
192  m_sel_col_start = 0;
193  m_sel_col_count = m_grid->GetNumberCols();
194  m_sel_row_start = rows[0];
195  m_sel_row_count = rows.Count();
196  }
197  else
198  {
199  m_sel_row_start = m_grid->GetGridCursorRow();
200  m_sel_col_start = m_grid->GetGridCursorCol();
201  m_sel_row_count = m_sel_row_start >= 0 ? 1 : 0;
202  m_sel_col_count = m_sel_col_start >= 0 ? 1 : 0;
203  }
204 }
205 
206 
208 {
209  wxMenu menu;
210 
211  showPopupMenu( menu );
212 }
213 
214 
215 void GRID_TRICKS::onGridLabelLeftClick( wxGridEvent& aEvent )
216 {
218 
219  aEvent.Skip();
220 }
221 
222 
224 {
225  wxMenu menu;
226 
227  for( int i = 0; i < m_grid->GetNumberCols(); ++i )
228  {
229  int id = GRIDTRICKS_FIRST_SHOWHIDE + i;
230  menu.AppendCheckItem( id, m_grid->GetColLabelValue( i ) );
231  menu.Check( id, m_grid->IsColShown( i ) );
232  }
233 
234  m_grid->PopupMenu( &menu );
235 }
236 
237 
238 void GRID_TRICKS::showPopupMenu( wxMenu& menu )
239 {
240  menu.Append( GRIDTRICKS_ID_CUT, _( "Cut\tCTRL+X" ), _( "Clear selected cells placing original contents on clipboard" ) );
241  menu.Append( GRIDTRICKS_ID_COPY, _( "Copy\tCTRL+C" ), _( "Copy selected cells to clipboard" ) );
242  menu.Append( GRIDTRICKS_ID_PASTE, _( "Paste\tCTRL+V" ), _( "Paste clipboard cells to matrix at current cell" ) );
243  menu.Append( GRIDTRICKS_ID_SELECT, _( "Select All\tCTRL+A" ), _( "Select all cells" ) );
244 
245  getSelectedArea();
246 
247  // if nothing is selected, disable cut and copy.
249  {
250  menu.Enable( GRIDTRICKS_ID_CUT, false );
251  menu.Enable( GRIDTRICKS_ID_COPY, false );
252  }
253 
254  menu.Enable( GRIDTRICKS_ID_PASTE, false );
255 
256  if( wxTheClipboard->Open() )
257  {
258  if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
259  menu.Enable( GRIDTRICKS_ID_PASTE, true );
260 
261  wxTheClipboard->Close();
262  }
263 
264  m_grid->PopupMenu( &menu );
265 }
266 
267 
268 void GRID_TRICKS::onPopupSelection( wxCommandEvent& event )
269 {
270  doPopupSelection( event );
271 }
272 
273 
274 void GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
275 {
276  int menu_id = event.GetId();
277 
278  // assume getSelectedArea() was called by rightClickPopupMenu() and there's
279  // no way to have gotten here without that having been called.
280 
281  switch( menu_id )
282  {
283  case GRIDTRICKS_ID_CUT:
284  case GRIDTRICKS_ID_COPY:
285  cutcopy( menu_id == GRIDTRICKS_ID_CUT );
286  break;
287 
288  case GRIDTRICKS_ID_PASTE:
289  paste_clipboard();
290  break;
291 
293  m_grid->SelectAll();
294  break;
295 
296  default:
297  if( menu_id >= GRIDTRICKS_FIRST_SHOWHIDE )
298  {
299  int col = menu_id - GRIDTRICKS_FIRST_SHOWHIDE;
300 
301  if( m_grid->IsColShown( col ) )
302  m_grid->HideCol( col );
303  else
304  m_grid->ShowCol( col );
305  }
306  }
307 }
308 
309 
310 void GRID_TRICKS::onKeyDown( wxKeyEvent& ev )
311 {
312  if( isCtl( 'A', ev ) )
313  {
314  m_grid->SelectAll();
315  return;
316  }
317  else if( isCtl( 'C', ev ) )
318  {
319  getSelectedArea();
320  cutcopy( false );
321  return;
322  }
323  else if( isCtl( 'V', ev ) )
324  {
325  getSelectedArea();
326  paste_clipboard();
327  return;
328  }
329  else if( isCtl( 'X', ev ) )
330  {
331  getSelectedArea();
332  cutcopy( true );
333  return;
334  }
335 
336  // space-bar toggling of checkboxes
337  if( ev.GetKeyCode() == ' ' )
338  {
339  int row = m_grid->GetGridCursorRow();
340  int col = m_grid->GetGridCursorCol();
341 
342  if( m_grid->IsVisible( row, col ) && toggleCell( row, col ) )
343  return;
344  }
345 
346  // ctrl-tab for exit grid
347 #ifdef __APPLE__
348  bool ctrl = ev.RawControlDown();
349 #else
350  bool ctrl = ev.ControlDown();
351 #endif
352 
353  if( ctrl && ev.GetKeyCode() == WXK_TAB )
354  {
355  wxWindow* test = m_grid->GetNextSibling();
356 
357  if( !test )
358  test = m_grid->GetParent()->GetNextSibling();
359 
360  while( test && !test->IsTopLevel() )
361  {
362  test->SetFocus();
363 
364  if( test->HasFocus() )
365  break;
366 
367  if( !test->GetChildren().empty() )
368  test = test->GetChildren().front();
369  else if( test->GetNextSibling() )
370  test = test->GetNextSibling();
371  else
372  {
373  while( test )
374  {
375  test = test->GetParent();
376 
377  if( test && test->IsTopLevel() )
378  {
379  break;
380  }
381  else if( test && test->GetNextSibling() )
382  {
383  test = test->GetNextSibling();
384  break;
385  }
386  }
387  }
388  }
389 
390  return;
391  }
392 
393  ev.Skip( true );
394 }
395 
396 
398 {
399  if( wxTheClipboard->Open() )
400  {
401  if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
402  {
403  wxTextDataObject data;
404 
405  wxTheClipboard->GetData( data );
406 
407  paste_text( data.GetText() );
408  }
409 
410  wxTheClipboard->Close();
411  m_grid->ForceRefresh();
412  }
413 }
414 
415 
416 void GRID_TRICKS::paste_text( const wxString& cb_text )
417 {
418  wxGridTableBase* tbl = m_grid->GetTable();
419 
420  const int cur_row = m_grid->GetGridCursorRow();
421  const int cur_col = m_grid->GetGridCursorCol();
422  int start_row;
423  int end_row;
424  int start_col;
425  int end_col;
426  bool is_selection = false;
427 
428  if( cur_row < 0 || cur_col < 0 )
429  {
430  wxBell();
431  return;
432  }
433 
434  if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
435  {
436  if( m_sel_row_count > 1 )
437  is_selection = true;
438  }
439  else
440  {
441  if( m_grid->IsSelection() )
442  is_selection = true;
443  }
444 
445  wxStringTokenizer rows( cb_text, ROW_SEP, wxTOKEN_RET_EMPTY );
446 
447  // If selection of cells is present
448  // then a clipboard pastes to selected cells only.
449  if( is_selection )
450  {
451  start_row = m_sel_row_start;
452  end_row = m_sel_row_start + m_sel_row_count;
453  start_col = m_sel_col_start;
454  end_col = m_sel_col_start + m_sel_col_count;
455  }
456  // Otherwise, paste whole clipboard
457  // starting from cell with cursor.
458  else
459  {
460  start_row = cur_row;
461  end_row = cur_row + rows.CountTokens();
462 
463  if( end_row > tbl->GetNumberRows() )
464  end_row = tbl->GetNumberRows();
465 
466  start_col = cur_col;
467  end_col = start_col; // end_col actual value calculates later
468  }
469 
470  for( int row = start_row; row < end_row; ++row )
471  {
472  // If number of selected rows bigger than count of rows in
473  // the clipboard, paste from the clipboard again and again
474  // while end of the selection is reached.
475  if( !rows.HasMoreTokens() )
476  rows.SetString( cb_text, ROW_SEP, wxTOKEN_RET_EMPTY );
477 
478  wxString rowTxt = rows.GetNextToken();
479 
480  wxStringTokenizer cols( rowTxt, COL_SEP, wxTOKEN_RET_EMPTY );
481 
482  if( !is_selection )
483  {
484  end_col = cur_col + cols.CountTokens();
485 
486  if( end_col > tbl->GetNumberCols() )
487  end_col = tbl->GetNumberCols();
488  }
489 
490  for( int col = start_col; col < end_col; ++col )
491  {
492  // If number of selected columns bigger than count of columns in
493  // the clipboard, paste from the clipboard again and again while
494  // end of the selection is reached.
495  if( !cols.HasMoreTokens() )
496  cols.SetString( rowTxt, COL_SEP, wxTOKEN_RET_EMPTY );
497 
498  wxString cellTxt = cols.GetNextToken();
499  tbl->SetValue( row, col, cellTxt );
500  }
501  }
502 }
503 
504 
505 void GRID_TRICKS::cutcopy( bool doCut )
506 {
507  if( wxTheClipboard->Open() )
508  {
509  wxGridTableBase* tbl = m_grid->GetTable();
510  wxString txt;
511 
512  // fill txt with a format that is compatible with most spreadsheets
513  for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
514  {
515  for( int col = m_sel_col_start; col < m_sel_col_start + m_sel_col_count; ++col )
516  {
517  txt += tbl->GetValue( row, col );
518 
519  if( col < m_sel_col_start + m_sel_col_count - 1 ) // that was not last column
520  txt += COL_SEP;
521 
522  if( doCut )
523  tbl->SetValue( row, col, wxEmptyString );
524  }
525  txt += ROW_SEP;
526  }
527 
528  wxTheClipboard->SetData( new wxTextDataObject( txt ) );
529  wxTheClipboard->Close();
530 
531  if( doCut )
532  m_grid->ForceRefresh();
533  }
534 }
535 
536 
537 void GRID_TRICKS::onUpdateUI( wxUpdateUIEvent& event )
538 {
539  // Respect ROW selectionMode when moving cursor
540 
541  if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
542  {
543  int cursorRow = m_grid->GetGridCursorRow();
544  bool cursorInSelectedRow = false;
545 
546  for( int row : m_grid->GetSelectedRows() )
547  {
548  if( row == cursorRow )
549  {
550  cursorInSelectedRow = true;
551  break;
552  }
553  }
554 
555  if( !cursorInSelectedRow && cursorRow >= 0 )
556  m_grid->SelectRow( cursorRow );
557  }
558 }
void onGridLabelLeftClick(wxGridEvent &event)
void getSelectedArea()
Puts the selected area into a sensible rectangle of m_sel_{row,col}_{start,count} above.
int m_sel_row_count
Definition: grid_tricks.h:64
void onGridCellLeftClick(wxGridEvent &event)
void onGridCellRightClick(wxGridEvent &event)
virtual bool handleDoubleClick(wxGridEvent &aEvent)
int m_sel_col_start
Definition: grid_tricks.h:63
static bool isCtl(int aChar, const wxKeyEvent &e)
Definition: grid_tricks.h:70
GRID_TRICKS(WX_GRID *aGrid)
Definition: grid_tricks.cpp:39
bool toggleCell(int aRow, int aCol)
Definition: grid_tricks.cpp:58
void onGridCellLeftDClick(wxGridEvent &event)
WX_GRID * m_grid
I don't own the grid, but he owns me.
Definition: grid_tricks.h:58
virtual void paste_text(const wxString &cb_text)
void onPopupSelection(wxCommandEvent &event)
int m_sel_col_count
Definition: grid_tricks.h:65
bool showEditor(int aRow, int aCol)
Definition: grid_tricks.cpp:99
#define NULL
int m_sel_row_start
Definition: grid_tricks.h:62
virtual void paste_clipboard()
void onUpdateUI(wxUpdateUIEvent &event)
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:172
void onGridLabelRightClick(wxGridEvent &event)
#define ROW_SEP
Definition: grid_tricks.cpp:36
virtual void doPopupSelection(wxCommandEvent &event)
virtual void showPopupMenu(wxMenu &menu)
#define _(s)
Definition: 3d_actions.cpp:31
#define COL_SEP
Definition: grid_tricks.cpp:35
virtual void cutcopy(bool doCut)
void onKeyDown(wxKeyEvent &ev)
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:99