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 int* widths = new int[aNumberFields + extraFields];
79
80 for( int i = 0; i < aNumberFields; i++ )
81 widths[i] = -1;
82
83 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_LABEL ) )
84 widths[aNumberFields + *idx] = -1; // background status text field (variable size)
85
86 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
87 widths[aNumberFields + *idx] = 75; // background progress button
88
89 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_CANCEL ) )
90 widths[aNumberFields + *idx] = 20; // background stop button
91
92 if( std::optional<int> idx = fieldIndex( FIELD::WARNING ) )
93 widths[aNumberFields + *idx] = 20; // warning button
94
95 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
96 widths[aNumberFields + *idx] = 20; // notifications button
97
98#ifdef __WXOSX__
99 // offset from the right edge
100 widths[aNumberFields + extraFields - 1] = 10;
101#endif
102
103 SetStatusWidths( aNumberFields + extraFields, widths );
104 delete[] widths;
105
106 int* styles = new int[aNumberFields + extraFields];
107
108 for( int i = 0; i < aNumberFields + extraFields; i++ )
109 styles[i] = wxSB_FLAT;
110
111 SetStatusStyles( aNumberFields + extraFields, styles );
112 delete[] styles;
113
114 m_backgroundTxt = new wxStaticText( this, wxID_ANY, wxT( "" ) );
115
116 m_backgroundProgressBar = new wxGauge( this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize,
117 wxGA_HORIZONTAL | wxGA_SMOOTH );
118
119 if( showCancel )
120 {
121 m_backgroundStopButton = new wxButton( this, wxID_ANY, "X", wxDefaultPosition,
122 wxDefaultSize, wxBU_EXACTFIT );
123 }
124
125 if( showNotification )
126 {
127 m_notificationsButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
128 wxDefaultSize, wxBU_EXACTFIT );
129
130 m_notificationsButton->SetPadding( 0 );
132 m_notificationsButton->SetShowBadge( true );
133 m_notificationsButton->SetBitmapCentered( true );
134
136 }
137
138 if( showWarning )
139 {
140 m_warningButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
141 wxDefaultSize, wxBU_EXACTFIT );
142
143 m_warningButton->SetPadding( 0 );
145 m_warningButton->SetBitmapCentered( true );
146 m_warningButton->SetToolTip( _( "View load messages" ) );
147 m_warningButton->Hide();
148
149 m_warningButton->Bind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
150 }
151
152 Bind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
154
156 Layout();
157}
158
159
161{
163 m_notificationsButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onNotificationsIconClick, this );
164
165 if( m_warningButton )
166 m_warningButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
167
168 Unbind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
170 this );
171}
172
173
174void KISTATUSBAR::onNotificationsIconClick( wxCommandEvent& aEvent )
175{
176 wxCHECK( m_notificationsButton, /* void */ );
177 wxPoint pos = m_notificationsButton->GetScreenPosition();
178
179 wxRect r;
180 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
181 {
182 GetFieldRect( m_normalFieldsCount + *idx, r );
183 pos.x += r.GetWidth();
184 }
185
186 Pgm().GetNotificationsManager().ShowList( this, pos );
187}
188
189
190void KISTATUSBAR::onBackgroundProgressClick( wxMouseEvent& aEvent )
191{
192 wxPoint pos = m_backgroundProgressBar->GetScreenPosition();
193
194 wxRect r;
195 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
196 {
197 GetFieldRect( m_normalFieldsCount + *idx, r );
198 pos.x += r.GetWidth();
199 }
200
201 Pgm().GetBackgroundJobMonitor().ShowList( this, pos );
202}
203
204
205void KISTATUSBAR::onSize( wxSizeEvent& aEvent )
206{
207 constexpr int padding = 5;
208
209 wxRect r;
210 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r );
211 int x = r.GetLeft();
212 int y = r.GetTop();
213 int textHeight = KIUI::GetTextSize( wxT( "bp" ), this ).y;
214
215 if( r.GetHeight() > textHeight )
216 y += ( r.GetHeight() - textHeight ) / 2;
217
218 m_backgroundTxt->SetPosition( { x, y } );
219
220 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_GAUGE ), r );
221 x = r.GetLeft();
222 y = r.GetTop();
223 int w = r.GetWidth();
224 int h = r.GetHeight();
225 wxSize buttonSize( 0, 0 );
226
228 {
229 buttonSize = m_backgroundStopButton->GetEffectiveMinSize();
230 m_backgroundStopButton->SetPosition( { x + w - buttonSize.GetWidth(), y } );
231 m_backgroundStopButton->SetSize( buttonSize.GetWidth(), h );
232 buttonSize.x += padding;
233 }
234
235 m_backgroundProgressBar->SetPosition( { x + padding, y } );
236 m_backgroundProgressBar->SetSize( w - buttonSize.GetWidth() - padding, h );
237
239 {
240 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::NOTIFICATION ), r );
241 x = r.GetLeft();
242 y = r.GetTop();
243 h = r.GetHeight();
244 buttonSize = m_notificationsButton->GetEffectiveMinSize();
245 m_notificationsButton->SetPosition( { x, y } );
246 m_notificationsButton->SetSize( buttonSize.GetWidth() + 6, h );
247 }
248
249 if( m_warningButton )
250 {
251 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::WARNING ), r );
252 x = r.GetLeft();
253 y = r.GetTop();
254 h = r.GetHeight();
255 buttonSize = m_warningButton->GetEffectiveMinSize();
256 m_warningButton->SetPosition( { x, y } );
257 m_warningButton->SetSize( buttonSize.GetWidth() + 6, h );
258 }
259}
260
261
263{
265
267 m_backgroundStopButton->Show( aCancellable );
268}
269
270
278
279
281{
282 int value = m_backgroundProgressBar->GetRange();
283
284 if( value <= aAmount )
285 value = aAmount;
286
287 m_backgroundProgressBar->SetValue( value );
288}
289
290
292{
293 m_backgroundProgressBar->SetRange( aAmount );
294}
295
296
297void KISTATUSBAR::SetBackgroundStatusText( const wxString& aTxt )
298{
299 m_backgroundTxt->SetLabel( aTxt );
300
301 // When there are multiple normal fields, the last normal field (typically used for
302 // file watcher status on Windows) can visually overlap with the background job label
303 // since both have variable width. Save and clear that field when showing background
304 // text, and restore it when the background text is cleared.
305 if( m_normalFieldsCount > 1 )
306 {
307 int adjacentField = m_normalFieldsCount - 1;
308
309 if( !aTxt.empty() )
310 {
311 m_savedStatusText = GetStatusText( adjacentField );
312 SetStatusText( wxEmptyString, adjacentField );
313 }
314 else if( !m_savedStatusText.empty() )
315 {
316 SetStatusText( m_savedStatusText, adjacentField );
317 m_savedStatusText.clear();
318 }
319 }
320}
321
322
324{
325 wxCHECK( m_notificationsButton, /* void */ );
326 wxString cnt = "";
327
328 if( aCount > 0 )
329 cnt = fmt::format( "{}", aCount );
330
331 m_notificationsButton->SetBadgeText( cnt );
332
333 // force a repaint or it wont until it gets activity
334 Refresh();
335}
336
337
338void KISTATUSBAR::SetLoadWarningMessages( const wxString& aMessages )
339{
340 {
341 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
342 m_loadWarningMessages.clear();
343
344 wxStringTokenizer tokenizer( aMessages, wxS( "\n" ), wxTOKEN_STRTOK );
345
346 while( tokenizer.HasMoreTokens() )
347 {
348 LOAD_MESSAGE msg;
349 msg.message = tokenizer.GetNextToken();
350 msg.severity = RPT_SEVERITY_WARNING; // Default to warning for font substitutions
351 m_loadWarningMessages.push_back( msg );
352 }
353 }
354
356}
357
358
359void KISTATUSBAR::AddLoadWarningMessages( const std::vector<LOAD_MESSAGE>& aMessages )
360{
361 wxLogTrace( traceLibraries, "KISTATUSBAR::AddLoadWarningMessages: this=%p, count=%zu",
362 this, aMessages.size() );
363
364 if( aMessages.empty() )
365 return;
366
367 {
368 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
369 m_loadWarningMessages.insert( m_loadWarningMessages.end(), aMessages.begin(), aMessages.end() );
370 wxLogTrace( traceLibraries, " -> total messages now=%zu", m_loadWarningMessages.size() );
371 }
372
373 // Update UI on main thread
374 wxLogTrace( traceLibraries, " -> calling CallAfter for updateWarningUI" );
375 CallAfter( [this]() { updateWarningUI(); } );
376}
377
378
380{
381 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
382 return m_loadWarningMessages.size();
383}
384
385
387{
388 wxLogTrace( traceLibraries, "KISTATUSBAR::updateWarningUI: this=%p, m_warningButton=%p",
389 this, m_warningButton );
390
391 if( !m_warningButton )
392 {
393 wxLogTrace( traceLibraries, " -> no warning button, returning early" );
394 return;
395 }
396
397 size_t messageCount;
398 {
399 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
400 messageCount = m_loadWarningMessages.size();
401 }
402
403 wxLogTrace( traceLibraries, " -> message count=%zu, showing button=%s",
404 messageCount, messageCount > 0 ? "true" : "false" );
405
406 m_warningButton->Show( messageCount > 0 );
407
408 if( messageCount > 0 )
409 {
410 m_warningButton->SetToolTip( wxString::Format( _( "View %zu load message(s)" ), messageCount ) );
411
412 // Show count badge on the warning button
413 m_warningButton->SetShowBadge( true );
414 wxString badgeText = messageCount > 99
415 ? wxString( "99+" )
416 : wxString::Format( wxS( "%zu" ), messageCount );
417 m_warningButton->SetBadgeText( badgeText );
418
419 wxLogTrace( traceLibraries, " -> badge set to '%s'", badgeText );
420 }
421
422 Layout();
423 Refresh();
424 wxLogTrace( traceLibraries, " -> Layout and Refresh complete" );
425}
426
427
429{
430 {
431 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
432 m_loadWarningMessages.clear();
433 }
434
435 if( m_warningButton )
436 {
437 m_warningButton->Hide();
438 m_warningButton->SetShowBadge( false );
439 m_warningButton->SetBadgeText( wxEmptyString );
440 Layout();
441 Refresh();
442 }
443}
444
445
446void KISTATUSBAR::onLoadWarningsIconClick( wxCommandEvent& aEvent )
447{
448 // Copy messages under lock to avoid holding lock during modal dialog
449 std::vector<LOAD_MESSAGE> messages;
450 {
451 std::lock_guard<std::mutex> lock( m_loadWarningMutex );
452 messages = m_loadWarningMessages;
453 }
454
455 if( messages.empty() )
456 return;
457
458 DIALOG_HTML_REPORTER dlg( GetParent(), wxID_ANY, _( "Load Messages" ) );
459
460 for( const LOAD_MESSAGE& msg : messages )
461 dlg.m_Reporter->Report( msg.message, msg.severity );
462
463 dlg.m_Reporter->Flush();
464 dlg.ShowModal();
465}
466
467void KISTATUSBAR::SetEllipsedTextField( const wxString& aText, int aFieldId )
468{
469 wxRect fieldRect;
470 int width = -1;
471 wxString etext = aText;
472
473 // Only GetFieldRect() returns the current size for variable size fields
474 // Other methods return -1 for the width of these fields.
475 if( GetFieldRect( aFieldId, fieldRect ) )
476 width = fieldRect.GetWidth();
477
478 if( width > 20 )
479 {
480 wxClientDC dc( this );
481
482 // Gives a margin to the text to be sure it is not clamped at its end
483 int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
484 etext = wxControl::Ellipsize( etext, dc, wxELLIPSIZE_MIDDLE, width - margin );
485 }
486
487 SetStatusText( etext, aFieldId );
488}
489
490
491std::optional<int> KISTATUSBAR::fieldIndex( FIELD aField ) const
492{
493 switch( aField )
494 {
495 case FIELD::BGJOB_LABEL: return 0;
496 case FIELD::BGJOB_GAUGE: return 1;
498 {
500 return 2;
501
502 break;
503 }
504 case FIELD::WARNING:
505 {
507 {
508 int offset = 2;
509
511 offset++;
512
513 return offset;
514 }
515
516 break;
517 }
519 {
521 {
522 int offset = 2;
523
525 offset++;
526
528 offset++;
529
530 return offset;
531 }
532
533 break;
534 }
535 }
536
537 return std::nullopt;
538}
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
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 onSize(wxSizeEvent &aEvent)
int m_normalFieldsCount
wxString m_savedStatusText
Saved text from adjacent field during background jobs.
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.
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:138
virtual NOTIFICATIONS_MANAGER & GetNotificationsManager() const
Definition pgm_base.h:143
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.