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.

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