1 #ifndef slic3r_GUI_NotificationManager_hpp_
2 #define slic3r_GUI_NotificationManager_hpp_
3 
4 #include "Event.hpp"
5 #include "I18N.hpp"
6 
7 #include <libslic3r/ObjectID.hpp>
8 #include <libslic3r/Technologies.hpp>
9 
10 #include <wx/time.h>
11 
12 #include <string>
13 #include <vector>
14 #include <deque>
15 #include <unordered_set>
16 
17 namespace Slic3r {
18 namespace GUI {
19 
20 using EjectDriveNotificationClickedEvent = SimpleEvent;
21 wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
22 using ExportGcodeNotificationClickedEvent = SimpleEvent;
23 wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
24 using PresetUpdateAvailableClickedEvent = SimpleEvent;
25 wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent);
26 
27 class GLCanvas3D;
28 class ImGuiWrapper;
29 
30 enum class NotificationType
31 {
32 	CustomNotification,
33 	// Notification on end of slicing and G-code processing (the full G-code preview is available).
34 	// Contains a hyperlink to export the G-code to a removable media.
35 	SlicingComplete,
36 //	SlicingNotPossible,
37 	// Notification on end of export to a removable media, with hyperling to eject the external media.
38 	// Obsolete by ExportFinished
39 //	ExportToRemovableFinished,
40 	// Notification on end of export, with hyperling to see folder and eject if export was to external media.
41 	// Own subclass.
42 	ExportFinished,
43 	// Works on OSX only.
44 	//FIXME Do we want to have it on Linux and Windows? Is it possible to get the Disconnect event on Windows?
45 	Mouse3dDisconnected,
46 //	Mouse3dConnected,
47 //	NewPresetsAviable,
48 	// Notification on the start of PrusaSlicer, when a new PrusaSlicer version is published.
49 	// Contains a hyperlink to open a web browser pointing to the PrusaSlicer download location.
50 	NewAppAvailable,
51 	// Notification on the start of PrusaSlicer, when updates of system profiles are detected.
52 	// Contains a hyperlink to execute installation of the new system profiles.
53 	PresetUpdateAvailable,
54 //	LoadingFailed,
55 	// Not used - instead Slicing error is used for both slicing and validate errors.
56 //	ValidateError,
57 	// Slicing error produced by BackgroundSlicingProcess::validate() or by the BackgroundSlicingProcess background
58 	// thread thowing a SlicingError exception.
59 	SlicingError,
60 	// Slicing warnings, issued by the slicing process.
61 	// Slicing warnings are registered for a particular Print milestone or a PrintObject and its milestone.
62 	SlicingWarning,
63 	// Object partially outside the print volume. Cannot slice.
64 	PlaterError,
65 	// Object fully outside the print volume, or extrusion outside the print volume. Slicing is not disabled.
66 	PlaterWarning,
67 	// Progress bar instead of text.
68 	ProgressBar,
69 	// Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider.
70     EmptyColorChangeCode,
71     // Notification that custom supports/seams were deleted after mesh repair.
72     CustomSupportsAndSeamRemovedAfterRepair
73 };
74 
75 class NotificationManager
76 {
77 public:
78 	enum class NotificationLevel : int
79 	{
80 		// The notifications will be presented in the order of importance, thus these enum values
81 		// are sorted by the importance.
82 		// "Good to know" notification, usually but not always with a quick fade-out.
83 		RegularNotification = 1,
84 		// Information notification without a fade-out or with a longer fade-out.
85 		ImportantNotification,
86 		// Important notification with progress bar, no fade-out, might appear again after closing.
87 		ProgressBarNotification,
88 		// Warning, no fade-out.
89 		WarningNotification,
90 		// Error, no fade-out.
91 		ErrorNotification,
92 	};
93 
94 	NotificationManager(wxEvtHandler* evt_handler);
95 
96 	// Push a prefabricated notification from basic_notifications (see the table at the end of this file).
97 	void push_notification(const NotificationType type, int timestamp = 0);
98 	// Push a NotificationType::CustomNotification with NotificationLevel::RegularNotification and 10s fade out interval.
99 	void push_notification(const std::string& text, int timestamp = 0);
100 	// Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotification.
101 	// ErrorNotification and ImportantNotification are never faded out.
102     void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "",
103                            std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>(), int timestamp = 0);
104 	// Creates Slicing Error notification with a custom text and no fade out.
105 	void push_slicing_error_notification(const std::string& text);
106 	// Creates Slicing Warning notification with a custom text and no fade out.
107 	void push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step);
108 	// marks slicing errors as gray
109 	void set_all_slicing_errors_gray(bool g);
110 	// marks slicing warings as gray
111 	void set_all_slicing_warnings_gray(bool g);
112 //	void set_slicing_warning_gray(const std::string& text, bool g);
113 	// immediately stops showing slicing errors
114 	void close_slicing_errors_and_warnings();
115 	// Release those slicing warnings, which refer to an ObjectID, which is not in the list.
116 	// living_oids is expected to be sorted.
117 	void remove_slicing_warnings_of_released_objects(const std::vector<ObjectID>& living_oids);
118 	// Object partially outside of the printer working space, cannot print. No fade out.
119 	void push_plater_error_notification(const std::string& text);
120 	// Object fully out of the printer working space and such. No fade out.
121 	void push_plater_warning_notification(const std::string& text);
122 	// Closes error or warning of the same text
123 	void close_plater_error_notification(const std::string& text);
124 	void close_plater_warning_notification(const std::string& text);
125 	// Creates special notification slicing complete.
126 	// If large = true (Plater side bar is closed), then printing time and export button is shown
127 	// at the notification and fade-out is disabled. Otherwise the fade out time is set to 10s.
128 	void push_slicing_complete_notification(int timestamp, bool large);
129 	// Add a print time estimate to an existing SlicingComplete notification.
130 	void set_slicing_complete_print_time(const std::string &info);
131 	// Called when the side bar changes its visibility, as the "slicing complete" notification supplements
132 	// the "slicing info" normally shown at the side bar.
133 	void set_slicing_complete_large(bool large);
134 	// Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button
135 	void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
136 	// notification with progress bar
137 	void push_progress_bar_notification(const std::string& text, float percentage = 0);
138 	void set_progress_bar_percentage(const std::string& text, float percentage);
139 	// Close old notification ExportFinished.
140 	void new_export_began(bool on_removable);
141 	// finds ExportFinished notification and closes it if it was to removable device
142 	void device_ejected();
143 	// renders notifications in queue and deletes expired ones
144 	void render_notifications(float overlay_width);
145 	// finds and closes all notifications of given type
146 	void close_notification_of_type(const NotificationType type);
147 	// Which view is active? Plater or G-code preview? Hide warnings in G-code preview.
148     void set_in_preview(bool preview);
149 	// Move to left to avoid colision with variable layer height gizmo.
set_move_from_overlay(bool move)150 	void set_move_from_overlay(bool move) { m_move_from_overlay = move; }
151 /*
152 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
153 
154 	bool requires_update() const { return m_requires_update; }
155 	bool requires_render() const { return m_requires_render; }
156 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
157 */
158 private:
159 	// duration 0 means not disapearing
160 	struct NotificationData {
161 		NotificationType         type;
162 		NotificationLevel        level;
163 		// Fade out time
164 		const int                duration;
165 		const std::string        text1;
166 		const std::string        hypertext;
167 		// Callback for hypertext - returns true if notification should close after triggering
168 		// Usually sends event to UI thread thru wxEvtHandler.
169 		// Examples in basic_notifications.
170         std::function<bool(wxEvtHandler*)> callback;
171 		const std::string        text2;
172 	};
173 
174 	// Cache of IDs to identify and reuse ImGUI windows.
175 	class NotificationIDProvider
176 	{
177 	public:
178 		int 		allocate_id();
179 		void 		release_id(int id);
180 
181 	private:
182 		// Next ID used for naming the ImGUI windows.
183 		int       			m_next_id{ 1 };
184 		// IDs of ImGUI windows, which were released and they are ready for reuse.
185 		std::vector<int>	m_released_ids;
186 	};
187 
188 	//Pop notification - shows only once to user.
189 	class PopNotification
190 	{
191 	public:
192 
193 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
194 		enum class EState
195 		{
196 			Unknown,
197 			Hidden,
198 			FadingOutRender,  // Requesting Render
199 			FadingOutStatic,
200 			ClosePending,     // Requesting Render
201 			Finished,         // Requesting Render
202 		};
203 #else
204 		enum class RenderResult
205 		{
206 			Finished,
207 			ClosePending,
208 			Static,
209 			Countdown,
210 			Hovered
211 		};
212 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
213 
214 		PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler);
~PopNotification()215 		virtual ~PopNotification() { if (m_id) m_id_provider.release_id(m_id); }
216 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
217 		void                   render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width);
218 #else
219 		RenderResult           render(GLCanvas3D& canvas, const float& initial_y, bool move_from_overlay, float overlay_width);
220 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
221 		// close will dissapear notification on next render
close()222 		void                   close() { m_close_pending = true; }
223 		// data from newer notification of same type
224 		void                   update(const NotificationData& n);
is_finished() const225 		bool                   is_finished() const { return m_finished || m_close_pending; }
226 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
is_hovered() const227 		bool                   is_hovered() const { return m_hovered; }
228 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
229 		// returns top after movement
get_top() const230 		float                  get_top() const { return m_top_y; }
231 		//returns top in actual frame
get_current_top() const232 		float                  get_current_top() const { return m_top_y; }
get_type() const233 		const NotificationType get_type() const { return m_data.type; }
get_data() const234 		const NotificationData& get_data() const { return m_data; }
is_gray() const235 		const bool             is_gray() const { return m_is_gray; }
236 		// Call equals one second down
substract_remaining_time(int seconds)237 		void                   substract_remaining_time(int seconds) { m_remaining_time -= seconds; }
set_gray(bool g)238 		void                   set_gray(bool g) { m_is_gray = g; }
set_paused(bool p)239 		void                   set_paused(bool p) { m_paused = p; }
240 		bool                   compare_text(const std::string& text);
hide(bool h)241         void                   hide(bool h) { m_hidden = h; }
242 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
243 		// sets m_next_render with time of next mandatory rendering
244 		void                   update_state();
next_render() const245 		int64_t 		       next_render() const { return m_next_render; }
246 		/*
247 		bool				   requires_render() const { return m_state == EState::FadingOutRender || m_state == EState::ClosePending || m_state == EState::Finished; }
248 		bool				   requires_update() const { return m_state != EState::Hidden; }
249 		*/
get_state() const250 		EState                 get_state() const { return m_state; }
251 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
252 	protected:
253 		// Call after every size change
254 		void         init();
255 		// Part of init()
256 		virtual void count_spaces();
257 		// Calculetes correct size but not se it in imgui!
258 		virtual void set_next_window_size(ImGuiWrapper& imgui);
259 		virtual void render_text(ImGuiWrapper& imgui,
260 			                     const float win_size_x, const float win_size_y,
261 			                     const float win_pos_x , const float win_pos_y);
262 		virtual void render_close_button(ImGuiWrapper& imgui,
263 			                             const float win_size_x, const float win_size_y,
264 			                             const float win_pos_x , const float win_pos_y);
265 #if !ENABLE_NEW_NOTIFICATIONS_FADE_OUT
266 		void         render_countdown(ImGuiWrapper& imgui,
267 			                          const float win_size_x, const float win_size_y,
268 			                          const float win_pos_x , const float win_pos_y);
269 #endif // !ENABLE_NEW_NOTIFICATIONS_FADE_OUT
270 		virtual void render_hypertext(ImGuiWrapper& imgui,
271 			                          const float text_x, const float text_y,
272 		                              const std::string text,
273 		                              bool more = false);
274 		// Left sign could be error or warning sign
275 		void         render_left_sign(ImGuiWrapper& imgui);
276 		virtual void render_minimize_button(ImGuiWrapper& imgui,
277 			                                const float win_pos_x, const float win_pos_y);
278 		// Hypertext action, returns true if notification should close.
279 		// Action is stored in NotificationData::callback as std::function<bool(wxEvtHandler*)>
280 		virtual bool on_text_click();
281 
282 		const NotificationData m_data;
283 
284 		// For reusing ImGUI windows.
285 		NotificationIDProvider &m_id_provider;
286 
287 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
288 		EState           m_state                { EState::Unknown };
289 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
290 
291 		int              m_id                   { 0 };
292 		bool			 m_initialized          { false };
293 		// Main text
294 		std::string      m_text1;
295 		// Clickable text
296 		std::string      m_hypertext;
297 		// Aditional text after hypertext - currently not used
298 		std::string      m_text2;
299 		// Countdown variables
300 		long             m_remaining_time;
301 		bool             m_counting_down;
302 		long             m_last_remaining_time;
303 		bool             m_paused               { false };
304 		int              m_countdown_frame      { 0 };
305 		bool             m_fading_out           { false };
306 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
307 		int64_t		 	 m_fading_start         { 0LL };
308 		// time of last done render when fading
309 		int64_t		 	 m_last_render_fading   { 0LL };
310 		// first appereance of notification or last hover;
311 		int64_t		 	 m_notification_start;
312 		// time to next must-do render
313 		int64_t          m_next_render          { std::numeric_limits<int64_t>::max() };
314 #else
315 		// total time left when fading beggins
316 		float            m_fading_time{ 0.0f };
317 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
318 		float            m_current_fade_opacity { 1.0f };
319 		// If hidden the notif is alive but not visible to user
320 		bool             m_hidden               { false };
321 		//  m_finished = true - does not render, marked to delete
322 		bool             m_finished             { false };
323 		// Will go to m_finished next render
324 		bool             m_close_pending        { false };
325 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
326 		bool             m_hovered              { false };
327 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
328 		// variables to count positions correctly
329 		// all space without text
330 		float            m_window_width_offset;
331 		// Space on left side without text
332 		float            m_left_indentation;
333 		// Total size of notification window - varies based on monitor
334 		float            m_window_height        { 56.0f };
335 		float            m_window_width         { 450.0f };
336 		//Distance from bottom of notifications to top of this notification
337 		float            m_top_y                { 0.0f };
338 
339 		// Height of text
340 		// Used as basic scaling unit!
341 		float            m_line_height;
342 		std::vector<int> m_endlines;
343 		// Gray are f.e. eorrors when its uknown if they are still valid
344 		bool             m_is_gray              { false };
345 		//if multiline = true, notification is showing all lines(>2)
346 		bool             m_multiline            { false };
347 		// True if minimized button is rendered, helps to decide where is area for invisible close button
348 		bool             m_minimize_b_visible   { false };
349 		int              m_lines_count{ 1 };
350 	    // Target for wxWidgets events sent by clicking on the hyperlink available at some notifications.
351 		wxEvtHandler*    m_evt_handler;
352 	};
353 
354 	class SlicingCompleteLargeNotification : public PopNotification
355 	{
356 	public:
357 		SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool largeds);
358 		void set_large(bool l);
get_large()359 		bool get_large() { return m_is_large; }
360 
361 		void set_print_info(const std::string &info);
362 	protected:
363 		virtual void render_text(ImGuiWrapper& imgui,
364 			                     const float win_size_x, const float win_size_y,
365 			                     const float win_pos_x, const float win_pos_y)
366 			                     override;
367 		bool        m_is_large;
368 		bool        m_has_print_info { false };
369 		std::string m_print_info { std::string() };
370 	};
371 
372 	class SlicingWarningNotification : public PopNotification
373 	{
374 	public:
SlicingWarningNotification(const NotificationData & n,NotificationIDProvider & id_provider,wxEvtHandler * evt_handler)375 		SlicingWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {}
376 		ObjectID 	object_id;
377 		int    		warning_step;
378 	};
379 
380 	class ProgressBarNotification : public PopNotification
381 	{
382 	public:
ProgressBarNotification(const NotificationData & n,NotificationIDProvider & id_provider,wxEvtHandler * evt_handler,float percentage)383 		ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); }
set_percentage(float percent)384 		void set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) m_progress_complete = true; else m_progress_complete = false; }
385 	protected:
386 		void init();
387 		void render_text(ImGuiWrapper& imgui,
388 			const float win_size_x, const float win_size_y,
389 			const float win_pos_x, const float win_pos_y) override;
390 		void         render_bar(ImGuiWrapper& imgui,
391 			const float win_size_x, const float win_size_y,
392 			const float win_pos_x, const float win_pos_y);
393 		bool m_progress_complete{ false };
394 		float m_percentage;
395 	};
396 
397 	class ExportFinishedNotification : public PopNotification
398 	{
399 	public:
ExportFinishedNotification(const NotificationData & n,NotificationIDProvider & id_provider,wxEvtHandler * evt_handler,bool to_removable,const std::string & export_path,const std::string & export_dir_path)400 		ExportFinishedNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool to_removable,const std::string& export_path,const std::string& export_dir_path)
401 			: PopNotification(n, id_provider, evt_handler)
402 			, m_to_removable(to_removable)
403 			, m_export_path(export_path)
404 			, m_export_dir_path(export_dir_path)
405 		    {
406 				m_multiline = true;
407 			}
408 		bool        m_to_removable;
409 		std::string m_export_path;
410 		std::string m_export_dir_path;
411 	protected:
412 		// Reserves space on right for more buttons
413 		void count_spaces() override;
414 		void render_text(ImGuiWrapper& imgui,
415 						 const float win_size_x, const float win_size_y,
416 						 const float win_pos_x, const float win_pos_y) override;
417 		// Renders also button to open directory with exported path and eject removable media
418 		void render_close_button(ImGuiWrapper& imgui,
419 								 const float win_size_x, const float win_size_y,
420 								 const float win_pos_x, const float win_pos_y) override;
421 		void         render_eject_button(ImGuiWrapper& imgui,
422 			                             const float win_size_x, const float win_size_y,
423 			                             const float win_pos_x, const float win_pos_y);
render_minimize_button(ImGuiWrapper & imgui,const float win_pos_x,const float win_pos_y)424 		void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override
425 			{ m_minimize_b_visible = false; }
426 		bool on_text_click() override;
427 		// local time of last hover for showing tooltip
428 		long      m_hover_time { 0 };
429 	};
430 
431 	//pushes notification into the queue of notifications that are rendered
432 	//can be used to create custom notification
433 	bool push_notification_data(const NotificationData& notification_data, int timestamp);
434 	bool push_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, int timestamp);
435 	//finds older notification of same type and moves it to the end of queue. returns true if found
436 	bool activate_existing(const NotificationManager::PopNotification* notification);
437 	// Put the more important notifications to the bottom of the list.
438 	void sort_notifications();
439 	// If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed.
440     bool has_slicing_error_notification();
441 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
442 	// perform update_state on each notification and ask for more frames if needed
443 	void update_notifications();
444 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
445 
446 	// Target for wxWidgets events sent by clicking on the hyperlink available at some notifications.
447 	wxEvtHandler*                m_evt_handler;
448 	// Cache of IDs to identify and reuse ImGUI windows.
449 	NotificationIDProvider 		 m_id_provider;
450 	std::deque<std::unique_ptr<PopNotification>> m_pop_notifications;
451 	// Last render time in seconds for fade out control.
452 	long                         m_last_time { 0 };
453 	// When mouse hovers over some notification, the fade-out of all notifications is suppressed.
454 	bool                         m_hovered { false };
455 	//timestamps used for slicing finished - notification could be gone so it needs to be stored here
456 	std::unordered_set<int>      m_used_timestamps;
457 	// True if G-code preview is active. False if the Plater is active.
458 	bool                         m_in_preview { false };
459 	// True if the layer editing is enabled in Plater, so that the notifications are shifted left of it.
460 	bool                         m_move_from_overlay { false };
461 /*
462 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
463 	bool						 m_requires_update{ false };
464 	bool						 m_requires_render{ false };
465 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
466 */
467 	//prepared (basic) notifications
468     static const NotificationData basic_notifications[];
469 };
470 
471 }//namespace GUI
472 }//namespace Slic3r
473 
474 #endif //slic3r_GUI_NotificationManager_hpp_
475