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 
39 GRID_TRICKS::GRID_TRICKS( wxGrid* aGrid ):
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  m_showEditorOnMouseUp = false;
48 
49  aGrid->Connect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridCellLeftClick ), NULL, this );
50  aGrid->Connect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( GRID_TRICKS::onGridCellLeftDClick ), NULL, this );
51  aGrid->Connect( wxEVT_GRID_CELL_RIGHT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridCellRightClick ), NULL, this );
52  aGrid->Connect( wxEVT_GRID_LABEL_RIGHT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridLabelRightClick ), NULL, this );
53  aGrid->Connect( GRIDTRICKS_FIRST_ID, GRIDTRICKS_LAST_ID, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( GRID_TRICKS::onPopupSelection ), NULL, this );
54  aGrid->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( GRID_TRICKS::onKeyDown ), NULL, this );
55  aGrid->GetGridWindow()->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( GRID_TRICKS::onMouseUp ), NULL, this );
56  aGrid->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( GRID_TRICKS::onUpdateUI ), NULL, this );
57 }
58 
59 
60 bool GRID_TRICKS::toggleCell( int aRow, int aCol )
61 {
62  auto renderer = m_grid->GetCellRenderer( aRow, aCol );
63  bool isCheckbox = ( dynamic_cast<wxGridCellBoolRenderer*>( renderer ) != nullptr );
64  renderer->DecRef();
65 
66  if( isCheckbox )
67  {
68  wxGridTableBase* model = m_grid->GetTable();
69 
70  if( model->CanGetValueAs( aRow, aCol, wxGRID_VALUE_BOOL )
71  && model->CanSetValueAs( aRow, aCol, wxGRID_VALUE_BOOL ))
72  {
73  model->SetValueAsBool( aRow, aCol, !model->GetValueAsBool( aRow, aCol ));
74  }
75  else // fall back to string processing
76  {
77  if( model->GetValue( aRow, aCol ) == wxT( "1" ) )
78  model->SetValue( aRow, aCol, wxT( "0" ) );
79  else
80  model->SetValue( aRow, aCol, wxT( "1" ) );
81  }
82 
83  // Mac needs this for the keyboard events; Linux appears to always need it.
84  m_grid->ForceRefresh();
85 
86  // Let any clients know
87  wxGridEvent event( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, aRow, aCol );
88  event.SetString( model->GetValue( aRow, aCol ) );
89  m_grid->GetEventHandler()->ProcessEvent( event );
90 
91  return true;
92  }
93 
94  return false;
95 }
96 
97 
98 bool GRID_TRICKS::showEditor( int aRow, int aCol )
99 {
100  if( m_grid->IsEditable() && !m_grid->IsReadOnly( aRow, aCol ) )
101  {
102  if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
103  m_grid->SelectRow( aRow );
104 
105  m_grid->SetGridCursor( aRow, aCol );
106 
107  // For several reasons we can't enable the control here. There's the whole
108  // SetInSetFocus() issue/hack in wxWidgets, and there's also wxGrid's MouseUp
109  // handler which doesn't notice it's processing a MouseUp until after it has
110  // disabled the editor yet again. So we wait for the MouseUp.
111  m_showEditorOnMouseUp = true;
112 
113  return true;
114  }
115 
116  return false;
117 }
118 
119 
120 void GRID_TRICKS::onGridCellLeftClick( wxGridEvent& aEvent )
121 {
122  int row = aEvent.GetRow();
123  int col = aEvent.GetCol();
124 
125  // Don't make users click twice to toggle a checkbox or edit a text cell
126 
127  if( !aEvent.GetModifiers() )
128  {
129  if( toggleCell( row, col ) )
130  return;
131 
132  if( showEditor( row, col ) )
133  return;
134  }
135 
136  aEvent.Skip();
137 }
138 
139 
140 void GRID_TRICKS::onGridCellLeftDClick( wxGridEvent& aEvent )
141 {
142  if( !handleDoubleClick( aEvent ) )
143  onGridCellLeftClick( aEvent );
144 }
145 
146 
147 void GRID_TRICKS::onMouseUp( wxMouseEvent& aEvent )
148 {
150  {
151  // Some wxGridCellEditors don't have the SetInSetFocus() hack. Even when they do,
152  // it sometimes fails. Activating the control here seems to avoid those issues.
153  if( m_grid->CanEnableCellControl() )
154  {
155  // Yes, the first of these also shows the control. Well, at least sometimes.
156  // The second call corrects those (as yet undefined) "other times".
157  m_grid->EnableCellEditControl();
158  m_grid->ShowCellEditControl();
159  }
160  m_showEditorOnMouseUp = false;
161  }
162  else
163  aEvent.Skip();
164 }
165 
166 
167 bool GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent )
168 {
169  // Double-click processing must be handled by specific sub-classes
170  return false;
171 }
172 
173 
175 {
176  wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
177  wxGridCellCoordsArray botRight = m_grid->GetSelectionBlockBottomRight();
178 
179  wxArrayInt cols = m_grid->GetSelectedCols();
180  wxArrayInt rows = m_grid->GetSelectedRows();
181 
182  if( topLeft.Count() && botRight.Count() )
183  {
184  m_sel_row_start = topLeft[0].GetRow();
185  m_sel_col_start = topLeft[0].GetCol();
186 
187  m_sel_row_count = botRight[0].GetRow() - m_sel_row_start + 1;
188  m_sel_col_count = botRight[0].GetCol() - m_sel_col_start + 1;
189  }
190  else if( cols.Count() )
191  {
192  m_sel_col_start = cols[0];
193  m_sel_col_count = cols.Count();
194  m_sel_row_start = 0;
195  m_sel_row_count = m_grid->GetNumberRows();
196  }
197  else if( rows.Count() )
198  {
199  m_sel_col_start = 0;
200  m_sel_col_count = m_grid->GetNumberCols();
201  m_sel_row_start = rows[0];
202  m_sel_row_count = rows.Count();
203  }
204  else
205  {
206  m_sel_row_start = m_grid->GetGridCursorRow();
207  m_sel_col_start = m_grid->GetGridCursorCol();
208  m_sel_row_count = m_sel_row_start >= 0 ? 1 : 0;
209  m_sel_col_count = m_sel_col_start >= 0 ? 1 : 0;
210  }
211 }
212 
213 
215 {
216  wxMenu menu;
217 
218  showPopupMenu( menu );
219 }
220 
221 
223 {
224  wxMenu menu;
225 
226  for( int i = 0; i < m_grid->GetNumberCols(); ++i )
227  {
228  int id = GRIDTRICKS_FIRST_SHOWHIDE + i;
229  menu.AppendCheckItem( id, m_grid->GetColLabelValue( i ) );
230  menu.Check( id, m_grid->IsColShown( i ) );
231  }
232 
233  m_grid->PopupMenu( &menu );
234 }
235 
236 
237 void GRID_TRICKS::showPopupMenu( wxMenu& menu )
238 {
239  menu.Append( GRIDTRICKS_ID_CUT, _( "Cut\tCTRL+X" ), _( "Clear selected cells placing original contents on clipboard" ) );
240  menu.Append( GRIDTRICKS_ID_COPY, _( "Copy\tCTRL+C" ), _( "Copy selected cells to clipboard" ) );
241  menu.Append( GRIDTRICKS_ID_PASTE, _( "Paste\tCTRL+V" ), _( "Paste clipboard cells to matrix at current cell" ) );
242  menu.Append( GRIDTRICKS_ID_SELECT, _( "Select All\tCTRL+A" ), _( "Select all cells" ) );
243 
244  getSelectedArea();
245 
246  // if nothing is selected, disable cut and copy.
248  {
249  menu.Enable( GRIDTRICKS_ID_CUT, false );
250  menu.Enable( GRIDTRICKS_ID_COPY, false );
251  }
252 
253  menu.Enable( GRIDTRICKS_ID_PASTE, false );
254 
255  if( wxTheClipboard->Open() )
256  {
257  if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
258  menu.Enable( GRIDTRICKS_ID_PASTE, true );
259 
260  wxTheClipboard->Close();
261  }
262 
263  m_grid->PopupMenu( &menu );
264 }
265 
266 
267 void GRID_TRICKS::onPopupSelection( wxCommandEvent& event )
268 {
269  doPopupSelection( event );
270 }
271 
272 
273 void GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
274 {
275  int menu_id = event.GetId();
276 
277  // assume getSelectedArea() was called by rightClickPopupMenu() and there's
278  // no way to have gotten here without that having been called.
279 
280  switch( menu_id )
281  {
282  case GRIDTRICKS_ID_CUT:
283  case GRIDTRICKS_ID_COPY:
284  cutcopy( menu_id == GRIDTRICKS_ID_CUT );
285  break;
286 
287  case GRIDTRICKS_ID_PASTE:
288  paste_clipboard();
289  break;
290 
292  m_grid->SelectAll();
293  break;
294 
295  default:
296  if( menu_id >= GRIDTRICKS_FIRST_SHOWHIDE )
297  {
298  int col = menu_id - GRIDTRICKS_FIRST_SHOWHIDE;
299 
300  if( m_grid->IsColShown( col ) )
301  m_grid->HideCol( col );
302  else
303  m_grid->ShowCol( col );
304  }
305  }
306 }
307 
308 
309 void GRID_TRICKS::onKeyDown( wxKeyEvent& ev )
310 {
311  if( isCtl( 'A', ev ) )
312  {
313  m_grid->SelectAll();
314  return;
315  }
316  else if( isCtl( 'C', ev ) )
317  {
318  getSelectedArea();
319  cutcopy( false );
320  return;
321  }
322  else if( isCtl( 'V', ev ) )
323  {
324  getSelectedArea();
325  paste_clipboard();
326  return;
327  }
328  else if( isCtl( 'X', ev ) )
329  {
330  getSelectedArea();
331  cutcopy( true );
332  return;
333  }
334 
335  // space-bar toggling of checkboxes
336  if( ev.GetKeyCode() == ' ' )
337  {
338  int row = m_grid->GetGridCursorRow();
339  int col = m_grid->GetGridCursorCol();
340 
341  if( m_grid->IsVisible( row, col ) && toggleCell( row, col ) )
342  return;
343  }
344 
345  // shift-return for OK
346  if( ev.GetKeyCode() == WXK_RETURN && ev.ShiftDown() )
347  {
348  wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
349  return;
350  }
351 
352  ev.Skip( true );
353 }
354 
355 
357 {
358  if( wxTheClipboard->Open() )
359  {
360  if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
361  {
362  wxTextDataObject data;
363 
364  wxTheClipboard->GetData( data );
365 
366  paste_text( data.GetText() );
367  }
368 
369  wxTheClipboard->Close();
370  m_grid->ForceRefresh();
371  }
372 }
373 
374 
375 void GRID_TRICKS::paste_text( const wxString& cb_text )
376 {
377  wxGridTableBase* tbl = m_grid->GetTable();
378 
379  const int cur_row = m_grid->GetGridCursorRow();
380  const int cur_col = m_grid->GetGridCursorCol();
381 
382  if( cur_row < 0 || cur_col < 0 )
383  {
384  wxBell();
385  return;
386  }
387 
388  wxStringTokenizer rows( cb_text, ROW_SEP, wxTOKEN_RET_EMPTY );
389 
390  // if clipboard rows would extend past end of current table size...
391  if( int( rows.CountTokens() ) > tbl->GetNumberRows() - cur_row )
392  {
393  int newRowsNeeded = rows.CountTokens() - ( tbl->GetNumberRows() - cur_row );
394 
395  tbl->AppendRows( newRowsNeeded );
396  }
397 
398  for( int row = cur_row; rows.HasMoreTokens(); ++row )
399  {
400  wxString rowTxt = rows.GetNextToken();
401 
402  wxStringTokenizer cols( rowTxt, COL_SEP, wxTOKEN_RET_EMPTY );
403 
404  for( int col = cur_col; cols.HasMoreTokens(); ++col )
405  {
406  wxString cellTxt = cols.GetNextToken();
407  tbl->SetValue( row, col, cellTxt );
408  }
409  }
410 }
411 
412 
413 void GRID_TRICKS::cutcopy( bool doCut )
414 {
415  if( wxTheClipboard->Open() )
416  {
417  wxGridTableBase* tbl = m_grid->GetTable();
418  wxString txt;
419 
420  // fill txt with a format that is compatible with most spreadsheets
421  for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
422  {
423  for( int col = m_sel_col_start; col < m_sel_col_start + m_sel_col_count; ++col )
424  {
425  txt += tbl->GetValue( row, col );
426 
427  if( col < m_sel_col_start + m_sel_col_count - 1 ) // that was not last column
428  txt += COL_SEP;
429 
430  if( doCut )
431  tbl->SetValue( row, col, wxEmptyString );
432  }
433  txt += ROW_SEP;
434  }
435 
436  wxTheClipboard->SetData( new wxTextDataObject( txt ) );
437  wxTheClipboard->Close();
438 
439  if( doCut )
440  m_grid->ForceRefresh();
441  }
442 }
443 
444 
445 void GRID_TRICKS::onUpdateUI( wxUpdateUIEvent& event )
446 {
447  // Respect ROW selectionMode when moving cursor
448 
449  if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
450  {
451  int cursorRow = m_grid->GetGridCursorRow();
452  bool cursorInSelectedRow = false;
453 
454  for( int row : m_grid->GetSelectedRows() )
455  {
456  if( row == cursorRow )
457  {
458  cursorInSelectedRow = true;
459  break;
460  }
461  }
462 
463  if( !cursorInSelectedRow )
464  m_grid->SelectRow( cursorRow );
465  }
466 }
wxGrid * m_grid
I don&#39;t own the grid, but he owns me.
Definition: grid_tricks.h:58
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)
GRID_TRICKS(wxGrid *aGrid)
Definition: grid_tricks.cpp:39
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:72
bool toggleCell(int aRow, int aCol)
Definition: grid_tricks.cpp:60
void onGridCellLeftDClick(wxGridEvent &event)
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:98
int m_sel_row_start
Definition: grid_tricks.h:62
virtual void paste_clipboard()
void onUpdateUI(wxUpdateUIEvent &event)
void onGridLabelRightClick(wxGridEvent &event)
#define ROW_SEP
Definition: grid_tricks.cpp:36
bool m_showEditorOnMouseUp
Definition: grid_tricks.h:67
virtual void doPopupSelection(wxCommandEvent &event)
virtual void showPopupMenu(wxMenu &menu)
#define COL_SEP
Definition: grid_tricks.cpp:35
size_t i
Definition: json11.cpp:597
void onMouseUp(wxMouseEvent &aEvent)
virtual void cutcopy(bool doCut)
void onKeyDown(wxKeyEvent &ev)