You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

715 lines
21 KiB

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