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.

406 lines
14 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <kiplatform/ui.h>
  24. #include <widgets/font_choice.h>
  25. #include <widgets/color_swatch.h>
  26. #include <widgets/wx_grid.h>
  27. #include <widgets/grid_text_helpers.h>
  28. #include <widgets/grid_color_swatch_helpers.h>
  29. #include <grid_tricks.h>
  30. #include <scintilla_tricks.h>
  31. #include <confirm.h>
  32. #include <board.h>
  33. #include <board_commit.h>
  34. #include <board_design_settings.h>
  35. #include <footprint.h>
  36. #include <pcb_textbox.h>
  37. #include <pcb_tablecell.h>
  38. #include <pcb_table.h>
  39. #include <project.h>
  40. #include <pcb_edit_frame.h>
  41. #include <pcb_layer_box_selector.h>
  42. #include <tool/tool_manager.h>
  43. #include <tools/pcb_actions.h>
  44. #include <dialog_table_properties.h>
  45. DIALOG_TABLE_PROPERTIES::DIALOG_TABLE_PROPERTIES( PCB_BASE_EDIT_FRAME* aFrame, PCB_TABLE* aTable ) :
  46. DIALOG_TABLE_PROPERTIES_BASE( aFrame ),
  47. m_frame( aFrame ),
  48. m_table( aTable ),
  49. m_borderWidth( aFrame, m_borderWidthLabel, m_borderWidthCtrl, m_borderWidthUnits ),
  50. m_separatorsWidth( aFrame, m_separatorsWidthLabel, m_separatorsWidthCtrl, m_separatorsWidthUnits )
  51. {
  52. m_grid = new WX_GRID( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
  53. m_grid->CreateGrid( m_table->GetRowCount(), m_table->GetColCount() );
  54. m_grid->EnableEditing( true );
  55. m_grid->EnableGridLines( true );
  56. m_grid->EnableDragGridSize( false );
  57. m_grid->SetMargins( 0, 0 );
  58. m_grid->SetCellHighlightROPenWidth( 0 );
  59. m_grid->EnableDragColMove( false );
  60. m_grid->EnableDragColSize( false );
  61. m_grid->SetColLabelSize( 0 );
  62. m_grid->EnableDragRowMove( false );
  63. m_grid->EnableDragRowSize( false );
  64. m_grid->SetRowLabelSize( 0 );
  65. m_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_TOP );
  66. m_gridSizer->Add( m_grid, 1, wxEXPAND, 5 );
  67. m_grid->PushEventHandler( new GRID_TRICKS( m_grid ) );
  68. for( int row = 0; row < m_table->GetRowCount(); ++row )
  69. {
  70. for( int col = 0; col < m_table->GetColCount(); ++col )
  71. {
  72. PCB_TABLECELL* cell = m_table->GetCell( row, col );
  73. wxGridCellAttr* attr = new wxGridCellAttr;
  74. if( cell->GetColSpan() == 0 || cell->GetRowSpan() == 0 )
  75. {
  76. attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
  77. attr->SetReadOnly();
  78. }
  79. else
  80. {
  81. attr->SetEditor( new GRID_CELL_STC_EDITOR( true,
  82. [this, cell]( wxStyledTextEvent& aEvent, SCINTILLA_TRICKS* aScintillaTricks )
  83. {
  84. aScintillaTricks->DoTextVarAutocomplete(
  85. // getTokensFn
  86. [this, cell]( const wxString& xRef, wxArrayString* tokens )
  87. {
  88. m_frame->GetContextualTextVars( cell, xRef, tokens );
  89. } );
  90. } ) );
  91. }
  92. m_grid->SetAttr( row, col, attr );
  93. }
  94. }
  95. if( m_table->GetParentFootprint() )
  96. {
  97. // Do not allow locking items in the footprint editor
  98. m_cbLocked->Show( false );
  99. }
  100. // Configure the layers list selector. Note that footprints are built outside the current
  101. // board and so we may need to show all layers if the text is on an unactivated layer.
  102. if( !m_frame->GetBoard()->IsLayerEnabled( m_table->GetLayer() ) )
  103. m_LayerSelectionCtrl->ShowNonActivatedLayers( true );
  104. m_LayerSelectionCtrl->SetLayersHotkeys( false );
  105. m_LayerSelectionCtrl->SetBoardFrame( m_frame );
  106. m_LayerSelectionCtrl->Resync();
  107. for( const auto& [lineStyle, lineStyleDesc] : lineTypeNames )
  108. {
  109. m_borderStyleCombo->Append( lineStyleDesc.name, KiBitmap( lineStyleDesc.bitmap ) );
  110. m_separatorsStyleCombo->Append( lineStyleDesc.name, KiBitmap( lineStyleDesc.bitmap ) );
  111. }
  112. m_borderStyleCombo->Append( DEFAULT_STYLE );
  113. m_separatorsStyleCombo->Append( DEFAULT_STYLE );
  114. SetupStandardButtons();
  115. Layout();
  116. // Now all widgets have the size fixed, call FinishDialogSettings
  117. finishDialogSettings();
  118. }
  119. DIALOG_TABLE_PROPERTIES::~DIALOG_TABLE_PROPERTIES()
  120. {
  121. // Delete the GRID_TRICKS.
  122. m_grid->PopEventHandler( true );
  123. }
  124. bool DIALOG_TABLE_PROPERTIES::TransferDataToWindow()
  125. {
  126. if( !wxDialog::TransferDataToWindow() )
  127. return false;
  128. //
  129. // Cell Contents
  130. //
  131. wxColour coveredColor = wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE );
  132. if( KIPLATFORM::UI::IsDarkTheme() )
  133. coveredColor = coveredColor.ChangeLightness( 140 );
  134. else
  135. coveredColor = coveredColor.ChangeLightness( 100 );
  136. for( int row = 0; row < m_table->GetRowCount(); ++row )
  137. {
  138. for( int col = 0; col < m_table->GetColCount(); ++col )
  139. {
  140. PCB_TABLECELL* tableCell;
  141. if( IsBackLayer( m_table->GetLayer() ) )
  142. tableCell = m_table->GetCell( row, m_table->GetColCount() - 1 - col );
  143. else
  144. tableCell = m_table->GetCell( row, col );
  145. if( tableCell->GetColSpan() == 0 || tableCell->GetRowSpan() == 0 )
  146. m_grid->SetCellValue( row, col, coveredColor.GetAsString() );
  147. else
  148. m_grid->SetCellValue( row, col, tableCell->GetText() );
  149. }
  150. }
  151. CallAfter( [this]()
  152. {
  153. for( int row = 0; row < m_table->GetRowCount(); ++row )
  154. {
  155. for( int col = 0; col < m_table->GetColCount(); ++col )
  156. {
  157. PCB_TABLECELL* tableCell = m_table->GetCell( row, col );
  158. if( tableCell->IsSelected() )
  159. {
  160. m_grid->SetGridCursor( row, col );
  161. m_grid->EnableCellEditControl();
  162. m_grid->ShowCellEditControl();
  163. return;
  164. }
  165. }
  166. }
  167. } );
  168. sizeGridToTable();
  169. //
  170. // Table Properties
  171. //
  172. m_LayerSelectionCtrl->SetLayerSelection( m_table->GetLayer() );
  173. m_cbLocked->SetValue( m_table->IsLocked() );
  174. m_borderCheckbox->SetValue( m_table->StrokeExternal() );
  175. m_headerBorder->SetValue( m_table->StrokeHeader() );
  176. if( m_table->GetBorderStroke().GetWidth() >= 0 )
  177. m_borderWidth.SetValue( m_table->GetBorderStroke().GetWidth() );
  178. int style = static_cast<int>( m_table->GetBorderStroke().GetLineStyle() );
  179. if( style == -1 )
  180. m_borderStyleCombo->SetStringSelection( DEFAULT_STYLE );
  181. else if( style < (int) lineTypeNames.size() )
  182. m_borderStyleCombo->SetSelection( style );
  183. else
  184. wxFAIL_MSG( "Line type not found in the type lookup map" );
  185. m_borderWidth.Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
  186. m_borderStyleLabel->Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
  187. m_borderStyleCombo->Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
  188. bool rows = m_table->StrokeRows() && m_table->GetSeparatorsStroke().GetWidth() >= 0;
  189. bool cols = m_table->StrokeColumns() && m_table->GetSeparatorsStroke().GetWidth() >= 0;
  190. m_rowSeparators->SetValue( rows );
  191. m_colSeparators->SetValue( cols );
  192. if( m_table->GetSeparatorsStroke().GetWidth() >= 0 )
  193. m_separatorsWidth.SetValue( m_table->GetSeparatorsStroke().GetWidth() );
  194. style = static_cast<int>( m_table->GetSeparatorsStroke().GetLineStyle() );
  195. if( style == -1 )
  196. m_separatorsStyleCombo->SetStringSelection( DEFAULT_STYLE );
  197. else if( style < (int) lineTypeNames.size() )
  198. m_separatorsStyleCombo->SetSelection( style );
  199. else
  200. wxFAIL_MSG( "Line type not found in the type lookup map" );
  201. m_separatorsWidth.Enable( rows || cols );
  202. m_separatorsStyleLabel->Enable( rows || cols );
  203. m_separatorsStyleCombo->Enable( rows || cols );
  204. return true;
  205. }
  206. void DIALOG_TABLE_PROPERTIES::onBorderChecked( wxCommandEvent& aEvent )
  207. {
  208. BOARD_DESIGN_SETTINGS& bds = m_frame->GetDesignSettings();
  209. PCB_LAYER_ID currentLayer = ToLAYER_ID( m_LayerSelectionCtrl->GetLayerSelection() );
  210. int defaultLineThickness = bds.GetLineThickness( currentLayer );
  211. bool border = m_borderCheckbox->GetValue();
  212. if( border && m_borderWidth.GetValue() < 0 )
  213. m_borderWidth.SetValue( defaultLineThickness );
  214. m_borderWidth.Enable( border );
  215. m_borderStyleLabel->Enable( border );
  216. m_borderStyleCombo->Enable( border );
  217. bool row = m_rowSeparators->GetValue();
  218. bool col = m_colSeparators->GetValue();
  219. if( ( row || col ) && m_separatorsWidth.GetValue() < 0 )
  220. m_separatorsWidth.SetValue( defaultLineThickness );
  221. m_separatorsWidth.Enable( row || col );
  222. m_separatorsStyleLabel->Enable( row || col );
  223. m_separatorsStyleCombo->Enable( row || col );
  224. }
  225. bool DIALOG_TABLE_PROPERTIES::TransferDataFromWindow()
  226. {
  227. if( !m_grid->CommitPendingChanges() )
  228. return false;
  229. if( !wxDialog::TransferDataFromWindow() )
  230. return false;
  231. BOARD_COMMIT commit( m_frame );
  232. commit.Modify( m_table );
  233. // If no other command in progress, prepare undo command
  234. // (for a command in progress, will be made later, at the completion of command)
  235. bool pushCommit = ( m_table->GetEditFlags() == 0 );
  236. // Set IN_EDIT flag to force undo/redo/abort proper operation and avoid new calls to
  237. // SaveCopyInUndoList for the same text if is moved, and then rotated, edited, etc....
  238. if( !pushCommit )
  239. m_table->SetFlags( IN_EDIT );
  240. for( int row = 0; row < m_table->GetRowCount(); ++row )
  241. {
  242. for( int col = 0; col < m_table->GetColCount(); ++col )
  243. {
  244. PCB_TABLECELL* tableCell;
  245. if( IsBackLayer( m_table->GetLayer() ) )
  246. tableCell = m_table->GetCell( row, m_table->GetColCount() - 1 - col );
  247. else
  248. tableCell = m_table->GetCell( row, col );
  249. wxString txt = m_grid->GetCellValue( row, col );
  250. #ifdef __WXMAC__
  251. // On macOS CTRL+Enter produces '\r' instead of '\n' regardless of EOL setting.
  252. // Replace it now.
  253. txt.Replace( "\r", "\n" );
  254. #elif defined( __WINDOWS__ )
  255. // On Windows, a new line is coded as \r\n. We use only \n in kicad files and in
  256. // drawing routines so strip the \r char.
  257. txt.Replace( "\r", "" );
  258. #endif
  259. tableCell->SetText( txt );
  260. }
  261. }
  262. m_table->SetLayer( ToLAYER_ID( m_LayerSelectionCtrl->GetLayerSelection() ) );
  263. m_table->SetLocked( m_cbLocked->GetValue() );
  264. m_table->SetStrokeExternal( m_borderCheckbox->GetValue() );
  265. m_table->SetStrokeHeader( m_headerBorder->GetValue() );
  266. {
  267. STROKE_PARAMS stroke = m_table->GetBorderStroke();
  268. if( m_borderCheckbox->GetValue() )
  269. stroke.SetWidth( std::max( 0, m_borderWidth.GetIntValue() ) );
  270. else
  271. stroke.SetWidth( -1 );
  272. auto it = lineTypeNames.begin();
  273. std::advance( it, m_borderStyleCombo->GetSelection() );
  274. if( it == lineTypeNames.end() )
  275. stroke.SetLineStyle( LINE_STYLE::DEFAULT );
  276. else
  277. stroke.SetLineStyle( it->first );
  278. m_table->SetBorderStroke( stroke );
  279. }
  280. m_table->SetStrokeRows( m_rowSeparators->GetValue() );
  281. m_table->SetStrokeColumns( m_colSeparators->GetValue() );
  282. {
  283. STROKE_PARAMS stroke = m_table->GetSeparatorsStroke();
  284. if( m_rowSeparators->GetValue() || m_colSeparators->GetValue() )
  285. stroke.SetWidth( std::max( 0, m_separatorsWidth.GetIntValue() ) );
  286. else
  287. stroke.SetWidth( -1 );
  288. auto it = lineTypeNames.begin();
  289. std::advance( it, m_separatorsStyleCombo->GetSelection() );
  290. if( it == lineTypeNames.end() )
  291. stroke.SetLineStyle( LINE_STYLE::DEFAULT );
  292. else
  293. stroke.SetLineStyle( it->first );
  294. m_table->SetSeparatorsStroke( stroke );
  295. }
  296. if( pushCommit )
  297. commit.Push( _( "Edit Table" ), SKIP_CONNECTIVITY );
  298. return true;
  299. }
  300. void DIALOG_TABLE_PROPERTIES::sizeGridToTable()
  301. {
  302. Layout(); // Make sure we get the current client size for the grid
  303. wxSize availableGridSize = m_grid->GetClientSize();
  304. if( availableGridSize.x == 0 || availableGridSize.y == 0 )
  305. return;
  306. BOX2I tableBBox = m_table->GetBoundingBox();
  307. double scalerX = static_cast<double>( availableGridSize.x ) / tableBBox.GetWidth();
  308. double scalerY = static_cast<double>( availableGridSize.y ) / tableBBox.GetHeight();
  309. for( int row = 0; row < m_table->GetRowCount(); ++row )
  310. m_grid->SetRowSize( row, std::floor( m_table->GetRowHeight( row ) * scalerY ) );
  311. for( int col = 0; col < m_table->GetColCount(); ++col )
  312. m_grid->SetColSize( col, std::floor( m_table->GetColWidth( col ) * scalerX ) );
  313. }
  314. void DIALOG_TABLE_PROPERTIES::onSize( wxSizeEvent& aEvent )
  315. {
  316. if( m_table )
  317. sizeGridToTable();
  318. aEvent.Skip();
  319. }