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.

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