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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <wx/button.h>
22#include <wx/statusbr.h>
23#include <wx/gauge.h>
24#include <wx/stattext.h>
25#include <wx/statbmp.h>
26#include <wx/scrolwin.h>
27#include <wx/textctrl.h>
28#include <wx/sizer.h>
29#include <wx/font.h>
30#include <wx/artprov.h>
31#include <wx/tokenzr.h>
32#include <fmt/format.h>
33#include <array>
34#include <ranges>
35#include <widgets/kistatusbar.h>
37#include <widgets/ui_common.h>
39#include <wx/frame.h>
40#include <wx/time.h>
41#include <kiplatform/ui.h>
42#include <map>
43#include <pgm_base.h>
46#include <bitmaps.h>
47#include <reporter.h>
48#include <trace_helpers.h>
49#include <wx/dcclient.h>
50
51
52class ERROR_CARD : public wxPanel
53{
54public:
55 ERROR_CARD( wxWindow* aParent, const KI_ERROR& aError, int aWrapWidth ) :
56 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ), wxBORDER_NONE )
57 {
58 wxColour fg, bg;
60 SetBackgroundColour( bg );
61 SetForegroundColour( fg );
62
63 wxBoxSizer* outerSizer = new wxBoxSizer( wxHORIZONTAL );
64
65 wxArtID artId = wxART_WARNING;
66
67 switch( aError.GetSeverity() )
68 {
69 case RPT_SEVERITY_ERROR: artId = wxART_ERROR; break;
70 case RPT_SEVERITY_INFO: artId = wxART_INFORMATION; break;
71 default:
72 break;
73 }
74
75 wxStaticBitmap* icon = new wxStaticBitmap(
76 this, wxID_ANY, wxArtProvider::GetBitmapBundle( artId, wxART_OTHER, FromDIP( wxSize( 16, 16 ) ) ) );
77 icon->SetBackgroundColour( bg );
78 outerSizer->Add( icon, 0, wxALL, 4 );
79
80 wxBoxSizer* textSizer = new wxBoxSizer( wxVERTICAL );
81
82 if( aError.HasTitle() )
83 {
84 wxStaticText* title = new wxStaticText( this, wxID_ANY, aError.GetTitle() );
85 title->SetFont( KIUI::GetControlFont( this ).Bold() );
86
87 if( aWrapWidth > 0 )
88 title->Wrap( aWrapWidth );
89
90 textSizer->Add( title, 0, wxALL | wxEXPAND, 1 );
91 }
92
93 if( aError.HasDescription() )
94 {
95 wxStaticText* desc = new wxStaticText( this, wxID_ANY, aError.GetDescription() );
96
97 if( aWrapWidth > 0 )
98 desc->Wrap( aWrapWidth );
99
100 textSizer->Add( desc, 0, wxALL | wxEXPAND, 1 );
101 }
102
103 if( aError.HasDebugText() )
104 {
105 WX_COLLAPSIBLE_PANE* pane = new WX_COLLAPSIBLE_PANE( this, wxID_ANY, _( "Additional information" ) );
106 pane->Collapse();
107 pane->SetBackgroundColour( bg );
108 textSizer->Add( pane, 0, wxEXPAND | wxALL, 1 );
109
110 wxWindow* paneWin = pane->GetPane();
111 paneWin->SetBackgroundColour( bg );
112 const wxString& text = aError.GetDebugText();
113 int paneHeight = GetCharHeight() * ( 2 + text.Freq( '\n' ) );
114
115 wxTextCtrl* debugText = new wxTextCtrl( paneWin, wxID_ANY, text, wxDefaultPosition,
116 FromDIP( wxSize( -1, paneHeight ) ),
117 wxTE_MULTILINE | wxTE_READONLY | wxTE_DONTWRAP );
118
119 wxFont monoFont = debugText->GetFont();
120 monoFont.SetFamily( wxFONTFAMILY_TELETYPE );
121 debugText->SetFont( monoFont );
122
123 wxBoxSizer* paneSizer = new wxBoxSizer( wxVERTICAL );
124 paneSizer->Add( debugText, 1, wxEXPAND | wxALL, 2 );
125 paneWin->SetSizer( paneSizer );
126 paneWin->Layout();
127
128 pane->Bind( WX_COLLAPSIBLE_PANE_CHANGED,
129 [this]( wxCommandEvent& aEvt )
130 {
131 aEvt.Skip();
132
133 wxWindow* scrolled = GetParent();
134 wxWindow* frame = scrolled->GetParent();
135
136 scrolled->Layout();
137
138 if( wxSizer* sizer = scrolled->GetSizer() )
139 sizer->Fit( scrolled );
140
141 frame->Layout();
142 frame->Refresh();
143 } );
144 }
145
146 outerSizer->Add( textSizer, 1, wxEXPAND | wxTOP, 3 );
147
148 SetSizer( outerSizer );
149 Layout();
150 }
151};
152
153
154static long long g_warning_list_closed_timer = 0;
155
156
157class STATUSBAR_WARNING_LIST : public wxFrame
158{
159public:
160 STATUSBAR_WARNING_LIST( KISTATUSBAR* aStatusBar, wxWindow* aParent ) :
161 wxFrame( aParent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
162 wxFRAME_NO_TASKBAR | wxBORDER_STATIC ),
163 m_statusBar( aStatusBar )
164 {
165 // FromDIP() dispatches through the window vtable, so it may not be used until the
166 // wxFrame base is constructed
167 const wxSize panelSize = FromDIP( wxSize( 600, 200 ) );
168
169 SetSize( panelSize );
170 SetSizeHints( panelSize, wxDefaultSize );
171
172 wxColour fg, bg;
174 SetBackgroundColour( bg );
175
176 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
177
179 new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxBORDER_SIMPLE );
180 m_scrolledWindow->SetScrollRate( 5, 5 );
181 m_scrolledWindow->SetBackgroundColour( bg );
182 m_scrolledWindow->SetForegroundColour( fg );
183
184 m_contentSizer = new wxBoxSizer( wxVERTICAL );
185 m_scrolledWindow->SetSizer( m_contentSizer );
186 m_scrolledWindow->Layout();
187
188 mainSizer->Add( m_scrolledWindow, 1, wxEXPAND, 0 );
189
190 wxBoxSizer* btnSizer = new wxBoxSizer( wxHORIZONTAL );
191 btnSizer->AddStretchSpacer( 1 );
192 wxButton* clearButton = new wxButton( this, wxID_CLEAR, _( "Clear Warnings" ) );
193 clearButton->Bind( wxEVT_BUTTON, &STATUSBAR_WARNING_LIST::onClearButtonClick, this );
194 btnSizer->Add( clearButton, 0, wxALL, 5 );
195 mainSizer->Add( btnSizer, 0, wxEXPAND | wxTOP | wxRIGHT | wxLEFT | wxBOTTOM, 5 );
196
197 SetSizer( mainSizer );
198 Layout();
199
200 Bind( wxEVT_KILL_FOCUS, &STATUSBAR_WARNING_LIST::onFocusLoss, this );
201 m_scrolledWindow->Bind( wxEVT_KILL_FOCUS, &STATUSBAR_WARNING_LIST::onFocusLoss, this );
202 Bind( wxEVT_CHAR_HOOK, &STATUSBAR_WARNING_LIST::onCharHook, this );
203 Bind( wxEVT_CLOSE_WINDOW, &STATUSBAR_WARNING_LIST::onClose, this );
204
206 }
207
208
210 {
211 Unbind( wxEVT_KILL_FOCUS, &STATUSBAR_WARNING_LIST::onFocusLoss, this );
212 m_scrolledWindow->Unbind( wxEVT_KILL_FOCUS, &STATUSBAR_WARNING_LIST::onFocusLoss, this );
213 Unbind( wxEVT_CHAR_HOOK, &STATUSBAR_WARNING_LIST::onCharHook, this );
214 Unbind( wxEVT_CLOSE_WINDOW, &STATUSBAR_WARNING_LIST::onClose, this );
215 }
216
217
219 {
220 m_contentSizer->Clear( true );
221
222 int wrapWidth = m_scrolledWindow->GetClientSize().x;
223
224 if( wrapWidth < 100 )
225 wrapWidth = FromDIP( 580 );
226 else
227 wrapWidth -= FromDIP( 40 ); // Card borders/margins + severity icon + padding
228
229 auto messages = m_statusBar->GetWarningMessages();
230
231 for( const auto& msgs : messages | std::views::values )
232 {
233 for( const KI_ERROR& msg : msgs )
234 {
235 ERROR_CARD* card = new ERROR_CARD( m_scrolledWindow, msg, wrapWidth );
236 m_contentSizer->Add( card, 0, wxEXPAND | wxALL, 2 );
237 }
238 }
239
240 m_scrolledWindow->Layout();
242 Layout();
243 Refresh();
244 }
245
246
247private:
248 void onFocusLoss( wxFocusEvent& aEvent )
249 {
250 if( !IsDescendant( aEvent.GetWindow() ) )
251 {
252 Close( true );
253 g_warning_list_closed_timer = wxGetLocalTimeMillis().GetValue();
254 }
255
256 aEvent.Skip();
257 }
258
259
260 void onCharHook( wxKeyEvent& aEvent )
261 {
262 if( aEvent.GetKeyCode() == WXK_ESCAPE )
263 {
264 Close( true );
265 g_warning_list_closed_timer = wxGetLocalTimeMillis().GetValue();
266 return;
267 }
268
269 aEvent.Skip();
270 }
271
272
273 void onClose( wxCloseEvent& aEvent )
274 {
275 if( m_statusBar )
276 m_statusBar->CloseWarningList();
277
278 aEvent.Skip();
279 }
280
281
282 void onClearButtonClick( wxCommandEvent& aEvent )
283 {
284 if( m_statusBar )
285 m_statusBar->ClearWarningMessages();
286
287 // ClearWarningMessages triggers updateWarningUI which will close the panel
288 }
289
291 wxScrolledWindow* m_scrolledWindow;
292 wxBoxSizer* m_contentSizer;
293};
294
295
296KISTATUSBAR::KISTATUSBAR( int aNumberFields, wxWindow* parent, wxWindowID id, STYLE_FLAGS aFlags ) :
297 wxStatusBar( parent, id, wxSTB_SIZEGRIP | wxSTB_ELLIPSIZE_MIDDLE | wxSTB_SHOW_TIPS | wxFULL_REPAINT_ON_RESIZE ),
298 m_backgroundStopButton( nullptr ),
299 m_notificationsButton( nullptr ),
300 m_warningButton( nullptr ),
301 m_warningList( nullptr ),
302 m_normalFieldsCount( aNumberFields ),
303 m_styleFlags( aFlags )
304{
305#ifdef __WXOSX__
306 // we need +1 extra field on OSX to offset from the rounded corner on the right
307 // OSX doesn't use resize grippers like the other platforms and the statusbar field
308 // includes the rounded part
309 int extraFields = 3;
310#else
311 int extraFields = 2;
312#endif
313
314 bool showNotification = ( m_styleFlags & NOTIFICATION_ICON );
315 bool showCancel = ( m_styleFlags & CANCEL_BUTTON );
316 bool showWarning = ( m_styleFlags & WARNING_ICON );
317
318 if( showCancel )
319 extraFields++;
320
321 if( showWarning )
322 extraFields++;
323
324 if( showNotification )
325 extraFields++;
326
327 m_backgroundTxt = new wxStaticText( this, wxID_ANY, wxT( "" ), wxDefaultPosition,
328 wxDefaultSize, wxALIGN_RIGHT | wxST_NO_AUTORESIZE );
329
330 m_backgroundProgressBar = new wxGauge( this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize,
331 wxGA_HORIZONTAL | wxGA_SMOOTH );
332
333 if( showCancel )
334 {
335 m_backgroundStopButton = new wxButton( this, wxID_ANY, "X", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
336 }
337
338 if( showNotification )
339 {
340 m_notificationsButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
341 wxDefaultSize, wxBU_EXACTFIT );
342
343 m_notificationsButton->SetPadding( 0 );
345 m_notificationsButton->SetShowBadge( true );
346 m_notificationsButton->SetBitmapCentered( true );
347
349 }
350
351 if( showWarning )
352 {
353 m_warningButton = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
354 wxDefaultSize, wxBU_EXACTFIT );
355
356 m_warningButton->SetPadding( 0 );
358 m_warningButton->SetBitmapCentered( true );
359 m_warningButton->SetToolTip( _( "View load messages" ) );
360 m_warningButton->Hide();
361
362
363 m_warningButton->Bind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
364 }
365
366 m_fieldWidths.assign( aNumberFields + extraFields, -1 );
367
368#ifdef __WXOSX__
369 // offset from the right edge
370 int padding = KIUI::GetTextSize( wxT( "M" ), this ).x;
371 m_fieldWidths[aNumberFields + extraFields - 1] = padding;
372#endif
373
374 SetFieldsCount( m_fieldWidths.size(), m_fieldWidths.data() );
375
376 std::vector<int> styles( aNumberFields + extraFields, wxSB_FLAT );
377 SetStatusStyles( styles.size(), styles.data() );
378
379 Bind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
381
383 Layout();
384}
385
386
388{
390 m_notificationsButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onNotificationsIconClick, this );
391
392 if( m_warningButton )
393 m_warningButton->Unbind( wxEVT_BUTTON, &KISTATUSBAR::onLoadWarningsIconClick, this );
394
396
397 Unbind( wxEVT_SIZE, &KISTATUSBAR::onSize, this );
399 this );
400}
401
402
403void KISTATUSBAR::onNotificationsIconClick( wxCommandEvent& aEvent )
404{
405 wxCHECK( m_notificationsButton, /* void */ );
406 wxPoint pos = m_notificationsButton->GetScreenPosition();
407
408 wxRect r;
409 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
410 {
411 GetFieldRect( m_normalFieldsCount + *idx, r );
412 pos.x += r.GetWidth();
413 }
414
415 Pgm().GetNotificationsManager().ShowList( this, pos );
416}
417
418
419void KISTATUSBAR::onBackgroundProgressClick( wxMouseEvent& aEvent )
420{
421 wxPoint pos = m_backgroundProgressBar->GetScreenPosition();
422
423 wxRect r;
424 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
425 {
426 GetFieldRect( m_normalFieldsCount + *idx, r );
427 pos.x += r.GetWidth();
428 }
429
430 Pgm().GetBackgroundJobMonitor().ShowList( this, pos );
431}
432
433
434void KISTATUSBAR::onSize( wxSizeEvent& aEvent )
435{
437}
438
439
441{
442 constexpr int padding = 5;
443
444 wxRect r;
445 int sbField = m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL );
446
447 if( sbField >= 0 && sbField < GetFieldsCount() )
448 {
449 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r );
450 int x = r.GetLeft();
451 int y = r.GetTop();
452 int textHeight = KIUI::GetTextSize( wxT( "bp" ), this ).y;
453
454 if( r.GetHeight() > textHeight )
455 y += ( r.GetHeight() - textHeight ) / 2;
456
457 m_backgroundTxt->SetPosition( { x, y } );
458 m_backgroundTxt->SetSize( r.GetWidth(), textHeight );
460 }
461
462 sbField = m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_GAUGE );
463
464 if( sbField >= 0 && sbField < GetFieldsCount() )
465 {
466 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_GAUGE ), r );
467 int x = r.GetLeft();
468 int y = r.GetTop();
469 int w = r.GetWidth();
470 int h = r.GetHeight();
471 wxSize buttonSize( 0, 0 );
472
474 {
475 buttonSize = m_backgroundStopButton->GetEffectiveMinSize();
476 m_backgroundStopButton->SetPosition( { x + w - buttonSize.GetWidth(), y } );
477 m_backgroundStopButton->SetSize( buttonSize.GetWidth(), h );
478 buttonSize.x += padding;
479 }
480
481 m_backgroundProgressBar->SetPosition( { x + padding, y } );
482 m_backgroundProgressBar->SetSize( w - buttonSize.GetWidth() - padding, h );
483
485 {
486 sbField = m_normalFieldsCount + *fieldIndex( FIELD::NOTIFICATION );
487
488 if( sbField >= 0 && sbField < GetFieldsCount() )
489 {
490 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::NOTIFICATION ), r );
491 x = r.GetLeft();
492 y = r.GetTop();
493 h = r.GetHeight();
494 buttonSize = m_notificationsButton->GetEffectiveMinSize();
495 m_notificationsButton->SetPosition( { x, y } );
496 m_notificationsButton->SetSize( buttonSize.GetWidth() + 6, h );
497 }
498 }
499 }
500
501 if( m_warningButton )
502 {
503 sbField = m_normalFieldsCount + *fieldIndex( FIELD::WARNING );
504
505 if( sbField >= 0 && sbField < GetFieldsCount() )
506 {
507 GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::WARNING ), r );
508 int x = r.GetLeft();
509 int y = r.GetTop();
510 int h = r.GetHeight();
511 wxSize buttonSize = m_warningButton->GetEffectiveMinSize();
512 m_warningButton->SetPosition( { x, y } );
513 m_warningButton->SetSize( buttonSize.GetWidth() + 6, h );
514 }
515 }
516}
517
518
520{
522
524 m_backgroundStopButton->Show( aCancellable );
525
527}
528
529
539
540
542{
543 int range = m_backgroundProgressBar->GetRange();
544
545 if( aAmount > range )
546 aAmount = range;
547
548 m_backgroundProgressBar->SetValue( aAmount );
549}
550
551
553{
554 m_backgroundProgressBar->SetRange( aAmount );
555}
556
557
558void KISTATUSBAR::SetBackgroundStatusText( const wxString& aTxt )
559{
560 m_backgroundRawText = aTxt;
562
563 // When there are multiple normal fields, the last normal field (typically used for
564 // file watcher status on Windows) can visually overlap with the background job label
565 // since both have variable width. Save and clear that field when showing background
566 // text, and restore it when the background text is cleared.
567 if( m_normalFieldsCount > 1 )
568 {
569 int adjacentField = m_normalFieldsCount - 1;
570 wxString currentText = GetStatusText( adjacentField );
571
572 if( !aTxt.empty() )
573 {
574 if( !currentText.empty() )
575 m_savedStatusText = currentText;
576
577 SetStatusText( wxEmptyString, adjacentField );
578 }
579 else if( !m_savedStatusText.empty() )
580 {
581 SetStatusText( m_savedStatusText, adjacentField );
582 m_savedStatusText.clear();
583 }
584 }
585}
586
587
589{
590 if( m_fieldWidths.empty() )
591 return;
592
593 int padding = KIUI::GetTextSize( wxT( "M" ), this ).x;
594
595 // The background job label and gauge only carry content while a job is running. When idle
596 // they must collapse to zero so they do not consume stretch reserved for the normal fields.
597 bool jobActive = m_backgroundProgressBar && m_backgroundProgressBar->IsShown();
598
599 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_LABEL ) )
600 m_fieldWidths[m_normalFieldsCount + *idx] = jobActive ? -1 : 0;
601
602 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_GAUGE ) )
603 m_fieldWidths[m_normalFieldsCount + *idx] = jobActive ? 75 : 0;
604
605 if( std::optional<int> idx = fieldIndex( FIELD::BGJOB_CANCEL ) )
606 {
608 m_fieldWidths[m_normalFieldsCount + *idx] = m_backgroundStopButton->GetSize().x + padding;
609 else
611 }
612
613 if( std::optional<int> idx = fieldIndex( FIELD::WARNING ) )
614 {
615 if( m_warningButton && m_warningButton->IsShown() )
616 m_fieldWidths[m_normalFieldsCount + *idx] = m_warningButton->GetSize().x + padding;
617 else
619 }
620
621 if( std::optional<int> idx = fieldIndex( FIELD::NOTIFICATION ) )
622 {
624 m_fieldWidths[m_normalFieldsCount + *idx] = m_notificationsButton->GetSize().x + padding;
625 else
627 }
628
629 SetStatusWidths( static_cast<int>( m_fieldWidths.size() ), m_fieldWidths.data() );
632}
633
634
636{
637 wxRect r;
638
639 if( !GetFieldRect( m_normalFieldsCount + *fieldIndex( FIELD::BGJOB_LABEL ), r ) )
640 return;
641
642 wxString text = m_backgroundRawText;
643
644 if( !text.empty() && r.GetWidth() > 4 )
645 {
646 wxClientDC dc( this );
647 int margin = KIUI::GetTextSize( wxT( "XX" ), this ).x;
648 text = wxControl::Ellipsize( text, dc, wxELLIPSIZE_END, std::max( 0, r.GetWidth() - margin ) );
649 }
650
651 m_backgroundTxt->SetLabel( text );
652}
653
654
656{
657 wxCHECK( m_notificationsButton, /* void */ );
658 wxString cnt = "";
659
660 if( aCount > 0 )
661 cnt = fmt::format( "{}", aCount );
662
663 m_notificationsButton->SetBadgeText( cnt );
664
665 // force a repaint or it wont until it gets activity
666 Refresh();
667}
668
669
670void KISTATUSBAR::AddWarningMessages( const wxString& aSource, const wxString& aMessages )
671{
672 {
673 std::lock_guard<std::mutex> lock( m_warningMutex );
674
675 wxStringTokenizer tokenizer( aMessages, wxS( "\n" ), wxTOKEN_STRTOK );
676
677 while( tokenizer.HasMoreTokens() )
678 {
679 KI_ERROR msg;
680 msg.SetTitle( tokenizer.GetNextToken() );
681 msg.SetSeverity( RPT_SEVERITY_WARNING ); // Default to warning for font substitutions
682 m_warningMessages[aSource].push_back( msg );
683 }
684 }
685
687}
688
689
690void KISTATUSBAR::AddWarningMessages( const wxString& aSource, const std::vector<KI_ERROR>& aMessages )
691{
692 wxLogTrace( traceLibraries, "KISTATUSBAR::AddWarningMessages: this=%p, count=%zu",
693 this, aMessages.size() );
694
695 if( aMessages.empty() )
696 return;
697
698 size_t totalMessageCount = 0;
699
700 {
701 std::lock_guard<std::mutex> lock( m_warningMutex );
702 m_warningMessages[aSource].insert( m_warningMessages[aSource].end(), aMessages.begin(), aMessages.end() );
703
704 for( const auto& [source, messages] : m_warningMessages )
705 totalMessageCount += messages.size();
706 }
707
708 wxLogTrace( traceLibraries, " -> total messages now=%zu", totalMessageCount );
709
710 // Update UI on main thread
711 wxLogTrace( traceLibraries, " -> calling CallAfter for updateWarningUI" );
712 CallAfter( [this]() { updateWarningUI(); } );
713}
714
715
717{
718 std::lock_guard<std::mutex> lock( m_warningMutex );
719
720 size_t count = 0;
721
722 for( const auto& [source, messages] : m_warningMessages )
723 count += messages.size();
724
725 return count;
726}
727
728
729std::map<wxString, std::vector<KI_ERROR>> KISTATUSBAR::GetWarningMessages() const
730{
731 std::lock_guard<std::mutex> lock( m_warningMutex );
732
733 // TODO(JE) this is NG, we should use a vector not an unordered map
734 // Copy into a std::map so sources are sorted by name for stable display order.
735 return { m_warningMessages.begin(), m_warningMessages.end() };
736}
737
738
740{
741 wxLogTrace( traceLibraries, "KISTATUSBAR::updateWarningUI: this=%p, m_warningButton=%p",
742 this, m_warningButton );
743
744 if( !m_warningButton )
745 {
746 wxLogTrace( traceLibraries, " -> no warning button, returning early" );
747 return;
748 }
749
750 size_t messageCount;
751 {
752 std::lock_guard<std::mutex> lock( m_warningMutex );
753
754 messageCount = 0;
755
756 for( const std::vector<KI_ERROR>& messages : m_warningMessages | std::views::values )
757 messageCount += messages.size();
758 }
759
760 wxLogTrace( traceLibraries, " -> message count=%zu, showing button=%s",
761 messageCount, messageCount > 0 ? "true" : "false" );
762
763 m_warningButton->Show( messageCount > 0 );
764 m_warningButton->SetShowBadge( messageCount > 0 );
766
767 if( messageCount > 0 )
768 {
769 m_warningButton->SetToolTip( wxString::Format( _( "View %zu message(s)" ), messageCount ) );
770
771 // Show count badge on the warning button
772 wxString badgeText = messageCount > 99
773 ? wxString( "99+" )
774 : wxString::Format( wxS( "%zu" ), messageCount );
775 m_warningButton->SetBadgeText( badgeText );
776
777 wxLogTrace( traceLibraries, " -> badge set to '%s'", badgeText );
778 }
779 else
780 {
781 m_warningButton->SetBadgeText( wxEmptyString );
782 m_warningButton->SetToolTip( _( "View messages" ) );
783 }
784
785 if( m_warningList )
786 {
787 if( messageCount > 0 )
788 m_warningList->rebuildMessages();
789 else
791 }
792
793 Layout();
794 Refresh();
795}
796
797
798void KISTATUSBAR::ClearWarningMessages( const wxString& aSource )
799{
800 {
801 std::lock_guard<std::mutex> lock( m_warningMutex );
802
803 if( aSource.IsEmpty() )
804 m_warningMessages.clear();
805 else if( auto it = m_warningMessages.find( aSource ); it != m_warningMessages.end() )
806 m_warningMessages.erase( it );
807 }
808
810}
811
812
813void KISTATUSBAR::onLoadWarningsIconClick( wxCommandEvent& aEvent )
814{
815 // Debounce clicking on the icon with a list already showing
816 if( wxGetLocalTimeMillis().GetValue() - g_warning_list_closed_timer < 300 )
817 {
819 return;
820 }
821
822 if( m_warningList )
823 {
825 return;
826 }
827
828 if( GetLoadWarningCount() == 0 )
829 return;
830
832}
833
834
836{
837 wxCHECK( m_warningButton, /* void */ );
838
839 m_warningList = new STATUSBAR_WARNING_LIST( this, GetParent() );
841 m_warningList->Show();
843}
844
845
847{
849 return;
850
851 wxRect iconRect = m_warningButton->GetScreenRect();
852
853 wxSize windowSize = m_warningList->GetSize();
854 wxPoint pos;
855 pos.x = iconRect.GetRight() + 1 - windowSize.GetWidth();
856 pos.y = iconRect.GetTop() - windowSize.GetHeight();
857
858 m_warningList->SetPosition( pos );
859 m_warningList->Layout();
860}
861
862
864{
865 if( !m_warningList )
866 return;
867
868 m_warningList->Destroy();
869 m_warningList = nullptr;
870}
871
872
873
874std::optional<int> KISTATUSBAR::fieldIndex( FIELD aField ) const
875{
876 switch( aField )
877 {
878 case FIELD::BGJOB_LABEL: return 0;
879 case FIELD::BGJOB_GAUGE: return 1;
880 case FIELD::BGJOB_CANCEL:
881 {
883 return 2;
884
885 break;
886 }
887 case FIELD::WARNING:
888 {
890 {
891 int offset = 2;
892
894 offset++;
895
896 return offset;
897 }
898
899 break;
900 }
901 case FIELD::NOTIFICATION:
902 {
904 {
905 int offset = 2;
906
908 offset++;
909
911 offset++;
912
913 return offset;
914 }
915
916 break;
917 }
918 }
919
920 return std::nullopt;
921}
922
923
924void KISTATUSBAR::SetStatusWidths( int aSize, const int* aWidths )
925{
926 wxStatusBar::SetStatusWidths( aSize, aWidths );
927
928 for( int i = 0; ( i < aSize ) && ( i < static_cast<int>( m_fieldWidths.size() ) ); i++ )
929 m_fieldWidths[i] = *( aWidths + i );
930}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
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.
ERROR_CARD(wxWindow *aParent, const KI_ERROR &aError, int aWrapWidth)
KISTATUSBAR is a wxStatusBar suitable for Kicad manager.
Definition kistatusbar.h:50
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<KI_ERROR> variant from other threads...
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()
std::unordered_map< wxString, std::vector< KI_ERROR > > m_warningMessages
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 CloseWarningList()
Close the warning message overlay panel, if it is shown.
void updateWarningUI()
Update warning button visibility and badge (main thread only)
virtual void SetStatusWidths(int aSize, const int *aWidths) override
std::map< wxString, std::vector< KI_ERROR > > GetWarningMessages() const
Get a copy of all stored warning/error messages, grouped by source and sorted by source name for stab...
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.
void PositionWarningPanel()
Position the warning message overlay panel above the warning icon.
wxStaticText * m_backgroundTxt
void ShowBackgroundProgressBar(bool aCancellable=false)
Show the background progress bar.
void openWarningList()
STATUSBAR_WARNING_LIST * m_warningList
Overlay panel listing the warning messages.
void SetNotificationCount(int aCount)
Set the notification count on the notifications button.
Holds a structured error message.
Definition ki_error.h:38
const wxString & GetTitle() const
Definition ki_error.h:51
SEVERITY GetSeverity() const
Definition ki_error.h:54
bool HasTitle() const
Definition ki_error.h:82
const wxString & GetDescription() const
Definition ki_error.h:52
bool HasDescription() const
Definition ki_error.h:83
KI_ERROR & SetSeverity(SEVERITY aSeverity)
Definition ki_error.h:76
KI_ERROR & SetTitle(const wxString &aTitle)
Definition ki_error.h:58
bool HasDebugText() const
Definition ki_error.h:84
const wxString & GetDebugText() const
Definition ki_error.h:53
void ShowList(wxWindow *aParent, wxPoint aPos)
Show the notification list.
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
Definition pgm_base.h:129
virtual NOTIFICATIONS_MANAGER & GetNotificationsManager() const
Definition pgm_base.h:134
void onClose(wxCloseEvent &aEvent)
STATUSBAR_WARNING_LIST(KISTATUSBAR *aStatusBar, wxWindow *aParent)
wxScrolledWindow * m_scrolledWindow
void onClearButtonClick(wxCommandEvent &aEvent)
wxBoxSizer * m_contentSizer
void onCharHook(wxKeyEvent &aEvent)
~STATUSBAR_WARNING_LIST() override
void onFocusLoss(wxFocusEvent &aEvent)
KISTATUSBAR * m_statusBar
A better wxCollapsiblePane that.
void Collapse(bool aCollapse=true)
bool SetBackgroundColour(const wxColour &aColor) override
#define _(s)
const wxChar *const traceLibraries
Flag to enable library table and library manager tracing.
static long long g_warning_list_closed_timer
void GetInfoBarColours(wxColour &aFGColour, wxColour &aBGColour)
Return the background and foreground colors for info bars in the current scheme.
Definition wxgtk/ui.cpp:70
void ForceFocus(wxWindow *aWindow)
Pass the current focus to the window.
Definition wxgtk/ui.cpp:127
KICOMMON_API wxFont GetControlFont(wxWindow *aWindow)
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
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
VECTOR2I end
wxLogTrace helper definitions.
Functions to provide common constants and other functions to assist in making a consistent UI.
const KICOMMON_API wxEventTypeTag< wxCommandEvent > WX_COLLAPSIBLE_PANE_CHANGED