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.

959 lines
34 KiB

5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2009 Wayne Stambaugh <stambaughw@gmail.com>
  5. * Copyright (C) 2014-2024 KiCad Developers, see CHANGELOG.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 "dialog_sheet_properties.h"
  25. #include <kiface_base.h>
  26. #include <wx/string.h>
  27. #include <wx/log.h>
  28. #include <wx/tooltip.h>
  29. #include <common.h>
  30. #include <confirm.h>
  31. #include <kidialog.h>
  32. #include <validators.h>
  33. #include <wx_filename.h>
  34. #include <wildcards_and_files_ext.h>
  35. #include <widgets/std_bitmap_button.h>
  36. #include <kiplatform/ui.h>
  37. #include <sch_commit.h>
  38. #include <sch_edit_frame.h>
  39. #include <sch_io/sch_io.h>
  40. #include <sch_sheet.h>
  41. #include <schematic.h>
  42. #include <bitmaps.h>
  43. #include <eeschema_settings.h>
  44. #include <settings/color_settings.h>
  45. #include <trace_helpers.h>
  46. #include "panel_eeschema_color_settings.h"
  47. #include "wx/dcclient.h"
  48. #include "string_utils.h"
  49. DIALOG_SHEET_PROPERTIES::DIALOG_SHEET_PROPERTIES( SCH_EDIT_FRAME* aParent, SCH_SHEET* aSheet,
  50. bool* aIsUndoable, bool* aClearAnnotationNewItems,
  51. bool* aUpdateHierarchyNavigator,
  52. wxString* aSourceSheetFilename ) :
  53. DIALOG_SHEET_PROPERTIES_BASE( aParent ),
  54. m_frame( aParent ),
  55. m_isUndoable( aIsUndoable ),
  56. m_clearAnnotationNewItems( aClearAnnotationNewItems ),
  57. m_updateHierarchyNavigator( aUpdateHierarchyNavigator ),
  58. m_sourceSheetFilename( aSourceSheetFilename ),
  59. m_borderWidth( aParent, m_borderWidthLabel, m_borderWidthCtrl, m_borderWidthUnits ),
  60. m_dummySheet( *aSheet ),
  61. m_dummySheetNameField( VECTOR2I( -1, -1 ), SHEETNAME, &m_dummySheet )
  62. {
  63. m_sheet = aSheet;
  64. m_fields = new FIELDS_GRID_TABLE( this, aParent, m_grid, m_sheet );
  65. m_delayedFocusRow = SHEETNAME;
  66. m_delayedFocusColumn = FDC_VALUE;
  67. // Give a bit more room for combobox editors
  68. m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
  69. m_grid->SetTable( m_fields );
  70. m_grid->PushEventHandler( new FIELDS_GRID_TRICKS( m_grid, this, &aParent->Schematic(),
  71. [&]( wxCommandEvent& aEvent )
  72. {
  73. OnAddField( aEvent );
  74. } ) );
  75. m_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
  76. // Show/hide columns according to user's preference
  77. if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
  78. {
  79. m_grid->ShowHideColumns( cfg->m_Appearance.edit_sheet_visible_columns );
  80. m_shownColumns = m_grid->GetShownColumns();
  81. }
  82. if( m_frame->GetColorSettings()->GetOverrideSchItemColors() )
  83. m_infoBar->ShowMessage( _( "Note: individual item colors overridden in Preferences." ) );
  84. wxSize minSize = m_pageNumberTextCtrl->GetMinSize();
  85. int minWidth = m_pageNumberTextCtrl->GetTextExtent( wxT( "XXX.XXX" ) ).GetWidth();
  86. m_pageNumberTextCtrl->SetMinSize( wxSize( minWidth, minSize.GetHeight() ) );
  87. wxToolTip::Enable( true );
  88. SetupStandardButtons();
  89. // Configure button logos
  90. m_bpAdd->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
  91. m_bpDelete->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
  92. m_bpMoveUp->SetBitmap( KiBitmapBundle( BITMAPS::small_up ) );
  93. m_bpMoveDown->SetBitmap( KiBitmapBundle( BITMAPS::small_down ) );
  94. // Set font sizes
  95. m_hierarchicalPathLabel->SetFont( KIUI::GetInfoFont( this ) );
  96. m_hierarchicalPath->SetFont( KIUI::GetInfoFont( this ) );
  97. // wxFormBuilder doesn't include this event...
  98. m_grid->Connect( wxEVT_GRID_CELL_CHANGING,
  99. wxGridEventHandler( DIALOG_SHEET_PROPERTIES::OnGridCellChanging ),
  100. nullptr, this );
  101. }
  102. DIALOG_SHEET_PROPERTIES::~DIALOG_SHEET_PROPERTIES()
  103. {
  104. if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
  105. {
  106. cfg->m_Appearance.edit_sheet_visible_columns = m_grid->GetShownColumnsAsString();
  107. cfg->m_Appearance.edit_sheet_width = GetSize().x;
  108. cfg->m_Appearance.edit_sheet_height = GetSize().y;
  109. }
  110. // Prevents crash bug in wxGrid's d'tor
  111. m_grid->DestroyTable( m_fields );
  112. m_grid->Disconnect( wxEVT_GRID_CELL_CHANGING,
  113. wxGridEventHandler( DIALOG_SHEET_PROPERTIES::OnGridCellChanging ),
  114. nullptr, this );
  115. // Delete the GRID_TRICKS.
  116. m_grid->PopEventHandler( true );
  117. }
  118. bool DIALOG_SHEET_PROPERTIES::TransferDataToWindow()
  119. {
  120. if( !wxDialog::TransferDataToWindow() )
  121. return false;
  122. // Push a copy of each field into m_updateFields
  123. for( SCH_FIELD& field : m_sheet->GetFields() )
  124. {
  125. SCH_FIELD field_copy( field );
  126. #ifdef __WINDOWS__
  127. // Filenames are stored using unix notation
  128. if( field_copy.GetId() == SHEETFILENAME )
  129. {
  130. wxString filename = field_copy.GetText();
  131. filename.Replace( wxT( "/" ), wxT( "\\" ) );
  132. field_copy.SetText( filename );
  133. }
  134. #endif
  135. // change offset to be symbol-relative
  136. field_copy.Offset( -m_sheet->GetPosition() );
  137. m_fields->push_back( field_copy );
  138. }
  139. // notify the grid
  140. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_fields->size() );
  141. m_grid->ProcessTableMessage( msg );
  142. AdjustGridColumns();
  143. // border width
  144. m_borderWidth.SetValue( m_sheet->GetBorderWidth() );
  145. // set up color swatches
  146. KIGFX::COLOR4D borderColor = m_sheet->GetBorderColor();
  147. KIGFX::COLOR4D backgroundColor = m_sheet->GetBackgroundColor();
  148. m_borderSwatch->SetDefaultColor( COLOR4D::UNSPECIFIED );
  149. m_backgroundSwatch->SetDefaultColor( COLOR4D::UNSPECIFIED );
  150. m_borderSwatch->SetSwatchColor( borderColor, false );
  151. m_backgroundSwatch->SetSwatchColor( backgroundColor, false );
  152. KIGFX::COLOR4D canvas = m_frame->GetColorSettings()->GetColor( LAYER_SCHEMATIC_BACKGROUND );
  153. m_borderSwatch->SetSwatchBackground( canvas );
  154. m_backgroundSwatch->SetSwatchBackground( canvas );
  155. SCH_SHEET_PATH instance = m_frame->GetCurrentSheet();
  156. instance.push_back( m_sheet );
  157. m_pageNumberTextCtrl->ChangeValue( instance.GetPageNumber() );
  158. m_cbExcludeFromSim->SetValue( m_sheet->GetExcludedFromSim() );
  159. m_cbExcludeFromBom->SetValue( m_sheet->GetExcludedFromBOM() );
  160. m_cbExcludeFromBoard->SetValue( m_sheet->GetExcludedFromBoard() );
  161. m_cbDNP->SetValue( m_sheet->GetDNP() );
  162. return true;
  163. }
  164. bool DIALOG_SHEET_PROPERTIES::Validate()
  165. {
  166. LIB_ID id;
  167. if( !m_grid->CommitPendingChanges() || !m_grid->Validate() )
  168. return false;
  169. // Check for missing field names.
  170. for( size_t i = SHEET_MANDATORY_FIELDS; i < m_fields->size(); ++i )
  171. {
  172. SCH_FIELD& field = m_fields->at( i );
  173. if( field.GetName( false ).empty() && !field.GetText().empty() )
  174. {
  175. DisplayErrorMessage( this, _( "Fields must have a name." ) );
  176. m_delayedFocusColumn = FDC_NAME;
  177. m_delayedFocusRow = i;
  178. return false;
  179. }
  180. }
  181. return true;
  182. }
  183. static bool positioningChanged( const SCH_FIELD& a, const SCH_FIELD& b )
  184. {
  185. if( a.GetPosition() != b.GetPosition() )
  186. return true;
  187. if( a.GetHorizJustify() != b.GetHorizJustify() )
  188. return true;
  189. if( a.GetVertJustify() != b.GetVertJustify() )
  190. return true;
  191. if( a.GetTextAngle() != b.GetTextAngle() )
  192. return true;
  193. return false;
  194. }
  195. static bool positioningChanged( FIELDS_GRID_TABLE* a, std::vector<SCH_FIELD>& b )
  196. {
  197. for( size_t i = 0; i < SHEET_MANDATORY_FIELDS; ++i )
  198. {
  199. if( positioningChanged( a->at( i ), b.at( i ) ) )
  200. return true;
  201. }
  202. return false;
  203. }
  204. bool DIALOG_SHEET_PROPERTIES::TransferDataFromWindow()
  205. {
  206. wxCHECK( m_sheet && m_frame, false );
  207. if( !wxDialog::TransferDataFromWindow() ) // Calls our Validate() method.
  208. return false;
  209. SCH_COMMIT commit( m_frame );
  210. commit.Modify( m_sheet, m_frame->GetScreen() );
  211. if( m_isUndoable )
  212. *m_isUndoable = true;
  213. // Sheet file names can be relative or absolute.
  214. wxString sheetFileName = m_fields->at( SHEETFILENAME ).GetText();
  215. // Ensure filepath is not empty. (In normal use will be caught by grid validators,
  216. // but unedited data from existing files can be bad.)
  217. if( sheetFileName.IsEmpty() )
  218. {
  219. DisplayError( this, _( "A sheet must have a valid file name." ) );
  220. return false;
  221. }
  222. // Ensure the filename extension is OK. (In normal use will be caught by grid validators,
  223. // but unedited data from existing files can be bad.)
  224. sheetFileName = EnsureFileExtension( sheetFileName, FILEEXT::KiCadSchematicFileExtension );
  225. // Ensure sheetFileName is legal
  226. if( !IsFullFileNameValid( sheetFileName ) )
  227. {
  228. DisplayError( this, _( "A sheet must have a valid file name." ) );
  229. return false;
  230. }
  231. wxFileName fn( sheetFileName );
  232. wxString newRelativeFilename = fn.GetFullPath();
  233. // Inside Eeschema, filenames are stored using unix notation
  234. newRelativeFilename.Replace( wxT( "\\" ), wxT( "/" ) );
  235. wxString oldFilename = m_sheet->GetFields()[ SHEETFILENAME ].GetText();
  236. oldFilename.Replace( wxT( "\\" ), wxT( "/" ) );
  237. bool filename_changed = oldFilename != newRelativeFilename;
  238. if( filename_changed || m_sheet->IsNew() )
  239. {
  240. SCH_SCREEN* currentScreen = m_frame->GetCurrentSheet().LastScreen();
  241. wxCHECK( currentScreen, false );
  242. bool clearFileName = false;
  243. // This can happen for the root sheet when opening Eeschema in the stand alone mode.
  244. if( currentScreen->GetFileName().IsEmpty() )
  245. {
  246. clearFileName = true;
  247. currentScreen->SetFileName( m_frame->Prj().AbsolutePath( wxT( "noname.kicad_sch" ) ) );
  248. }
  249. wxFileName tmp( fn );
  250. wxFileName screenFileName = currentScreen->GetFileName();
  251. if( fn.IsAbsolute() && fn.MakeRelativeTo( screenFileName.GetPath() ) )
  252. {
  253. wxMessageDialog makeRelDlg( this, _( "Use relative path for sheet file?" ),
  254. _( "Sheet File Path" ),
  255. wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTER );
  256. makeRelDlg.SetExtendedMessage( _( "Using relative hierarchical sheet file name paths "
  257. "improves schematic portability across systems and "
  258. "platforms. Using absolute paths can result in "
  259. "portability issues." ) );
  260. makeRelDlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Use Relative Path" ) ),
  261. wxMessageDialog::ButtonLabel( _( "Use Absolute Path" ) ) );
  262. if( makeRelDlg.ShowModal() == wxID_YES )
  263. {
  264. wxLogTrace( tracePathsAndFiles, "\n Converted absolute path: '%s'"
  265. "\n to relative path: '%s'",
  266. tmp.GetPath(),
  267. fn.GetPath() );
  268. m_fields->at( SHEETFILENAME ).SetText( fn.GetFullPath() );
  269. newRelativeFilename = fn.GetFullPath();
  270. }
  271. }
  272. if( !onSheetFilenameChanged( newRelativeFilename ) )
  273. {
  274. if( clearFileName )
  275. currentScreen->SetFileName( wxEmptyString );
  276. return false;
  277. }
  278. else if( m_updateHierarchyNavigator )
  279. {
  280. *m_updateHierarchyNavigator = true;
  281. }
  282. if( clearFileName )
  283. currentScreen->SetFileName( wxEmptyString );
  284. // One last validity check (and potential repair) just to be sure to be sure
  285. SCH_SHEET_LIST repairedList;
  286. repairedList.BuildSheetList( &m_frame->Schematic().Root(), true );
  287. }
  288. wxString newSheetname = m_fields->at( SHEETNAME ).GetText();
  289. if( ( newSheetname != m_sheet->GetName() ) && m_updateHierarchyNavigator )
  290. *m_updateHierarchyNavigator = true;
  291. if( newSheetname.IsEmpty() )
  292. newSheetname = _( "Untitled Sheet" );
  293. m_fields->at( SHEETNAME ).SetText( newSheetname );
  294. // change all field positions from relative to absolute
  295. for( unsigned i = 0; i < m_fields->size(); ++i )
  296. m_fields->at( i ).Offset( m_sheet->GetPosition() );
  297. if( positioningChanged( m_fields, m_sheet->GetFields() ) )
  298. m_sheet->ClearFieldsAutoplaced();
  299. for( int ii = m_fields->GetNumberRows() - 1; ii >= SHEET_MANDATORY_FIELDS; ii-- )
  300. {
  301. SCH_FIELD& field = m_fields->at( ii );
  302. const wxString& fieldName = field.GetCanonicalName();
  303. if( field.IsEmpty() )
  304. m_fields->erase( m_fields->begin() + ii );
  305. else if( fieldName.IsEmpty() )
  306. field.SetName( _( "untitled" ) );
  307. }
  308. m_sheet->SetFields( *m_fields );
  309. m_sheet->SetBorderWidth( m_borderWidth.GetIntValue() );
  310. COLOR_SETTINGS* colorSettings = m_frame->GetColorSettings();
  311. if( colorSettings->GetOverrideSchItemColors()
  312. && ( m_sheet->GetBorderColor() != m_borderSwatch->GetSwatchColor() ||
  313. m_sheet->GetBackgroundColor() != m_backgroundSwatch->GetSwatchColor() ) )
  314. {
  315. wxPanel temp( this );
  316. temp.Hide();
  317. PANEL_EESCHEMA_COLOR_SETTINGS prefs( &temp );
  318. wxString checkboxLabel = prefs.m_optOverrideColors->GetLabel();
  319. KIDIALOG dlg( this, _( "Note: item colors are overridden in the current color theme." ),
  320. KIDIALOG::KD_WARNING );
  321. dlg.ShowDetailedText( wxString::Format( _( "To see individual item colors uncheck '%s'\n"
  322. "in Preferences > Schematic Editor > Colors." ),
  323. checkboxLabel ) );
  324. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  325. dlg.ShowModal();
  326. }
  327. m_sheet->SetBorderColor( m_borderSwatch->GetSwatchColor() );
  328. m_sheet->SetBackgroundColor( m_backgroundSwatch->GetSwatchColor() );
  329. m_sheet->SetExcludedFromSim( m_cbExcludeFromSim->GetValue() );
  330. m_sheet->SetExcludedFromBOM( m_cbExcludeFromBom->GetValue() );
  331. m_sheet->SetExcludedFromBoard( m_cbExcludeFromBoard->GetValue() );
  332. m_sheet->SetDNP( m_cbDNP->GetValue() );
  333. SCH_SHEET_PATH instance = m_frame->GetCurrentSheet();
  334. instance.push_back( m_sheet );
  335. instance.SetPageNumber( m_pageNumberTextCtrl->GetValue() );
  336. m_frame->TestDanglingEnds();
  337. // Refresh all sheets in case ordering changed.
  338. for( SCH_ITEM* item : m_frame->GetScreen()->Items().OfType( SCH_SHEET_T ) )
  339. m_frame->UpdateItem( item );
  340. return true;
  341. }
  342. bool DIALOG_SHEET_PROPERTIES::onSheetFilenameChanged( const wxString& aNewFilename )
  343. {
  344. wxString msg;
  345. wxFileName sheetFileName( EnsureFileExtension( aNewFilename,
  346. FILEEXT::KiCadSchematicFileExtension ) );
  347. // Sheet file names are relative to the path of the current sheet. This allows for
  348. // nesting of schematic files in subfolders. Screen file names are always absolute.
  349. SCHEMATIC& schematic = m_frame->Schematic();
  350. SCH_SHEET_LIST fullHierarchy = schematic.Hierarchy();
  351. wxFileName screenFileName( sheetFileName );
  352. wxFileName tmp( sheetFileName );
  353. SCH_SCREEN* currentScreen = m_frame->GetCurrentSheet().LastScreen();
  354. wxCHECK( currentScreen, false );
  355. // SCH_SCREEN file names are always absolute.
  356. wxFileName currentScreenFileName = currentScreen->GetFileName();
  357. if( !screenFileName.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS,
  358. currentScreenFileName.GetPath() ) )
  359. {
  360. msg = wxString::Format( _( "Cannot normalize new sheet schematic file path:\n"
  361. "'%s'\n"
  362. "against parent sheet schematic file path:\n"
  363. "'%s'." ),
  364. sheetFileName.GetPath(),
  365. currentScreenFileName.GetPath() );
  366. DisplayError( this, msg );
  367. return false;
  368. }
  369. wxString newAbsoluteFilename = screenFileName.GetFullPath();
  370. // Inside Eeschema, filenames are stored using unix notation
  371. newAbsoluteFilename.Replace( wxT( "\\" ), wxT( "/" ) );
  372. bool renameFile = false;
  373. bool loadFromFile = false;
  374. bool clearAnnotation = false;
  375. bool isExistingSheet = false;
  376. SCH_SCREEN* useScreen = nullptr;
  377. SCH_SCREEN* oldScreen = nullptr;
  378. // Search for a schematic file having the same filename already in use in the hierarchy
  379. // or on disk, in order to reuse it.
  380. if( !schematic.Root().SearchHierarchy( newAbsoluteFilename, &useScreen ) )
  381. {
  382. loadFromFile = wxFileExists( newAbsoluteFilename );
  383. wxLogTrace( tracePathsAndFiles, "\n Sheet requested file '%s', %s",
  384. newAbsoluteFilename,
  385. loadFromFile ? "found" : "not found" );
  386. }
  387. if( m_sheet->GetScreen() == nullptr ) // New just created sheet.
  388. {
  389. if( !m_frame->AllowCaseSensitiveFileNameClashes( m_sheet->GetFileName(), newAbsoluteFilename ) )
  390. return false;
  391. if( useScreen || loadFromFile ) // Load from existing file.
  392. {
  393. clearAnnotation = true;
  394. if( !IsOK( this, wxString::Format( _( "'%s' already exists." ),
  395. sheetFileName.GetFullName() )
  396. + wxT( "\n\n" )
  397. + wxString::Format( _( "Link '%s' to this file?" ),
  398. newAbsoluteFilename ) ) )
  399. {
  400. return false;
  401. }
  402. }
  403. // If we are drawing a sheet from a design block/sheet import, we need to copy the sheet to the current directory.
  404. else if( m_sourceSheetFilename && !m_sourceSheetFilename->IsEmpty() )
  405. {
  406. loadFromFile = true;
  407. if( !wxCopyFile( *m_sourceSheetFilename, newAbsoluteFilename, false ) )
  408. {
  409. msg.Printf( _( "Failed to copy schematic file '%s' to destination '%s'." ),
  410. currentScreenFileName.GetFullPath(), newAbsoluteFilename );
  411. DisplayError( m_frame, msg );
  412. return false;
  413. }
  414. }
  415. else // New file.
  416. {
  417. m_frame->InitSheet( m_sheet, newAbsoluteFilename );
  418. }
  419. }
  420. else // Existing sheet.
  421. {
  422. isExistingSheet = true;
  423. if( !m_frame->AllowCaseSensitiveFileNameClashes( m_sheet->GetFileName(), newAbsoluteFilename ) )
  424. return false;
  425. // We are always using here a case insensitive comparison to avoid issues
  426. // under Windows, although under Unix filenames are case sensitive.
  427. // But many users create schematic under both Unix and Windows
  428. // **
  429. // N.B. 1: aSheet->GetFileName() will return a relative path
  430. // aSheet->GetScreen()->GetFileName() returns a full path
  431. //
  432. // N.B. 2: newFilename uses the unix notation for separator.
  433. // so we must use it also to compare the old and new filenames
  434. wxString oldAbsoluteFilename = m_sheet->GetScreen()->GetFileName();
  435. oldAbsoluteFilename.Replace( wxT( "\\" ), wxT( "/" ) );
  436. if( newAbsoluteFilename.Cmp( oldAbsoluteFilename ) != 0 )
  437. {
  438. // Sheet file name changes cannot be undone.
  439. if( m_isUndoable )
  440. *m_isUndoable = false;
  441. if( useScreen || loadFromFile ) // Load from existing file.
  442. {
  443. clearAnnotation = true;
  444. if( !IsOK( this, wxString::Format( _( "Change '%s' link from '%s' to '%s'?" ),
  445. newAbsoluteFilename,
  446. m_sheet->GetFileName(),
  447. sheetFileName.GetFullName() )
  448. + wxT( "\n\n" )
  449. + _( "This action cannot be undone." ) ) )
  450. {
  451. return false;
  452. }
  453. if( loadFromFile )
  454. m_sheet->SetScreen( nullptr );
  455. }
  456. else // Save to new file name.
  457. {
  458. if( m_sheet->GetScreenCount() > 1 )
  459. {
  460. if( !IsOK( this, wxString::Format( _( "Create new file '%s' with contents "
  461. "of '%s'?" ),
  462. sheetFileName.GetFullName(),
  463. m_sheet->GetFileName() )
  464. + wxT( "\n\n" )
  465. + _( "This action cannot be undone." ) ) )
  466. {
  467. return false;
  468. }
  469. }
  470. renameFile = true;
  471. }
  472. }
  473. if( renameFile )
  474. {
  475. IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
  476. // If the associated screen is shared by more than one sheet, do not
  477. // change the filename of the corresponding screen here.
  478. // (a new screen will be created later)
  479. // if it is not shared, update the filename
  480. if( m_sheet->GetScreenCount() <= 1 )
  481. m_sheet->GetScreen()->SetFileName( newAbsoluteFilename );
  482. try
  483. {
  484. pi->SaveSchematicFile( newAbsoluteFilename, m_sheet, &schematic );
  485. }
  486. catch( const IO_ERROR& ioe )
  487. {
  488. msg = wxString::Format( _( "Error occurred saving schematic file '%s'." ),
  489. newAbsoluteFilename );
  490. DisplayErrorMessage( this, msg, ioe.What() );
  491. msg = wxString::Format( _( "Failed to save schematic '%s'" ),
  492. newAbsoluteFilename );
  493. m_frame->SetMsgPanel( wxEmptyString, msg );
  494. return false;
  495. }
  496. // If the associated screen is shared by more than one sheet, remove the
  497. // screen and reload the file to a new screen. Failure to do this will trash
  498. // the screen reference counting in complex hierarchies.
  499. if( m_sheet->GetScreenCount() > 1 )
  500. {
  501. oldScreen = m_sheet->GetScreen();
  502. m_sheet->SetScreen( nullptr );
  503. loadFromFile = true;
  504. }
  505. }
  506. }
  507. SCH_SHEET_PATH& currentSheet = m_frame->GetCurrentSheet();
  508. if( useScreen )
  509. {
  510. // Create a temporary sheet for recursion testing to prevent a possible recursion error.
  511. std::unique_ptr< SCH_SHEET> tmpSheet = std::make_unique<SCH_SHEET>( &schematic );
  512. tmpSheet->GetFields()[SHEETNAME] = m_fields->at( SHEETNAME );
  513. tmpSheet->GetFields()[SHEETFILENAME].SetText( sheetFileName.GetFullPath() );
  514. tmpSheet->SetScreen( useScreen );
  515. // No need to check for valid library IDs if we are using an existing screen.
  516. if( m_frame->CheckSheetForRecursion( tmpSheet.get(), &currentSheet ) )
  517. return false;
  518. // It's safe to set the sheet screen now.
  519. m_sheet->SetScreen( useScreen );
  520. SCH_SHEET_LIST sheetHierarchy( m_sheet ); // The hierarchy of the loaded file.
  521. sheetHierarchy.AddNewSymbolInstances( currentSheet, m_frame->Prj().GetProjectName() );
  522. sheetHierarchy.AddNewSheetInstances( currentSheet,
  523. fullHierarchy.GetLastVirtualPageNumber() );
  524. }
  525. else if( loadFromFile )
  526. {
  527. bool restoreSheet = false;
  528. if( isExistingSheet )
  529. {
  530. // Temporarily remove the sheet from the current schematic page so that recursion
  531. // and symbol library link tests can be performed with the modified sheet settings.
  532. restoreSheet = true;
  533. currentSheet.LastScreen()->Remove( m_sheet );
  534. }
  535. if( !m_frame->LoadSheetFromFile( m_sheet, &currentSheet, newAbsoluteFilename )
  536. || m_frame->CheckSheetForRecursion( m_sheet, &currentSheet ) )
  537. {
  538. if( restoreSheet )
  539. {
  540. // If we cleared the previous screen, restore it before returning to the user
  541. if( oldScreen )
  542. m_sheet->SetScreen( oldScreen );
  543. currentSheet.LastScreen()->Append( m_sheet );
  544. }
  545. return false;
  546. }
  547. if( restoreSheet )
  548. currentSheet.LastScreen()->Append( m_sheet );
  549. }
  550. if( m_clearAnnotationNewItems )
  551. *m_clearAnnotationNewItems = clearAnnotation;
  552. // Rebuild the entire connection graph.
  553. m_frame->RecalculateConnections( nullptr, GLOBAL_CLEANUP );
  554. return true;
  555. }
  556. void DIALOG_SHEET_PROPERTIES::OnGridCellChanging( wxGridEvent& event )
  557. {
  558. bool success = true;
  559. wxGridCellEditor* editor = m_grid->GetCellEditor( event.GetRow(), event.GetCol() );
  560. wxControl* control = editor->GetControl();
  561. wxTextEntry* textControl = dynamic_cast<wxTextEntry*>( control );
  562. // Short-circuit the validator's more generic "can't be empty" message for the
  563. // two mandatory fields:
  564. if( event.GetRow() == SHEETNAME && event.GetCol() == FDC_VALUE )
  565. {
  566. if( textControl && textControl->IsEmpty() )
  567. {
  568. wxMessageBox( _( "A sheet must have a name." ) );
  569. success = false;
  570. }
  571. }
  572. else if( event.GetRow() == SHEETFILENAME && event.GetCol() == FDC_VALUE && textControl )
  573. {
  574. if( textControl->IsEmpty() )
  575. {
  576. wxMessageBox( _( "A sheet must have a file specified." ) );
  577. success = false;
  578. }
  579. }
  580. if( success && control && control->GetValidator() )
  581. success = control->GetValidator()->Validate( control );
  582. if( !success )
  583. {
  584. event.Veto();
  585. m_delayedFocusRow = event.GetRow();
  586. m_delayedFocusColumn = event.GetCol();
  587. }
  588. editor->DecRef();
  589. }
  590. void DIALOG_SHEET_PROPERTIES::OnAddField( wxCommandEvent& event )
  591. {
  592. if( !m_grid->CommitPendingChanges() )
  593. return;
  594. int fieldID = m_fields->size();
  595. SCH_FIELD newField( VECTOR2I( 0, 0 ), fieldID, m_sheet,
  596. SCH_SHEET::GetDefaultFieldName( fieldID ) );
  597. newField.SetTextAngle( m_fields->at( SHEETNAME ).GetTextAngle() );
  598. m_fields->push_back( newField );
  599. // notify the grid
  600. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
  601. m_grid->ProcessTableMessage( msg );
  602. m_grid->MakeCellVisible( m_fields->size() - 1, 0 );
  603. m_grid->SetGridCursor( m_fields->size() - 1, 0 );
  604. m_grid->EnableCellEditControl();
  605. m_grid->ShowCellEditControl();
  606. }
  607. void DIALOG_SHEET_PROPERTIES::OnDeleteField( wxCommandEvent& event )
  608. {
  609. wxArrayInt selectedRows = m_grid->GetSelectedRows();
  610. if( selectedRows.empty() && m_grid->GetGridCursorRow() >= 0 )
  611. selectedRows.push_back( m_grid->GetGridCursorRow() );
  612. if( selectedRows.empty() )
  613. return;
  614. for( int row : selectedRows )
  615. {
  616. if( row < SHEET_MANDATORY_FIELDS )
  617. {
  618. DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
  619. SHEET_MANDATORY_FIELDS ) );
  620. return;
  621. }
  622. }
  623. m_grid->CommitPendingChanges( true /* quiet mode */ );
  624. // Reverse sort so deleting a row doesn't change the indexes of the other rows.
  625. selectedRows.Sort( []( int* first, int* second ) { return *second - *first; } );
  626. for( int row : selectedRows )
  627. {
  628. m_fields->erase( m_fields->begin() + row );
  629. // notify the grid
  630. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, row, 1 );
  631. m_grid->ProcessTableMessage( msg );
  632. if( m_grid->GetNumberRows() > 0 )
  633. {
  634. m_grid->MakeCellVisible( std::max( 0, row-1 ), m_grid->GetGridCursorCol() );
  635. m_grid->SetGridCursor( std::max( 0, row-1 ), m_grid->GetGridCursorCol() );
  636. }
  637. }
  638. }
  639. void DIALOG_SHEET_PROPERTIES::OnMoveUp( wxCommandEvent& event )
  640. {
  641. if( !m_grid->CommitPendingChanges() )
  642. return;
  643. int i = m_grid->GetGridCursorRow();
  644. if( i > SHEET_MANDATORY_FIELDS )
  645. {
  646. SCH_FIELD tmp = m_fields->at( (unsigned) i );
  647. m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
  648. m_fields->insert( m_fields->begin() + i - 1, tmp );
  649. m_grid->ForceRefresh();
  650. m_grid->SetGridCursor( i - 1, m_grid->GetGridCursorCol() );
  651. m_grid->MakeCellVisible( m_grid->GetGridCursorRow(), m_grid->GetGridCursorCol() );
  652. }
  653. else
  654. {
  655. wxBell();
  656. }
  657. }
  658. void DIALOG_SHEET_PROPERTIES::OnMoveDown( wxCommandEvent& event )
  659. {
  660. if( !m_grid->CommitPendingChanges() )
  661. return;
  662. int i = m_grid->GetGridCursorRow();
  663. if( i >= SHEET_MANDATORY_FIELDS && i < m_grid->GetNumberRows() - 1 )
  664. {
  665. SCH_FIELD tmp = m_fields->at( (unsigned) i );
  666. m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
  667. m_fields->insert( m_fields->begin() + i + 1, tmp );
  668. m_grid->ForceRefresh();
  669. m_grid->SetGridCursor( i + 1, m_grid->GetGridCursorCol() );
  670. m_grid->MakeCellVisible( m_grid->GetGridCursorRow(), m_grid->GetGridCursorCol() );
  671. }
  672. else
  673. {
  674. wxBell();
  675. }
  676. }
  677. void DIALOG_SHEET_PROPERTIES::AdjustGridColumns()
  678. {
  679. // Account for scroll bars
  680. int width = KIPLATFORM::UI::GetUnobscuredSize( m_grid ).x;
  681. m_grid->AutoSizeColumn( 0 );
  682. m_grid->SetColSize( 0, std::max( 72, m_grid->GetColSize( 0 ) ) );
  683. int fixedColsWidth = m_grid->GetColSize( 0 );
  684. for( int i = 2; i < m_grid->GetNumberCols(); i++ )
  685. fixedColsWidth += m_grid->GetColSize( i );
  686. m_grid->SetColSize( 1, std::max( 120, width - fixedColsWidth ) );
  687. }
  688. void DIALOG_SHEET_PROPERTIES::OnUpdateUI( wxUpdateUIEvent& event )
  689. {
  690. std::bitset<64> shownColumns = m_grid->GetShownColumns();
  691. if( shownColumns != m_shownColumns )
  692. {
  693. m_shownColumns = shownColumns;
  694. if( !m_grid->IsCellEditControlShown() )
  695. AdjustGridColumns();
  696. }
  697. // Propagate changes in sheetname to displayed hierarchical path
  698. wxString path = m_frame->GetCurrentSheet().PathHumanReadable( false );
  699. if( path.Last() != '/' )
  700. path.Append( '/' );
  701. wxGridCellEditor* editor = m_grid->GetCellEditor( SHEETNAME, FDC_VALUE );
  702. wxControl* control = editor->GetControl();
  703. wxTextEntry* textControl = dynamic_cast<wxTextEntry*>( control );
  704. wxString sheetName;
  705. if( textControl )
  706. sheetName = textControl->GetValue();
  707. else
  708. sheetName = m_grid->GetCellValue( SHEETNAME, FDC_VALUE );
  709. m_dummySheet.SetFields( *m_fields );
  710. m_dummySheetNameField.SetText( sheetName );
  711. path += m_dummySheetNameField.GetShownText( false );
  712. editor->DecRef();
  713. wxClientDC dc( m_hierarchicalPathLabel );
  714. int width = m_sizerBottom->GetSize().x - m_stdDialogButtonSizer->GetSize().x
  715. - m_hierarchicalPathLabel->GetSize().x
  716. - 30;
  717. path = wxControl::Ellipsize( path, dc, wxELLIPSIZE_START, width, wxELLIPSIZE_FLAGS_NONE );
  718. if( m_hierarchicalPath->GetLabel() != path )
  719. m_hierarchicalPath->SetLabel( path );
  720. // Handle a delayed focus
  721. if( m_delayedFocusRow >= 0 )
  722. {
  723. m_grid->SetFocus();
  724. m_grid->MakeCellVisible( m_delayedFocusRow, m_delayedFocusColumn );
  725. m_grid->SetGridCursor( m_delayedFocusRow, m_delayedFocusColumn );
  726. m_grid->EnableCellEditControl( true );
  727. m_grid->ShowCellEditControl();
  728. m_delayedFocusRow = -1;
  729. m_delayedFocusColumn = -1;
  730. }
  731. }
  732. void DIALOG_SHEET_PROPERTIES::OnSizeGrid( wxSizeEvent& event )
  733. {
  734. auto new_size = event.GetSize();
  735. if( m_size != new_size )
  736. {
  737. m_size = new_size;
  738. AdjustGridColumns();
  739. }
  740. // Always propagate for a grid repaint (needed if the height changes, as well as width)
  741. event.Skip();
  742. }
  743. void DIALOG_SHEET_PROPERTIES::OnInitDlg( wxInitDialogEvent& event )
  744. {
  745. TransferDataToWindow();
  746. // Now all widgets have the size fixed, call FinishDialogSettings
  747. finishDialogSettings();
  748. EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  749. if( cfg && cfg->m_Appearance.edit_sheet_width > 0 && cfg->m_Appearance.edit_sheet_height > 0 )
  750. SetSize( cfg->m_Appearance.edit_sheet_width, cfg->m_Appearance.edit_sheet_height );
  751. }