KiCad PCB EDA Suite
Loading...
Searching...
No Matches
notifications_manager.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 (C) 2023 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/filename.h>
26#include <wx/frame.h>
27#include <wx/hyperlink.h>
28#include <wx/panel.h>
29#include <wx/scrolwin.h>
30#include <wx/sizer.h>
31#include <wx/settings.h>
32#include <wx/stattext.h>
33#include <wx/string.h>
34
35#include <paths.h>
36
38#include <widgets/kistatusbar.h>
39#include <json_common.h>
40
41#include "core/wx_stl_compat.h"
42
43#include <algorithm>
44#include <fstream>
45#include <map>
47#include <optional>
48#include <string>
49#include <tuple>
50#include <vector>
51#include <wx/string.h>
52
53
54NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( NOTIFICATION, title, description, href, key, date )
55
56class NOTIFICATION_PANEL : public wxPanel
57{
58public:
59 NOTIFICATION_PANEL( wxWindow* aParent, NOTIFICATIONS_MANAGER* aManager, NOTIFICATION* aNoti ) :
60 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, 75 ),
61 wxBORDER_SIMPLE ),
62 m_hlDetails( nullptr ),
63 m_notification( aNoti ),
64 m_manager( aManager )
65 {
66 SetSizeHints( wxDefaultSize, wxDefaultSize );
67
68 wxBoxSizer* mainSizer;
69 mainSizer = new wxBoxSizer( wxVERTICAL );
70
71 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
72
73 m_stTitle = new wxStaticText( this, wxID_ANY, aNoti->title );
74 m_stTitle->Wrap( -1 );
75 m_stTitle->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT,
76 wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
77 mainSizer->Add( m_stTitle, 0, wxALL | wxEXPAND, 1 );
78
79 m_stDescription = new wxStaticText( this, wxID_ANY, aNoti->description, wxDefaultPosition,
80 wxDefaultSize, 0 );
81 m_stDescription->Wrap( -1 );
82 mainSizer->Add( m_stDescription, 0, wxALL | wxEXPAND, 1 );
83
84 wxBoxSizer* tailSizer;
85 tailSizer = new wxBoxSizer( wxHORIZONTAL );
86
87 if( !aNoti->href.IsEmpty() )
88 {
89 m_hlDetails =
90 new wxHyperlinkCtrl( this, wxID_ANY, _( "View Details" ), aNoti->href,
91 wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE );
92 tailSizer->Add( m_hlDetails, 0, wxALL, 2 );
93 }
94
95 m_hlDismiss = new wxHyperlinkCtrl( this, wxID_ANY, _( "Dismiss" ), aNoti->href,
96 wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE );
97 tailSizer->Add( m_hlDismiss, 0, wxALL, 2 );
98
99 mainSizer->Add( tailSizer, 1, wxEXPAND, 5 );
100
101 if( m_hlDetails != nullptr )
102 {
103 m_hlDetails->Bind( wxEVT_HYPERLINK, &NOTIFICATION_PANEL::onDetails, this );
104 }
105
106 m_hlDismiss->Bind( wxEVT_HYPERLINK, &NOTIFICATION_PANEL::onDismiss, this );
107
108 SetSizer( mainSizer );
109 Layout();
110 }
111
112private:
113 void onDetails( wxHyperlinkEvent& aEvent )
114 {
115 wxString url = aEvent.GetURL();
116
117 if( url.StartsWith( wxS( "kicad://" ) ) )
118 {
119 url.Replace( wxS( "kicad://" ), wxS( "" ) );
120
121 if( url == wxS( "pcm" ) )
122 {
123 // TODO
124 }
125 }
126 else
127 {
128 wxLaunchDefaultBrowser( aEvent.GetURL(), wxBROWSER_NEW_WINDOW );
129 }
130 }
131
132 void onDismiss( wxHyperlinkEvent& aEvent )
133 {
134 CallAfter(
135 [this]()
136 {
137 // This will cause this panel to get deleted
138 m_manager->Remove( m_notification->key );
139 } );
140 }
141
142 wxStaticText* m_stTitle;
143 wxStaticText* m_stDescription;
144 wxHyperlinkCtrl* m_hlDetails;
145 wxHyperlinkCtrl* m_hlDismiss;
148};
149
150
151class NOTIFICATIONS_LIST : public wxFrame
152{
153public:
154 NOTIFICATIONS_LIST( NOTIFICATIONS_MANAGER* aManager, wxWindow* parent, const wxPoint& pos ) :
155 wxFrame( parent, wxID_ANY, _( "Notifications" ), pos, wxSize( 300, 150 ),
156 wxFRAME_NO_TASKBAR | wxBORDER_SIMPLE ),
157 m_manager( aManager )
158 {
159 SetSizeHints( wxDefaultSize, wxDefaultSize );
160
161 wxBoxSizer* bSizer1;
162 bSizer1 = new wxBoxSizer( wxVERTICAL );
163
164 m_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition,
165 wxSize( -1, -1 ), wxVSCROLL );
166 m_scrolledWindow->SetScrollRate( 5, 5 );
167 m_contentSizer = new wxBoxSizer( wxVERTICAL );
168
169 m_scrolledWindow->SetSizer( m_contentSizer );
170 m_scrolledWindow->Layout();
172 bSizer1->Add( m_scrolledWindow, 1, wxEXPAND | wxALL, 0 );
173
174 m_noNotificationsText = new wxStaticText(
175 m_scrolledWindow, wxID_ANY, _( "There are no notifications available" ),
176 wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
177 m_noNotificationsText->Wrap( -1 );
178 m_contentSizer->Add( m_noNotificationsText, 1, wxALL | wxEXPAND, 5 );
179
180 Bind( wxEVT_KILL_FOCUS, &NOTIFICATIONS_LIST::onFocusLoss, this );
181
182 SetSizer( bSizer1 );
183 Layout();
184
185 SetFocus();
186 }
187
188
189 void onFocusLoss( wxFocusEvent& aEvent )
190 {
191 // check if a child like say, the hyperlink texts got focus
192 if( !IsDescendant( aEvent.GetWindow() ) )
193 Close( true );
194
195 aEvent.Skip();
196 }
197
198
199 void Add( NOTIFICATION* aNoti )
200 {
201 m_noNotificationsText->Hide();
202
204 m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 );
205 m_scrolledWindow->Layout();
207
208 // call this at this window otherwise the child panels dont resize width properly
209 Layout();
210
211 m_panelMap[aNoti] = panel;
212 }
213
214
215 void Remove( NOTIFICATION* aNoti )
216 {
217 auto it = m_panelMap.find( aNoti );
218 if( it != m_panelMap.end() )
219 {
220 NOTIFICATION_PANEL* panel = m_panelMap[aNoti];
221 m_contentSizer->Detach( panel );
222 panel->Destroy();
223
224 m_panelMap.erase( it );
225
226 // ensure the window contents get shifted as needed
227 m_scrolledWindow->Layout();
228 Layout();
229 }
230
231 if( m_panelMap.size() == 0 )
232 {
233 m_noNotificationsText->Show();
234 }
235 }
236
237private:
238 wxScrolledWindow* m_scrolledWindow;
240 wxBoxSizer* m_contentSizer;
241 std::unordered_map<NOTIFICATION*, NOTIFICATION_PANEL*> m_panelMap;
245};
246
247
249{
250 m_destFileName = wxFileName( PATHS::GetUserCachePath(), wxT( "notifications.json" ) );
251}
252
253
255{
256 nlohmann::json saved_json;
257
258 std::ifstream saved_json_stream( m_destFileName.GetFullPath().fn_str() );
259
260 try
261 {
262 saved_json_stream >> saved_json;
263
264 m_notifications = saved_json.get<std::vector<NOTIFICATION>>();
265 }
266 catch( std::exception& )
267 {
268 // failed to load the json, which is fine, default to no notificaitons
269 }
270
271 if( wxGetEnv( wxT( "KICAD_TEST_NOTI" ), nullptr ) )
272 {
273 CreateOrUpdate( wxS( "test" ), wxS( "Test Notification" ), wxS( "Test please ignore" ),
274 wxS( "https://kicad.org" ) );
275 }
276}
277
278
280{
281 std::ofstream jsonFileStream( m_destFileName.GetFullPath().fn_str() );
282
283 nlohmann::json saveJson = nlohmann::json( m_notifications );
284 jsonFileStream << std::setw( 4 ) << saveJson << std::endl;
285 jsonFileStream.flush();
286 jsonFileStream.close();
287}
288
289
290void NOTIFICATIONS_MANAGER::CreateOrUpdate( const wxString& aKey,
291 const wxString& aTitle,
292 const wxString& aDescription,
293 const wxString& aHref )
294{
295 wxCHECK_RET( !aKey.IsEmpty(), wxS( "Notification key must not be empty" ) );
296
297 auto it = std::find_if( m_notifications.begin(), m_notifications.end(),
298 [&]( const NOTIFICATION& noti )
299 {
300 return noti.key == aKey;
301 } );
302
303 if( it != m_notifications.end() )
304 {
305 NOTIFICATION& noti = *it;
306
307 noti.title = aTitle;
308 noti.description = aDescription;
309 noti.href = aHref;
310 }
311 else
312 {
313 m_notifications.emplace_back( NOTIFICATION{ aTitle, aDescription, aHref,
314 aKey, wxEmptyString } );
315 }
316
317 if( m_shownDialogs.size() > 0 )
318 {
319 // update dialogs
321 {
322 list->Add( &m_notifications.back() );
323 }
324 }
325
326 for( KISTATUSBAR* statusBar : m_statusBars )
327 {
328 statusBar->SetNotificationCount( m_notifications.size() );
329 }
330
331 Save();
332}
333
334
335void NOTIFICATIONS_MANAGER::Remove( const wxString& aKey )
336{
337 auto it = std::find_if( m_notifications.begin(), m_notifications.end(),
338 [&]( const NOTIFICATION& noti )
339 {
340 return noti.key == aKey;
341 } );
342
343 if( it == m_notifications.end() )
344 {
345 return;
346 }
347
348 if( m_shownDialogs.size() > 0 )
349 {
350 // update dialogs
351
353 {
354 list->Remove( &(*it) );
355 }
356 }
357
358 m_notifications.erase( it );
359
360 Save();
361
362 for( KISTATUSBAR* statusBar : m_statusBars )
363 {
364 statusBar->SetNotificationCount( m_notifications.size() );
365 }
366}
367
368
370{
371 NOTIFICATIONS_LIST* evtWindow = dynamic_cast<NOTIFICATIONS_LIST*>( aEvent.GetEventObject() );
372
373 m_shownDialogs.erase( std::remove_if( m_shownDialogs.begin(), m_shownDialogs.end(),
374 [&]( NOTIFICATIONS_LIST* dialog )
375 {
376 return dialog == evtWindow;
377 } ) );
378
379 aEvent.Skip();
380}
381
382
383void NOTIFICATIONS_MANAGER::ShowList( wxWindow* aParent, wxPoint aPos )
384{
385 NOTIFICATIONS_LIST* list = new NOTIFICATIONS_LIST( this, aParent, aPos );
386
387 for( NOTIFICATION& job : m_notifications )
388 {
389 list->Add( &job );
390 }
391
392 m_shownDialogs.push_back( list );
393
394 list->Bind( wxEVT_CLOSE_WINDOW, &NOTIFICATIONS_MANAGER::onListWindowClosed, this );
395
396 // correct the position
397 wxSize windowSize = list->GetSize();
398 list->SetPosition( aPos - windowSize );
399
400 list->Show();
401}
402
403
405{
406 m_statusBars.push_back( aStatusBar );
407
408 // notifications should already be loaded so set the initial notification count
409 aStatusBar->SetNotificationCount( m_notifications.size() );
410}
411
412
414{
415 m_statusBars.erase( std::remove_if( m_statusBars.begin(), m_statusBars.end(),
416 [&]( KISTATUSBAR* statusBar )
417 {
418 return statusBar == aStatusBar;
419 } ) );
420}
KISTATUSBAR is a wxStatusBar suitable for Kicad manager.
Definition: kistatusbar.h:45
void SetNotificationCount(int aCount)
Sets the notification count on the notifications button A value of 0 will hide the count.
void onFocusLoss(wxFocusEvent &aEvent)
void Add(NOTIFICATION *aNoti)
NOTIFICATIONS_MANAGER * m_manager
Text to be displayed when no notifications are present, this gets a Show/Hide call as needed.
std::unordered_map< NOTIFICATION *, NOTIFICATION_PANEL * > m_panelMap
void Remove(NOTIFICATION *aNoti)
wxScrolledWindow * m_scrolledWindow
Inner content of the scrolled window, add panels here.
wxStaticText * m_noNotificationsText
NOTIFICATIONS_LIST(NOTIFICATIONS_MANAGER *aManager, wxWindow *parent, const wxPoint &pos)
void Save()
Saves notifications to disk.
void onListWindowClosed(wxCloseEvent &aEvent)
Handles removing the shown list window from our list of shown windows.
void Load()
Loads notifications stored from disk.
void RegisterStatusBar(KISTATUSBAR *aStatusBar)
Add a status bar for handling.
void CreateOrUpdate(const wxString &aKey, const wxString &aTitle, const wxString &aDescription, const wxString &aHref=wxEmptyString)
Creates a notification with the given parameters or updates an existing one with the same key.
void Remove(const wxString &aKey)
Remove a notification by key.
std::vector< KISTATUSBAR * > m_statusBars
The cached file path to read/write notifications on disk.
void UnregisterStatusBar(KISTATUSBAR *aStatusBar)
Removes status bar from handling.
std::vector< NOTIFICATION > m_notifications
Currently shown notification lists.
void ShowList(wxWindow *aParent, wxPoint aPos)
Shows the notification list.
std::vector< NOTIFICATIONS_LIST * > m_shownDialogs
Status bars registered for updates.
wxHyperlinkCtrl * m_hlDetails
void onDismiss(wxHyperlinkEvent &aEvent)
NOTIFICATION_PANEL(wxWindow *aParent, NOTIFICATIONS_MANAGER *aManager, NOTIFICATION *aNoti)
void onDetails(wxHyperlinkEvent &aEvent)
NOTIFICATIONS_MANAGER * m_manager
wxHyperlinkCtrl * m_hlDismiss
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
Definition: paths.cpp:344
#define _(s)
wxString description
Additional message displayed under title.
wxString title
Title of the notification.
wxString href
URL if any to link to for details.
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UPDATE_REQUEST, platform, arch, current_version, lang, last_check) struct UPDATE_RESPONSE