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 m_scrolledWindow->Bind( wxEVT_KILL_FOCUS, &NOTIFICATIONS_LIST::onFocusLoss, this );
182
183 SetSizer( bSizer1 );
184 Layout();
185
186 SetFocus();
187 }
188
189
190 void onFocusLoss( wxFocusEvent& aEvent )
191 {
192 // check if a child like say, the hyperlink texts got focus
193 if( !IsDescendant( aEvent.GetWindow() ) )
194 Close( true );
195
196 aEvent.Skip();
197 }
198
199
200 void Add( NOTIFICATION* aNoti )
201 {
202 m_noNotificationsText->Hide();
203
205 m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 );
206 m_scrolledWindow->Layout();
208
209 // call this at this window otherwise the child panels dont resize width properly
210 Layout();
211
212 m_panelMap[aNoti] = panel;
213 }
214
215
216 void Remove( NOTIFICATION* aNoti )
217 {
218 auto it = m_panelMap.find( aNoti );
219 if( it != m_panelMap.end() )
220 {
221 NOTIFICATION_PANEL* panel = m_panelMap[aNoti];
222 m_contentSizer->Detach( panel );
223 panel->Destroy();
224
225 m_panelMap.erase( it );
226
227 // ensure the window contents get shifted as needed
228 m_scrolledWindow->Layout();
229 Layout();
230 }
231
232 if( m_panelMap.size() == 0 )
233 {
234 m_noNotificationsText->Show();
235 }
236 }
237
238private:
239 wxScrolledWindow* m_scrolledWindow;
241 wxBoxSizer* m_contentSizer;
242 std::unordered_map<NOTIFICATION*, NOTIFICATION_PANEL*> m_panelMap;
246};
247
248
250{
251 m_destFileName = wxFileName( PATHS::GetUserCachePath(), wxT( "notifications.json" ) );
252}
253
254
256{
257 nlohmann::json saved_json;
258
259 std::ifstream saved_json_stream( m_destFileName.GetFullPath().fn_str() );
260
261 try
262 {
263 saved_json_stream >> saved_json;
264
265 m_notifications = saved_json.get<std::vector<NOTIFICATION>>();
266 }
267 catch( std::exception& )
268 {
269 // failed to load the json, which is fine, default to no notificaitons
270 }
271
272 if( wxGetEnv( wxT( "KICAD_TEST_NOTI" ), nullptr ) )
273 {
274 CreateOrUpdate( wxS( "test" ), wxS( "Test Notification" ), wxS( "Test please ignore" ),
275 wxS( "https://kicad.org" ) );
276 }
277}
278
279
281{
282 std::ofstream jsonFileStream( m_destFileName.GetFullPath().fn_str() );
283
284 nlohmann::json saveJson = nlohmann::json( m_notifications );
285 jsonFileStream << std::setw( 4 ) << saveJson << std::endl;
286 jsonFileStream.flush();
287 jsonFileStream.close();
288}
289
290
291void NOTIFICATIONS_MANAGER::CreateOrUpdate( const wxString& aKey,
292 const wxString& aTitle,
293 const wxString& aDescription,
294 const wxString& aHref )
295{
296 wxCHECK_RET( !aKey.IsEmpty(), wxS( "Notification key must not be empty" ) );
297
298 auto it = std::find_if( m_notifications.begin(), m_notifications.end(),
299 [&]( const NOTIFICATION& noti )
300 {
301 return noti.key == aKey;
302 } );
303
304 if( it != m_notifications.end() )
305 {
306 NOTIFICATION& noti = *it;
307
308 noti.title = aTitle;
309 noti.description = aDescription;
310 noti.href = aHref;
311 }
312 else
313 {
314 m_notifications.emplace_back( NOTIFICATION{ aTitle, aDescription, aHref,
315 aKey, wxEmptyString } );
316 }
317
318 if( m_shownDialogs.size() > 0 )
319 {
320 // update dialogs
322 {
323 list->Add( &m_notifications.back() );
324 }
325 }
326
327 for( KISTATUSBAR* statusBar : m_statusBars )
328 {
329 statusBar->SetNotificationCount( m_notifications.size() );
330 }
331
332 Save();
333}
334
335
336void NOTIFICATIONS_MANAGER::Remove( const wxString& aKey )
337{
338 auto it = std::find_if( m_notifications.begin(), m_notifications.end(),
339 [&]( const NOTIFICATION& noti )
340 {
341 return noti.key == aKey;
342 } );
343
344 if( it == m_notifications.end() )
345 {
346 return;
347 }
348
349 if( m_shownDialogs.size() > 0 )
350 {
351 // update dialogs
352
354 {
355 list->Remove( &(*it) );
356 }
357 }
358
359 m_notifications.erase( it );
360
361 Save();
362
363 for( KISTATUSBAR* statusBar : m_statusBars )
364 {
365 statusBar->SetNotificationCount( m_notifications.size() );
366 }
367}
368
369
371{
372 NOTIFICATIONS_LIST* evtWindow = dynamic_cast<NOTIFICATIONS_LIST*>( aEvent.GetEventObject() );
373
374 m_shownDialogs.erase( std::remove_if( m_shownDialogs.begin(), m_shownDialogs.end(),
375 [&]( NOTIFICATIONS_LIST* dialog )
376 {
377 return dialog == evtWindow;
378 } ) );
379
380 aEvent.Skip();
381}
382
383
384void NOTIFICATIONS_MANAGER::ShowList( wxWindow* aParent, wxPoint aPos )
385{
386 NOTIFICATIONS_LIST* list = new NOTIFICATIONS_LIST( this, aParent, aPos );
387
388 for( NOTIFICATION& job : m_notifications )
389 {
390 list->Add( &job );
391 }
392
393 m_shownDialogs.push_back( list );
394
395 list->Bind( wxEVT_CLOSE_WINDOW, &NOTIFICATIONS_MANAGER::onListWindowClosed, this );
396
397 // correct the position
398 wxSize windowSize = list->GetSize();
399 list->SetPosition( aPos - windowSize );
400
401 list->Show();
402}
403
404
406{
407 m_statusBars.push_back( aStatusBar );
408
409 // notifications should already be loaded so set the initial notification count
410 aStatusBar->SetNotificationCount( m_notifications.size() );
411}
412
413
415{
416 m_statusBars.erase( std::remove_if( m_statusBars.begin(), m_statusBars.end(),
417 [&]( KISTATUSBAR* statusBar )
418 {
419 return statusBar == aStatusBar;
420 } ) );
421}
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:365
#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