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
22#include <id.h>
23#include <kiplatform/ui.h>
24#include <widgets/wx_infobar.h>
25#include "wx/artprov.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
35#ifdef __WXMSW__
36#include <dpi_scaling_common.h>
37#endif
38
39
40wxDEFINE_EVENT( KIEVT_SHOW_INFOBAR, wxCommandEvent );
41wxDEFINE_EVENT( KIEVT_DISMISS_INFOBAR, wxCommandEvent );
42
43BEGIN_EVENT_TABLE( WX_INFOBAR, wxInfoBarGeneric )
44 EVT_COMMAND( wxID_ANY, KIEVT_SHOW_INFOBAR, WX_INFOBAR::onShowInfoBar )
45 EVT_COMMAND( wxID_ANY, KIEVT_DISMISS_INFOBAR, WX_INFOBAR::onDismissInfoBar )
46
47 EVT_SYS_COLOUR_CHANGED( WX_INFOBAR::onThemeChange )
50END_EVENT_TABLE()
51
52
53WX_INFOBAR::WX_INFOBAR( wxWindow* aParent, wxWindowID aWinid, bool aOverlay )
54 : wxInfoBarGeneric( aParent, aWinid ),
55 m_showTime( 0 ),
56 m_updateLock( false ),
57 m_overlay( aOverlay ),
58 m_showTimer( nullptr ),
60{
61 m_showTimer = new wxTimer( this, ID_CLOSE_INFOBAR );
62
63 wxColour fg, bg;
65 SetBackgroundColour( bg );
66 SetForegroundColour( fg );
67
68#ifdef __WXMAC__
69 // Infobar is broken on Mac without the effects
70 SetShowHideEffects( wxSHOW_EFFECT_ROLL_TO_BOTTOM, wxSHOW_EFFECT_ROLL_TO_TOP );
71 SetEffectDuration( 200 );
72#else
73 // Infobar freezes canvas on Windows with the effect, and GTK looks bad with it
74 SetShowHideEffects( wxSHOW_EFFECT_NONE, wxSHOW_EFFECT_NONE );
75#endif
76
77 // The infobar seems to start too small, so increase its height
78 int sx, sy;
79 GetSize( &sx, &sy );
80 sy = 1.5 * sy;
81
82 // The bitmap gets cutoff sometimes with the default size, so force it to be the same
83 // height as the infobar.
84 wxSizer* sizer = GetSizer();
85 wxSize iconSize = wxArtProvider::GetSizeHint( wxART_BUTTON );
86
87#ifdef __WXMSW__
88 DPI_SCALING_COMMON dpi( nullptr, aParent );
89 iconSize.x *= dpi.GetContentScaleFactor();
90 sx *= dpi.GetContentScaleFactor();
91 sy *= dpi.GetContentScaleFactor();
92#endif
93
94 SetSize( sx, sy );
95
96 sizer->SetItemMinSize( (size_t) 0, iconSize.x, sy );
97
98 // Forcefully remove all existing buttons added by the wx constructors.
99 // The default close button doesn't work with the AUI manager update scheme, so this
100 // ensures any close button displayed is ours.
102
103 Layout();
104
105 m_parent->Bind( wxEVT_SIZE, &WX_INFOBAR::onSize, this );
106}
107
108
110{
111 m_parent->Unbind( wxEVT_SIZE, &WX_INFOBAR::onSize, this );
112
113 delete m_showTimer;
114}
115
116
117void WX_INFOBAR::SetShowTime( int aTime )
118{
119 m_showTime = aTime;
120}
121
122
123void WX_INFOBAR::QueueShowMessage( const wxString& aMessage, int aFlags )
124{
125 wxCommandEvent* evt = new wxCommandEvent( KIEVT_SHOW_INFOBAR );
126
127 evt->SetString( aMessage.c_str() );
128 evt->SetInt( aFlags );
129
130 GetEventHandler()->QueueEvent( evt );
131}
132
133
135{
136 wxCommandEvent* evt = new wxCommandEvent( KIEVT_DISMISS_INFOBAR );
137
138 GetEventHandler()->QueueEvent( evt );
139}
140
141
142void WX_INFOBAR::ShowMessageFor( const wxString& aMessage, int aTime, int aFlags,
143 MESSAGE_TYPE aType )
144{
145 // Don't do anything if we requested the UI update
146 if( m_updateLock )
147 return;
148
149 m_showTime = aTime;
150 ShowMessage( aMessage, aFlags );
151
152 m_type = aType;
153}
154
155
156void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags )
157{
158 // Don't do anything if we requested the UI update
159 if( m_updateLock )
160 return;
161
162 m_updateLock = true;
163
164 m_message = aMessage;
165 m_message.Trim();
166
167 wxInfoBarGeneric::ShowMessage( m_message, aFlags );
168
169 // Force infoBar to full-width. Yes, this should happen automatically, and it does -- sometimes.
170 FixSize();
172
173 if( m_showTime > 0 )
174 m_showTimer->StartOnce( m_showTime );
175
176 m_type = MESSAGE_TYPE::GENERIC;
177 m_updateLock = false;
178}
179
180
181void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags, MESSAGE_TYPE aType )
182{
183 // Don't do anything if we requested the UI update
184 if( m_updateLock )
185 return;
186
187 ShowMessage( aMessage, aFlags );
188
189 m_type = aType;
190}
191
192
194{
195 if( !IsShownOnScreen() )
196 return;
197
198 // Don't do anything if we requested the UI update
199 if( m_updateLock )
200 return;
201
202 m_updateLock = true;
203
204 wxInfoBarGeneric::Dismiss();
205
208
209 if( m_callback )
210 (*m_callback)();
211
212 m_updateLock = false;
213}
214
215
216void WX_INFOBAR::onThemeChange( wxSysColourChangedEvent& aEvent )
217{
218 wxColour fg, bg;
220 SetBackgroundColour( bg );
221 SetForegroundColour( fg );
222
223 if( wxBitmapButton* btn = GetCloseButton() )
224 {
225 wxString tooltip = btn->GetToolTipText();
227 AddCloseButton( tooltip );
228 }
229}
230
231
232// Big hack here. We've had many bugs for a long time with the info bar not being full-width. Things got
233// better in the 9.0 time-frame, but then started to degrade again in 10.0/11.0.
234//
235// This attempts to fix things by correcting the size every time the infobar is shown. But even that has
236// flies in the ointment, as the "normal" infobar height is calculated before the icon is added to it, and
237// is shorter.
238//
239// So we have to squirrel away the icon, calculate the size, and then re-attach the icon.
240
242{
243 wxSizer* sizer = GetSizer();
244
245 if( !sizer || sizer->GetItemCount() == 0 )
246 return;
247
248#if wxCHECK_VERSION( 3, 3, 0 )
249 // On wx 3.3 item 0 is not the icon. It is a nested sizer that holds everything.
250 // Detaching it leaves the infobar empty. Then doSize() reads a null item and crashes.
251 doSize();
252 Layout();
253#else
254 wxWindow* icon = sizer->GetItem( (size_t) 0 )->GetWindow();
255 sizer->Detach( 0 );
256
257 doSize();
258
259 sizer->Prepend( icon, 0, wxLEFT | wxRIGHT, 5 );
260 Layout();
261#endif
262}
263
264
265void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
266{
267 doSize();
269
270 aEvent.Skip();
271}
272
273
275{
276 int barWidth = GetSize().GetWidth();
277 wxSizer* sizer = GetSizer();
278
279 if( !sizer )
280 return;
281
282 // wx3.3 moved the sizer we previously wanted deeper into sizers...
283 // do we actually still need this for wx3.3?
284 #if wxCHECK_VERSION( 3, 3, 0 )
285 wxSizerItem* outerSizer = sizer->GetItem( (size_t) 0 );
286 wxSizerItem* textSizer = nullptr;
287
288 if (outerSizer->IsSizer())
289 {
290 wxBoxSizer* innerSizer1 = dynamic_cast<wxBoxSizer*>( outerSizer->GetSizer() );
291 wxBoxSizer* innerSizer2 =
292 dynamic_cast<wxBoxSizer*>( innerSizer1->GetItem((size_t)0)->GetSizer() );
293
294 if( innerSizer2 )
295 textSizer = innerSizer2->GetItem( 1 );
296 }
297 #else
298 wxSizerItem* textSizer = sizer->GetItem( 1 );
299 #endif
300
301 if( textSizer )
302 {
303 if( wxStaticText* textCtrl = dynamic_cast<wxStaticText*>( textSizer->GetWindow() ) )
304 textCtrl->SetLabelText( m_message );
305 }
306
307 int parentWidth = m_parent->GetClientSize().GetWidth();
308
309 if( barWidth != parentWidth )
310 SetSize( parentWidth, GetSize().GetHeight() );
311
312 if( textSizer )
313 {
314 if( wxStaticText* textCtrl = dynamic_cast<wxStaticText*>( textSizer->GetWindow() ) )
315 {
316 // Re-wrap the text (this is done automatically later but we need it now)
317 // And count how many lines we need. If we have embedded newlines, then
318 // multiply the number of lines by the text min height to find the correct
319 // min height for the control. The min height of the text control will be the size
320 // of a single line of text. This assumes that two lines of text are larger
321 // than the height of the icon for the bar.
322 textCtrl->Wrap( -1 );
323 wxString wrapped_text = textCtrl->GetLabel();
324 int line_count = wrapped_text.Freq( '\n' ) + 1;
325 int txt_h, txt_v;
326 wxWindowDC dc( textCtrl );
327 dc.GetTextExtent( wxT( "Xp" ), &txt_h, &txt_v );
328
329 int height = txt_v * line_count;
330 int margins = txt_v - 1;
331 SetMinSize( wxSize( GetSize().GetWidth(), height + margins ) );
332
333 textCtrl->Wrap( -1 );
334 }
335 }
336}
337
338
340{
341 if( !m_overlay )
342 return;
343
344 int width = m_parent->GetClientSize().GetWidth();
345 int y = 0;
346
347 for( wxWindow* child : m_parent->GetChildren() )
348 {
349 WX_INFOBAR* bar = dynamic_cast<WX_INFOBAR*>( child );
350
351 if( !bar || !bar->IsShown() )
352 continue;
353
354 int height = bar->GetEffectiveMinSize().GetHeight();
355
356 bar->SetSize( 0, y, width, height );
357
358 // The GAL backend window is a sibling that raises itself whenever it is shown
359 bar->Raise();
360
361 y += height;
362 }
363
364 if( EDA_DRAW_PANEL_GAL* canvas = dynamic_cast<EDA_DRAW_PANEL_GAL*>( m_parent ) )
365 canvas->UpdateOverlayExclusions();
366}
367
368
370{
371 if( !m_overlay )
372 return;
373
374 // A GAL canvas skips repaints when no target is dirty, leaving the uncovered strip stale
375 if( EDA_DRAW_PANEL_GAL* canvas = dynamic_cast<EDA_DRAW_PANEL_GAL*>( m_parent ) )
376 {
377 canvas->ForceRefresh();
378
379 // ForceRefresh() gives up silently if the context is busy, so queue a backstop
380 canvas->RequestRefresh();
381 }
382 else
383 {
384 m_parent->Refresh();
385 }
386}
387
388
389void WX_INFOBAR::AddButton( wxWindowID aId, const wxString& aLabel )
390{
391 wxButton* button = new wxButton( this, aId, aLabel );
392
393 AddButton( button );
394}
395
396
397void WX_INFOBAR::AddButton( wxButton* aButton )
398{
399 wxSizer* sizer = GetSizer();
400
401 wxASSERT( aButton );
402
403#ifdef __WXMAC__
404 // Based on the code in the original class:
405 // smaller buttons look better in the (narrow) info bar under OS X
406 aButton->SetWindowVariant( wxWINDOW_VARIANT_SMALL );
407#endif // __WXMAC__
408
409 auto element = sizer->Add( aButton, wxSizerFlags( 0 ).Centre().Border( wxRIGHT ) );
410
411 element->SetFlag( wxSTRETCH_MASK );
412
413 if( IsShownOnScreen() )
414 {
415 Layout();
416 sizer->Fit( this );
417 }
418}
419
420
421void WX_INFOBAR::AddLink(const wxString& aLinkText, const std::function<void(wxHyperlinkEvent&)>& aFn )
422{
423 wxSizer* sizer = GetSizer();
424 wxHyperlinkCtrl* button = new wxHyperlinkCtrl( this, wxID_ANY, aLinkText, wxEmptyString );
425
426 button->Bind( wxEVT_COMMAND_HYPERLINK, aFn );
427
428 sizer->Add( button, wxSizerFlags().Centre().Border( wxRIGHT ).Shaped() );
429
430 if( IsShownOnScreen() )
431 {
432 Layout();
433 sizer->Fit( this );
434 }
435}
436
437
438void WX_INFOBAR::AddCloseButton( const wxString& aTooltip )
439{
440 wxBitmapButton* button = wxBitmapButton::NewCloseButton( this, ID_CLOSE_INFOBAR );
441
442 button->SetToolTip( aTooltip );
443
444 AddButton( button );
445}
446
447
449{
450 wxSizer* sizer = GetSizer();
451
452 if( sizer->GetItemCount() == 0 )
453 return;
454
455 // The last item is already the spacer
456 if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
457 return;
458
459 for( int i = (int) sizer->GetItemCount() - 1; i >= 0; i-- )
460 {
461 wxSizerItem* sItem = sizer->GetItem( i );
462
463 // The spacer is the end of the custom buttons
464 if( sItem->IsSpacer() )
465 break;
466
467 if( wxWindow* button = sItem->GetWindow() )
468 {
469 sizer->Detach( button );
470 button->Destroy();
471 }
472 }
473}
474
475
477{
478 return GetCloseButton();
479}
480
481
482wxBitmapButton* WX_INFOBAR::GetCloseButton() const
483{
484 wxSizer* sizer = GetSizer();
485
486 if( !sizer )
487 return nullptr;
488
489 if( sizer->GetItemCount() == 0 )
490 return nullptr;
491
492 if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
493 return nullptr;
494
495 wxSizerItem* item = sizer->GetItem( sizer->GetItemCount() - 1 );
496
497 if( item && item->GetWindow() && item->GetWindow()->GetId() == ID_CLOSE_INFOBAR )
498 return static_cast<wxBitmapButton*>( item->GetWindow() );
499
500 return nullptr;
501}
502
503
504void WX_INFOBAR::onShowInfoBar( wxCommandEvent& aEvent )
505{
508 ShowMessage( aEvent.GetString(), aEvent.GetInt() );
509}
510
511
512void WX_INFOBAR::onDismissInfoBar( wxCommandEvent& aEvent )
513{
514 Dismiss();
515}
516
517
518void WX_INFOBAR::onCloseButton( wxCommandEvent& aEvent )
519{
520 Dismiss();
521}
522
523
524void WX_INFOBAR::onTimer( wxTimerEvent& aEvent )
525{
526 // Reset and clear the timer
527 m_showTimer->Stop();
528 m_showTime = 0;
529
530 Dismiss();
531}
532
533
534EDA_INFOBAR_PANEL::EDA_INFOBAR_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos,
535 const wxSize& aSize, long aStyle, const wxString& aName )
536 : wxPanel( aParent, aId, aPos, aSize, aStyle, aName )
537{
538 m_mainSizer = new wxFlexGridSizer( 1, 0, 0 );
539
540 m_mainSizer->SetFlexibleDirection( wxBOTH );
541 m_mainSizer->AddGrowableCol( 0, 1 );
542
543 SetSizer( m_mainSizer );
544}
545
546
548{
549 wxASSERT( aInfoBar );
550
551 aInfoBar->Reparent( this );
552 m_mainSizer->Add( aInfoBar, 1, wxEXPAND, 0 );
553 m_mainSizer->Layout();
554}
555
556
557void EDA_INFOBAR_PANEL::AddOtherItem( wxWindow* aOtherItem )
558{
559 wxASSERT( aOtherItem );
560
561 aOtherItem->Reparent( this );
562 m_mainSizer->Add( aOtherItem, 1, wxEXPAND, 0 );
563
564 m_mainSizer->AddGrowableRow( 1, 1 );
565 m_mainSizer->Layout();
566}
567
568
569REPORTER& INFOBAR_REPORTER::Report( const wxString& aText, SEVERITY aSeverity )
570{
571 m_message.reset( new wxString( aText ) );
572 m_severity = aSeverity;
573 m_messageSet = true;
574
575 return *this;
576}
577
578
580{
581 return m_message && !m_message->IsEmpty();
582}
583
584
586{
587 // Don't do anything if no message was ever given
588 if( !m_infoBar || !m_messageSet )
589 return;
590
591 // Consume the pending report
592 m_messageSet = false;
593
594 // Short circuit if the message is empty and it is already hidden
595 if( !HasMessage() && !m_infoBar->IsShownOnScreen() )
596 return;
597
598 int icon = wxICON_NONE;
599
600 switch( m_severity )
601 {
602 case RPT_SEVERITY_UNDEFINED: icon = wxICON_INFORMATION; break;
603 case RPT_SEVERITY_INFO: icon = wxICON_INFORMATION; break;
604 case RPT_SEVERITY_EXCLUSION: icon = wxICON_WARNING; break;
605 case RPT_SEVERITY_ACTION: icon = wxICON_WARNING; break;
606 case RPT_SEVERITY_WARNING: icon = wxICON_WARNING; break;
607 case RPT_SEVERITY_ERROR: icon = wxICON_ERROR; break;
608 case RPT_SEVERITY_IGNORE: icon = wxICON_INFORMATION; break;
609 case RPT_SEVERITY_DEBUG: icon = wxICON_INFORMATION; break;
610 }
611
612 if( m_message->EndsWith( wxS( "\n" ) ) )
613 *m_message = m_message->Left( m_message->Length() - 1 );
614
615 if( HasMessage() )
616 m_infoBar->QueueShowMessage( *m_message, icon );
617 else
618 m_infoBar->QueueDismiss();
619}
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.
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:310
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:347
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
void Finalize() override
Update the infobar with the reported text.
SEVERITY m_severity
Definition wx_infobar.h:349
std::unique_ptr< wxString > m_message
Definition wx_infobar.h:348
bool HasMessage() const override
Returns true if any messages were reported.
REPORTER()
Definition reporter.h:75
A modified version of the wxInfoBar class that allows us to:
Definition wx_infobar.h:76
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 refreshParent()
Repaint the parent so that the area the infobar covered is not left stale.
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:261
bool m_overlay
True if this infobar is drawn on top of its parent.
Definition wx_infobar.h:259
INFOBAR_MESSAGE_TYPE MESSAGE_TYPE
Definition wx_infobar.h:94
void AddLink(const wxString &aLinkText, const std::function< void(wxHyperlinkEvent &)> &aFn)
Add a hypertext link to the infobar.
WX_INFOBAR(wxWindow *aParent, wxWindowID aWinid=wxID_ANY, bool aOverlay=false)
Construct an infobar.
void stackOverlays()
Lay out every overlay infobar sharing this one's parent as a top-anchored stack, so that two infobars...
std::optional< std::function< void(void)> > m_callback
Optional callback made when closing infobar.
Definition wx_infobar.h:264
wxString m_message
The original message without wrapping.
Definition wx_infobar.h:262
int m_showTime
The time to show the infobar. 0 = don't auto hide.
Definition wx_infobar.h:257
bool m_updateLock
True if this infobar requested the UI update.
Definition wx_infobar.h:258
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.
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.
void onSize(wxSizeEvent &aEvent)
void QueueDismiss()
Send the infobar an event telling it to hide itself.
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:41
@ GENERIC
GENERIC Are messages that do not have special handling.