1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2018-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 
20 #include "symbol_preview_widget.h"
21 #include <sch_view.h>
22 #include <gal/gal_display_options.h>
23 #include <symbol_lib_table.h>
24 #include <sch_preview_panel.h>
25 #include <pgm_base.h>
26 #include <sch_painter.h>
27 #include <eda_draw_frame.h>
28 #include <eeschema_settings.h>
29 #include <settings/settings_manager.h>
30 #include <wx/log.h>
31 #include <wx/stattext.h>
32 
33 
SYMBOL_PREVIEW_WIDGET(wxWindow * aParent,KIWAY & aKiway,EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvasType)34 SYMBOL_PREVIEW_WIDGET::SYMBOL_PREVIEW_WIDGET( wxWindow* aParent, KIWAY& aKiway,
35                                               EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvasType ) :
36     wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ),
37     m_kiway( aKiway ),
38     m_preview( nullptr ), m_status( nullptr ), m_statusSizer( nullptr ), m_previewItem( nullptr )
39 {
40     auto common_settings = Pgm().GetCommonSettings();
41     auto app_settings = Pgm().GetSettingsManager().GetAppSettings<EESCHEMA_SETTINGS>();
42 
43     m_galDisplayOptions.ReadConfig( *common_settings, app_settings->m_Window, this );
44     m_galDisplayOptions.m_forceDisplayCursor = false;
45 
46     EDA_DRAW_PANEL_GAL::GAL_TYPE canvasType = aCanvasType;
47 
48     // Allows only a CAIRO or OPENGL canvas:
49     if( canvasType != EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL
50             && canvasType != EDA_DRAW_PANEL_GAL::GAL_FALLBACK )
51     {
52         canvasType = EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL;
53     }
54 
55     m_preview = new SCH_PREVIEW_PANEL( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ),
56                                        m_galDisplayOptions, canvasType );
57     m_preview->SetStealsFocus( false );
58     m_preview->ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_NEVER );
59     m_preview->GetGAL()->SetAxesEnabled( false );
60 
61     // Do not display the grid: the look is not good for a small canvas area.
62     // But mainly, due to some strange bug I (JPC) was unable to fix, the grid creates
63     // strange artifacts on Windows when Eeschema is run from KiCad manager (but not in
64     // stand alone...).
65     m_preview->GetGAL()->SetGridVisibility( false );
66 
67     // Early initialization of the canvas background color,
68     // before any OnPaint event is fired for the canvas using a wrong bg color
69     KIGFX::VIEW* view = m_preview->GetView();
70     auto settings = static_cast<KIGFX::SCH_RENDER_SETTINGS*>( view->GetPainter()->GetSettings() );
71 
72     if( auto* theme = Pgm().GetSettingsManager().GetColorSettings( app_settings->m_ColorTheme ) )
73         settings->LoadColors( theme );
74 
75     const COLOR4D& backgroundColor = settings->GetBackgroundColor();
76     const COLOR4D& foregroundColor = settings->GetCursorColor();
77 
78     m_preview->GetGAL()->SetClearColor( backgroundColor );
79 
80     m_statusPanel = new wxPanel( this );
81     m_statusPanel->SetBackgroundColour( backgroundColor.ToColour() );
82     m_status = new wxStaticText( m_statusPanel, wxID_ANY, wxEmptyString );
83     m_status->SetForegroundColour( settings->GetLayerColor( LAYER_REFERENCEPART ).ToColour() );
84     m_statusSizer = new wxBoxSizer( wxVERTICAL );
85     m_statusSizer->Add( 0, 0, 1 );  // add a spacer
86     m_statusSizer->Add( m_status, 0, wxALIGN_CENTER );
87     m_statusSizer->Add( 0, 0, 1 );  // add a spacer
88     m_statusPanel->SetSizer( m_statusSizer );
89 
90     // Give the status panel the same color scheme as the canvas so it isn't jarring when
91     // switched to.
92     m_statusPanel->SetBackgroundColour( backgroundColor.ToColour() );
93     m_statusPanel->SetForegroundColour( foregroundColor.ToColour() );
94 
95     // Give the preview panel a small top border to align its top with the status panel,
96     // and give the status panel a small bottom border to align its bottom with the preview
97     // panel.
98     m_outerSizer = new wxBoxSizer( wxVERTICAL );
99     m_outerSizer->Add( m_preview, 1, wxTOP | wxEXPAND, 5 );
100     m_outerSizer->Add( m_statusPanel, 1, wxBOTTOM | wxEXPAND, 5 );
101 
102     // Hide the status panel to start
103     m_statusPanel->Hide();
104 
105     SetSizer( m_outerSizer );
106     Layout();
107 
108     Connect( wxEVT_SIZE, wxSizeEventHandler( SYMBOL_PREVIEW_WIDGET::onSize ), nullptr, this );
109 }
110 
111 
~SYMBOL_PREVIEW_WIDGET()112 SYMBOL_PREVIEW_WIDGET::~SYMBOL_PREVIEW_WIDGET()
113 {
114     if( m_previewItem )
115         m_preview->GetView()->Remove( m_previewItem );
116 
117     delete m_previewItem;
118 }
119 
120 
SetStatusText(wxString const & aText)121 void SYMBOL_PREVIEW_WIDGET::SetStatusText( wxString const& aText )
122 {
123     m_status->SetLabel( aText );
124     m_preview->Hide();
125     m_statusPanel->Show();
126     Layout();
127 }
128 
129 
onSize(wxSizeEvent & aEvent)130 void SYMBOL_PREVIEW_WIDGET::onSize( wxSizeEvent& aEvent )
131 {
132     if( m_previewItem )
133     {
134         fitOnDrawArea();
135         m_preview->ForceRefresh();
136     }
137 
138     aEvent.Skip();
139 }
140 
141 
fitOnDrawArea()142 void SYMBOL_PREVIEW_WIDGET::fitOnDrawArea()
143 {
144     if( !m_previewItem )
145         return;
146 
147     // set the view scale to fit the item on screen
148     KIGFX::VIEW* view = m_preview->GetView();
149 
150     // Calculate the drawing area size, in internal units, for a scaling factor = 1.0
151     view->SetScale( 1.0 );
152     VECTOR2D  clientSize = view->ToWorld( m_preview->GetClientSize(), false );
153     // Calculate the draw scale to fit the drawing area
154     double    scale = std::min( fabs( clientSize.x / m_itemBBox.GetWidth() ),
155                                 fabs( clientSize.y / m_itemBBox.GetHeight() ) );
156 
157     // Above calculation will yield an exact fit; add a bit of whitespace around symbol
158     scale /= 1.2;
159 
160     // Now fix the best scale
161     view->SetScale( scale );
162     view->SetCenter( m_itemBBox.Centre() );
163 }
164 
165 
DisplaySymbol(const LIB_ID & aSymbolID,int aUnit,int aConvert)166 void SYMBOL_PREVIEW_WIDGET::DisplaySymbol( const LIB_ID& aSymbolID, int aUnit, int aConvert )
167 {
168     KIGFX::VIEW* view = m_preview->GetView();
169     auto settings = static_cast<KIGFX::SCH_RENDER_SETTINGS*>( view->GetPainter()->GetSettings() );
170     std::unique_ptr< LIB_SYMBOL > symbol;
171 
172     try
173     {
174         LIB_SYMBOL* tmp = m_kiway.Prj().SchSymbolLibTable()->LoadSymbol( aSymbolID );
175 
176         if( tmp )
177             symbol = tmp->Flatten();
178     }
179     catch( const IO_ERROR& ioe )
180     {
181         wxLogError( _( "Error loading symbol %s from library '%s'." ) + wxS( "\n%s" ),
182                     aSymbolID.GetLibItemName().wx_str(),
183                     aSymbolID.GetLibNickname().wx_str(),
184                     ioe.What() );
185     }
186 
187     if( m_previewItem )
188     {
189         view->Remove( m_previewItem );
190         delete m_previewItem;
191         m_previewItem = nullptr;
192     }
193 
194     if( symbol )
195     {
196         // This will flatten derived parts so that the correct final symbol can be shown.
197         m_previewItem = symbol.release();
198 
199         // If unit isn't specified for a multi-unit part, pick the first.  (Otherwise we'll
200         // draw all of them.)
201         settings->m_ShowUnit = ( m_previewItem->IsMulti() && aUnit == 0 ) ? 1 : aUnit;
202 
203         // For symbols having a De Morgan body style, use the first style
204         settings->m_ShowConvert =
205                 ( m_previewItem->HasConversion() && aConvert == 0 ) ? 1 : aConvert;
206 
207         view->Add( m_previewItem );
208 
209         // Get the symbol size, in internal units
210         m_itemBBox = m_previewItem->GetUnitBoundingBox( settings->m_ShowUnit,
211                                                         settings->m_ShowConvert );
212 
213         if( !m_preview->IsShown() )
214         {
215             m_preview->Show();
216             m_statusPanel->Hide();
217             Layout();   // Ensure panel size is up to date.
218         }
219 
220         // Calculate the draw scale to fit the drawing area
221         fitOnDrawArea();
222     }
223 
224     m_preview->ForceRefresh();
225 }
226 
227 
DisplayPart(LIB_SYMBOL * aSymbol,int aUnit,int aConvert)228 void SYMBOL_PREVIEW_WIDGET::DisplayPart( LIB_SYMBOL* aSymbol, int aUnit, int aConvert )
229 {
230     KIGFX::VIEW* view = m_preview->GetView();
231 
232     if( m_previewItem )
233     {
234         view->Remove( m_previewItem );
235         delete m_previewItem;
236         m_previewItem = nullptr;
237     }
238 
239     if( aSymbol )
240     {
241         m_previewItem = new LIB_SYMBOL( *aSymbol );
242 
243         // For symbols having a De Morgan body style, use the first style
244         auto settings =
245                 static_cast<KIGFX::SCH_RENDER_SETTINGS*>( view->GetPainter()->GetSettings() );
246 
247         // If unit isn't specified for a multi-unit part, pick the first.  (Otherwise we'll
248         // draw all of them.)
249         settings->m_ShowUnit = ( m_previewItem->IsMulti() && aUnit == 0 ) ? 1 : aUnit;
250 
251         settings->m_ShowConvert =
252                 ( m_previewItem->HasConversion() && aConvert == 0 ) ? 1 : aConvert;
253 
254         view->Add( m_previewItem );
255 
256         // Get the symbol size, in internal units
257         m_itemBBox = aSymbol->GetUnitBoundingBox( settings->m_ShowUnit, settings->m_ShowConvert );
258 
259         // Calculate the draw scale to fit the drawing area
260         fitOnDrawArea();
261     }
262 
263     m_preview->ForceRefresh();
264     m_preview->Show();
265     m_statusPanel->Hide();
266     Layout();
267 }
268