1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2020 Ian McInerney <ian.s.mcinerney@ieee.org>
5  * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <id.h>
22 #include <kiplatform/ui.h>
23 #include <widgets/infobar.h>
24 #include "wx/artprov.h"
25 #include <wx/aui/framemanager.h>
26 #include <wx/debug.h>
27 #include <wx/infobar.h>
28 #include <wx/sizer.h>
29 #include <wx/timer.h>
30 #include <wx/hyperlink.h>
31 #include <wx/bmpbuttn.h>
32 #include <eda_base_frame.h>
33 
34 
35 wxDEFINE_EVENT( KIEVT_SHOW_INFOBAR,    wxCommandEvent );
36 wxDEFINE_EVENT( KIEVT_DISMISS_INFOBAR, wxCommandEvent );
37 
BEGIN_EVENT_TABLE(WX_INFOBAR,wxInfoBarGeneric)38 BEGIN_EVENT_TABLE( WX_INFOBAR, wxInfoBarGeneric )
39     EVT_COMMAND( wxID_ANY, KIEVT_SHOW_INFOBAR,    WX_INFOBAR::onShowInfoBar )
40     EVT_COMMAND( wxID_ANY, KIEVT_DISMISS_INFOBAR, WX_INFOBAR::onDismissInfoBar )
41 
42     EVT_BUTTON( ID_CLOSE_INFOBAR, WX_INFOBAR::onCloseButton )
43     EVT_TIMER(  ID_CLOSE_INFOBAR, WX_INFOBAR::onTimer )
44 END_EVENT_TABLE()
45 
46 
47 WX_INFOBAR::WX_INFOBAR( wxWindow* aParent, wxAuiManager* aMgr, wxWindowID aWinid )
48         : wxInfoBarGeneric( aParent, aWinid ),
49           m_showTime( 0 ),
50           m_updateLock( false ),
51           m_showTimer( nullptr ),
52           m_auiManager( aMgr ),
53           m_type( MESSAGE_TYPE::GENERIC )
54 {
55     m_showTimer = new wxTimer( this, ID_CLOSE_INFOBAR );
56 
57 #ifdef __WXMAC__
58     // wxWidgets hard-codes wxSYS_COLOUR_INFOBK to { 0xFF, 0xFF, 0xD3 } on Mac.
59     if( KIPLATFORM::UI::IsDarkTheme() )
60         SetBackgroundColour( wxColour( 28, 27, 20 ) );
61     else
62         SetBackgroundColour( wxColour( 255, 249, 189 ) );
63 
64     // Infobar is broken on Mac without the effects
65     SetShowHideEffects( wxSHOW_EFFECT_ROLL_TO_BOTTOM, wxSHOW_EFFECT_ROLL_TO_TOP );
66     SetEffectDuration( 300 );
67 #else
68     // Infobar freezes canvas on Windows with the effect, and GTK looks bad with it
69     SetShowHideEffects( wxSHOW_EFFECT_NONE, wxSHOW_EFFECT_NONE );
70 #endif
71 
72 
73     // The infobar seems to start too small, so increase its height
74     int sx, sy;
75     GetSize( &sx, &sy );
76     sy = 1.5 * sy;
77     SetSize( sx, sy );
78 
79     // The bitmap gets cutoff sometimes with the default size, so force it to be the same
80     // height as the infobar.
81     wxSizer* sizer    = GetSizer();
82     wxSize   iconSize = wxArtProvider::GetSizeHint( wxART_BUTTON );
83 
84     sizer->SetItemMinSize( (size_t) 0, iconSize.x, sy );
85 
86     // Forcefully remove all existing buttons added by the wx constructors.
87     // The default close button doesn't work with the AUI manager update scheme, so this
88     // ensures any close button displayed is ours.
89     RemoveAllButtons();
90 
91     Layout();
92 
93     m_parent->Bind( wxEVT_SIZE, &WX_INFOBAR::onSize, this );
94 }
95 
96 
~WX_INFOBAR()97 WX_INFOBAR::~WX_INFOBAR()
98 {
99     delete m_showTimer;
100 }
101 
102 
SetShowTime(int aTime)103 void WX_INFOBAR::SetShowTime( int aTime )
104 {
105     m_showTime = aTime;
106 }
107 
108 
QueueShowMessage(const wxString & aMessage,int aFlags)109 void WX_INFOBAR::QueueShowMessage( const wxString& aMessage, int aFlags )
110 {
111     wxCommandEvent* evt = new wxCommandEvent( KIEVT_SHOW_INFOBAR );
112 
113     evt->SetString( aMessage.c_str() );
114     evt->SetInt( aFlags );
115 
116     GetEventHandler()->QueueEvent( evt );
117 }
118 
119 
QueueDismiss()120 void WX_INFOBAR::QueueDismiss()
121 {
122     wxCommandEvent* evt = new wxCommandEvent( KIEVT_DISMISS_INFOBAR );
123 
124     GetEventHandler()->QueueEvent( evt );
125 }
126 
127 
ShowMessageFor(const wxString & aMessage,int aTime,int aFlags,MESSAGE_TYPE aType)128 void WX_INFOBAR::ShowMessageFor( const wxString& aMessage, int aTime, int aFlags,
129                                  MESSAGE_TYPE aType )
130 {
131     // Don't do anything if we requested the UI update
132     if( m_updateLock )
133         return;
134 
135     m_showTime = aTime;
136     ShowMessage( aMessage, aFlags );
137 
138     m_type = aType;
139 }
140 
141 
ShowMessage(const wxString & aMessage,int aFlags)142 void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags )
143 {
144     // Don't do anything if we requested the UI update
145     if( m_updateLock )
146         return;
147 
148     m_updateLock = true;
149 
150     wxInfoBarGeneric::ShowMessage( aMessage, aFlags );
151 
152     if( m_auiManager )
153         updateAuiLayout( true );
154 
155     if( m_showTime > 0 )
156         m_showTimer->StartOnce( m_showTime );
157 
158     m_type = MESSAGE_TYPE::GENERIC;
159     m_updateLock = false;
160 }
161 
162 
ShowMessage(const wxString & aMessage,int aFlags,MESSAGE_TYPE aType)163 void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags, MESSAGE_TYPE aType )
164 {
165     // Don't do anything if we requested the UI update
166     if( m_updateLock )
167         return;
168 
169     ShowMessage( aMessage, aFlags );
170 
171     m_type = aType;
172 }
173 
174 
Dismiss()175 void WX_INFOBAR::Dismiss()
176 {
177     // Don't do anything if we requested the UI update
178     if( m_updateLock )
179         return;
180 
181     m_updateLock = true;
182 
183     wxInfoBarGeneric::Dismiss();
184 
185     if( m_auiManager )
186         updateAuiLayout( false );
187 
188     if( m_callback )
189         (*m_callback)();
190 
191     m_updateLock = false;
192 }
193 
194 
onSize(wxSizeEvent & aEvent)195 void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
196 {
197     int barWidth = GetSize().GetWidth();
198 
199     // Calculate the horizontal size: because the infobar is shown on top of the draw canvas
200     // it is adjusted to the canvas width.
201     // On Mac, the canvas is the parent
202     // On other OS the parent is EDA_BASE_FRAME that contains the canvas
203     int parentWidth = m_parent->GetClientSize().GetWidth();
204     EDA_BASE_FRAME* frame = dynamic_cast<EDA_BASE_FRAME*>( m_parent );
205 
206     if( frame && frame->GetToolCanvas() )
207         parentWidth = frame->GetToolCanvas()->GetSize().GetWidth();
208 
209 
210     if( barWidth != parentWidth )
211         SetSize( parentWidth, GetSize().GetHeight() );
212 
213     aEvent.Skip();
214 }
215 
216 
updateAuiLayout(bool aShow)217 void WX_INFOBAR::updateAuiLayout( bool aShow )
218 {
219     wxASSERT( m_auiManager );
220 
221     wxAuiPaneInfo& pane = m_auiManager->GetPane( this );
222 
223     // If the infobar is in a pane, then show/hide the pane
224     if( pane.IsOk() )
225     {
226         if( aShow )
227             pane.Show();
228         else
229             pane.Hide();
230     }
231 
232     // Update the AUI manager regardless
233     m_auiManager->Update();
234 }
235 
236 
AddButton(wxWindowID aId,const wxString & aLabel)237 void WX_INFOBAR::AddButton( wxWindowID aId, const wxString& aLabel )
238 {
239     wxButton* button = new wxButton( this, aId, aLabel );
240 
241     AddButton( button );
242 }
243 
244 
AddButton(wxButton * aButton)245 void WX_INFOBAR::AddButton( wxButton* aButton )
246 {
247     wxSizer* sizer = GetSizer();
248 
249     wxASSERT( aButton );
250 
251 #ifdef __WXMAC__
252     // Based on the code in the original class:
253     // smaller buttons look better in the (narrow) info bar under OS X
254     aButton->SetWindowVariant( wxWINDOW_VARIANT_SMALL );
255 #endif // __WXMAC__
256     sizer->Add( aButton, wxSizerFlags().Centre().Border( wxRIGHT ) );
257 
258     if( IsShown() )
259         sizer->Layout();
260 }
261 
262 
AddButton(wxHyperlinkCtrl * aHypertextButton)263 void WX_INFOBAR::AddButton( wxHyperlinkCtrl* aHypertextButton )
264 {
265     wxSizer* sizer = GetSizer();
266 
267     wxASSERT( aHypertextButton );
268 
269     sizer->Add( aHypertextButton, wxSizerFlags().Centre().Border( wxRIGHT ) );
270 
271     if( IsShown() )
272         sizer->Layout();
273 }
274 
275 
AddCloseButton(const wxString & aTooltip)276 void WX_INFOBAR::AddCloseButton( const wxString& aTooltip )
277 {
278     wxBitmapButton* button = wxBitmapButton::NewCloseButton( this, ID_CLOSE_INFOBAR );
279 
280     button->SetToolTip( aTooltip );
281 
282     AddButton( button );
283 }
284 
285 
RemoveAllButtons()286 void WX_INFOBAR::RemoveAllButtons()
287 {
288     wxSizer* sizer = GetSizer();
289 
290     if( sizer->GetItemCount() == 0 )
291         return;
292 
293     // The last item is already the spacer
294     if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
295         return;
296 
297     for( int i = sizer->GetItemCount() - 1; i >= 0; i-- )
298     {
299         wxSizerItem* sItem = sizer->GetItem( i );
300 
301         // The spacer is the end of the custom buttons
302         if( sItem->IsSpacer() )
303             break;
304 
305         delete sItem->GetWindow();
306     }
307 }
308 
309 
HasCloseButton() const310 bool WX_INFOBAR::HasCloseButton() const
311 {
312     wxSizer* sizer = GetSizer();
313 
314     if( sizer->GetItemCount() == 0 )
315         return false;
316 
317     if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
318         return false;
319 
320     wxSizerItem* item = sizer->GetItem( sizer->GetItemCount() - 1 );
321 
322     return ( item->GetWindow()->GetId() == ID_CLOSE_INFOBAR );
323 }
324 
325 
onShowInfoBar(wxCommandEvent & aEvent)326 void WX_INFOBAR::onShowInfoBar( wxCommandEvent& aEvent )
327 {
328     RemoveAllButtons();
329     AddCloseButton();
330     ShowMessage( aEvent.GetString(), aEvent.GetInt() );
331 }
332 
333 
onDismissInfoBar(wxCommandEvent & aEvent)334 void WX_INFOBAR::onDismissInfoBar( wxCommandEvent& aEvent )
335 {
336     Dismiss();
337 }
338 
339 
onCloseButton(wxCommandEvent & aEvent)340 void WX_INFOBAR::onCloseButton( wxCommandEvent& aEvent )
341 {
342     Dismiss();
343 }
344 
345 
onTimer(wxTimerEvent & aEvent)346 void WX_INFOBAR::onTimer( wxTimerEvent& aEvent )
347 {
348     // Reset and clear the timer
349     m_showTimer->Stop();
350     m_showTime = 0;
351 
352     Dismiss();
353 }
354 
355 
EDA_INFOBAR_PANEL(wxWindow * aParent,wxWindowID aId,const wxPoint & aPos,const wxSize & aSize,long aStyle,const wxString & aName)356 EDA_INFOBAR_PANEL::EDA_INFOBAR_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos,
357                                       const wxSize& aSize, long aStyle, const wxString& aName )
358          : wxPanel( aParent, aId, aPos, aSize, aStyle, aName )
359 {
360     m_mainSizer = new wxFlexGridSizer( 1, 0, 0 );
361 
362     m_mainSizer->SetFlexibleDirection( wxBOTH );
363     m_mainSizer->AddGrowableCol( 0, 1 );
364 
365     SetSizer( m_mainSizer );
366 }
367 
368 
AddInfoBar(WX_INFOBAR * aInfoBar)369 void EDA_INFOBAR_PANEL::AddInfoBar( WX_INFOBAR* aInfoBar )
370 {
371     wxASSERT( aInfoBar );
372 
373     aInfoBar->Reparent( this );
374     m_mainSizer->Add( aInfoBar, 1, wxEXPAND, 0 );
375     m_mainSizer->Layout();
376 }
377 
378 
AddOtherItem(wxWindow * aOtherItem)379 void EDA_INFOBAR_PANEL::AddOtherItem( wxWindow* aOtherItem )
380 {
381     wxASSERT( aOtherItem );
382 
383     aOtherItem->Reparent( this );
384     m_mainSizer->Add( aOtherItem, 1, wxEXPAND, 0 );
385 
386     m_mainSizer->AddGrowableRow( 1, 1 );
387     m_mainSizer->Layout();
388 }
389