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 <widgets/kistatusbar.h>
35#include <widgets/ui_common.h>
36#include <pgm_base.h>
39#include <bitmaps.h>
40#include <reporter.h>
42#include <trace_helpers.h>
43#include <wx/dcclient.h>
44
45
46KISTATUSBAR::KISTATUSBAR( int aNumberFields, wxWindow* parent, wxWindowID id, STYLE_FLAGS aFlags ) :
47 wxStatusBar( parent, id ),
48 m_backgroundStopButton( nullptr ),
49 m_notificationsButton( nullptr ),
50 m_warningButton( nullptr ),
51 m_normalFieldsCount( aNumberFields ),
52 m_styleFlags( aFlags )
53{
54#ifdef __WXOSX__
55 // we need +1 extra field on OSX to offset from the rounded corner on the right
56 // OSX doesn't use resize grippers like the other platforms and the statusbar field
57 // includes the rounded part
58 int extraFields = 3;
59#else
60 int extraFields = 2;
61#endif
62
63 bool showNotification = ( m_styleFlags & NOTIFICATION_ICON );
64 bool showCancel = ( m_styleFlags & CANCEL_BUTTON );
65 bool showWarning = ( m_styleFlags & WARNING_ICON );
66
67 if( showCancel )
68 extraFields++;
69
70 if( showWarning )
71 extraFields++;
72
73 if( showNotification )
74 extraFields++;
75
76 SetFieldsCount( aNumberFields + extraFields );
77
78 m_fieldWidths.assign( aNumberFields + extraFields, -1 );
79
80#ifdef __WXOSX__
81 // offset from the right edge
82 m_fieldWidths[aNumberFields + extraFields - 1] = 10;
83#endif
84
85 SetStatusWidths( aNumberFields + extraFields, m_fieldWidths.data() );
86
87 int* styles = new int[aNumberFields + extraFields];
88
89 for( int i = 0; i < aNumberFields + extraFields; i++ )
90 styles[i] = wxSB_FLAT;
91
92 SetStatusStyles( aNumberFields + extraFields, styles );
93 delete[] styles;
94
95 m_backgroundTxt = new wxStaticText( this, wxID_ANY, wxT( "" ), wxDefaultPosition,
96 wxDefaultSize, wxALIGN_RIGHT | wxST_NO_AUTORESIZE );
97
98 m_backgroundProgressBar = new wxGauge( this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize,
99 wxGA_HORIZONTAL | wxGA_SMOOTH );
100
101 if( showCancel )
102 {
103 m_backgroundStopButton = new wxButton( this, wxID_ANY, "X", wxDefaultPosition,
104 wxDefaultSize, wxBU_EXACTFIT );
105 }
106
107 if( showNotification )
108 {
109 m_notificationsButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
110 wxDefaultSize, wxBU_EXACTFIT );
111
112 m_notificationsButton->SetPadding( 0 );
114 m_notificationsButton->SetShowBadge( true );
115 m_notificationsButton->SetBitmapCentered( true );
116
118 }
119
120 if( showWarning )
121 {
122 m_warningButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
123 wxDefaultSize, wxBU_EXACTFIT );
124
125 m_warningButton->SetPadding( 0 );
127 m_warningButton->SetBitmapCentered( true );
128 m_warningButton->SetToolTip( _( "View load messages" ) );
129 m_warningButton->Hide();
130
131 m_warningButton->Bind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
132 }
133
134 Bind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
136
138 Layout();
139}
140
141
143{
145 m_notificationsButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onNotificationsIconClick, this );
146
147 if( m_warningButton )
148 m_warningButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
149
150 Unbind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
152 this );
153}
154
155
156void KISTATUSBAR::onNotificationsIconClick( wxCommandEvent& aEvent )
157{
158 wxCHECK( m_notificationsButton, /* void */ );
159 wxPoint pos = m_notificationsButton->GetScreenPosition();
160
161 wxRect r;
162 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
163 {
164 GetFieldRect( m_normalFieldsCount + *idx, r );
165 pos.x += r.GetWidth();
166 }
167
168 Pgm().GetNotificationsManager().ShowList( this, pos );
169}
170
171
172void KISTATUSBAR::onBackgroundProgressClick( wxMouseEvent& aEvent )
173{
174 wxPoint pos = m_backgroundProgressBar->GetScreenPosition();
175
176 wxRect r;
177 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
178 {
179 GetFieldRect( m_normalFieldsCount + *idx, r );
180 pos.x += r.GetWidth();
181 }
182
183 Pgm().GetBackgroundJobMonitor().ShowList( this, pos );
184}
185
186
187void KISTATUSBAR::onSize( wxSizeEvent& aEvent )
188{
190}
191
192
194{
195 constexpr int padding = 5;
196
197 wxRect r;
198 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r );
199 int x = r.GetLeft();
200 int y = r.GetTop();
201 int textHeight = KIUI::GetTextSize( wxT( "bp" ), this ).y;
202
203 if( r.GetHeight() > textHeight )
204 y += ( r.GetHeight() - textHeight ) / 2;
205
206 m_backgroundTxt->SetPosition( { x, y } );
207 m_backgroundTxt->SetSize( r.GetWidth(), textHeight );
209
210 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_GAUGE ), r );
211 x = r.GetLeft();
212 y = r.GetTop();
213 int w = r.GetWidth();
214 int h = r.GetHeight();
215 wxSize buttonSize( 0, 0 );
216
218 {
219 buttonSize = m_backgroundStopButton->GetEffectiveMinSize();
220 m_backgroundStopButton->SetPosition( { x + w - buttonSize.GetWidth(), y } );
221 m_backgroundStopButton->SetSize( buttonSize.GetWidth(), h );
222 buttonSize.x += padding;
223 }
224
225 m_backgroundProgressBar->SetPosition( { x + padding, y } );
226 m_backgroundProgressBar->SetSize( w - buttonSize.GetWidth() - padding, h );
227
229 {
230 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::NOTIFICATION ), r );
231 x = r.GetLeft();
232 y = r.GetTop();
233 h = r.GetHeight();
234 buttonSize = m_notificationsButton->GetEffectiveMinSize();
235 m_notificationsButton->SetPosition( { x, y } );
236 m_notificationsButton->SetSize( buttonSize.GetWidth() + 6, h );
237 }
238
239 if( m_warningButton )
240 {
241 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::WARNING ), r );
242 x = r.GetLeft();
243 y = r.GetTop();
244 h = r.GetHeight();
245 buttonSize = m_warningButton->GetEffectiveMinSize();
246 m_warningButton->SetPosition( { x, y } );
247 m_warningButton->SetSize( buttonSize.GetWidth() + 6, h );
248 }
249}
250
251
253{
255
257 m_backgroundStopButton->Show( aCancellable );
258
260}
261
262
272
273
275{
276 int range = m_backgroundProgressBar->GetRange();
277
278 if( aAmount > range )
279 aAmount = range;
280
281 m_backgroundProgressBar->SetValue( aAmount );
282}
283
284
286{
287 m_backgroundProgressBar->SetRange( aAmount );
288}
289
290
291void KISTATUSBAR::SetBackgroundStatusText( const wxString& aTxt )
292{
293 m_backgroundRawText = aTxt;
295
296 // When there are multiple normal fields, the last normal field (typically used for
297 // file watcher status on Windows) can visually overlap with the background job label
298 // since both have variable width. Save and clear that field when showing background
299 // text, and restore it when the background text is cleared.
300 if( m_normalFieldsCount > 1 )
301 {
302 int adjacentField = m_normalFieldsCount - 1;
303
304 if( !aTxt.empty() )
305 {
306 m_savedStatusText = GetStatusText( adjacentField );
307 SetStatusText( wxEmptyString, adjacentField );
308 }
309 else if( !m_savedStatusText.empty() )
310 {
311 SetStatusText( m_savedStatusText, adjacentField );
312 m_savedStatusText.clear();
313 }
314 }
315}
316
317
319{
320 if( m_fieldWidths.empty() )
321 return;
322
323 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_LABEL ) )
325
326 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
328
329 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_CANCEL ) )
330 {
332 m_backgroundStopButton && m_backgroundStopButton->IsShown() ? 20 : 0;
333 }
334
335 if( std::optional<int> idx = fieldIndex( FIELD::WARNING ) )
336 {
338 m_warningButton && m_warningButton->IsShown() ? 20 : 0;
339 }
340
341 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
342 {
344 m_notificationsButton && m_notificationsButton->IsShown() ? 20 : 0;
345 }
346
347 SetStatusWidths( static_cast<int>( m_fieldWidths.size() ), m_fieldWidths.data() );
350}
351
352
354{
355 wxRect r;
356
357 if( !GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r ) )
358 return;
359
360 wxString text = m_backgroundRawText;
361
362 if( !text.empty() && r.GetWidth() > 4 )
363 {
364 wxClientDC dc( this );
365 int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
366 text = wxControl::Ellipsize( text, dc, wxELLIPSIZE_END, std::max( 0, r.GetWidth() - margin ) );
367 }
368
369 m_backgroundTxt->SetLabel( text );
370}
371
372
374{
375 wxCHECK( m_notificationsButton, /* void */ );
376 wxString cnt = "";
377
378 if( aCount > 0 )
379 cnt = fmt::format( "{}", aCount );
380
381 m_notificationsButton->SetBadgeText( cnt );
382
383 // force a repaint or it wont until it gets activity
384 Refresh();
385}
386
387
388void KISTATUSBAR::SetLoadWarningMessages( const wxString& aMessages )
389{
390 {
391 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
392 m_loadWarningMessages.clear();
393
394 wxStringTokenizer tokenizer( aMessages, wxS( "\n" ), wxTOKEN_STRTOK );
395
396 while( tokenizer.HasMoreTokens() )
397 {
398 LOAD_MESSAGE msg;
399 msg.message = tokenizer.GetNextToken();
400 msg.severity = RPT_SEVERITY_WARNING; // Default to warning for font substitutions
401 m_loadWarningMessages.push_back( msg );
402 }
403 }
404
406}
407
408
409void KISTATUSBAR::AddLoadWarningMessages( const std::vector<LOAD_MESSAGE>& aMessages )
410{
411 wxLogTrace( traceLibraries, "KISTATUSBAR::AddLoadWarningMessages: this=%p, count=%zu",
412 this, aMessages.size() );
413
414 if( aMessages.empty() )
415 return;
416
417 {
418 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
419 m_loadWarningMessages.insert( m_loadWarningMessages.end(), aMessages.begin(), aMessages.end() );
420 wxLogTrace( traceLibraries, " -> total messages now=%zu", m_loadWarningMessages.size() );
421 }
422
423 // Update UI on main thread
424 wxLogTrace( traceLibraries, " -> calling CallAfter for updateWarningUI" );
425 CallAfter( [this]() { updateWarningUI(); } );
426}
427
428
430{
431 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
432 return m_loadWarningMessages.size();
433}
434
435
437{
438 wxLogTrace( traceLibraries, "KISTATUSBAR::updateWarningUI: this=%p, m_warningButton=%p",
439 this, m_warningButton );
440
441 if( !m_warningButton )
442 {
443 wxLogTrace( traceLibraries, " -> no warning button, returning early" );
444 return;
445 }
446
447 size_t messageCount;
448 {
449 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
450 messageCount = m_loadWarningMessages.size();
451 }
452
453 wxLogTrace( traceLibraries, " -> message count=%zu, showing button=%s",
454 messageCount, messageCount > 0 ? "true" : "false" );
455
456 m_warningButton->Show( messageCount > 0 );
458
459 if( messageCount > 0 )
460 {
461 m_warningButton->SetToolTip( wxString::Format( _( "View %zu load message(s)" ), messageCount ) );
462
463 // Show count badge on the warning button
464 m_warningButton->SetShowBadge( true );
465 wxString badgeText = messageCount > 99
466 ? wxString( "99+" )
467 : wxString::Format( wxS( "%zu" ), messageCount );
468 m_warningButton->SetBadgeText( badgeText );
469
470 wxLogTrace( traceLibraries, " -> badge set to '%s'", badgeText );
471 }
472
473 Layout();
474 Refresh();
475 wxLogTrace( traceLibraries, " -> Layout and Refresh complete" );
476}
477
478
480{
481 {
482 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
483 m_loadWarningMessages.clear();
484 }
485
486 if( m_warningButton )
487 {
488 m_warningButton->Hide();
489 m_warningButton->SetShowBadge( false );
490 m_warningButton->SetBadgeText( wxEmptyString );
492 Layout();
493 Refresh();
494 }
495}
496
497
498void KISTATUSBAR::onLoadWarningsIconClick( wxCommandEvent& aEvent )
499{
500 // Copy messages under lock to avoid holding lock during modal dialog
501 std::vector<LOAD_MESSAGE> messages;
502 {
503 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
504 messages = m_loadWarningMessages;
505 }
506
507 if( messages.empty() )
508 return;
509
510 DIALOG_HTML_REPORTER dlg( GetParent(), wxID_ANY, _( "Load Messages" ) );
511
512 for( const LOAD_MESSAGE& msg : messages )
513 dlg.m_Reporter->Report( msg.message, msg.severity );
514
515 dlg.m_Reporter->Flush();
516 dlg.ShowModal();
517}
518
519void KISTATUSBAR::SetEllipsedTextField( const wxString& aText, int aFieldId )
520{
521 wxRect fieldRect;
522 int width = -1;
523 wxString etext = aText;
524
525 // Only GetFieldRect() returns the current size for variable size fields
526 // Other methods return -1 for the width of these fields.
527 if( GetFieldRect( aFieldId, fieldRect ) )
528 width = fieldRect.GetWidth();
529
530 if( width > 20 )
531 {
532 wxClientDC dc( this );
533
534 // Gives a margin to the text to be sure it is not clamped at its end
535 int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
536 etext = wxControl::Ellipsize( etext, dc, wxELLIPSIZE_MIDDLE, width - margin );
537 }
538
539 SetStatusText( etext, aFieldId );
540}
541
542
543std::optional<int> KISTATUSBAR::fieldIndex( FIELD aField ) const
544{
545 switch( aField )
546 {
547 case FIELD::BGJOB_LABEL: return 0;
548 case FIELD::BGJOB_GAUGE: return 1;
550 {
552 return 2;
553
554 break;
555 }
556 case FIELD::WARNING:
557 {
559 {
560 int offset = 2;
561
563 offset++;
564
565 return offset;
566 }
567
568 break;
569 }
571 {
573 {
574 int offset = 2;
575
577 offset++;
578
580 offset++;
581
582 return offset;
583 }
584
585 break;
586 }
587 }
588
589 return std::nullopt;
590}
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.
Class DIALOG_HTML_REPORTER.
WX_HTML_REPORT_BOX * m_Reporter
int ShowModal() override
STYLE_FLAGS m_styleFlags
BITMAP_BUTTON * m_notificationsButton
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.
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
wxString m_savedStatusText
Saved text from adjacent field during background jobs.
void updateBackgroundText()
void SetBackgroundProgress(int aAmount)
Set the current progress of the progress bar.
wxGauge * m_backgroundProgressBar
void AddLoadWarningMessages(const std::vector< LOAD_MESSAGE > &aMessages)
Add warning/error messages thread-safely.
wxButton * m_backgroundStopButton
std::mutex m_loadWarningMutex
Protects m_loadWarningMessages.
void updateWarningUI()
Update warning button visibility and badge (main thread only)
std::vector< LOAD_MESSAGE > m_loadWarningMessages
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 SetLoadWarningMessages(const wxString &aMessages)
void SetNotificationCount(int aCount)
Set the notification count on the notifications button.
void ClearLoadWarningMessages()
void ShowList(wxWindow *aParent, wxPoint aPos)
Show the notification list.
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
Definition pgm_base.h:137
virtual NOTIFICATIONS_MANAGER & GetNotificationsManager() const
Definition pgm_base.h:142
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
void Refresh()
Update the board display after modifying it by a python script (note: it is automatically called by a...
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:53
wxString message
Definition kistatusbar.h:54
SEVERITY severity
Definition kistatusbar.h:55
wxLogTrace helper definitions.
Functions to provide common constants and other functions to assist in making a consistent UI.