1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #include <pcb_edit_frame.h>
20 #include <panel_setup_layers.h>
21 #include <panel_setup_text_and_graphics.h>
22 #include <panel_setup_constraints.h>
23 #include <dialogs/panel_setup_netclasses.h>
24 #include <panel_setup_tracks_and_vias.h>
25 #include <panel_setup_mask_and_paste.h>
26 #include <../board_stackup_manager/panel_board_stackup.h>
27 #include <../board_stackup_manager/panel_board_finish.h>
28 #include <confirm.h>
29 #include <board_design_settings.h>
30 #include <kiface_base.h>
31 #include <drc/drc_item.h>
32 #include <dialog_import_settings.h>
33 #include <io_mgr.h>
34 #include <dialogs/panel_setup_severities.h>
35 #include <panel_text_variables.h>
36 #include <project.h>
37 #include <project/project_file.h>
38 #include <settings/settings_manager.h>
39 #include <widgets/resettable_panel.h>
40 #include <widgets/wx_progress_reporters.h>
41 #include <wildcards_and_files_ext.h>
42 
43 #include "dialog_board_setup.h"
44 #include "panel_setup_rules.h"
45 
46 #include <wx/treebook.h>
47 
48 using std::placeholders::_1;
49 
50 
DIALOG_BOARD_SETUP(PCB_EDIT_FRAME * aFrame)51 DIALOG_BOARD_SETUP::DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame ) :
52         PAGED_DIALOG( aFrame, _( "Board Setup" ), false,
53                       _( "Import Settings from Another Board..." ) ),
54         m_frame( aFrame )
55 {
56     BOARD*                 board = aFrame->GetBoard();
57     BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
58 
59     m_layers = new PANEL_SETUP_LAYERS( this, aFrame );
60     m_textAndGraphics = new PANEL_SETUP_TEXT_AND_GRAPHICS( this, aFrame );
61     m_constraints = new PANEL_SETUP_CONSTRAINTS( this, aFrame );
62     m_rules = new PANEL_SETUP_RULES( this, aFrame );
63     m_tracksAndVias = new PANEL_SETUP_TRACKS_AND_VIAS( this, aFrame, m_constraints );
64     m_maskAndPaste = new PANEL_SETUP_MASK_AND_PASTE( this, aFrame );
65     m_physicalStackup = new PANEL_SETUP_BOARD_STACKUP( this, aFrame, m_layers );
66     m_boardFinish = new PANEL_SETUP_BOARD_FINISH( this, board );
67 
68     m_severities = new PANEL_SETUP_SEVERITIES( this, DRC_ITEM::GetItemsWithSeverities(),
69                                                bds.m_DRCSeverities );
70 
71     m_netclasses = new PANEL_SETUP_NETCLASSES( this, &bds.GetNetClasses(),
72                                                board->GetNetClassAssignmentCandidates(), false );
73 
74     m_textVars = new PANEL_TEXT_VARIABLES( m_treebook, &Prj() );
75 
76     /*
77      * WARNING: If you change page names you MUST update calls to ShowBoardSetupDialog().
78      */
79 
80     m_treebook->AddPage( new wxPanel( this ),  _( "Board Stackup" ) );
81 
82     m_currentPage = -1;
83 
84     /*
85      * WARNING: Code currently relies on the layers setup coming before the physical stackup panel,
86      * and thus transferring data to the board first.  See comment in
87      * PANEL_SETUP_BOARD_STACKUP::TransferDataFromWindow and rework this logic if it is determined
88      * that the order of these pages should be changed.
89      */
90     m_treebook->AddSubPage( m_layers,  _( "Board Editor Layers" ) );
91     m_layerSetupPage = 1;
92 
93     m_treebook->AddSubPage( m_physicalStackup,  _( "Physical Stackup" ) );
94     // Change this value if m_physicalStackup is not the page 2 of m_treebook
95     m_physicalStackupPage = 2;  // The page number (from 0) to select the m_physicalStackup panel
96 
97     m_treebook->AddSubPage( m_boardFinish, _( "Board Finish" ) );
98     m_treebook->AddSubPage( m_maskAndPaste,  _( "Solder Mask/Paste" ) );
99 
100     m_treebook->AddPage( new wxPanel( this ),  _( "Text & Graphics" ) );
101     m_treebook->AddSubPage( m_textAndGraphics,  _( "Defaults" ) );
102     m_treebook->AddSubPage( m_textVars, _( "Text Variables" ) );
103 
104     m_treebook->AddPage( new wxPanel( this ),  _( "Design Rules" ) );
105     m_treebook->AddSubPage( m_constraints,  _( "Constraints" ) );
106     m_treebook->AddSubPage( m_tracksAndVias, _( "Pre-defined Sizes" ) );
107     m_treebook->AddSubPage( m_netclasses,  _( "Net Classes" ) );
108     m_treebook->AddSubPage( m_rules, _( "Custom Rules" ) );
109     m_treebook->AddSubPage( m_severities, _( "Violation Severity" ) );
110 
111     for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
112    	    m_macHack.push_back( true );
113 
114     m_treebook->SetMinSize( wxSize( -1, 480 ) );
115 
116 	// Connect Events
117 	m_treebook->Connect( wxEVT_TREEBOOK_PAGE_CHANGED,
118                          wxBookCtrlEventHandler( DIALOG_BOARD_SETUP::OnPageChange ), nullptr,
119                          this );
120 
121     finishDialogSettings();
122 
123     if( Prj().IsReadOnly() )
124     {
125         m_infoBar->ShowMessage( _( "Project is missing or read-only. Some settings will not "
126                                    "be editable." ), wxICON_WARNING );
127     }
128 }
129 
130 
~DIALOG_BOARD_SETUP()131 DIALOG_BOARD_SETUP::~DIALOG_BOARD_SETUP()
132 {
133 	m_treebook->Disconnect( wxEVT_TREEBOOK_PAGE_CHANGED,
134                             wxBookCtrlEventHandler( DIALOG_BOARD_SETUP::OnPageChange ), nullptr,
135                             this );
136 }
137 
138 
OnPageChange(wxBookCtrlEvent & event)139 void DIALOG_BOARD_SETUP::OnPageChange( wxBookCtrlEvent& event )
140 {
141     int page = event.GetSelection();
142 
143     // Ensure layer page always gets updated even if we aren't moving towards it
144     if( m_currentPage == m_physicalStackupPage )
145         m_layers->SyncCopperLayers( m_physicalStackup->GetCopperLayerCount() );
146 
147     if( page == m_physicalStackupPage )
148         m_physicalStackup->OnLayersOptionsChanged( m_layers->GetUILayerMask() );
149     else if( Prj().IsReadOnly() )
150         KIUI::Disable( m_treebook->GetPage( page ) );
151 
152     m_currentPage = page;
153 
154 #ifdef __WXMAC__
155     // Work around an OSX bug where the wxGrid children don't get placed correctly until
156     // the first resize event
157     if( m_macHack[ page ] )
158     {
159         wxSize pageSize = m_treebook->GetPage( page )->GetSize();
160         pageSize.x -= 1;
161         pageSize.y += 2;
162 
163         m_treebook->GetPage( page )->SetSize( pageSize );
164         m_macHack[ page ] = false;
165     }
166 #endif
167 }
168 
169 
OnAuxiliaryAction(wxCommandEvent & event)170 void DIALOG_BOARD_SETUP::OnAuxiliaryAction( wxCommandEvent& event )
171 {
172     DIALOG_IMPORT_SETTINGS importDlg( this, m_frame );
173 
174     if( importDlg.ShowModal() == wxID_CANCEL )
175         return;
176 
177     wxFileName boardFn( importDlg.GetFilePath() );
178     wxFileName projectFn( boardFn );
179 
180     projectFn.SetExt( ProjectFileExtension );
181 
182     if( !m_frame->GetSettingsManager()->LoadProject( projectFn.GetFullPath(), false ) )
183     {
184         wxString msg = wxString::Format( _( "Error importing settings from board:\n"
185                                             "Associated project file %s could not be loaded" ),
186                                          projectFn.GetFullPath() );
187         DisplayErrorMessage( this, msg );
188 
189         return;
190     }
191 
192     // Flag so user can stop work if it will result in deleted inner copper layers
193     // and still clean up this function properly.
194     bool okToProceed = true;
195 
196     PROJECT* otherPrj = m_frame->GetSettingsManager()->GetProject( projectFn.GetFullPath() );
197 
198     PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD_SEXP ) );
199 
200     BOARD* otherBoard = nullptr;
201 
202     try
203     {
204         WX_PROGRESS_REPORTER progressReporter( this, _( "Loading PCB" ), 1 );
205 
206         otherBoard = pi->Load( boardFn.GetFullPath(), nullptr, nullptr, nullptr,
207                                &progressReporter );
208 
209         if( importDlg.m_LayersOpt->GetValue() )
210         {
211             BOARD* loadedBoard = m_frame->GetBoard();
212 
213             // Check if "Import Settings" board has more layers than the current board.
214             okToProceed = m_layers->CheckCopperLayerCount( loadedBoard, otherBoard );
215         }
216     }
217     catch( const IO_ERROR& ioe )
218     {
219         // You wouldn't think boardFn.GetFullPath() would throw, but we get a stack buffer
220         // underflow from ASAN.  While it's probably an ASAN error, a second try/catch doesn't
221         // cost us much.
222         try
223         {
224             if( ioe.Problem() != wxT( "CANCEL" ) )
225             {
226                 wxString msg = wxString::Format( _( "Error loading board file:\n%s" ),
227                                                  boardFn.GetFullPath() );
228                 DisplayErrorMessage( this, msg, ioe.What() );
229             }
230 
231             if( otherPrj != &m_frame->Prj() )
232                 m_frame->GetSettingsManager()->UnloadProject( otherPrj, false );
233         }
234         catch(...)
235         {
236             // That was already our best-efforts
237         }
238 
239         return;
240     }
241 
242 
243     if( okToProceed )
244     {
245         otherBoard->SetProject( otherPrj );
246 
247         // If layers options are imported, import also the stackup
248         // layers options and stackup are linked, so they cannot be imported
249         // separately, and stackup can be imported only after layers options
250         if( importDlg.m_LayersOpt->GetValue() )
251         {
252             m_layers->ImportSettingsFrom( otherBoard );
253             m_physicalStackup->ImportSettingsFrom( otherBoard );
254         }
255 
256         if( importDlg.m_TextAndGraphicsOpt->GetValue() )
257             m_textAndGraphics->ImportSettingsFrom( otherBoard );
258 
259         if( importDlg.m_ConstraintsOpt->GetValue() )
260             m_constraints->ImportSettingsFrom( otherBoard );
261 
262         if( importDlg.m_NetclassesOpt->GetValue() )
263             m_netclasses->ImportSettingsFrom( &otherBoard->GetDesignSettings().GetNetClasses() );
264 
265         if( importDlg.m_TracksAndViasOpt->GetValue() )
266             m_tracksAndVias->ImportSettingsFrom( otherBoard );
267 
268         if( importDlg.m_MaskAndPasteOpt->GetValue() )
269             m_maskAndPaste->ImportSettingsFrom( otherBoard );
270 
271         if( importDlg.m_SeveritiesOpt->GetValue() )
272             m_severities->ImportSettingsFrom( otherBoard->GetDesignSettings().m_DRCSeverities );
273 
274         if( otherPrj != &m_frame->Prj() )
275             otherBoard->ClearProject();
276     }
277 
278     // Clean up and free memory before leaving
279     if( otherPrj != &m_frame->Prj() )
280         m_frame->GetSettingsManager()->UnloadProject( otherPrj, false );
281 
282     delete otherBoard;
283 }
284