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.

955 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 The 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 ), FIELD_T::SHEET_NAME, &m_dummySheet )
  62. {
  63. m_sheet = aSheet;
  64. m_fields = new FIELDS_GRID_TABLE( this, aParent, m_grid, m_sheet );
  65. m_delayedFocusRow = 0;
  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, so convert to Windows notation
  128. if( field_copy.GetId() == FIELD_T::SHEET_FILENAME )
  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 = 0; i < m_fields->size(); ++i )
  171. {
  172. SCH_FIELD& field = m_fields->at( i );
  173. if( field.IsMandatory() )
  174. continue;
  175. if( field.GetName( false ).empty() && !field.GetText().empty() )
  176. {
  177. DisplayErrorMessage( this, _( "Fields must have a name." ) );
  178. m_delayedFocusColumn = FDC_NAME;
  179. m_delayedFocusRow = (int) i;
  180. return false;
  181. }
  182. }
  183. return true;
  184. }
  185. static bool positioningChanged( const SCH_FIELD& a, const SCH_FIELD& b )
  186. {
  187. if( a.GetPosition() != b.GetPosition() )
  188. return true;
  189. if( a.GetHorizJustify() != b.GetHorizJustify() )
  190. return true;
  191. if( a.GetVertJustify() != b.GetVertJustify() )
  192. return true;
  193. if( a.GetTextAngle() != b.GetTextAngle() )
  194. return true;
  195. return false;
  196. }
  197. static bool positioningChanged( FIELDS_GRID_TABLE* a, SCH_SHEET* b )
  198. {
  199. if( positioningChanged( a->GetField( FIELD_T::SHEET_NAME ),
  200. b->GetField( FIELD_T::SHEET_NAME ) ) )
  201. {
  202. return true;
  203. }
  204. if( positioningChanged( a->GetField( FIELD_T::SHEET_FILENAME ),
  205. b->GetField( FIELD_T::SHEET_FILENAME ) ) )
  206. {
  207. return true;
  208. }
  209. return false;
  210. }
  211. bool DIALOG_SHEET_PROPERTIES::TransferDataFromWindow()
  212. {
  213. wxCHECK( m_sheet && m_frame, false );
  214. if( !wxDialog::TransferDataFromWindow() ) // Calls our Validate() method.
  215. return false;
  216. if( m_isUndoable )
  217. *m_isUndoable = true;
  218. // Sheet file names can be relative or absolute.
  219. wxString sheetFileName = m_fields->GetField( FIELD_T::SHEET_FILENAME )->GetText();
  220. // Ensure filepath is not empty. (In normal use will be caught by grid validators,
  221. // but unedited data from existing files can be bad.)
  222. if( sheetFileName.IsEmpty() )
  223. {
  224. DisplayError( this, _( "A sheet must have a valid file name." ) );
  225. return false;
  226. }
  227. // Ensure the filename extension is OK. (In normal use will be caught by grid validators,
  228. // but unedited data from existing files can be bad.)
  229. sheetFileName = EnsureFileExtension( sheetFileName, FILEEXT::KiCadSchematicFileExtension );
  230. // Ensure sheetFileName is legal
  231. if( !IsFullFileNameValid( sheetFileName ) )
  232. {
  233. DisplayError( this, _( "A sheet must have a valid file name." ) );
  234. return false;
  235. }
  236. wxFileName fn( sheetFileName );
  237. wxString newRelativeFilename = fn.GetFullPath();
  238. // Inside Eeschema, filenames are stored using unix notation
  239. newRelativeFilename.Replace( wxT( "\\" ), wxT( "/" ) );
  240. wxString oldFilename = m_sheet->GetField( FIELD_T::SHEET_FILENAME )->GetText();
  241. oldFilename.Replace( wxT( "\\" ), wxT( "/" ) );
  242. bool filename_changed = oldFilename != newRelativeFilename;
  243. if( filename_changed || m_sheet->IsNew() )
  244. {
  245. SCH_SCREEN* currentScreen = m_frame->GetCurrentSheet().LastScreen();
  246. wxCHECK( currentScreen, false );
  247. bool clearFileName = false;
  248. // This can happen for the root sheet when opening Eeschema in the stand alone mode.
  249. if( currentScreen->GetFileName().IsEmpty() )
  250. {
  251. clearFileName = true;
  252. currentScreen->SetFileName( m_frame->Prj().AbsolutePath( wxT( "noname.kicad_sch" ) ) );
  253. }
  254. wxFileName tmp( fn );
  255. wxFileName screenFileName = currentScreen->GetFileName();
  256. if( fn.IsAbsolute() && fn.MakeRelativeTo( screenFileName.GetPath() ) )
  257. {
  258. wxMessageDialog makeRelDlg( this, _( "Use relative path for sheet file?" ),
  259. _( "Sheet File Path" ),
  260. wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTER );
  261. makeRelDlg.SetExtendedMessage( _( "Using relative hierarchical sheet file name paths "
  262. "improves schematic portability across systems and "
  263. "platforms. Using absolute paths can result in "
  264. "portability issues." ) );
  265. makeRelDlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Use Relative Path" ) ),
  266. wxMessageDialog::ButtonLabel( _( "Use Absolute Path" ) ) );
  267. if( makeRelDlg.ShowModal() == wxID_YES )
  268. {
  269. wxLogTrace( tracePathsAndFiles, "\n Converted absolute path: '%s'"
  270. "\n to relative path: '%s'",
  271. tmp.GetPath(),
  272. fn.GetPath() );
  273. m_fields->GetField( FIELD_T::SHEET_FILENAME )->SetText( fn.GetFullPath() );
  274. newRelativeFilename = fn.GetFullPath();
  275. }
  276. }
  277. if( !onSheetFilenameChanged( newRelativeFilename ) )
  278. {
  279. if( clearFileName )
  280. currentScreen->SetFileName( wxEmptyString );
  281. else
  282. FindField( *m_fields, FIELD_T::SHEET_FILENAME )->SetText( oldFilename );
  283. return false;
  284. }
  285. else if( m_updateHierarchyNavigator )
  286. {
  287. *m_updateHierarchyNavigator = true;
  288. }
  289. if( clearFileName )
  290. currentScreen->SetFileName( wxEmptyString );
  291. // One last validity check (and potential repair) just to be sure to be sure
  292. SCH_SHEET_LIST repairedList;
  293. repairedList.BuildSheetList( &m_frame->Schematic().Root(), true );
  294. }
  295. wxString newSheetname = m_fields->GetField( FIELD_T::SHEET_NAME )->GetText();
  296. if( ( newSheetname != m_sheet->GetName() ) && m_updateHierarchyNavigator )
  297. *m_updateHierarchyNavigator = true;
  298. if( newSheetname.IsEmpty() )
  299. newSheetname = _( "Untitled Sheet" );
  300. m_fields->GetField( FIELD_T::SHEET_NAME )->SetText( newSheetname );
  301. // change all field positions from relative to absolute
  302. for( SCH_FIELD& m_field : *m_fields)
  303. m_field.Offset( m_sheet->GetPosition() );
  304. if( positioningChanged( m_fields, m_sheet ) )
  305. m_sheet->SetFieldsAutoplaced( AUTOPLACE_NONE );
  306. for( int ii = m_fields->GetNumberRows() - 1; ii >= 0; ii-- )
  307. {
  308. SCH_FIELD& field = m_fields->at( ii );
  309. if( field.IsMandatory() )
  310. continue;
  311. const wxString& fieldName = field.GetCanonicalName();
  312. if( field.IsEmpty() )
  313. m_fields->erase( m_fields->begin() + ii );
  314. else if( fieldName.IsEmpty() )
  315. field.SetName( _( "untitled" ) );
  316. }
  317. m_sheet->SetFields( *m_fields );
  318. m_sheet->SetBorderWidth( m_borderWidth.GetIntValue() );
  319. COLOR_SETTINGS* colorSettings = m_frame->GetColorSettings();
  320. if( colorSettings->GetOverrideSchItemColors()
  321. && ( m_sheet->GetBorderColor() != m_borderSwatch->GetSwatchColor() ||
  322. m_sheet->GetBackgroundColor() != m_backgroundSwatch->GetSwatchColor() ) )
  323. {
  324. wxPanel temp( this );
  325. temp.Hide();
  326. PANEL_EESCHEMA_COLOR_SETTINGS prefs( &temp );
  327. wxString checkboxLabel = prefs.m_optOverrideColors->GetLabel();
  328. KIDIALOG dlg( this, _( "Note: item colors are overridden in the current color theme." ),
  329. KIDIALOG::KD_WARNING );
  330. dlg.ShowDetailedText( wxString::Format( _( "To see individual item colors uncheck '%s'\n"
  331. "in Preferences > Schematic Editor > Colors." ),
  332. checkboxLabel ) );
  333. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  334. dlg.ShowModal();
  335. }
  336. m_sheet->SetBorderColor( m_borderSwatch->GetSwatchColor() );
  337. m_sheet->SetBackgroundColor( m_backgroundSwatch->GetSwatchColor() );
  338. m_sheet->SetExcludedFromSim( m_cbExcludeFromSim->GetValue() );
  339. m_sheet->SetExcludedFromBOM( m_cbExcludeFromBom->GetValue() );
  340. m_sheet->SetExcludedFromBoard( m_cbExcludeFromBoard->GetValue() );
  341. m_sheet->SetDNP( m_cbDNP->GetValue() );
  342. SCH_SHEET_PATH instance = m_frame->GetCurrentSheet();
  343. instance.push_back( m_sheet );
  344. instance.SetPageNumber( m_pageNumberTextCtrl->GetValue() );
  345. m_frame->TestDanglingEnds();
  346. // Refresh all sheets in case ordering changed.
  347. for( SCH_ITEM* item : m_frame->GetScreen()->Items().OfType( SCH_SHEET_T ) )
  348. m_frame->UpdateItem( item );
  349. return true;
  350. }
  351. bool DIALOG_SHEET_PROPERTIES::onSheetFilenameChanged( const wxString& aNewFilename )
  352. {
  353. wxString msg;
  354. wxFileName sheetFileName( EnsureFileExtension( aNewFilename,
  355. FILEEXT::KiCadSchematicFileExtension ) );
  356. // Sheet file names are relative to the path of the current sheet. This allows for
  357. // nesting of schematic files in subfolders. Screen file names are always absolute.
  358. SCHEMATIC& schematic = m_frame->Schematic();
  359. SCH_SHEET_LIST fullHierarchy = schematic.Hierarchy();
  360. wxFileName screenFileName( sheetFileName );
  361. wxFileName tmp( sheetFileName );
  362. SCH_SCREEN* currentScreen = m_frame->GetCurrentSheet().LastScreen();
  363. wxCHECK( currentScreen, false );
  364. // SCH_SCREEN file names are always absolute.
  365. wxFileName currentScreenFileName = currentScreen->GetFileName();
  366. if( !screenFileName.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS,
  367. currentScreenFileName.GetPath() ) )
  368. {
  369. msg = wxString::Format( _( "Cannot normalize new sheet schematic file path:\n"
  370. "'%s'\n"
  371. "against parent sheet schematic file path:\n"
  372. "'%s'." ),
  373. sheetFileName.GetPath(),
  374. currentScreenFileName.GetPath() );
  375. DisplayError( this, msg );
  376. return false;
  377. }
  378. wxString newAbsoluteFilename = screenFileName.GetFullPath();
  379. // Inside Eeschema, filenames are stored using unix notation
  380. newAbsoluteFilename.Replace( wxT( "\\" ), wxT( "/" ) );
  381. bool renameFile = false;
  382. bool loadFromFile = false;
  383. bool clearAnnotation = false;
  384. bool isExistingSheet = false;
  385. SCH_SCREEN* useScreen = nullptr;
  386. SCH_SCREEN* oldScreen = nullptr;
  387. // Search for a schematic file having the same filename already in use in the hierarchy
  388. // or on disk, in order to reuse it.
  389. if( !schematic.Root().SearchHierarchy( newAbsoluteFilename, &useScreen ) )
  390. {
  391. loadFromFile = wxFileExists( newAbsoluteFilename );
  392. wxLogTrace( tracePathsAndFiles, "\n Sheet requested file '%s', %s",
  393. newAbsoluteFilename,
  394. loadFromFile ? "found" : "not found" );
  395. }
  396. if( m_sheet->GetScreen() == nullptr ) // New just created sheet.
  397. {
  398. if( !m_frame->AllowCaseSensitiveFileNameClashes( m_sheet->GetFileName(),
  399. newAbsoluteFilename ) )
  400. return false;
  401. if( useScreen || loadFromFile ) // Load from existing file.
  402. {
  403. clearAnnotation = true;
  404. if( !IsOK( this, wxString::Format( _( "'%s' already exists." ),
  405. sheetFileName.GetFullName() )
  406. + wxT( "\n\n" )
  407. + wxString::Format( _( "Link '%s' to this file?" ),
  408. newAbsoluteFilename ) ) )
  409. {
  410. return false;
  411. }
  412. }
  413. // If we are drawing a sheet from a design block/sheet import, we need to copy the
  414. // sheet to the current directory.
  415. else if( m_sourceSheetFilename && !m_sourceSheetFilename->IsEmpty() )
  416. {
  417. loadFromFile = true;
  418. if( !wxCopyFile( *m_sourceSheetFilename, newAbsoluteFilename, false ) )
  419. {
  420. msg.Printf( _( "Failed to copy schematic file '%s' to destination '%s'." ),
  421. currentScreenFileName.GetFullPath(), newAbsoluteFilename );
  422. DisplayError( m_frame, msg );
  423. return false;
  424. }
  425. }
  426. else // New file.
  427. {
  428. m_frame->InitSheet( m_sheet, newAbsoluteFilename );
  429. }
  430. }
  431. else // Existing sheet.
  432. {
  433. isExistingSheet = true;
  434. if( !m_frame->AllowCaseSensitiveFileNameClashes( m_sheet->GetFileName(),
  435. newAbsoluteFilename ) )
  436. return false;
  437. // We are always using here a case insensitive comparison to avoid issues
  438. // under Windows, although under Unix filenames are case sensitive.
  439. // But many users create schematic under both Unix and Windows
  440. // **
  441. // N.B. 1: aSheet->GetFileName() will return a relative path
  442. // aSheet->GetScreen()->GetFileName() returns a full path
  443. //
  444. // N.B. 2: newFilename uses the unix notation for separator.
  445. // so we must use it also to compare the old and new filenames
  446. wxString oldAbsoluteFilename = m_sheet->GetScreen()->GetFileName();
  447. oldAbsoluteFilename.Replace( wxT( "\\" ), wxT( "/" ) );
  448. if( newAbsoluteFilename.Cmp( oldAbsoluteFilename ) != 0 )
  449. {
  450. // Sheet file name changes cannot be undone.
  451. if( m_isUndoable )
  452. *m_isUndoable = false;
  453. if( useScreen || loadFromFile ) // Load from existing file.
  454. {
  455. clearAnnotation = true;
  456. oldScreen = m_sheet->GetScreen();
  457. if( !IsOK( this, wxString::Format( _( "Change '%s' link from '%s' to '%s'?" ),
  458. newAbsoluteFilename,
  459. m_sheet->GetFileName(),
  460. sheetFileName.GetFullName() )
  461. + wxT( "\n\n" )
  462. + _( "This action cannot be undone." ) ) )
  463. {
  464. return false;
  465. }
  466. if( loadFromFile )
  467. m_sheet->SetScreen( nullptr );
  468. }
  469. else // Save to new file name.
  470. {
  471. if( m_sheet->GetScreenCount() > 1 )
  472. {
  473. if( !IsOK( this, wxString::Format( _( "Create new file '%s' with contents "
  474. "of '%s'?" ),
  475. sheetFileName.GetFullName(),
  476. m_sheet->GetFileName() )
  477. + wxT( "\n\n" )
  478. + _( "This action cannot be undone." ) ) )
  479. {
  480. return false;
  481. }
  482. }
  483. renameFile = true;
  484. }
  485. }
  486. if( renameFile )
  487. {
  488. IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
  489. // If the associated screen is shared by more than one sheet, do not
  490. // change the filename of the corresponding screen here.
  491. // (a new screen will be created later)
  492. // if it is not shared, update the filename
  493. if( m_sheet->GetScreenCount() <= 1 )
  494. m_sheet->GetScreen()->SetFileName( newAbsoluteFilename );
  495. try
  496. {
  497. pi->SaveSchematicFile( newAbsoluteFilename, m_sheet, &schematic );
  498. }
  499. catch( const IO_ERROR& ioe )
  500. {
  501. msg = wxString::Format( _( "Error occurred saving schematic file '%s'." ),
  502. newAbsoluteFilename );
  503. DisplayErrorMessage( this, msg, ioe.What() );
  504. msg = wxString::Format( _( "Failed to save schematic '%s'" ),
  505. newAbsoluteFilename );
  506. m_frame->SetMsgPanel( wxEmptyString, msg );
  507. return false;
  508. }
  509. // If the associated screen is shared by more than one sheet, remove the
  510. // screen and reload the file to a new screen. Failure to do this will trash
  511. // the screen reference counting in complex hierarchies.
  512. if( m_sheet->GetScreenCount() > 1 )
  513. {
  514. oldScreen = m_sheet->GetScreen();
  515. m_sheet->SetScreen( nullptr );
  516. loadFromFile = true;
  517. }
  518. }
  519. }
  520. SCH_SHEET_PATH& currentSheet = m_frame->GetCurrentSheet();
  521. if( useScreen )
  522. {
  523. // Create a temporary sheet for recursion testing to prevent a possible recursion error.
  524. std::unique_ptr< SCH_SHEET> tmpSheet = std::make_unique<SCH_SHEET>( &schematic );
  525. *tmpSheet->GetField( FIELD_T::SHEET_NAME ) = m_fields->GetField( FIELD_T::SHEET_NAME );
  526. tmpSheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( sheetFileName.GetFullPath() );
  527. tmpSheet->SetScreen( useScreen );
  528. // No need to check for valid library IDs if we are using an existing screen.
  529. if( m_frame->CheckSheetForRecursion( tmpSheet.get(), &currentSheet ) )
  530. return false;
  531. // It's safe to set the sheet screen now.
  532. m_sheet->SetScreen( useScreen );
  533. SCH_SHEET_LIST sheetHierarchy( m_sheet ); // The hierarchy of the loaded file.
  534. sheetHierarchy.AddNewSymbolInstances( currentSheet, m_frame->Prj().GetProjectName() );
  535. sheetHierarchy.AddNewSheetInstances( currentSheet,
  536. fullHierarchy.GetLastVirtualPageNumber() );
  537. }
  538. else if( loadFromFile )
  539. {
  540. bool restoreSheet = false;
  541. if( isExistingSheet )
  542. {
  543. // Temporarily remove the sheet from the current schematic page so that recursion
  544. // and symbol library link tests can be performed with the modified sheet settings.
  545. restoreSheet = true;
  546. currentSheet.LastScreen()->Remove( m_sheet );
  547. }
  548. if( !m_frame->LoadSheetFromFile( m_sheet, &currentSheet, newAbsoluteFilename, false, true )
  549. || m_frame->CheckSheetForRecursion( m_sheet, &currentSheet ) )
  550. {
  551. if( restoreSheet )
  552. {
  553. // If we cleared the previous screen, restore it before returning to the user
  554. if( oldScreen )
  555. m_sheet->SetScreen( oldScreen );
  556. currentSheet.LastScreen()->Append( m_sheet );
  557. }
  558. return false;
  559. }
  560. if( restoreSheet )
  561. currentSheet.LastScreen()->Append( m_sheet );
  562. }
  563. if( m_clearAnnotationNewItems )
  564. *m_clearAnnotationNewItems = clearAnnotation;
  565. // Rebuild the entire connection graph.
  566. m_frame->RecalculateConnections( nullptr, GLOBAL_CLEANUP );
  567. return true;
  568. }
  569. void DIALOG_SHEET_PROPERTIES::OnGridCellChanging( wxGridEvent& event )
  570. {
  571. bool success = true;
  572. wxGridCellEditor* editor = m_grid->GetCellEditor( event.GetRow(), event.GetCol() );
  573. wxControl* control = editor->GetControl();
  574. if( control && control->GetValidator() )
  575. success = control->GetValidator()->Validate( control );
  576. if( !success )
  577. {
  578. event.Veto();
  579. m_delayedFocusRow = event.GetRow();
  580. m_delayedFocusColumn = event.GetCol();
  581. }
  582. editor->DecRef();
  583. }
  584. void DIALOG_SHEET_PROPERTIES::OnAddField( wxCommandEvent& event )
  585. {
  586. if( !m_grid->CommitPendingChanges() )
  587. return;
  588. SCH_FIELD newField( { 0, 0 }, FIELD_T::SHEET_USER, m_sheet,
  589. GetUserFieldName( (int) m_fields->size(), DO_TRANSLATE ) );
  590. newField.SetTextAngle( m_fields->GetField( FIELD_T::SHEET_NAME )->GetTextAngle() );
  591. m_fields->push_back( newField );
  592. // notify the grid
  593. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
  594. m_grid->ProcessTableMessage( msg );
  595. m_grid->MakeCellVisible( m_fields->size() - 1, 0 );
  596. m_grid->SetGridCursor( m_fields->size() - 1, 0 );
  597. m_grid->EnableCellEditControl();
  598. m_grid->ShowCellEditControl();
  599. }
  600. void DIALOG_SHEET_PROPERTIES::OnDeleteField( wxCommandEvent& event )
  601. {
  602. wxArrayInt selectedRows = m_grid->GetSelectedRows();
  603. if( selectedRows.empty() && m_grid->GetGridCursorRow() >= 0 )
  604. selectedRows.push_back( m_grid->GetGridCursorRow() );
  605. if( selectedRows.empty() )
  606. return;
  607. for( int row : selectedRows )
  608. {
  609. if( row < m_fields->GetMandatoryRowCount() )
  610. {
  611. DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
  612. m_fields->GetMandatoryRowCount() ) );
  613. return;
  614. }
  615. }
  616. m_grid->CommitPendingChanges( true /* quiet mode */ );
  617. // Reverse sort so deleting a row doesn't change the indexes of the other rows.
  618. selectedRows.Sort( []( int* first, int* second ) { return *second - *first; } );
  619. for( int row : selectedRows )
  620. {
  621. m_fields->erase( m_fields->begin() + row );
  622. // notify the grid
  623. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, row, 1 );
  624. m_grid->ProcessTableMessage( msg );
  625. if( m_grid->GetNumberRows() > 0 )
  626. {
  627. m_grid->MakeCellVisible( std::max( 0, row-1 ), m_grid->GetGridCursorCol() );
  628. m_grid->SetGridCursor( std::max( 0, row-1 ), m_grid->GetGridCursorCol() );
  629. }
  630. }
  631. }
  632. void DIALOG_SHEET_PROPERTIES::OnMoveUp( wxCommandEvent& event )
  633. {
  634. if( !m_grid->CommitPendingChanges() )
  635. return;
  636. int i = m_grid->GetGridCursorRow();
  637. if( i > m_fields->GetMandatoryRowCount() )
  638. {
  639. SCH_FIELD tmp = m_fields->at( (unsigned) i );
  640. m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
  641. m_fields->insert( m_fields->begin() + i - 1, tmp );
  642. m_grid->ForceRefresh();
  643. m_grid->SetGridCursor( i - 1, m_grid->GetGridCursorCol() );
  644. m_grid->MakeCellVisible( m_grid->GetGridCursorRow(), m_grid->GetGridCursorCol() );
  645. }
  646. else
  647. {
  648. wxBell();
  649. }
  650. }
  651. void DIALOG_SHEET_PROPERTIES::OnMoveDown( wxCommandEvent& event )
  652. {
  653. if( !m_grid->CommitPendingChanges() )
  654. return;
  655. int i = m_grid->GetGridCursorRow();
  656. if( i >= m_fields->GetMandatoryRowCount() && i < m_grid->GetNumberRows() - 1 )
  657. {
  658. SCH_FIELD tmp = m_fields->at( (unsigned) i );
  659. m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
  660. m_fields->insert( m_fields->begin() + i + 1, tmp );
  661. m_grid->ForceRefresh();
  662. m_grid->SetGridCursor( i + 1, m_grid->GetGridCursorCol() );
  663. m_grid->MakeCellVisible( m_grid->GetGridCursorRow(), m_grid->GetGridCursorCol() );
  664. }
  665. else
  666. {
  667. wxBell();
  668. }
  669. }
  670. void DIALOG_SHEET_PROPERTIES::AdjustGridColumns()
  671. {
  672. // Account for scroll bars
  673. int width = KIPLATFORM::UI::GetUnobscuredSize( m_grid ).x;
  674. m_grid->AutoSizeColumn( 0 );
  675. m_grid->SetColSize( 0, std::max( 72, m_grid->GetColSize( 0 ) ) );
  676. int fixedColsWidth = m_grid->GetColSize( 0 );
  677. for( int i = 2; i < m_grid->GetNumberCols(); i++ )
  678. fixedColsWidth += m_grid->GetColSize( i );
  679. m_grid->SetColSize( 1, std::max( 120, width - fixedColsWidth ) );
  680. }
  681. void DIALOG_SHEET_PROPERTIES::OnUpdateUI( wxUpdateUIEvent& event )
  682. {
  683. std::bitset<64> shownColumns = m_grid->GetShownColumns();
  684. if( shownColumns != m_shownColumns )
  685. {
  686. m_shownColumns = shownColumns;
  687. if( !m_grid->IsCellEditControlShown() )
  688. AdjustGridColumns();
  689. }
  690. // Propagate changes in sheetname to displayed hierarchical path
  691. int sheetnameRow = m_fields->GetFieldRow( FIELD_T::SHEET_NAME );
  692. wxString path = m_frame->GetCurrentSheet().PathHumanReadable( false );
  693. if( path.Last() != '/' )
  694. path.Append( '/' );
  695. wxGridCellEditor* editor = m_grid->GetCellEditor( sheetnameRow, FDC_VALUE );
  696. wxControl* control = editor->GetControl();
  697. wxTextEntry* textControl = dynamic_cast<wxTextEntry*>( control );
  698. wxString sheetName;
  699. if( textControl )
  700. sheetName = textControl->GetValue();
  701. else
  702. sheetName = m_grid->GetCellValue( sheetnameRow, FDC_VALUE );
  703. m_dummySheet.SetFields( *m_fields );
  704. m_dummySheetNameField.SetText( sheetName );
  705. path += m_dummySheetNameField.GetShownText( false );
  706. editor->DecRef();
  707. wxClientDC dc( m_hierarchicalPathLabel );
  708. int width = m_sizerBottom->GetSize().x - m_stdDialogButtonSizer->GetSize().x
  709. - m_hierarchicalPathLabel->GetSize().x
  710. - 30;
  711. path = wxControl::Ellipsize( path, dc, wxELLIPSIZE_START, width, wxELLIPSIZE_FLAGS_NONE );
  712. if( m_hierarchicalPath->GetLabel() != path )
  713. m_hierarchicalPath->SetLabel( path );
  714. // Handle a delayed focus
  715. if( m_delayedFocusRow >= 0 )
  716. {
  717. m_grid->SetFocus();
  718. m_grid->MakeCellVisible( m_delayedFocusRow, m_delayedFocusColumn );
  719. m_grid->SetGridCursor( m_delayedFocusRow, m_delayedFocusColumn );
  720. m_grid->EnableCellEditControl( true );
  721. m_grid->ShowCellEditControl();
  722. m_delayedFocusRow = -1;
  723. m_delayedFocusColumn = -1;
  724. }
  725. }
  726. void DIALOG_SHEET_PROPERTIES::OnSizeGrid( wxSizeEvent& event )
  727. {
  728. auto new_size = event.GetSize();
  729. if( m_size != new_size )
  730. {
  731. m_size = new_size;
  732. AdjustGridColumns();
  733. }
  734. // Always propagate for a grid repaint (needed if the height changes, as well as width)
  735. event.Skip();
  736. }
  737. void DIALOG_SHEET_PROPERTIES::OnInitDlg( wxInitDialogEvent& event )
  738. {
  739. TransferDataToWindow();
  740. // Now all widgets have the size fixed, call FinishDialogSettings
  741. finishDialogSettings();
  742. EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  743. if( cfg && cfg->m_Appearance.edit_sheet_width > 0 && cfg->m_Appearance.edit_sheet_height > 0 )
  744. SetSize( cfg->m_Appearance.edit_sheet_width, cfg->m_Appearance.edit_sheet_height );
  745. }