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.

898 lines
27 KiB

3 years ago
  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-2022 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 <wx/stc/stc.h>
  31. #include <widgets/grid_text_helpers.h>
  32. // It works for table data on clipboard for an Excel spreadsheet,
  33. // why not us too for now.
  34. #define COL_SEP wxT( '\t' )
  35. #define ROW_SEP wxT( '\n' )
  36. #define ROW_SEP_R wxT( '\r' )
  37. GRID_TRICKS::GRID_TRICKS( WX_GRID* aGrid ) :
  38. m_grid( aGrid ),
  39. m_addHandler( []( wxCommandEvent& ) {} )
  40. {
  41. init();
  42. }
  43. GRID_TRICKS::GRID_TRICKS( WX_GRID* aGrid, std::function<void( wxCommandEvent& )> aAddHandler ) :
  44. m_grid( aGrid ),
  45. m_addHandler( aAddHandler )
  46. {
  47. init();
  48. }
  49. void GRID_TRICKS::init()
  50. {
  51. m_sel_row_start = 0;
  52. m_sel_col_start = 0;
  53. m_sel_row_count = 0;
  54. m_sel_col_count = 0;
  55. m_grid->Connect( wxEVT_GRID_CELL_LEFT_CLICK,
  56. wxGridEventHandler( GRID_TRICKS::onGridCellLeftClick ), nullptr, this );
  57. m_grid->Connect( wxEVT_GRID_CELL_LEFT_DCLICK,
  58. wxGridEventHandler( GRID_TRICKS::onGridCellLeftDClick ), nullptr, this );
  59. m_grid->Connect( wxEVT_GRID_CELL_RIGHT_CLICK,
  60. wxGridEventHandler( GRID_TRICKS::onGridCellRightClick ), nullptr, this );
  61. m_grid->Connect( wxEVT_GRID_LABEL_RIGHT_CLICK,
  62. wxGridEventHandler( GRID_TRICKS::onGridLabelRightClick ), nullptr, this );
  63. m_grid->Connect( wxEVT_GRID_LABEL_LEFT_CLICK,
  64. wxGridEventHandler( GRID_TRICKS::onGridLabelLeftClick ), nullptr, this );
  65. m_grid->Connect( GRIDTRICKS_FIRST_ID, GRIDTRICKS_LAST_ID, wxEVT_COMMAND_MENU_SELECTED,
  66. wxCommandEventHandler( GRID_TRICKS::onPopupSelection ), nullptr, this );
  67. m_grid->Connect( wxEVT_CHAR_HOOK,
  68. wxCharEventHandler( GRID_TRICKS::onCharHook ), nullptr, this );
  69. m_grid->Connect( wxEVT_KEY_DOWN,
  70. wxKeyEventHandler( GRID_TRICKS::onKeyDown ), nullptr, this );
  71. m_grid->Connect( wxEVT_UPDATE_UI,
  72. wxUpdateUIEventHandler( GRID_TRICKS::onUpdateUI ), nullptr, this );
  73. // The handlers that control the tooltips must be on the actual grid window, not the grid
  74. m_grid->GetGridWindow()->Connect( wxEVT_MOTION,
  75. wxMouseEventHandler( GRID_TRICKS::onGridMotion ), nullptr,
  76. this );
  77. }
  78. bool GRID_TRICKS::isTextEntry( int aRow, int aCol )
  79. {
  80. wxGridCellEditor* editor = m_grid->GetCellEditor( aRow, aCol );
  81. bool retval = ( dynamic_cast<wxTextEntry*>( editor )
  82. || dynamic_cast<GRID_CELL_STC_EDITOR*>( editor ) );
  83. editor->DecRef();
  84. return retval;
  85. }
  86. bool GRID_TRICKS::isCheckbox( int aRow, int aCol )
  87. {
  88. wxGridCellRenderer* renderer = m_grid->GetCellRenderer( aRow, aCol );
  89. bool retval = ( dynamic_cast<wxGridCellBoolRenderer*>( renderer ) );
  90. renderer->DecRef();
  91. return retval;
  92. }
  93. bool GRID_TRICKS::isReadOnly( int aRow, int aCol )
  94. {
  95. return !m_grid->IsEditable() || m_grid->IsReadOnly( aRow, aCol );
  96. }
  97. bool GRID_TRICKS::toggleCell( int aRow, int aCol, bool aPreserveSelection )
  98. {
  99. if( isCheckbox( aRow, aCol ) )
  100. {
  101. if( !aPreserveSelection )
  102. {
  103. m_grid->ClearSelection();
  104. m_grid->SetGridCursor( aRow, aCol );
  105. }
  106. wxGridTableBase* model = m_grid->GetTable();
  107. if( model->CanGetValueAs( aRow, aCol, wxGRID_VALUE_BOOL )
  108. && model->CanSetValueAs( aRow, aCol, wxGRID_VALUE_BOOL ) )
  109. {
  110. model->SetValueAsBool( aRow, aCol, !model->GetValueAsBool( aRow, aCol ) );
  111. }
  112. else // fall back to string processing
  113. {
  114. if( model->GetValue( aRow, aCol ) == wxT( "1" ) )
  115. model->SetValue( aRow, aCol, wxT( "0" ) );
  116. else
  117. model->SetValue( aRow, aCol, wxT( "1" ) );
  118. }
  119. // Mac needs this for the keyboard events; Linux appears to always need it.
  120. m_grid->ForceRefresh();
  121. // Let any clients know
  122. wxGridEvent event( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, aRow, aCol );
  123. event.SetString( model->GetValue( aRow, aCol ) );
  124. m_grid->GetEventHandler()->ProcessEvent( event );
  125. return true;
  126. }
  127. return false;
  128. }
  129. bool GRID_TRICKS::showEditor( int aRow, int aCol )
  130. {
  131. if( m_grid->GetGridCursorRow() != aRow || m_grid->GetGridCursorCol() != aCol )
  132. m_grid->SetGridCursor( aRow, aCol );
  133. if( !isReadOnly( aRow, aCol ) )
  134. {
  135. m_grid->ClearSelection();
  136. m_sel_row_start = aRow;
  137. m_sel_col_start = aCol;
  138. m_sel_row_count = 1;
  139. m_sel_col_count = 1;
  140. if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
  141. {
  142. wxArrayInt rows = m_grid->GetSelectedRows();
  143. if( rows.size() != 1 || rows.Item( 0 ) != aRow )
  144. m_grid->SelectRow( aRow );
  145. }
  146. // For several reasons we can't enable the control here. There's the whole
  147. // SetInSetFocus() issue/hack in wxWidgets, and there's also wxGrid's MouseUp
  148. // handler which doesn't notice it's processing a MouseUp until after it has
  149. // disabled the editor yet again. So we re-use wxWidgets' slow-click hack,
  150. // which is processed later in the MouseUp handler.
  151. //
  152. // It should be pointed out that the fact that it's wxWidgets' hack doesn't
  153. // make it any less of a hack. Be extra careful with any modifications here.
  154. // See, in particular, https://bugs.launchpad.net/kicad/+bug/1817965.
  155. m_grid->ShowEditorOnMouseUp();
  156. return true;
  157. }
  158. return false;
  159. }
  160. void GRID_TRICKS::onGridCellLeftClick( wxGridEvent& aEvent )
  161. {
  162. int row = aEvent.GetRow();
  163. int col = aEvent.GetCol();
  164. // Don't make users click twice to toggle a checkbox or edit a text cell
  165. if( !aEvent.GetModifiers() )
  166. {
  167. bool toggled = false;
  168. if( toggleCell( row, col, true ) )
  169. toggled = true;
  170. else if( showEditor( row, col ) )
  171. return;
  172. // Apply checkbox changes to multi-selection.
  173. // Non-checkbox changes handled elsewhere
  174. if( toggled )
  175. {
  176. getSelectedArea();
  177. // We only want to apply this to whole rows. If the grid allows selecting individual
  178. // cells, and the selection contains dijoint cells, skip this logic.
  179. if( !m_grid->GetSelectedCells().IsEmpty() || m_sel_row_count < 2 )
  180. {
  181. // We preserved the selection in toggleCell above; so clear it now that we know
  182. // we aren't doing a multi-select edit
  183. m_grid->ClearSelection();
  184. return;
  185. }
  186. wxString newVal = m_grid->GetCellValue( row, col );
  187. for( int affectedRow = m_sel_row_start; affectedRow < m_sel_row_count; ++affectedRow )
  188. {
  189. if( affectedRow == row )
  190. continue;
  191. m_grid->SetCellValue( affectedRow, col, newVal );
  192. }
  193. }
  194. }
  195. aEvent.Skip();
  196. }
  197. void GRID_TRICKS::onGridCellLeftDClick( wxGridEvent& aEvent )
  198. {
  199. if( !handleDoubleClick( aEvent ) )
  200. onGridCellLeftClick( aEvent );
  201. }
  202. void GRID_TRICKS::onGridMotion( wxMouseEvent& aEvent )
  203. {
  204. // Always skip the event
  205. aEvent.Skip();
  206. wxPoint pt = aEvent.GetPosition();
  207. wxPoint pos = m_grid->CalcScrolledPosition( wxPoint( pt.x, pt.y ) );
  208. int col = m_grid->XToCol( pos.x );
  209. int row = m_grid->YToRow( pos.y );
  210. // Empty tooltip if the cell doesn't exist or the column doesn't have tooltips
  211. if( ( col == wxNOT_FOUND ) || ( row == wxNOT_FOUND ) || !m_tooltipEnabled[col] )
  212. {
  213. m_grid->GetGridWindow()->SetToolTip( wxS( "" ) );
  214. return;
  215. }
  216. // Set the tooltip to the string contained in the cell
  217. m_grid->GetGridWindow()->SetToolTip( m_grid->GetCellValue( row, col ) );
  218. }
  219. bool GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent )
  220. {
  221. // Double-click processing must be handled by specific sub-classes
  222. return false;
  223. }
  224. void GRID_TRICKS::getSelectedArea()
  225. {
  226. wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
  227. wxGridCellCoordsArray botRight = m_grid->GetSelectionBlockBottomRight();
  228. wxArrayInt cols = m_grid->GetSelectedCols();
  229. wxArrayInt rows = m_grid->GetSelectedRows();
  230. if( topLeft.Count() && botRight.Count() )
  231. {
  232. m_sel_row_start = topLeft[0].GetRow();
  233. m_sel_col_start = topLeft[0].GetCol();
  234. m_sel_row_count = botRight[0].GetRow() - m_sel_row_start + 1;
  235. m_sel_col_count = botRight[0].GetCol() - m_sel_col_start + 1;
  236. }
  237. else if( cols.Count() )
  238. {
  239. m_sel_col_start = cols[0];
  240. m_sel_col_count = cols.Count();
  241. m_sel_row_start = 0;
  242. m_sel_row_count = m_grid->GetNumberRows();
  243. }
  244. else if( rows.Count() )
  245. {
  246. m_sel_col_start = 0;
  247. m_sel_col_count = m_grid->GetNumberCols();
  248. m_sel_row_start = rows[0];
  249. m_sel_row_count = rows.Count();
  250. }
  251. else
  252. {
  253. m_sel_row_start = m_grid->GetGridCursorRow();
  254. m_sel_col_start = m_grid->GetGridCursorCol();
  255. m_sel_row_count = m_sel_row_start >= 0 ? 1 : 0;
  256. m_sel_col_count = m_sel_col_start >= 0 ? 1 : 0;
  257. }
  258. }
  259. void GRID_TRICKS::onGridCellRightClick( wxGridEvent& aEvent )
  260. {
  261. wxMenu menu;
  262. showPopupMenu( menu, aEvent );
  263. }
  264. void GRID_TRICKS::onGridLabelLeftClick( wxGridEvent& aEvent )
  265. {
  266. m_grid->CommitPendingChanges();
  267. aEvent.Skip();
  268. }
  269. void GRID_TRICKS::onGridLabelRightClick( wxGridEvent& )
  270. {
  271. wxMenu menu;
  272. for( int i = 0; i < m_grid->GetNumberCols(); ++i )
  273. {
  274. int id = GRIDTRICKS_FIRST_SHOWHIDE + i;
  275. menu.AppendCheckItem( id, m_grid->GetColLabelValue( i ) );
  276. menu.Check( id, m_grid->IsColShown( i ) );
  277. }
  278. m_grid->PopupMenu( &menu );
  279. }
  280. void GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
  281. {
  282. menu.Append( GRIDTRICKS_ID_CUT, _( "Cut" ) + "\tCtrl+X",
  283. _( "Clear selected cells placing original contents on clipboard" ) );
  284. menu.Append( GRIDTRICKS_ID_COPY, _( "Copy" ) + "\tCtrl+C",
  285. _( "Copy selected cells to clipboard" ) );
  286. menu.Append( GRIDTRICKS_ID_PASTE, _( "Paste" ) + "\tCtrl+V",
  287. _( "Paste clipboard cells to matrix at current cell" ) );
  288. menu.Append( GRIDTRICKS_ID_DELETE, _( "Delete" ) + "\tDel",
  289. _( "Clear contents of selected cells" ) );
  290. menu.Append( GRIDTRICKS_ID_SELECT, _( "Select All" ) + "\tCtrl+A",
  291. _( "Select all cells" ) );
  292. menu.Enable( GRIDTRICKS_ID_CUT, false );
  293. menu.Enable( GRIDTRICKS_ID_DELETE, false );
  294. menu.Enable( GRIDTRICKS_ID_PASTE, false );
  295. getSelectedArea();
  296. auto anyCellsWritable =
  297. [&]()
  298. {
  299. for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
  300. {
  301. for( int col = m_sel_col_start; col < m_sel_col_start + m_sel_col_count; ++col )
  302. {
  303. if( !isReadOnly( row, col ) && isTextEntry( row, col ) )
  304. return true;
  305. }
  306. }
  307. return false;
  308. };
  309. if( anyCellsWritable() )
  310. {
  311. menu.Enable( GRIDTRICKS_ID_CUT, true );
  312. menu.Enable( GRIDTRICKS_ID_DELETE, true );
  313. }
  314. // Paste can overflow the selection, so don't depend on the particular cell being writeable.
  315. wxLogNull doNotLog; // disable logging of failed clipboard actions
  316. if( wxTheClipboard->Open() )
  317. {
  318. if( wxTheClipboard->IsSupported( wxDF_TEXT )
  319. || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
  320. {
  321. if( m_grid->IsEditable() )
  322. menu.Enable( GRIDTRICKS_ID_PASTE, true );
  323. }
  324. wxTheClipboard->Close();
  325. }
  326. m_grid->PopupMenu( &menu );
  327. }
  328. void GRID_TRICKS::onPopupSelection( wxCommandEvent& event )
  329. {
  330. doPopupSelection( event );
  331. }
  332. void GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
  333. {
  334. int menu_id = event.GetId();
  335. // assume getSelectedArea() was called by rightClickPopupMenu() and there's
  336. // no way to have gotten here without that having been called.
  337. switch( menu_id )
  338. {
  339. case GRIDTRICKS_ID_CUT:
  340. cutcopy( true, true );
  341. break;
  342. case GRIDTRICKS_ID_COPY:
  343. cutcopy( true, false );
  344. break;
  345. case GRIDTRICKS_ID_DELETE:
  346. cutcopy( false, true );
  347. break;
  348. case GRIDTRICKS_ID_PASTE:
  349. paste_clipboard();
  350. break;
  351. case GRIDTRICKS_ID_SELECT:
  352. m_grid->SelectAll();
  353. break;
  354. default:
  355. if( menu_id >= GRIDTRICKS_FIRST_SHOWHIDE && m_grid->CommitPendingChanges( false ) )
  356. {
  357. int col = menu_id - GRIDTRICKS_FIRST_SHOWHIDE;
  358. if( m_grid->IsColShown( col ) )
  359. m_grid->HideCol( col );
  360. else
  361. m_grid->ShowCol( col );
  362. }
  363. }
  364. }
  365. void GRID_TRICKS::onCharHook( wxKeyEvent& ev )
  366. {
  367. bool handled = false;
  368. if( ( ev.GetKeyCode() == WXK_RETURN || ev.GetKeyCode() == WXK_NUMPAD_ENTER )
  369. && ev.GetModifiers() == wxMOD_NONE
  370. && m_grid->GetGridCursorRow() == m_grid->GetNumberRows() - 1 )
  371. {
  372. if( m_grid->IsCellEditControlShown() )
  373. {
  374. if( m_grid->CommitPendingChanges() )
  375. handled = true;
  376. }
  377. else
  378. {
  379. wxCommandEvent dummy;
  380. m_addHandler( dummy );
  381. handled = true;
  382. }
  383. }
  384. else if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'V' )
  385. {
  386. if( m_grid->IsCellEditControlShown() && wxTheClipboard->Open() )
  387. {
  388. if( wxTheClipboard->IsSupported( wxDF_TEXT )
  389. || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
  390. {
  391. wxTextDataObject data;
  392. wxTheClipboard->GetData( data );
  393. if( data.GetText().Contains( COL_SEP ) || data.GetText().Contains( ROW_SEP ) )
  394. {
  395. wxString stripped( data.GetText() );
  396. stripped.Replace( ROW_SEP, " " );
  397. stripped.Replace( ROW_SEP_R, " " );
  398. stripped.Replace( COL_SEP, " " );
  399. // Write to the CellEditControl if we can
  400. if( wxTextEntry* te = dynamic_cast<wxTextEntry*>( ev.GetEventObject() ) )
  401. te->WriteText( stripped );
  402. else
  403. paste_text( stripped );
  404. handled = true;
  405. }
  406. }
  407. wxTheClipboard->Close();
  408. m_grid->ForceRefresh();
  409. }
  410. }
  411. else if( ev.GetKeyCode() == WXK_ESCAPE )
  412. {
  413. if( m_grid->IsCellEditControlShown() )
  414. {
  415. m_grid->CancelPendingChanges();
  416. handled = true;
  417. }
  418. }
  419. if( !handled )
  420. ev.Skip( true );
  421. }
  422. void GRID_TRICKS::onKeyDown( wxKeyEvent& ev )
  423. {
  424. if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'A' )
  425. {
  426. m_grid->SelectAll();
  427. return;
  428. }
  429. else if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'C' )
  430. {
  431. getSelectedArea();
  432. cutcopy( true, false );
  433. return;
  434. }
  435. else if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'V' )
  436. {
  437. getSelectedArea();
  438. paste_clipboard();
  439. return;
  440. }
  441. else if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'X' )
  442. {
  443. getSelectedArea();
  444. cutcopy( true, true );
  445. return;
  446. }
  447. else if( !ev.GetModifiers() && ev.GetKeyCode() == WXK_DELETE )
  448. {
  449. getSelectedArea();
  450. cutcopy( false, true );
  451. return;
  452. }
  453. // space-bar toggling of checkboxes
  454. if( m_grid->IsEditable() && ev.GetKeyCode() == ' ' )
  455. {
  456. bool retVal = false;
  457. // If only rows can be selected, only toggle the first cell in a row
  458. if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
  459. {
  460. wxArrayInt rowSel = m_grid->GetSelectedRows();
  461. for( unsigned int rowInd = 0; rowInd < rowSel.GetCount(); rowInd++ )
  462. retVal |= toggleCell( rowSel[rowInd], 0, true );
  463. }
  464. // If only columns can be selected, only toggle the first cell in a column
  465. else if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectColumns )
  466. {
  467. wxArrayInt colSel = m_grid->GetSelectedCols();
  468. for( unsigned int colInd = 0; colInd < colSel.GetCount(); colInd++ )
  469. retVal |= toggleCell( 0, colSel[colInd], true );
  470. }
  471. // If the user can select the individual cells, toggle each cell selected
  472. else if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectCells )
  473. {
  474. wxArrayInt rowSel = m_grid->GetSelectedRows();
  475. wxArrayInt colSel = m_grid->GetSelectedCols();
  476. wxGridCellCoordsArray cellSel = m_grid->GetSelectedCells();
  477. wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
  478. wxGridCellCoordsArray botRight = m_grid->GetSelectionBlockBottomRight();
  479. // Iterate over every individually selected cell and try to toggle it
  480. for( unsigned int cellInd = 0; cellInd < cellSel.GetCount(); cellInd++ )
  481. {
  482. retVal |= toggleCell( cellSel[cellInd].GetRow(), cellSel[cellInd].GetCol(), true );
  483. }
  484. // Iterate over every column and try to toggle each cell in it
  485. for( unsigned int colInd = 0; colInd < colSel.GetCount(); colInd++ )
  486. {
  487. for( int row = 0; row < m_grid->GetNumberRows(); row++ )
  488. retVal |= toggleCell( row, colSel[colInd], true );
  489. }
  490. // Iterate over every row and try to toggle each cell in it
  491. for( unsigned int rowInd = 0; rowInd < rowSel.GetCount(); rowInd++ )
  492. {
  493. for( int col = 0; col < m_grid->GetNumberCols(); col++ )
  494. retVal |= toggleCell( rowSel[rowInd], col, true );
  495. }
  496. // Iterate over the selection blocks
  497. for( unsigned int blockInd = 0; blockInd < topLeft.GetCount(); blockInd++ )
  498. {
  499. wxGridCellCoords start = topLeft[blockInd];
  500. wxGridCellCoords end = botRight[blockInd];
  501. for( int row = start.GetRow(); row <= end.GetRow(); row++ )
  502. {
  503. for( int col = start.GetCol(); col <= end.GetCol(); col++ )
  504. retVal |= toggleCell( row, col, true );
  505. }
  506. }
  507. }
  508. // Return if there were any cells toggled
  509. if( retVal )
  510. return;
  511. }
  512. // ctrl-tab for exit grid
  513. #ifdef __APPLE__
  514. bool ctrl = ev.RawControlDown();
  515. #else
  516. bool ctrl = ev.ControlDown();
  517. #endif
  518. if( ctrl && ev.GetKeyCode() == WXK_TAB )
  519. {
  520. wxWindow* test = m_grid->GetNextSibling();
  521. if( !test )
  522. test = m_grid->GetParent()->GetNextSibling();
  523. while( test && !test->IsTopLevel() )
  524. {
  525. test->SetFocus();
  526. if( test->HasFocus() )
  527. break;
  528. if( !test->GetChildren().empty() )
  529. {
  530. test = test->GetChildren().front();
  531. }
  532. else if( test->GetNextSibling() )
  533. {
  534. test = test->GetNextSibling();
  535. }
  536. else
  537. {
  538. while( test )
  539. {
  540. test = test->GetParent();
  541. if( test && test->IsTopLevel() )
  542. {
  543. break;
  544. }
  545. else if( test && test->GetNextSibling() )
  546. {
  547. test = test->GetNextSibling();
  548. break;
  549. }
  550. }
  551. }
  552. }
  553. return;
  554. }
  555. ev.Skip( true );
  556. }
  557. void GRID_TRICKS::paste_clipboard()
  558. {
  559. wxLogNull doNotLog; // disable logging of failed clipboard actions
  560. if( m_grid->IsEditable() && wxTheClipboard->Open() )
  561. {
  562. if( wxTheClipboard->IsSupported( wxDF_TEXT )
  563. || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
  564. {
  565. wxTextDataObject data;
  566. wxTheClipboard->GetData( data );
  567. wxString text = data.GetText();
  568. #ifdef __WXMAC__
  569. // Some editors use windows linefeeds (\r\n), which wx re-writes to \n\n
  570. text.Replace( "\n\n", "\n" );
  571. #endif
  572. paste_text( text );
  573. }
  574. wxTheClipboard->Close();
  575. m_grid->ForceRefresh();
  576. }
  577. }
  578. void GRID_TRICKS::paste_text( const wxString& cb_text )
  579. {
  580. wxGridTableBase* tbl = m_grid->GetTable();
  581. const int cur_row = m_grid->GetGridCursorRow();
  582. const int cur_col = m_grid->GetGridCursorCol();
  583. int start_row;
  584. int end_row;
  585. int start_col;
  586. int end_col;
  587. bool is_selection = false;
  588. if( cur_row < 0 || cur_col < 0 )
  589. {
  590. wxBell();
  591. return;
  592. }
  593. if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
  594. {
  595. if( m_sel_row_count > 1 )
  596. is_selection = true;
  597. }
  598. else if( m_sel_col_count > 1 || m_sel_row_count > 1 )
  599. {
  600. is_selection = true;
  601. }
  602. wxStringTokenizer rows( cb_text, ROW_SEP, wxTOKEN_RET_EMPTY );
  603. // If selection of cells is present
  604. // then a clipboard pastes to selected cells only.
  605. if( is_selection )
  606. {
  607. start_row = m_sel_row_start;
  608. end_row = m_sel_row_start + m_sel_row_count;
  609. start_col = m_sel_col_start;
  610. end_col = m_sel_col_start + m_sel_col_count;
  611. }
  612. // Otherwise, paste whole clipboard
  613. // starting from cell with cursor.
  614. else
  615. {
  616. start_row = cur_row;
  617. end_row = cur_row + rows.CountTokens();
  618. if( end_row > tbl->GetNumberRows() )
  619. {
  620. if( m_addHandler )
  621. {
  622. for( int ii = end_row - tbl->GetNumberRows(); ii > 0; --ii )
  623. {
  624. wxCommandEvent dummy;
  625. m_addHandler( dummy );
  626. }
  627. }
  628. end_row = tbl->GetNumberRows();
  629. }
  630. start_col = cur_col;
  631. end_col = start_col; // end_col actual value calculates later
  632. }
  633. for( int row = start_row; row < end_row; ++row )
  634. {
  635. // If number of selected rows is larger than the count of rows on the clipboard, paste
  636. // again and again until the end of the selection is reached.
  637. if( !rows.HasMoreTokens() )
  638. rows.SetString( cb_text, ROW_SEP, wxTOKEN_RET_EMPTY );
  639. wxString rowTxt = rows.GetNextToken();
  640. wxStringTokenizer cols( rowTxt, COL_SEP, wxTOKEN_RET_EMPTY );
  641. if( !is_selection )
  642. end_col = cur_col + cols.CountTokens();
  643. for( int col = start_col; col < end_col && col < tbl->GetNumberCols(); ++col )
  644. {
  645. // Skip hidden columns
  646. if( !m_grid->IsColShown( col ) )
  647. {
  648. end_col++;
  649. continue;
  650. }
  651. // If number of selected cols is larger than the count of cols on the clipboard,
  652. // paste again and again until the end of the selection is reached.
  653. if( !cols.HasMoreTokens() )
  654. cols.SetString( rowTxt, COL_SEP, wxTOKEN_RET_EMPTY );
  655. wxString cellTxt = cols.GetNextToken();
  656. // Allow paste to anything that can take a string, including things like color
  657. // swatches and checkboxes
  658. if( tbl->CanSetValueAs( row, col, wxGRID_VALUE_STRING ) && !isReadOnly( row, col ) )
  659. {
  660. tbl->SetValue( row, col, cellTxt );
  661. wxGridEvent evt( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, row, col );
  662. m_grid->GetEventHandler()->ProcessEvent( evt );
  663. }
  664. // Allow paste to any cell that can accept a boolean value
  665. else if( tbl->CanSetValueAs( row, col, wxGRID_VALUE_BOOL ) )
  666. {
  667. tbl->SetValueAsBool( row, col, cellTxt == wxT( "1" ) );
  668. wxGridEvent evt( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, row, col );
  669. m_grid->GetEventHandler()->ProcessEvent( evt );
  670. }
  671. }
  672. }
  673. }
  674. void GRID_TRICKS::cutcopy( bool doCopy, bool doDelete )
  675. {
  676. wxLogNull doNotLog; // disable logging of failed clipboard actions
  677. if( doCopy && !wxTheClipboard->Open() )
  678. return;
  679. wxGridTableBase* tbl = m_grid->GetTable();
  680. wxString txt;
  681. // fill txt with a format that is compatible with most spreadsheets
  682. for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
  683. {
  684. for( int col = m_sel_col_start; col < m_sel_col_start + m_sel_col_count; ++col )
  685. {
  686. if( !m_grid->IsColShown( col ) )
  687. continue;
  688. txt += tbl->GetValue( row, col );
  689. if( col < m_sel_col_start + m_sel_col_count - 1 ) // that was not last column
  690. txt += COL_SEP;
  691. if( doDelete )
  692. {
  693. // Do NOT allow clear of things that can take strings but aren't textEntries
  694. // (ie: color swatches, textboxes, etc.).
  695. if( isTextEntry( row, col ) && !isReadOnly( row, col ) )
  696. tbl->SetValue( row, col, wxEmptyString );
  697. }
  698. }
  699. txt += ROW_SEP;
  700. }
  701. if( doCopy )
  702. {
  703. wxTheClipboard->SetData( new wxTextDataObject( txt ) );
  704. wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
  705. wxTheClipboard->Close();
  706. }
  707. if( doDelete )
  708. m_grid->ForceRefresh();
  709. }
  710. void GRID_TRICKS::onUpdateUI( wxUpdateUIEvent& event )
  711. {
  712. // Respect ROW selectionMode when moving cursor
  713. if( m_grid->GetSelectionMode() == wxGrid::wxGridSelectRows )
  714. {
  715. int cursorRow = m_grid->GetGridCursorRow();
  716. bool cursorInSelectedRow = false;
  717. for( int row : m_grid->GetSelectedRows() )
  718. {
  719. if( row == cursorRow )
  720. {
  721. cursorInSelectedRow = true;
  722. break;
  723. }
  724. }
  725. if( !cursorInSelectedRow && cursorRow >= 0 )
  726. m_grid->SelectRow( cursorRow );
  727. }
  728. }