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;
231
232 if( sbField >= 0 && sbField < GetFieldsCount() )
233 {
234 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r );
235 int x = r.GetLeft();
236 int y = r.GetTop();
237 int textHeight = KIUI::GetTextSize( wxT( "bp" ), this ).y;
238
239 if( r.GetHeight() > textHeight )
240 y += ( r.GetHeight() - textHeight ) / 2;
241
242 m_backgroundTxt->SetPosition( { x, y } );
243 m_backgroundTxt->SetSize( r.GetWidth(), textHeight );
245 }
246
248
249 if( sbField >= 0 && sbField < GetFieldsCount() )
250 {
251 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_GAUGE ), r );
252 int x = r.GetLeft();
253 int y = r.GetTop();
254 int w = r.GetWidth();
255 int h = r.GetHeight();
256 wxSize buttonSize( 0, 0 );
257
259 {
260 buttonSize = m_backgroundStopButton->GetEffectiveMinSize();
261 m_backgroundStopButton->SetPosition( { x + w - buttonSize.GetWidth(), y } );
262 m_backgroundStopButton->SetSize( buttonSize.GetWidth(), h );
263 buttonSize.x += padding;
264 }
265
266 m_backgroundProgressBar->SetPosition( { x + padding, y } );
267 m_backgroundProgressBar->SetSize( w - buttonSize.GetWidth() - padding, h );
268
270 {
272
273 if( sbField >= 0 && sbField < GetFieldsCount() )
274 {
275 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::NOTIFICATION ), r );
276 x = r.GetLeft();
277 y = r.GetTop();
278 h = r.GetHeight();
279 buttonSize = m_notificationsButton->GetEffectiveMinSize();
280 m_notificationsButton->SetPosition( { x, y } );
281 m_notificationsButton->SetSize( buttonSize.GetWidth() + 6, h );
282 }
283 }
284 }
285
286 if( m_warningButton )
287 {
289
290 if( sbField >= 0 && sbField < GetFieldsCount() )
291 {
292 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::WARNING ), r );
293 int x = r.GetLeft();
294 int y = r.GetTop();
295 int h = r.GetHeight();
296 wxSize buttonSize = m_warningButton->GetEffectiveMinSize();
297 m_warningButton->SetPosition( { x, y } );
298 m_warningButton->SetSize( buttonSize.GetWidth() + 6, h );
299 }
300 }
301}
302
303
305{
307
309 m_backgroundStopButton->Show( aCancellable );
310
312}
313
314
324
325
327{
328 int range = m_backgroundProgressBar->GetRange();
329
330 if( aAmount > range )
331 aAmount = range;
332
333 m_backgroundProgressBar->SetValue( aAmount );
334}
335
336
338{
339 m_backgroundProgressBar->SetRange( aAmount );
340}
341
342
343void KISTATUSBAR::SetBackgroundStatusText( const wxString& aTxt )
344{
345 m_backgroundRawText = aTxt;
347
348 // When there are multiple normal fields, the last normal field (typically used for
349 // file watcher status on Windows) can visually overlap with the background job label
350 // since both have variable width. Save and clear that field when showing background
351 // text, and restore it when the background text is cleared.
352 if( m_normalFieldsCount > 1 )
353 {
354 int adjacentField = m_normalFieldsCount - 1;
355
356 if( !aTxt.empty() )
357 {
358 m_savedStatusText = GetStatusText( adjacentField );
359 SetStatusText( wxEmptyString, adjacentField );
360 }
361 else if( !m_savedStatusText.empty() )
362 {
363 SetStatusText( m_savedStatusText, adjacentField );
364 m_savedStatusText.clear();
365 }
366 }
367}
368
369
371{
372 if( m_fieldWidths.empty() )
373 return;
374
375 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_LABEL ) )
377
378 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
380
381 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_CANCEL ) )
382 {
384 m_backgroundStopButton && m_backgroundStopButton->IsShown() ? 20 : 0;
385 }
386
387 if( std::optional<int> idx = fieldIndex( FIELD::WARNING ) )
388 {
390 m_warningButton && m_warningButton->IsShown() ? 20 : 0;
391 }
392
393 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
394 {
396 m_notificationsButton && m_notificationsButton->IsShown() ? 20 : 0;
397 }
398
399 SetStatusWidths( static_cast<int>( m_fieldWidths.size() ), m_fieldWidths.data() );
402}
403
404
406{
407 wxRect r;
408
409 if( !GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r ) )
410 return;
411
412 wxString text = m_backgroundRawText;
413
414 if( !text.empty() && r.GetWidth() > 4 )
415 {
416 wxClientDC dc( this );
417 int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
418 text = wxControl::Ellipsize( text, dc, wxELLIPSIZE_END, std::max( 0, r.GetWidth() - margin ) );
419 }
420
421 m_backgroundTxt->SetLabel( text );
422}
423
424
426{
427 wxCHECK( m_notificationsButton, /* void */ );
428 wxString cnt = "";
429
430 if( aCount > 0 )
431 cnt = fmt::format( "{}", aCount );
432
433 m_notificationsButton->SetBadgeText( cnt );
434
435 // force a repaint or it wont until it gets activity
436 Refresh();
437}
438
439
440void KISTATUSBAR::AddWarningMessages( const wxString& aSource, const wxString& aMessages )
441{
442 {
443 std::lock_guard<std::mutex> lock( m_warningMutex );
444
445 wxStringTokenizer tokenizer( aMessages, wxS( "\n" ), wxTOKEN_STRTOK );
446
447 while( tokenizer.HasMoreTokens() )
448 {
449 LOAD_MESSAGE msg;
450 msg.message = tokenizer.GetNextToken();
451 msg.severity = RPT_SEVERITY_WARNING; // Default to warning for font substitutions
452 m_warningMessages[aSource].push_back( msg );
453 }
454 }
455
457}
458
459
460void KISTATUSBAR::AddWarningMessages( const wxString& aSource, const std::vector<LOAD_MESSAGE>& aMessages )
461{
462 wxLogTrace( traceLibraries, "KISTATUSBAR::AddWarningMessages: this=%p, count=%zu",
463 this, aMessages.size() );
464
465 if( aMessages.empty() )
466 return;
467
468 size_t totalMessageCount = 0;
469
470 {
471 std::lock_guard<std::mutex> lock( m_warningMutex );
472 m_warningMessages[aSource].insert( m_warningMessages[aSource].end(), aMessages.begin(), aMessages.end() );
473
474 for( const auto& [source, messages] : m_warningMessages )
475 totalMessageCount += messages.size();
476 }
477
478 wxLogTrace( traceLibraries, " -> total messages now=%zu", totalMessageCount );
479
480 // Update UI on main thread
481 wxLogTrace( traceLibraries, " -> calling CallAfter for updateWarningUI" );
482 CallAfter( [this]() { updateWarningUI(); } );
483}
484
485
487{
488 std::lock_guard<std::mutex> lock( m_warningMutex );
489
490 size_t count = 0;
491
492 for( const auto& [source, messages] : m_warningMessages )
493 count += messages.size();
494
495 return count;
496}
497
498
500{
501 wxLogTrace( traceLibraries, "KISTATUSBAR::updateWarningUI: this=%p, m_warningButton=%p",
502 this, m_warningButton );
503
504 if( !m_warningButton )
505 {
506 wxLogTrace( traceLibraries, " -> no warning button, returning early" );
507 return;
508 }
509
510 size_t messageCount;
511 {
512 std::lock_guard<std::mutex> lock( m_warningMutex );
513
514 messageCount = 0;
515
516 for( const std::vector<LOAD_MESSAGE>& messages : m_warningMessages | std::views::values )
517 messageCount += messages.size();
518 }
519
520 wxLogTrace( traceLibraries, " -> message count=%zu, showing button=%s",
521 messageCount, messageCount > 0 ? "true" : "false" );
522
523 m_warningButton->Show( messageCount > 0 );
524 m_warningButton->SetShowBadge( messageCount > 0 );
526
527 if( messageCount > 0 )
528 {
529 m_warningButton->SetToolTip( wxString::Format( _( "View %zu message(s)" ), messageCount ) );
530
531 // Show count badge on the warning button
532 wxString badgeText = messageCount > 99
533 ? wxString( "99+" )
534 : wxString::Format( wxS( "%zu" ), messageCount );
535 m_warningButton->SetBadgeText( badgeText );
536
537 wxLogTrace( traceLibraries, " -> badge set to '%s'", badgeText );
538 }
539 else
540 {
541 m_warningButton->SetBadgeText( wxEmptyString );
542 m_warningButton->SetToolTip( _( "View messages" ) );
543 }
544
545 Layout();
546 Refresh();
547}
548
549
550void KISTATUSBAR::ClearWarningMessages( const wxString& aSource )
551{
552 {
553 std::lock_guard<std::mutex> lock( m_warningMutex );
554
555 if( aSource.IsEmpty() )
556 m_warningMessages.clear();
557 else if( auto it = m_warningMessages.find( aSource ); it != m_warningMessages.end() )
558 m_warningMessages.erase( it );
559 }
560
562}
563
564
565void KISTATUSBAR::onLoadWarningsIconClick( wxCommandEvent& aEvent )
566{
567 // Copy messages under lock to avoid holding lock during modal dialog
568 std::unordered_map<wxString, std::vector<LOAD_MESSAGE>> messages;
569 {
570 std::lock_guard<std::mutex> lock( m_warningMutex );
571 messages = m_warningMessages;
572 }
573
574 if( messages.empty() )
575 return;
576
577 STATUSBAR_WARNING_REPORTER_DIALOG dlg( GetParent(), this );
578
579 for( const std::vector<LOAD_MESSAGE>& source : std::views::values( messages ) )
580 for( const LOAD_MESSAGE& msg : source )
581 dlg.m_Reporter->Report( msg.message, msg.severity );
582
583 dlg.m_Reporter->Flush();
584 dlg.ShowModal();
585}
586
587void KISTATUSBAR::SetEllipsedTextField( const wxString& aText, int aFieldId )
588{
589 wxRect fieldRect;
590 int width = -1;
591 wxString etext = aText;
592
593 // Only GetFieldRect() returns the current size for variable size fields
594 // Other methods return -1 for the width of these fields.
595 if( GetFieldRect( aFieldId, fieldRect ) )
596 width = fieldRect.GetWidth();
597
598 if( width > 20 )
599 {
600 wxClientDC dc( this );
601
602 // Gives a margin to the text to be sure it is not clamped at its end
603 int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
604 etext = wxControl::Ellipsize( etext, dc, wxELLIPSIZE_MIDDLE, width - margin );
605 }
606
607 SetStatusText( etext, aFieldId );
608}
609
610
611std::optional<int> KISTATUSBAR::fieldIndex( FIELD aField ) const
612{
613 switch( aField )
614 {
615 case FIELD::BGJOB_LABEL: return 0;
616 case FIELD::BGJOB_GAUGE: return 1;
618 {
620 return 2;
621
622 break;
623 }
624 case FIELD::WARNING:
625 {
627 {
628 int offset = 2;
629
631 offset++;
632
633 return offset;
634 }
635
636 break;
637 }
639 {
641 {
642 int offset = 2;
643
645 offset++;
646
648 offset++;
649
650 return offset;
651 }
652
653 break;
654 }
655 }
656
657 return std::nullopt;
658}
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.