KiCad PCB EDA Suite
Loading...
Searching...
No Matches
wx_infobar.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2020 Ian McInerney <[email protected]>
5 * Copyright The 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
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <id.h>
22#include <kiplatform/ui.h>
23#include <widgets/wx_infobar.h>
24#include "wx/artprov.h"
25#include <wx/aui/framemanager.h>
26#include <wx/bmpbuttn.h>
27#include <wx/debug.h>
28#include <wx/hyperlink.h>
29#include <wx/infobar.h>
30#include <wx/sizer.h>
31#include <wx/stattext.h>
32#include <wx/timer.h>
33#include <wx/dcclient.h>
34#include <eda_base_frame.h>
35
36#ifdef __WXMSW__
37#include <dpi_scaling_common.h>
38#endif
39
40
41wxDEFINE_EVENT( KIEVT_SHOW_INFOBAR, wxCommandEvent );
42wxDEFINE_EVENT( KIEVT_DISMISS_INFOBAR, wxCommandEvent );
43
44BEGIN_EVENT_TABLE( WX_INFOBAR, wxInfoBarGeneric )
45 EVT_COMMAND( wxID_ANY, KIEVT_SHOW_INFOBAR, WX_INFOBAR::onShowInfoBar )
46 EVT_COMMAND( wxID_ANY, KIEVT_DISMISS_INFOBAR, WX_INFOBAR::onDismissInfoBar )
47
48 EVT_SYS_COLOUR_CHANGED( WX_INFOBAR::onThemeChange )
51END_EVENT_TABLE()
52
53
54WX_INFOBAR::WX_INFOBAR( wxWindow* aParent, wxAuiManager* aMgr, wxWindowID aWinid )
55 : wxInfoBarGeneric( aParent, aWinid ),
56 m_showTime( 0 ),
57 m_updateLock( false ),
58 m_showTimer( nullptr ),
59 m_auiManager( aMgr ),
61{
62 m_showTimer = new wxTimer( this, ID_CLOSE_INFOBAR );
63
64 wxColour fg, bg;
66 SetBackgroundColour( bg );
67 SetForegroundColour( fg );
68
69#ifdef __WXMAC__
70 // Infobar is broken on Mac without the effects
71 SetShowHideEffects( wxSHOW_EFFECT_ROLL_TO_BOTTOM, wxSHOW_EFFECT_ROLL_TO_TOP );
72 SetEffectDuration( 200 );
73#else
74 // Infobar freezes canvas on Windows with the effect, and GTK looks bad with it
75 SetShowHideEffects( wxSHOW_EFFECT_NONE, wxSHOW_EFFECT_NONE );
76#endif
77
78 // The infobar seems to start too small, so increase its height
79 int sx, sy;
80 GetSize( &sx, &sy );
81 sy = 1.5 * sy;
82
83 // The bitmap gets cutoff sometimes with the default size, so force it to be the same
84 // height as the infobar.
85 wxSizer* sizer = GetSizer();
86 wxSize iconSize = wxArtProvider::GetSizeHint( wxART_BUTTON );
87
88#ifdef __WXMSW__
89 DPI_SCALING_COMMON dpi( nullptr, aParent );
90 iconSize.x *= dpi.GetContentScaleFactor();
91 sx *= dpi.GetContentScaleFactor();
92 sy *= dpi.GetContentScaleFactor();
93#endif
94
95 SetSize( sx, sy );
96
97 sizer->SetItemMinSize( (size_t) 0, iconSize.x, sy );
98
99 // Forcefully remove all existing buttons added by the wx constructors.
100 // The default close button doesn't work with the AUI manager update scheme, so this
101 // ensures any close button displayed is ours.
103
104 Layout();
105
106 m_parent->Bind( wxEVT_SIZE, &WX_INFOBAR::onSize, this );
107}
108
109
111{
112 m_parent->Unbind( wxEVT_SIZE, &WX_INFOBAR::onSize, this );
113
114 delete m_showTimer;
115}
116
117
118void WX_INFOBAR::SetShowTime( int aTime )
119{
120 m_showTime = aTime;
121}
122
123
124void WX_INFOBAR::QueueShowMessage( const wxString& aMessage, int aFlags )
125{
126 wxCommandEvent* evt = new wxCommandEvent( KIEVT_SHOW_INFOBAR );
127
128 evt->SetString( aMessage.c_str() );
129 evt->SetInt( aFlags );
130
131 GetEventHandler()->QueueEvent( evt );
132}
133
134
136{
137 wxCommandEvent* evt = new wxCommandEvent( KIEVT_DISMISS_INFOBAR );
138
139 GetEventHandler()->QueueEvent( evt );
140}
141
142
143void WX_INFOBAR::ShowMessageFor( const wxString& aMessage, int aTime, int aFlags,
144 MESSAGE_TYPE aType )
145{
146 // Don't do anything if we requested the UI update
147 if( m_updateLock )
148 return;
149
150 m_showTime = aTime;
151 ShowMessage( aMessage, aFlags );
152
153 m_type = aType;
154}
155
156
157void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags )
158{
159 // Don't do anything if we requested the UI update
160 if( m_updateLock )
161 return;
162
163 m_updateLock = true;
164
165 m_message = aMessage;
166 m_message.Trim();
167
168 wxInfoBarGeneric::ShowMessage( m_message, aFlags );
169
170 // Force infoBar to full-width. Yes, this should happen automatically, and it does -- sometimes.
171 FixSize();
172
173 if( m_auiManager )
174 updateAuiLayout( true );
175
176 if( m_showTime > 0 )
177 m_showTimer->StartOnce( m_showTime );
178
179 m_type = MESSAGE_TYPE::GENERIC;
180 m_updateLock = false;
181}
182
183
184void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags, MESSAGE_TYPE aType )
185{
186 // Don't do anything if we requested the UI update
187 if( m_updateLock )
188 return;
189
190 ShowMessage( aMessage, aFlags );
191
192 m_type = aType;
193}
194
195
197{
198 if( !IsShownOnScreen() )
199 return;
200
201 // Don't do anything if we requested the UI update
202 if( m_updateLock )
203 return;
204
205 m_updateLock = true;
206
207 wxInfoBarGeneric::Dismiss();
208
209 if( m_auiManager )
210 updateAuiLayout( false );
211
212 if( m_callback )
213 (*m_callback)();
214
215 m_updateLock = false;
216}
217
218
219void WX_INFOBAR::onThemeChange( wxSysColourChangedEvent& aEvent )
220{
221 wxColour fg, bg;
223 SetBackgroundColour( bg );
224 SetForegroundColour( fg );
225
226 if( wxBitmapButton* btn = GetCloseButton() )
227 {
228 wxString tooltip = btn->GetToolTipText();
230 AddCloseButton( tooltip );
231 }
232}
233
234
235// Big hack here. We've had many bugs for a long time with the info bar not being full-width. Things got
236// better in the 9.0 time-frame, but then started to degrade again in 10.0/11.0.
237//
238// This attempts to fix things by correcting the size every time the infobar is shown. But even that has
239// flies in the ointment, as the "normal" infobar height is calculated before the icon is added to it, and
240// is shorter.
241//
242// So we have to squirrel away the icon, calculate the size, and then re-attach the icon.
243
245{
246 wxSizer* sizer = GetSizer();
247
248 if( !sizer || sizer->GetItemCount() == 0 )
249 return;
250
251#if wxCHECK_VERSION( 3, 3, 0 )
252 // On wx 3.3 item 0 is not the icon. It is a nested sizer that holds everything.
253 // Detaching it leaves the infobar empty. Then doSize() reads a null item and crashes.
254 doSize();
255 Layout();
256#else
257 wxWindow* icon = sizer->GetItem( (size_t) 0 )->GetWindow();
258 sizer->Detach( 0 );
259
260 doSize();
261
262 sizer->Prepend( icon, 0, wxLEFT | wxRIGHT, 5 );
263 Layout();
264#endif
265}
266
267
268void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
269{
270 doSize();
271
272 aEvent.Skip();
273}
274
275
277{
278 int barWidth = GetSize().GetWidth();
279 wxSizer* sizer = GetSizer();
280
281 if( !sizer )
282 return;
283
284 // wx3.3 moved the sizer we previously wanted deeper into sizers...
285 // do we actually still need this for wx3.3?
286 #if wxCHECK_VERSION( 3, 3, 0 )
287 wxSizerItem* outerSizer = sizer->GetItem( (size_t) 0 );
288 wxSizerItem* textSizer = nullptr;
289
290 if (outerSizer->IsSizer())
291 {
292 wxBoxSizer* innerSizer1 = dynamic_cast<wxBoxSizer*>( outerSizer->GetSizer() );
293 wxBoxSizer* innerSizer2 =
294 dynamic_cast<wxBoxSizer*>( innerSizer1->GetItem((size_t)0)->GetSizer() );
295
296 if( innerSizer2 )
297 textSizer = innerSizer2->GetItem( 1 );
298 }
299 #else
300 wxSizerItem* textSizer = sizer->GetItem( 1 );
301 #endif
302
303 if( textSizer )
304 {
305 if( wxStaticText* textCtrl = dynamic_cast<wxStaticText*>( textSizer->GetWindow() ) )
306 textCtrl->SetLabelText( m_message );
307 }
308
309 // Calculate the horizontal size: because the infobar is shown on top of the draw canvas
310 // it is adjusted to the canvas width.
311 // On Mac, the canvas is the parent
312 // On other OS the parent is EDA_BASE_FRAME that contains the canvas
313 int parentWidth = m_parent->GetClientSize().GetWidth();
314 EDA_BASE_FRAME* frame = dynamic_cast<EDA_BASE_FRAME*>( m_parent );
315
316 if( frame && frame->GetToolCanvas() )
317 parentWidth = frame->GetToolCanvas()->GetSize().GetWidth();
318
319 if( barWidth != parentWidth )
320 SetSize( parentWidth, GetSize().GetHeight() );
321
322 if( textSizer )
323 {
324 if( wxStaticText* textCtrl = dynamic_cast<wxStaticText*>( textSizer->GetWindow() ) )
325 {
326 // Re-wrap the text (this is done automatically later but we need it now)
327 // And count how many lines we need. If we have embedded newlines, then
328 // multiply the number of lines by the text min height to find the correct
329 // min height for the control. The min height of the text control will be the size
330 // of a single line of text. This assumes that two lines of text are larger
331 // than the height of the icon for the bar.
332 textCtrl->Wrap( -1 );
333 wxString wrapped_text = textCtrl->GetLabel();
334 int line_count = wrapped_text.Freq( '\n' ) + 1;
335 int txt_h, txt_v;
336 wxWindowDC dc( textCtrl );
337 dc.GetTextExtent( wxT( "Xp" ), &txt_h, &txt_v );
338
339 int height = txt_v * line_count;
340 int margins = txt_v - 1;
341 SetMinSize( wxSize( GetSize().GetWidth(), height + margins ) );
342
343 textCtrl->Wrap( -1 );
344 }
345 }
346}
347
348
350{
351 wxASSERT( m_auiManager );
352
353 wxAuiPaneInfo& pane = m_auiManager->GetPane( this );
354
355 // If the infobar is in a pane, then show/hide the pane
356 if( pane.IsOk() )
357 {
358 if( aShow )
359 pane.Show();
360 else
361 pane.Hide();
362 }
363
364 // Update the AUI manager regardless
365 m_auiManager->Update();
366}
367
368
369void WX_INFOBAR::AddButton( wxWindowID aId, const wxString& aLabel )
370{
371 wxButton* button = new wxButton( this, aId, aLabel );
372
373 AddButton( button );
374}
375
376
377void WX_INFOBAR::AddButton( wxButton* aButton )
378{
379 wxSizer* sizer = GetSizer();
380
381 wxASSERT( aButton );
382
383#ifdef __WXMAC__
384 // Based on the code in the original class:
385 // smaller buttons look better in the (narrow) info bar under OS X
386 aButton->SetWindowVariant( wxWINDOW_VARIANT_SMALL );
387#endif // __WXMAC__
388
389 auto element = sizer->Add( aButton, wxSizerFlags( 0 ).Centre().Border( wxRIGHT ) );
390
391 element->SetFlag( wxSTRETCH_MASK );
392
393 if( IsShownOnScreen() )
394 {
395 Layout();
396 sizer->Fit( this );
397 }
398}
399
400
401void WX_INFOBAR::AddButton( wxHyperlinkCtrl* aHypertextButton )
402{
403 wxSizer* sizer = GetSizer();
404
405 wxASSERT( aHypertextButton );
406
407 sizer->Add( aHypertextButton, wxSizerFlags().Centre().Border( wxRIGHT ).Shaped() );
408
409 if( IsShownOnScreen() )
410 {
411 Layout();
412 sizer->Fit( this );
413 }
414}
415
416
417void WX_INFOBAR::AddCloseButton( const wxString& aTooltip )
418{
419 wxBitmapButton* button = wxBitmapButton::NewCloseButton( this, ID_CLOSE_INFOBAR );
420
421 button->SetToolTip( aTooltip );
422
423 AddButton( button );
424}
425
426
428{
429 wxSizer* sizer = GetSizer();
430
431 if( sizer->GetItemCount() == 0 )
432 return;
433
434 // The last item is already the spacer
435 if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
436 return;
437
438 for( int i = (int) sizer->GetItemCount() - 1; i >= 0; i-- )
439 {
440 wxSizerItem* sItem = sizer->GetItem( i );
441
442 // The spacer is the end of the custom buttons
443 if( sItem->IsSpacer() )
444 break;
445
446 if( wxWindow* button = sItem->GetWindow() )
447 {
448 sizer->Detach( button );
449 button->Destroy();
450 }
451 }
452}
453
454
456{
457 return GetCloseButton();
458}
459
460
461wxBitmapButton* WX_INFOBAR::GetCloseButton() const
462{
463 wxSizer* sizer = GetSizer();
464
465 if( !sizer )
466 return nullptr;
467
468 if( sizer->GetItemCount() == 0 )
469 return nullptr;
470
471 if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
472 return nullptr;
473
474 wxSizerItem* item = sizer->GetItem( sizer->GetItemCount() - 1 );
475
476 if( item && item->GetWindow() && item->GetWindow()->GetId() == ID_CLOSE_INFOBAR )
477 return static_cast<wxBitmapButton*>( item->GetWindow() );
478
479 return nullptr;
480}
481
482
483void WX_INFOBAR::onShowInfoBar( wxCommandEvent& aEvent )
484{
487 ShowMessage( aEvent.GetString(), aEvent.GetInt() );
488}
489
490
491void WX_INFOBAR::onDismissInfoBar( wxCommandEvent& aEvent )
492{
493 Dismiss();
494}
495
496
497void WX_INFOBAR::onCloseButton( wxCommandEvent& aEvent )
498{
499 Dismiss();
500}
501
502
503void WX_INFOBAR::onTimer( wxTimerEvent& aEvent )
504{
505 // Reset and clear the timer
506 m_showTimer->Stop();
507 m_showTime = 0;
508
509 Dismiss();
510}
511
512
513EDA_INFOBAR_PANEL::EDA_INFOBAR_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos,
514 const wxSize& aSize, long aStyle, const wxString& aName )
515 : wxPanel( aParent, aId, aPos, aSize, aStyle, aName )
516{
517 m_mainSizer = new wxFlexGridSizer( 1, 0, 0 );
518
519 m_mainSizer->SetFlexibleDirection( wxBOTH );
520 m_mainSizer->AddGrowableCol( 0, 1 );
521
522 SetSizer( m_mainSizer );
523}
524
525
527{
528 wxASSERT( aInfoBar );
529
530 aInfoBar->Reparent( this );
531 m_mainSizer->Add( aInfoBar, 1, wxEXPAND, 0 );
532 m_mainSizer->Layout();
533}
534
535
536void EDA_INFOBAR_PANEL::AddOtherItem( wxWindow* aOtherItem )
537{
538 wxASSERT( aOtherItem );
539
540 aOtherItem->Reparent( this );
541 m_mainSizer->Add( aOtherItem, 1, wxEXPAND, 0 );
542
543 m_mainSizer->AddGrowableRow( 1, 1 );
544 m_mainSizer->Layout();
545}
546
547
548REPORTER& INFOBAR_REPORTER::Report( const wxString& aText, SEVERITY aSeverity )
549{
550 m_message.reset( new wxString( aText ) );
551 m_severity = aSeverity;
552 m_messageSet = true;
553
554 return *this;
555}
556
557
559{
560 return m_message && !m_message->IsEmpty();
561}
562
563
565{
566 // Don't do anything if no message was ever given
567 if( !m_infoBar || !m_messageSet )
568 return;
569
570 // Short circuit if the message is empty and it is already hidden
571 if( !HasMessage() && !m_infoBar->IsShownOnScreen() )
572 return;
573
574 int icon = wxICON_NONE;
575
576 switch( m_severity )
577 {
578 case RPT_SEVERITY_UNDEFINED: icon = wxICON_INFORMATION; break;
579 case RPT_SEVERITY_INFO: icon = wxICON_INFORMATION; break;
580 case RPT_SEVERITY_EXCLUSION: icon = wxICON_WARNING; break;
581 case RPT_SEVERITY_ACTION: icon = wxICON_WARNING; break;
582 case RPT_SEVERITY_WARNING: icon = wxICON_WARNING; break;
583 case RPT_SEVERITY_ERROR: icon = wxICON_ERROR; break;
584 case RPT_SEVERITY_IGNORE: icon = wxICON_INFORMATION; break;
585 case RPT_SEVERITY_DEBUG: icon = wxICON_INFORMATION; break;
586 }
587
588 if( m_message->EndsWith( wxS( "\n" ) ) )
589 *m_message = m_message->Left( m_message->Length() - 1 );
590
591 if( HasMessage() )
592 m_infoBar->QueueShowMessage( *m_message, icon );
593 else
594 m_infoBar->QueueDismiss();
595}
Class to handle configuration and automatic determination of the DPI scale to use for canvases.
double GetContentScaleFactor() const override
Get the content scale factor, which may be different from the scale factor on some platforms.
The base frame for deriving all KiCad main window classes.
EDA_INFOBAR_PANEL(wxWindow *aParent, wxWindowID aId=wxID_ANY, const wxPoint &aPos=wxDefaultPosition, const wxSize &aSize=wxSize(-1,-1), long aStyle=wxTAB_TRAVERSAL, const wxString &aName=wxEmptyString)
wxFlexGridSizer * m_mainSizer
Definition wx_infobar.h:311
void AddInfoBar(WX_INFOBAR *aInfoBar)
Add the given infobar object to the panel.
void AddOtherItem(wxWindow *aOtherItem)
Add the other item to the panel.
WX_INFOBAR * m_infoBar
Definition wx_infobar.h:348
void Finalize()
Update the infobar with the reported text.
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
SEVERITY m_severity
Definition wx_infobar.h:350
std::unique_ptr< wxString > m_message
Definition wx_infobar.h:349
bool HasMessage() const override
Returns true if any messages were reported.
REPORTER()
Definition reporter.h:74
virtual wxWindow * GetToolCanvas() const =0
Canvas access.
A modified version of the wxInfoBar class that allows us to:
Definition wx_infobar.h:77
void FixSize()
Address some long-standing bugs regarding infobars comming up only partial-width.
void SetShowTime(int aTime)
Set the time period to show the infobar.
void RemoveAllButtons()
Remove all the buttons that have been added by the user.
void ShowMessageFor(const wxString &aMessage, int aTime, int aFlags=wxICON_INFORMATION, MESSAGE_TYPE aType=WX_INFOBAR::MESSAGE_TYPE::GENERIC)
Show the infobar with the provided message and icon for a specific period of time.
bool HasCloseButton() const
MESSAGE_TYPE m_type
The type of message being displayed.
Definition wx_infobar.h:262
INFOBAR_MESSAGE_TYPE MESSAGE_TYPE
Definition wx_infobar.h:91
void updateAuiLayout(bool aShow)
Update the AUI pane to show or hide this infobar.
std::optional< std::function< void(void)> > m_callback
Optional callback made when closing infobar.
Definition wx_infobar.h:265
wxString m_message
The original message without wrapping.
Definition wx_infobar.h:263
int m_showTime
The time to show the infobar. 0 = don't auto hide.
Definition wx_infobar.h:258
bool m_updateLock
True if this infobar requested the UI update.
Definition wx_infobar.h:259
void onShowInfoBar(wxCommandEvent &aEvent)
Event handler for showing the infobar using a wxCommandEvent of the type KIEVT_SHOW_INFOBAR.
void doSize()
void onDismissInfoBar(wxCommandEvent &aEvent)
Event handler for dismissing the infobar using a wxCommandEvent of the type KIEVT_DISMISS_INFOBAR.
void AddButton(wxButton *aButton)
Add an already created button to the infobar.
void QueueShowMessage(const wxString &aMessage, int aFlags=wxICON_INFORMATION)
Send the infobar an event telling it to show a message.
WX_INFOBAR(wxWindow *aParent, wxAuiManager *aMgr=nullptr, wxWindowID aWinid=wxID_ANY)
Construct an infobar that can exist inside an AUI managed frame.
void onCloseButton(wxCommandEvent &aEvent)
Event handler for the close button.
wxBitmapButton * GetCloseButton() const
void Dismiss() override
Dismisses the infobar and updates the containing layout and AUI manager (if one is provided).
void onThemeChange(wxSysColourChangedEvent &aEvent)
Event handler for the color theme change event.
void AddCloseButton(const wxString &aTooltip=_("Hide this message."))
Add the default close button to the infobar on the right side.
wxTimer * m_showTimer
The timer counting the autoclose period.
Definition wx_infobar.h:260
void onTimer(wxTimerEvent &aEvent)
Event handler for the automatic closing timer.
void ShowMessage(const wxString &aMessage, int aFlags=wxICON_INFORMATION) override
Show the info bar with the provided message and icon.
wxAuiManager * m_auiManager
The AUI manager that contains this infobar.
Definition wx_infobar.h:261
void onSize(wxSizeEvent &aEvent)
void QueueDismiss()
Send the infobar an event telling it to hide itself.
Base window classes and related definitions.
void GetInfoBarColours(wxColour &aFGColour, wxColour &aBGColour)
Return the background and foreground colors for info bars in the current scheme.
Definition wxgtk/ui.cpp:70
SEVERITY
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_UNDEFINED
@ RPT_SEVERITY_EXCLUSION
@ RPT_SEVERITY_IGNORE
@ RPT_SEVERITY_DEBUG
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
wxDEFINE_EVENT(KIEVT_SHOW_INFOBAR, wxCommandEvent)
@ ID_CLOSE_INFOBAR
ID for the close button on the frame's infobar.
Definition wx_infobar.h:42
@ GENERIC
GENERIC Are messages that do not have special handling.