KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kistatusbar.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) 2023 Mark Roszko <[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
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU 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, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <wx/button.h>
26#include <wx/statusbr.h>
27#include <wx/gauge.h>
28#include <wx/stattext.h>
29#include <wx/tokenzr.h>
30#include <fmt/format.h>
31#include <array>
32#include <ranges>
33#include <widgets/kistatusbar.h>
36#include <widgets/ui_common.h>
37#include <pgm_base.h>
40#include <bitmaps.h>
41#include <reporter.h>
43#include <trace_helpers.h>
44#include <wx/dcclient.h>
45
46
48{
49public:
50 STATUSBAR_WARNING_REPORTER_DIALOG( wxWindow* aParent, KISTATUSBAR* aStatusBar ) :
51 DIALOG_HTML_REPORTER( aParent, wxID_ANY, _( "Messages" ) ),
52 m_statusBar( aStatusBar )
53 {
54 m_clearButton = new wxButton( this, wxID_CLEAR, _( "Clear" ) );
55 m_clearButton->Bind( wxEVT_BUTTON,
57
58 m_sdbSizer->Insert( 0, m_clearButton, 0, wxALL, 5 );
59 GetSizer()->Layout();
60 GetSizer()->Fit( this );
61 }
62
63private:
64 void onClearButtonClick( wxCommandEvent& aEvent )
65 {
66 if( m_statusBar )
67 m_statusBar->ClearWarningMessages();
68
69 EndModal( wxID_CLEAR );
70 }
71
72private:
74 wxButton* m_clearButton;
75};
76
77
78KISTATUSBAR::KISTATUSBAR( int aNumberFields, wxWindow* parent, wxWindowID id, STYLE_FLAGS aFlags ) :
79 wxStatusBar( parent, id ),
80 m_backgroundStopButton( nullptr ),
81 m_notificationsButton( nullptr ),
82 m_warningButton( nullptr ),
83 m_normalFieldsCount( aNumberFields ),
84 m_styleFlags( aFlags )
85{
86#ifdef __WXOSX__
87 // we need +1 extra field on OSX to offset from the rounded corner on the right
88 // OSX doesn't use resize grippers like the other platforms and the statusbar field
89 // includes the rounded part
90 int extraFields = 3;
91#else
92 int extraFields = 2;
93#endif
94
95 bool showNotification = ( m_styleFlags & NOTIFICATION_ICON );
96 bool showCancel = ( m_styleFlags & CANCEL_BUTTON );
97 bool showWarning = ( m_styleFlags & WARNING_ICON );
98
99 if( showCancel )
100 extraFields++;
101
102 if( showWarning )
103 extraFields++;
104
105 if( showNotification )
106 extraFields++;
107
108 SetFieldsCount( aNumberFields + extraFields );
109
110 m_fieldWidths.assign( aNumberFields + extraFields, -1 );
111
112#ifdef __WXOSX__
113 // offset from the right edge
114 m_fieldWidths[aNumberFields + extraFields - 1] = 10;
115#endif
116
117 SetStatusWidths( aNumberFields + extraFields, m_fieldWidths.data() );
118
119 int* styles = new int[aNumberFields + extraFields];
120
121 for( int i = 0; i < aNumberFields + extraFields; i++ )
122 styles[i] = wxSB_FLAT;
123
124 SetStatusStyles( aNumberFields + extraFields, styles );
125 delete[] styles;
126
127 m_backgroundTxt = new wxStaticText( this, wxID_ANY, wxT( "" ), wxDefaultPosition,
128 wxDefaultSize, wxALIGN_RIGHT | wxST_NO_AUTORESIZE );
129
130 m_backgroundProgressBar = new wxGauge( this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize,
131 wxGA_HORIZONTAL | wxGA_SMOOTH );
132
133 if( showCancel )
134 {
135 m_backgroundStopButton = new wxButton( this, wxID_ANY, "X", wxDefaultPosition,
136 wxDefaultSize, wxBU_EXACTFIT );
137 }
138
139 if( showNotification )
140 {
141 m_notificationsButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
142 wxDefaultSize, wxBU_EXACTFIT );
143
144 m_notificationsButton->SetPadding( 0 );
146 m_notificationsButton->SetShowBadge( true );
147 m_notificationsButton->SetBitmapCentered( true );
148
150 }
151
152 if( showWarning )
153 {
154 m_warningButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
155 wxDefaultSize, wxBU_EXACTFIT );
156
157 m_warningButton->SetPadding( 0 );
159 m_warningButton->SetBitmapCentered( true );
160 m_warningButton->SetToolTip( _( "View load messages" ) );
161 m_warningButton->Hide();
162
163 m_warningButton->Bind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
164 }
165
166 Bind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
168
170 Layout();
171}
172
173
175{
177 m_notificationsButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onNotificationsIconClick, this );
178
179 if( m_warningButton )
180 m_warningButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
181
182 Unbind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
184 this );
185}
186
187
188void KISTATUSBAR::onNotificationsIconClick( wxCommandEvent& aEvent )
189{
190 wxCHECK( m_notificationsButton, /* void */ );
191 wxPoint pos = m_notificationsButton->GetScreenPosition();
192
193 wxRect r;
194 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
195 {
196 GetFieldRect( m_normalFieldsCount + *idx, r );
197 pos.x += r.GetWidth();
198 }
199
200 Pgm().GetNotificationsManager().ShowList( this, pos );
201}
202
203
204void KISTATUSBAR::onBackgroundProgressClick( wxMouseEvent& aEvent )
205{
206 wxPoint pos = m_backgroundProgressBar->GetScreenPosition();
207
208 wxRect r;
209 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
210 {
211 GetFieldRect( m_normalFieldsCount + *idx, r );
212 pos.x += r.GetWidth();
213 }
214
215 Pgm().GetBackgroundJobMonitor().ShowList( this, pos );
216}
217
218
219void KISTATUSBAR::onSize( wxSizeEvent& aEvent )
220{
222}
223
224
226{
227 constexpr int padding = 5;
228
229 wxRect r;
230 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r );
231 int x = r.GetLeft();
232 int y = r.GetTop();
233 int textHeight = KIUI::GetTextSize( wxT( "bp" ), this ).y;
234
235 if( r.GetHeight() > textHeight )
236 y += ( r.GetHeight() - textHeight ) / 2;
237
238 m_backgroundTxt->SetPosition( { x, y } );
239 m_backgroundTxt->SetSize( r.GetWidth(), textHeight );
241
242 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_GAUGE ), r );
243 x = r.GetLeft();
244 y = r.GetTop();
245 int w = r.GetWidth();
246 int h = r.GetHeight();
247 wxSize buttonSize( 0, 0 );
248
250 {
251 buttonSize = m_backgroundStopButton->GetEffectiveMinSize();
252 m_backgroundStopButton->SetPosition( { x + w - buttonSize.GetWidth(), y } );
253 m_backgroundStopButton->SetSize( buttonSize.GetWidth(), h );
254 buttonSize.x += padding;
255 }
256
257 m_backgroundProgressBar->SetPosition( { x + padding, y } );
258 m_backgroundProgressBar->SetSize( w - buttonSize.GetWidth() - padding, h );
259
261 {
262 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::NOTIFICATION ), r );
263 x = r.GetLeft();
264 y = r.GetTop();
265 h = r.GetHeight();
266 buttonSize = m_notificationsButton->GetEffectiveMinSize();
267 m_notificationsButton->SetPosition( { x, y } );
268 m_notificationsButton->SetSize( buttonSize.GetWidth() + 6, h );
269 }
270
271 if( m_warningButton )
272 {
273 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::WARNING ), r );
274 x = r.GetLeft();
275 y = r.GetTop();
276 h = r.GetHeight();
277 buttonSize = m_warningButton->GetEffectiveMinSize();
278 m_warningButton->SetPosition( { x, y } );
279 m_warningButton->SetSize( buttonSize.GetWidth() + 6, h );
280 }
281}
282
283
285{
287
289 m_backgroundStopButton->Show( aCancellable );
290
292}
293
294
304
305
307{
308 int range = m_backgroundProgressBar->GetRange();
309
310 if( aAmount > range )
311 aAmount = range;
312
313 m_backgroundProgressBar->SetValue( aAmount );
314}
315
316
318{
319 m_backgroundProgressBar->SetRange( aAmount );
320}
321
322
323void KISTATUSBAR::SetBackgroundStatusText( const wxString& aTxt )
324{
325 m_backgroundRawText = aTxt;
327
328 // When there are multiple normal fields, the last normal field (typically used for
329 // file watcher status on Windows) can visually overlap with the background job label
330 // since both have variable width. Save and clear that field when showing background
331 // text, and restore it when the background text is cleared.
332 if( m_normalFieldsCount > 1 )
333 {
334 int adjacentField = m_normalFieldsCount - 1;
335
336 if( !aTxt.empty() )
337 {
338 m_savedStatusText = GetStatusText( adjacentField );
339 SetStatusText( wxEmptyString, adjacentField );
340 }
341 else if( !m_savedStatusText.empty() )
342 {
343 SetStatusText( m_savedStatusText, adjacentField );
344 m_savedStatusText.clear();
345 }
346 }
347}
348
349
351{
352 if( m_fieldWidths.empty() )
353 return;
354
355 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_LABEL ) )
357
358 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
360
361 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_CANCEL ) )
362 {
364 m_backgroundStopButton && m_backgroundStopButton->IsShown() ? 20 : 0;
365 }
366
367 if( std::optional<int> idx = fieldIndex( FIELD::WARNING ) )
368 {
370 m_warningButton && m_warningButton->IsShown() ? 20 : 0;
371 }
372
373 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
374 {
376 m_notificationsButton && m_notificationsButton->IsShown() ? 20 : 0;
377 }
378
379 SetStatusWidths( static_cast<int>( m_fieldWidths.size() ), m_fieldWidths.data() );
382}
383
384
386{
387 wxRect r;
388
389 if( !GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r ) )
390 return;
391
392 wxString text = m_backgroundRawText;
393
394 if( !text.empty() && r.GetWidth() > 4 )
395 {
396 wxClientDC dc( this );
397 int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
398 text = wxControl::Ellipsize( text, dc, wxELLIPSIZE_END, std::max( 0, r.GetWidth() - margin ) );
399 }
400
401 m_backgroundTxt->SetLabel( text );
402}
403
404
406{
407 wxCHECK( m_notificationsButton, /* void */ );
408 wxString cnt = "";
409
410 if( aCount > 0 )
411 cnt = fmt::format( "{}", aCount );
412
413 m_notificationsButton->SetBadgeText( cnt );
414
415 // force a repaint or it wont until it gets activity
416 Refresh();
417}
418
419
420void KISTATUSBAR::AddWarningMessages( const wxString& aSource, const wxString& aMessages )
421{
422 {
423 std::lock_guard<std::mutex> lock( m_warningMutex );
424
425 wxStringTokenizer tokenizer( aMessages, wxS( "\n" ), wxTOKEN_STRTOK );
426
427 while( tokenizer.HasMoreTokens() )
428 {
429 LOAD_MESSAGE msg;
430 msg.message = tokenizer.GetNextToken();
431 msg.severity = RPT_SEVERITY_WARNING; // Default to warning for font substitutions
432 m_warningMessages[aSource].push_back( msg );
433 }
434 }
435
437}
438
439
440void KISTATUSBAR::AddWarningMessages( const wxString& aSource, const std::vector<LOAD_MESSAGE>& aMessages )
441{
442 wxLogTrace( traceLibraries, "KISTATUSBAR::AddWarningMessages: this=%p, count=%zu",
443 this, aMessages.size() );
444
445 if( aMessages.empty() )
446 return;
447
448 size_t totalMessageCount = 0;
449
450 {
451 std::lock_guard<std::mutex> lock( m_warningMutex );
452 m_warningMessages[aSource].insert( m_warningMessages[aSource].end(), aMessages.begin(), aMessages.end() );
453
454 for( const auto& [source, messages] : m_warningMessages )
455 totalMessageCount += messages.size();
456 }
457
458 wxLogTrace( traceLibraries, " -> total messages now=%zu", totalMessageCount );
459
460 // Update UI on main thread
461 wxLogTrace( traceLibraries, " -> calling CallAfter for updateWarningUI" );
462 CallAfter( [this]() { updateWarningUI(); } );
463}
464
465
467{
468 std::lock_guard<std::mutex> lock( m_warningMutex );
469
470 size_t count = 0;
471
472 for( const auto& [source, messages] : m_warningMessages )
473 count += messages.size();
474
475 return count;
476}
477
478
480{
481 wxLogTrace( traceLibraries, "KISTATUSBAR::updateWarningUI: this=%p, m_warningButton=%p",
482 this, m_warningButton );
483
484 if( !m_warningButton )
485 {
486 wxLogTrace( traceLibraries, " -> no warning button, returning early" );
487 return;
488 }
489
490 size_t messageCount;
491 {
492 std::lock_guard<std::mutex> lock( m_warningMutex );
493
494 messageCount = 0;
495
496 for( const std::vector<LOAD_MESSAGE>& messages : m_warningMessages | std::views::values )
497 messageCount += messages.size();
498 }
499
500 wxLogTrace( traceLibraries, " -> message count=%zu, showing button=%s",
501 messageCount, messageCount > 0 ? "true" : "false" );
502
503 m_warningButton->Show( messageCount > 0 );
504 m_warningButton->SetShowBadge( messageCount > 0 );
506
507 if( messageCount > 0 )
508 {
509 m_warningButton->SetToolTip( wxString::Format( _( "View %zu message(s)" ), messageCount ) );
510
511 // Show count badge on the warning button
512 wxString badgeText = messageCount > 99
513 ? wxString( "99+" )
514 : wxString::Format( wxS( "%zu" ), messageCount );
515 m_warningButton->SetBadgeText( badgeText );
516
517 wxLogTrace( traceLibraries, " -> badge set to '%s'", badgeText );
518 }
519 else
520 {
521 m_warningButton->SetBadgeText( wxEmptyString );
522 m_warningButton->SetToolTip( _( "View messages" ) );
523 }
524
525 Layout();
526 Refresh();
527}
528
529
530void KISTATUSBAR::ClearWarningMessages( const wxString& aSource )
531{
532 {
533 std::lock_guard<std::mutex> lock( m_warningMutex );
534
535 if( aSource.IsEmpty() )
536 m_warningMessages.clear();
537 else if( auto it = m_warningMessages.find( aSource ); it != m_warningMessages.end() )
538 m_warningMessages.erase( it );
539 }
540
542}
543
544
545void KISTATUSBAR::onLoadWarningsIconClick( wxCommandEvent& aEvent )
546{
547 // Copy messages under lock to avoid holding lock during modal dialog
548 std::unordered_map<wxString, std::vector<LOAD_MESSAGE>> messages;
549 {
550 std::lock_guard<std::mutex> lock( m_warningMutex );
551 messages = m_warningMessages;
552 }
553
554 if( messages.empty() )
555 return;
556
557 STATUSBAR_WARNING_REPORTER_DIALOG dlg( GetParent(), this );
558
559 for( const std::vector<LOAD_MESSAGE>& source : std::views::values( messages ) )
560 for( const LOAD_MESSAGE& msg : source )
561 dlg.m_Reporter->Report( msg.message, msg.severity );
562
563 dlg.m_Reporter->Flush();
564 dlg.ShowModal();
565}
566
567void KISTATUSBAR::SetEllipsedTextField( const wxString& aText, int aFieldId )
568{
569 wxRect fieldRect;
570 int width = -1;
571 wxString etext = aText;
572
573 // Only GetFieldRect() returns the current size for variable size fields
574 // Other methods return -1 for the width of these fields.
575 if( GetFieldRect( aFieldId, fieldRect ) )
576 width = fieldRect.GetWidth();
577
578 if( width > 20 )
579 {
580 wxClientDC dc( this );
581
582 // Gives a margin to the text to be sure it is not clamped at its end
583 int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
584 etext = wxControl::Ellipsize( etext, dc, wxELLIPSIZE_MIDDLE, width - margin );
585 }
586
587 SetStatusText( etext, aFieldId );
588}
589
590
591std::optional<int> KISTATUSBAR::fieldIndex( FIELD aField ) const
592{
593 switch( aField )
594 {
595 case FIELD::BGJOB_LABEL: return 0;
596 case FIELD::BGJOB_GAUGE: return 1;
598 {
600 return 2;
601
602 break;
603 }
604 case FIELD::WARNING:
605 {
607 {
608 int offset = 2;
609
611 offset++;
612
613 return offset;
614 }
615
616 break;
617 }
619 {
621 {
622 int offset = 2;
623
625 offset++;
626
628 offset++;
629
630 return offset;
631 }
632
633 break;
634 }
635 }
636
637 return std::nullopt;
638}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
void ShowList(wxWindow *aParent, wxPoint aPos)
Shows the background job list.
A bitmap button widget that behaves like an AUI toolbar item's button when it is drawn.
wxStdDialogButtonSizer * m_sdbSizer
WX_HTML_REPORT_BOX * m_Reporter
DIALOG_HTML_REPORTER(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Report"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
int ShowModal() override
STYLE_FLAGS m_styleFlags
BITMAP_BUTTON * m_notificationsButton
void AddWarningMessages(const wxString &aSource, const wxString &aMessages)
Add warning/error messages (not thread-safe, use the std::vector<LOAD_MESSAGE> variant from other thr...
void onLoadWarningsIconClick(wxCommandEvent &aEvent)
std::optional< int > fieldIndex(FIELD aField) const
void SetBackgroundStatusText(const wxString &aTxt)
Set the status text that displays next to the progress bar.
BITMAP_BUTTON * m_warningButton
wxString m_backgroundRawText
Unellipsized background status text.
std::unordered_map< wxString, std::vector< LOAD_MESSAGE > > m_warningMessages
size_t GetLoadWarningCount() const
Get current message count (thread-safe).
void onBackgroundProgressClick(wxMouseEvent &aEvent)
KISTATUSBAR(int aNumberFields, wxWindow *parent, wxWindowID id, STYLE_FLAGS aFlags=DEFAULT_STYLE)
void updateAuxFieldWidths()
void layoutControls()
void onSize(wxSizeEvent &aEvent)
int m_normalFieldsCount
void ClearWarningMessages(const wxString &aSource=wxEmptyString)
Clears all warning messages from the given source (or all sources if aSource is empty)
wxString m_savedStatusText
Saved text from adjacent field during background jobs.
std::mutex m_warningMutex
Protects m_warningMessages.
void updateBackgroundText()
void SetBackgroundProgress(int aAmount)
Set the current progress of the progress bar.
wxGauge * m_backgroundProgressBar
wxButton * m_backgroundStopButton
void updateWarningUI()
Update warning button visibility and badge (main thread only)
void SetEllipsedTextField(const wxString &aText, int aFieldId)
Set the text in a field using wxELLIPSIZE_MIDDLE option to adjust the text size to the field size.
std::vector< int > m_fieldWidths
void HideBackgroundProgressBar()
Hide the background progress bar.
void onNotificationsIconClick(wxCommandEvent &aEvent)
void SetBackgroundProgressMax(int aAmount)
Set the max progress of the progress bar.
wxStaticText * m_backgroundTxt
void ShowBackgroundProgressBar(bool aCancellable=false)
Show the background progress bar.
void SetNotificationCount(int aCount)
Set the notification count on the notifications button.
void ShowList(wxWindow *aParent, wxPoint aPos)
Show the notification list.
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
Definition pgm_base.h:136
virtual NOTIFICATIONS_MANAGER & GetNotificationsManager() const
Definition pgm_base.h:141
void onClearButtonClick(wxCommandEvent &aEvent)
STATUSBAR_WARNING_REPORTER_DIALOG(wxWindow *aParent, KISTATUSBAR *aStatusBar)
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
void Flush()
Build the HTML messages page.
#define _(s)
const wxChar *const traceLibraries
Flag to enable library table and library manager tracing.
KICOMMON_API wxSize GetTextSize(const wxString &aSingleLine, wxWindow *aWindow)
Return the size of aSingleLine of text when it is rendered in aWindow using whatever font is currentl...
Definition ui_common.cpp:78
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
@ RPT_SEVERITY_WARNING
KISTATUSBAR is a wxStatusBar suitable for Kicad manager.
Definition kistatusbar.h:54
wxString message
Definition kistatusbar.h:55
SEVERITY severity
Definition kistatusbar.h:56
VECTOR2I end
wxLogTrace helper definitions.
Functions to provide common constants and other functions to assist in making a consistent UI.