1 #include "NotificationManager.hpp"
2 
3 #include "GUI_App.hpp"
4 #include "GUI.hpp"
5 #include "Plater.hpp"
6 #include "GLCanvas3D.hpp"
7 #include "ImGuiWrapper.hpp"
8 
9 #include "wxExtensions.hpp"
10 #include "libslic3r/Config.hpp"
11 
12 #include <boost/algorithm/string.hpp>
13 #include <boost/log/trivial.hpp>
14 #include <boost/bind/placeholders.hpp>
15 
16 #include <iostream>
17 
18 #include <wx/glcanvas.h>
19 
20 static constexpr float GAP_WIDTH = 10.0f;
21 static constexpr float SPACE_RIGHT_PANEL = 10.0f;
22 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
23 static constexpr float FADING_OUT_DURATION = 2.0f;
24 // Time in Miliseconds after next render when fading out is requested
25 static constexpr int   FADING_OUT_TIMEOUT = 100;
26 // If timeout is changed to higher than 1 second, substract_time call should be revorked
27 //static constexpr int   MAX_TIMEOUT_MILISECONDS = 1000;
28 //static constexpr int   MAX_TIMEOUT_SECONDS = 1;
29 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
30 
31 namespace Slic3r {
32 namespace GUI {
33 
34 wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
35 wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
36 wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent);
37 
38 const NotificationManager::NotificationData NotificationManager::basic_notifications[] = {
39 	//		{NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10,  _u8L("Slicing is not possible.")},
40 	//		{NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0,  _u8L("Exporting finished."),  _u8L("Eject drive.") },
41 	{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10,  _u8L("3D Mouse disconnected.") },
42 	//		{NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5,  _u8L("3D Mouse connected.") },
43 	//		{NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New Presets are available."),  _u8L("See here.") },
__anon6ca042bc0102()44 	{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20,  _u8L("Configuration update is available."),  _u8L("See more."), [](wxEvtHandler* evnthndlr){
45 		 if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; }},
__anon6ca042bc0202()46 	{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20,  _u8L("New version is available."),  _u8L("See Releases page."), [](wxEvtHandler* evnthndlr){
47 				wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
48 	{NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10,
49 			_u8L("You have just added a G-code for color change, but its value is empty.\n"
50 		  "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
51 	//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20,  _u8L("New vesion of PrusaSlicer is available.",  _u8L("Download page.") },
52 	//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20,  _u8L("Loading of model has Failed") },
53 	//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10,  _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
54 };
55 
56 namespace {
add_default_font(float pixel_size)57 	ImFont* add_default_font(float pixel_size)
58 	{
59 		ImGuiIO& io = ImGui::GetIO();
60 		ImFontConfig config;
61 		config.SizePixels = pixel_size;
62 		config.OversampleH = config.OversampleV = 1;
63 		config.PixelSnapH = true;
64 		ImFont* font = io.Fonts->AddFontDefault(&config);
65 		return font;
66 	}
67 
push_style_color(ImGuiCol idx,const ImVec4 & col,bool fading_out,float current_fade_opacity)68 	inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
69 	{
70 		if (fading_out)
71 			ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity));
72 		else
73 			ImGui::PushStyleColor(idx, col);
74 	}
75 
open_folder(const std::string & path)76 	void open_folder(const std::string& path)
77 	{
78 		// Code taken from desktop_open_datadir_folder()
79 
80 		// Execute command to open a file explorer, platform dependent.
81 		// FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
82 
83 #ifdef _WIN32
84 		const wxString widepath = from_u8(path);
85 		const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr };
86 		::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
87 #elif __APPLE__
88 		const char* argv[] = { "open", path.data(), nullptr };
89 		::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
90 #else
91 		const char* argv[] = { "xdg-open", path.data(), nullptr };
92 
93 		// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
94 		// because they may mess up the environment expected by the file manager.
95 		// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
96 		if (wxGetEnv("APPIMAGE", nullptr)) {
97 			// We're running from AppImage
98 			wxEnvVariableHashMap env_vars;
99 			wxGetEnvMap(&env_vars);
100 
101 			env_vars.erase("APPIMAGE");
102 			env_vars.erase("APPDIR");
103 			env_vars.erase("LD_LIBRARY_PATH");
104 			env_vars.erase("LD_PRELOAD");
105 			env_vars.erase("UNION_PRELOAD");
106 
107 			wxExecuteEnv exec_env;
108 			exec_env.env = std::move(env_vars);
109 
110 			wxString owd;
111 			if (wxGetEnv("OWD", &owd)) {
112 				// This is the original work directory from which the AppImage image was run,
113 				// set it as CWD for the child process:
114 				exec_env.cwd = std::move(owd);
115 			}
116 
117 			::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
118 		}
119 		else {
120 			// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
121 			::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
122 		}
123 #endif
124 	}
125 }
126 
127 #if 1
128 // Reuse ImGUI Windows.
allocate_id()129 int NotificationManager::NotificationIDProvider::allocate_id()
130 {
131 	int id;
132 	if (m_released_ids.empty())
133 		id = ++m_next_id;
134 	else {
135 		id = m_released_ids.back();
136 		m_released_ids.pop_back();
137 	}
138 	return id;
139 }
release_id(int id)140 void NotificationManager::NotificationIDProvider::release_id(int id)
141 {
142 	m_released_ids.push_back(id);
143 }
144 #else
145 // Don't reuse ImGUI Windows, allocate a new ID every time.
allocate_id()146 int NotificationManager::NotificationIDProvider::allocate_id() { return ++ m_next_id; }
release_id(int)147 void NotificationManager::NotificationIDProvider::release_id(int) {}
148 #endif
149 
150 //------PopNotification--------
PopNotification(const NotificationData & n,NotificationIDProvider & id_provider,wxEvtHandler * evt_handler)151 NotificationManager::PopNotification::PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler) :
152 	  m_data                (n)
153 	, m_id_provider   		(id_provider)
154 	, m_remaining_time      (n.duration)
155 	, m_last_remaining_time (n.duration)
156 	, m_counting_down       (n.duration != 0)
157 	, m_text1               (n.text1)
158 	, m_hypertext           (n.hypertext)
159 	, m_text2               (n.text2)
160 	, m_evt_handler         (evt_handler)
161 	, m_notification_start  (GLCanvas3D::timestamp_now())
162 {
163 	//init();
164 }
165 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
render(GLCanvas3D & canvas,float initial_y,bool move_from_overlay,float overlay_width)166 void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width)
167 {
168 	if (!m_initialized) {
169 		init();
170 	}
171 
172 	if (m_hidden) {
173 		m_top_y = initial_y - GAP_WIDTH;
174 		return;
175 	}
176 
177 	if (m_fading_out)
178 		m_last_render_fading = GLCanvas3D::timestamp_now();
179 
180 	Size cnv_size = canvas.get_canvas_size();
181 	ImGuiWrapper& imgui = *wxGetApp().imgui();
182 	ImVec2 mouse_pos = ImGui::GetMousePos();
183 	float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0);
184 
185 	if (m_line_height != ImGui::CalcTextSize("A").y)
186 		init();
187 
188 	set_next_window_size(imgui);
189 
190 	// top y of window
191 	m_top_y = initial_y + m_window_height;
192 
193 	ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - right_gap, 1.0f * (float)cnv_size.get_height() - m_top_y);
194 	imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f);
195 	imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always);
196 
197 	// find if hovered
198 	m_hovered = false;
199 	if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) {
200 		ImGui::SetNextWindowFocus();
201 		m_hovered = true;
202 	}
203 
204 	// color change based on fading out
205 	bool fading_pop = false;
206 	if (m_fading_out) {
207 		push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
208 		push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
209 		fading_pop = true;
210 	}
211 
212 	// background color
213 	if (m_is_gray) {
214 		ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
215 		push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
216 	}
217 	else if (m_data.level == NotificationLevel::ErrorNotification) {
218 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
219 		backcolor.x += 0.3f;
220 		push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
221 	}
222 	else if (m_data.level == NotificationLevel::WarningNotification) {
223 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
224 		backcolor.x += 0.3f;
225 		backcolor.y += 0.15f;
226 		push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
227 	}
228 
229 	// name of window - probably indentifies window and is shown so last_end add whitespaces according to id
230 	if (m_id == 0)
231 		m_id = m_id_provider.allocate_id();
232 	std::string name = "!!Ntfctn" + std::to_string(m_id);
233 	if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar)) {
234 		ImVec2 win_size = ImGui::GetWindowSize();
235 
236 		//FIXME: dont forget to us this for texts
237 		//GUI::format(_utf8(L()));
238 
239 		/*
240 		//countdown numbers
241 		ImGui::SetCursorPosX(15);
242 		ImGui::SetCursorPosY(15);
243 		imgui.text(std::to_string(m_remaining_time).c_str());
244 		*/
245 
246 		render_left_sign(imgui);
247 		render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
248 		render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
249 		m_minimize_b_visible = false;
250 		if (m_multiline && m_lines_count > 3)
251 			render_minimize_button(imgui, win_pos.x, win_pos.y);
252 	}
253 	imgui.end();
254 
255 	if (m_is_gray || m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification)
256 		ImGui::PopStyleColor();
257 
258 	if (fading_pop)
259 		ImGui::PopStyleColor(2);
260 }
261 #else
render(GLCanvas3D & canvas,const float & initial_y,bool move_from_overlay,float overlay_width)262 NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y, bool move_from_overlay, float overlay_width)
263 {
264 	if (!m_initialized) {
265 		init();
266 	}
267 	if (m_finished)
268 		return RenderResult::Finished;
269 	if (m_close_pending) {
270 		// request of extra frame will be done in caller function by ret val ClosePending
271 		m_finished = true;
272 		return RenderResult::ClosePending;
273 	}
274 	if (m_hidden) {
275 		m_top_y = initial_y - GAP_WIDTH;
276 		return RenderResult::Static;
277 	}
278 	RenderResult    ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static;
279 	Size            cnv_size = canvas.get_canvas_size();
280 	ImGuiWrapper&   imgui = *wxGetApp().imgui();
281 	bool            shown = true;
282 	ImVec2          mouse_pos = ImGui::GetMousePos();
283 	float           right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0);
284 
285 	if (m_line_height != ImGui::CalcTextSize("A").y)
286 		init();
287 
288 	set_next_window_size(imgui);
289 
290 	//top y of window
291 	m_top_y = initial_y + m_window_height;
292 	//top right position
293 
294 	ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - right_gap, 1.0f * (float)cnv_size.get_height() - m_top_y);
295 	imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f);
296 	imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always);
297 
298 	//find if hovered
299 	if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height)
300 	{
301 		ImGui::SetNextWindowFocus();
302 		ret_val = RenderResult::Hovered;
303 		//reset fading
304 		m_fading_out = false;
305 		m_current_fade_opacity = 1.f;
306 		m_remaining_time = m_data.duration;
307 		m_countdown_frame = 0;
308 	}
309 
310 	if (m_counting_down && m_remaining_time < 0)
311 		m_close_pending = true;
312 
313 	if (m_close_pending) {
314 		// request of extra frame will be done in caller function by ret val ClosePending
315 		m_finished = true;
316 		return RenderResult::ClosePending;
317 	}
318 
319 	// color change based on fading out
320 	bool fading_pop = false;
321 	if (m_fading_out) {
322 		if (!m_paused)
323 			m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f);
324 		push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
325 		push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
326 		fading_pop = true;
327 	}
328 	// background color
329 	if (m_is_gray) {
330 		ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
331 		push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
332 	} else if (m_data.level == NotificationLevel::ErrorNotification) {
333 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
334 		backcolor.x += 0.3f;
335 		push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
336 	} else if (m_data.level == NotificationLevel::WarningNotification) {
337 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
338 		backcolor.x += 0.3f;
339 		backcolor.y += 0.15f;
340 		push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
341 	}
342 
343 	//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
344 	if (! m_id)
345 		m_id = m_id_provider.allocate_id();
346 	std::string name;
347 	{
348 		// Create a unique ImGUI window name. The name may be recycled using a name of an already released notification.
349 		char buf[32];
350 		sprintf(buf, "!!Ntfctn%d", m_id);
351 		name = buf;
352 	}
353 	if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) {
354 		if (shown) {
355 
356 			ImVec2 win_size = ImGui::GetWindowSize();
357 
358 
359 			//FIXME: dont forget to us this for texts
360 			//GUI::format(_utf8(L()));
361 
362 			/*
363 			//countdown numbers
364 			ImGui::SetCursorPosX(15);
365 			ImGui::SetCursorPosY(15);
366 			imgui.text(std::to_string(m_remaining_time).c_str());
367 			*/
368 			if(m_counting_down)
369 				render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
370 			render_left_sign(imgui);
371 			render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
372 			render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
373 			m_minimize_b_visible = false;
374 			if (m_multiline && m_lines_count > 3)
375 				render_minimize_button(imgui, win_pos.x, win_pos.y);
376 		} else {
377 			// the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button)
378 			m_close_pending = true;
379 			canvas.set_as_dirty();
380 		}
381 	}
382 	imgui.end();
383 
384 	if (fading_pop) {
385 		ImGui::PopStyleColor();
386 		ImGui::PopStyleColor();
387 	}
388 	if (m_is_gray)
389 		ImGui::PopStyleColor();
390 	else if (m_data.level == NotificationLevel::ErrorNotification)
391 		ImGui::PopStyleColor();
392 	else if (m_data.level == NotificationLevel::WarningNotification)
393 		ImGui::PopStyleColor();
394 	return ret_val;
395 }
396 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
count_spaces()397 void NotificationManager::PopNotification::count_spaces()
398 {
399 	//determine line width
400 	m_line_height = ImGui::CalcTextSize("A").y;
401 
402 	m_left_indentation = m_line_height;
403 	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
404 		std::string text;
405 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
406 		float picture_width = ImGui::CalcTextSize(text.c_str()).x;
407 		m_left_indentation = picture_width + m_line_height / 2;
408 	}
409 	m_window_width_offset = m_left_indentation + m_line_height * 3.f;
410 	m_window_width = m_line_height * 25;
411 }
init()412 void NotificationManager::PopNotification::init()
413 {
414 	std::string text          = m_text1;
415 	int         last_end      = 0;
416 	            m_lines_count = 0;
417 
418 	count_spaces();
419 
420 	// count lines
421 	m_endlines.clear();
422 	while (last_end < text.length() - 1)
423 	{
424 		int next_hard_end = text.find_first_of('\n', last_end);
425 		if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) {
426 			//next line is ended by '/n'
427 			m_endlines.push_back(next_hard_end);
428 			last_end = next_hard_end + 1;
429 		} else {
430 			// find next suitable endline
431 			if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) {
432 				// more than one line till end
433 				int next_space = text.find_first_of(' ', last_end);
434 				if (next_space > 0 && next_space < text.length()) {
435 					int next_space_candidate = text.find_first_of(' ', next_space + 1);
436 					while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
437 						next_space = next_space_candidate;
438 						next_space_candidate = text.find_first_of(' ', next_space + 1);
439 					}
440 				} else {
441 					next_space = text.length();
442 				}
443 				// when one word longer than line.
444 				if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset) {
445 					float width_of_a = ImGui::CalcTextSize("a").x;
446 					int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
447 					while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) {
448 						letter_count++;
449 					}
450 					m_endlines.push_back(last_end + letter_count);
451 					last_end += letter_count;
452 				} else {
453 					m_endlines.push_back(next_space);
454 					last_end = next_space + 1;
455 				}
456 			}
457 			else {
458 				m_endlines.push_back(text.length());
459 				last_end = text.length();
460 			}
461 
462 		}
463 		m_lines_count++;
464 	}
465 	// hypertext calculation
466 	if (!m_hypertext.empty()) {
467 		int prev_end = m_endlines.size() > 1 ? m_endlines[m_endlines.size() - 2] : 0; // m_endlines.size() - 2 because we are fitting hypertext instead of last endline
468 		if (ImGui::CalcTextSize((escape_string_cstyle(text.substr(prev_end, last_end - prev_end)) + m_hypertext).c_str()).x > m_window_width - m_window_width_offset) {
469 			m_endlines.push_back(last_end);
470 			m_lines_count++;
471 		}
472 	}
473 	if (m_lines_count == 3)
474 		m_multiline = true;
475 	m_initialized = true;
476 }
set_next_window_size(ImGuiWrapper & imgui)477 void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui)
478 {
479 	m_window_height = m_multiline ?
480 		m_lines_count * m_line_height :
481 		2 * m_line_height;
482 	m_window_height += 1 * m_line_height; // top and bottom
483 }
484 
render_text(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)485 void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
486 {
487 	ImVec2      win_size(win_size_x, win_size_y);
488 	ImVec2      win_pos(win_pos_x, win_pos_y);
489 	float       x_offset = m_left_indentation;
490 	std::string fulltext = m_text1 + m_hypertext; //+ m_text2;
491 	ImVec2      text_size = ImGui::CalcTextSize(fulltext.c_str());
492 	// text posistions are calculated by lines count
493 	// large texts has "more" button or are displayed whole
494 	// smaller texts are divided as one liners and two liners
495 	if (m_lines_count > 2) {
496 		if (m_multiline) {
497 
498 			int last_end = 0;
499 			float starting_y = m_line_height/2;//10;
500 			float shift_y = m_line_height;// -m_line_height / 20;
501 			std::string line;
502 			for (size_t i = 0; i < m_lines_count; i++) {
503 				line.clear();
504 				ImGui::SetCursorPosX(x_offset);
505 				ImGui::SetCursorPosY(starting_y + i * shift_y);
506 				if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) {
507 					line = m_text1.substr(last_end, m_endlines[i] - last_end);
508 					last_end = m_endlines[i];
509 					if (m_text1.size() > m_endlines[i])
510 						last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
511 					imgui.text(line.c_str());
512 				}
513 			}
514 			//hyperlink text
515 			if (!m_hypertext.empty()) {
516 				render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty() ? "" : " ")).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext);
517 			}
518 
519 
520 		} else {
521 			// line1
522 			if (m_text1.size() >= m_endlines[0]) {
523 				ImGui::SetCursorPosX(x_offset);
524 				ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
525 				imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
526 			}
527 			// line2
528 			std::string line;
529 			ImGui::SetCursorPosX(x_offset);
530 			ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2);
531 			if (m_text1.size() >= m_endlines[1]) {
532 				line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
533 				if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) {
534 					line = line.substr(0, line.length() - 6);
535 					line += "..";
536 				} else
537 					line += "  ";
538 				imgui.text(line.c_str());
539 			}
540 			// "More" hypertext
541 			render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true);
542 		}
543 	} else {
544 		//text 1
545 		float cursor_y = win_size.y / 2 - text_size.y / 2;
546 		float cursor_x = x_offset;
547 		if(m_lines_count > 1) {
548 			// line1
549 			if (m_text1.length() >= m_endlines[0]) { // could be equal than substr takes whole string
550 				ImGui::SetCursorPosX(x_offset);
551 				ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
552 				imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
553 			}
554 			// line2
555 			ImGui::SetCursorPosX(x_offset);
556 			cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2;
557 			ImGui::SetCursorPosY(cursor_y);
558 			if (m_text1.length() > m_endlines[0]) { // must be greater otherwise theres nothing to show and m_text1[m_endlines[0]] is beyond last letter
559 				std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
560 				imgui.text(line.c_str());
561 				cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x;
562 			}
563 		} else {
564 			ImGui::SetCursorPosX(x_offset);
565 			ImGui::SetCursorPosY(cursor_y);
566 			imgui.text(m_text1.c_str());
567 			cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x;
568 		}
569 		//hyperlink text
570 		if (!m_hypertext.empty()) {
571 			render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext);
572 		}
573 
574 		//notification text 2
575 		//text 2 is suposed to be after the hyperlink - currently it is not used
576 		/*
577 		if (!m_text2.empty())
578 		{
579 			ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str());
580 			ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset);
581 			ImGui::SetCursorPosY(cursor_y);
582 			imgui.text(m_text2.c_str());
583 		}
584 		*/
585 	}
586 }
587 
render_hypertext(ImGuiWrapper & imgui,const float text_x,const float text_y,const std::string text,bool more)588 void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more)
589 {
590 	//invisible button
591 	ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
592 	ImGui::SetCursorPosX(text_x -4);
593 	ImGui::SetCursorPosY(text_y -5);
594 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
595 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
596 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
597 	if (imgui.button("   ", part_size.x + 6, part_size.y + 10))
598 	{
599 		if (more)
600 		{
601 			m_multiline = true;
602 			set_next_window_size(imgui);
603 		}
604 		else {
605 			m_close_pending = on_text_click();
606 		}
607 	}
608 	ImGui::PopStyleColor();
609 	ImGui::PopStyleColor();
610 	ImGui::PopStyleColor();
611 
612 	//hover color
613 	ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);//ImGui::GetStyleColorVec4(ImGuiCol_Button);
614 	if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
615 		orange_color.y += 0.2f;
616 
617 	//text
618 	push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
619 	ImGui::SetCursorPosX(text_x);
620 	ImGui::SetCursorPosY(text_y);
621 	imgui.text(text.c_str());
622 	ImGui::PopStyleColor();
623 
624 	//underline
625 	ImVec2 lineEnd = ImGui::GetItemRectMax();
626 	lineEnd.y -= 2;
627 	ImVec2 lineStart = lineEnd;
628 	lineStart.x = ImGui::GetItemRectMin().x;
629 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))));
630 
631 }
632 
render_close_button(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)633 void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
634 {
635 	ImVec2 win_size(win_size_x, win_size_y);
636 	ImVec2 win_pos(win_pos_x, win_pos_y);
637 	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
638 	orange_color.w = 0.8f;
639 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
640 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
641 	push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
642 	push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
643 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
644 
645 
646 	//button - if part if treggered
647 	std::string button_text;
648 	button_text = ImGui::CloseNotifButton;
649 
650 	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
651 		                           ImVec2(win_pos.x, win_pos.y + win_size.y - ( m_minimize_b_visible ? 2 * m_line_height : 0)),
652 		                           true))
653 	{
654 		button_text = ImGui::CloseNotifHoverButton;
655 	}
656 	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
657 	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
658 	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
659 	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
660 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
661 	{
662 		m_close_pending = true;
663 	}
664 
665 	//invisible large button
666 	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
667 	ImGui::SetCursorPosY(0);
668 	if (imgui.button(" ", m_line_height * 2.125, win_size.y - ( m_minimize_b_visible ? 2 * m_line_height : 0)))
669 	{
670 		m_close_pending = true;
671 	}
672 	ImGui::PopStyleColor();
673 	ImGui::PopStyleColor();
674 	ImGui::PopStyleColor();
675 	ImGui::PopStyleColor();
676 	ImGui::PopStyleColor();
677 }
678 #if !ENABLE_NEW_NOTIFICATIONS_FADE_OUT
render_countdown(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)679 void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
680 {
681 	/*
682 	ImVec2 win_size(win_size_x, win_size_y);
683 	ImVec2 win_pos(win_pos_x, win_pos_y);
684 
685 	//countdown dots
686 	std::string dot_text;
687 	dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
688 	ImGui::SetCursorPosX(win_size.x - m_line_height);
689 	//ImGui::SetCursorPosY(win_size.y / 2 - 24);
690 	ImGui::SetCursorPosY(0);
691 	imgui.text(dot_text.c_str());
692 
693 	dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
694 	ImGui::SetCursorPosX(win_size.x - m_line_height);
695 	//ImGui::SetCursorPosY(win_size.y / 2 - 9);
696 	ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2);
697 	imgui.text(dot_text.c_str());
698 
699 	dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
700 	ImGui::SetCursorPosX(win_size.x - m_line_height);
701 	//ImGui::SetCursorPosY(win_size.y / 2 + 6);
702 	ImGui::SetCursorPosY(win_size.y - m_line_height);
703 	imgui.text(dot_text.c_str());
704 	*/
705 	if (!m_fading_out && m_remaining_time <= m_data.duration / 4) {
706 		m_fading_out = true;
707 		m_fading_time = m_remaining_time;
708 	}
709 
710 	if (m_last_remaining_time != m_remaining_time) {
711 		m_last_remaining_time = m_remaining_time;
712 		m_countdown_frame = 0;
713 	}
714 	/*
715 	//countdown line
716 	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
717 	float  invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x);
718 	invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame);
719 	ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5);
720 	ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5);
721 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f);
722 	if (!m_paused)
723 		m_countdown_frame++;
724 		*/
725 }
726 #endif // !ENABLE_NEW_NOTIFICATIONS_FADE_OUT
render_left_sign(ImGuiWrapper & imgui)727 void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
728 {
729 	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
730 		std::string text;
731 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
732 		ImGui::SetCursorPosX(m_line_height / 3);
733 		ImGui::SetCursorPosY(m_window_height / 2 - m_line_height);
734 		imgui.text(text.c_str());
735 	}
736 }
render_minimize_button(ImGuiWrapper & imgui,const float win_pos_x,const float win_pos_y)737 void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
738 {
739 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
740 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
741 	push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
742 	push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
743 	push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
744 
745 
746 	//button - if part if treggered
747 	std::string button_text;
748 	button_text = ImGui::MinimalizeButton;
749 	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
750 		ImVec2(win_pos_x, win_pos_y + m_window_height),
751 		true))
752 	{
753 		button_text = ImGui::MinimalizeHoverButton;
754 	}
755 	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
756 	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
757 	ImGui::SetCursorPosX(m_window_width - m_line_height * 1.8f);
758 	ImGui::SetCursorPosY(m_window_height - button_size.y - 5);
759 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
760 	{
761 		m_multiline = false;
762 	}
763 
764 	ImGui::PopStyleColor();
765 	ImGui::PopStyleColor();
766 	ImGui::PopStyleColor();
767 	ImGui::PopStyleColor();
768 	ImGui::PopStyleColor();
769 	m_minimize_b_visible = true;
770 }
on_text_click()771 bool NotificationManager::PopNotification::on_text_click()
772 {
773 	if(m_data.callback != nullptr)
774 		return m_data.callback(m_evt_handler);
775 	return false;
776 }
update(const NotificationData & n)777 void NotificationManager::PopNotification::update(const NotificationData& n)
778 {
779 	m_text1          = n.text1;
780 	m_hypertext      = n.hypertext;
781     m_text2          = n.text2;
782 	init();
783 }
compare_text(const std::string & text)784 bool NotificationManager::PopNotification::compare_text(const std::string& text)
785 {
786 	std::string t1(m_text1);
787 	std::string t2(text);
788 	t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end());
789 	t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end());
790 	if (t1.compare(t2) == 0)
791 		return true;
792 	return false;
793 }
794 
795 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
update_state()796 void NotificationManager::PopNotification::update_state()
797 {
798 	if (!m_initialized)
799 		init();
800 
801 	m_next_render = std::numeric_limits<int64_t>::max();
802 
803 	if (m_hidden) {
804 		m_state = EState::Hidden;
805 		return;
806 	}
807 
808 	int64_t now = GLCanvas3D::timestamp_now();
809 
810 	if (m_hovered) {
811 		// reset fading
812 		m_fading_out = false;
813 		m_current_fade_opacity = 1.0f;
814 		m_remaining_time = m_data.duration;
815 		m_notification_start = now;
816 	}
817 
818 
819 
820 	if (m_counting_down) {
821 		int64_t up_time = now - m_notification_start;
822 
823 		if (m_fading_out && m_current_fade_opacity <= 0.0f)
824 			m_finished = true;
825 		else if (!m_fading_out && /*m_remaining_time <=0*/up_time >= m_data.duration * 1000) {
826 			m_fading_out = true;
827 			m_fading_start = now;
828 			m_last_render_fading = now;
829 		} else if (!m_fading_out) {
830 			m_next_render = m_data.duration * 1000 - up_time;//std::min<int64_t>(/*m_data.duration * 1000 - up_time*/m_remaining_time * 1000, MAX_TIMEOUT_MILISECONDS);
831 		}
832 
833 	}
834 
835 	if (m_finished) {
836 		m_state = EState::Finished;
837 		m_next_render = 0;
838 		return;
839 	}
840 	if (m_close_pending) {
841 		m_finished = true;
842 		m_state = EState::ClosePending;
843 		m_next_render = 0;
844 		return;
845 	}
846 	if (m_fading_out) {
847 		if (!m_paused) {
848 			m_state = EState::FadingOutStatic;
849 			int64_t curr_time      = now - m_fading_start;
850 			int64_t no_render_time = now - m_last_render_fading;
851 			m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast<float>(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f);
852 			auto next_render = FADING_OUT_TIMEOUT - no_render_time;
853 			if (next_render <= 0) {
854 				//m_last_render_fading = GLCanvas3D::timestamp_now();
855 				m_state = EState::FadingOutRender;
856 				m_next_render = 0;
857 			} else
858 				m_next_render = next_render;
859 		}
860 	}
861 }
862 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
863 
SlicingCompleteLargeNotification(const NotificationData & n,NotificationIDProvider & id_provider,wxEvtHandler * evt_handler,bool large)864 NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) :
865 	  NotificationManager::PopNotification(n, id_provider, evt_handler)
866 {
867 	set_large(large);
868 }
render_text(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)869 void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
870 {
871 	if (!m_is_large)
872 		PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
873 	else {
874 		ImVec2 win_size(win_size_x, win_size_y);
875 		ImVec2 win_pos(win_pos_x, win_pos_y);
876 
877 		ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str());
878 		float x_offset = m_left_indentation;
879 		std::string fulltext = m_text1 + m_hypertext + m_text2;
880 		ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
881 		float cursor_y = win_size.y / 2 - text_size.y / 2;
882 		if (m_has_print_info) {
883 			x_offset = 20;
884 			cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2;
885 			ImGui::SetCursorPosX(x_offset);
886 			ImGui::SetCursorPosY(cursor_y);
887 			imgui.text(m_print_info.c_str());
888 			cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2;
889 		}
890 		ImGui::SetCursorPosX(x_offset);
891 		ImGui::SetCursorPosY(cursor_y);
892 		imgui.text(m_text1.c_str());
893 
894 		render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext);
895 	}
896 }
set_print_info(const std::string & info)897 void NotificationManager::SlicingCompleteLargeNotification::set_print_info(const std::string &info)
898 {
899 	m_print_info = info;
900 	m_has_print_info = true;
901 	if (m_is_large)
902 		m_lines_count = 2;
903 }
set_large(bool l)904 void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l)
905 {
906 	m_is_large = l;
907 	m_counting_down = !l;
908 	m_hypertext = l ? _u8L("Export G-Code.") : std::string();
909 	m_hidden = !l;
910 }
911 //---------------ExportFinishedNotification-----------
count_spaces()912 void NotificationManager::ExportFinishedNotification::count_spaces()
913 {
914 	//determine line width
915 	m_line_height = ImGui::CalcTextSize("A").y;
916 
917 	m_left_indentation = m_line_height;
918 	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
919 		std::string text;
920 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
921 		float picture_width = ImGui::CalcTextSize(text.c_str()).x;
922 		m_left_indentation = picture_width + m_line_height / 2;
923 	}
924 	//TODO count this properly
925 	m_window_width_offset = m_left_indentation + m_line_height * (m_to_removable ? 6.f : 3.f);
926 	m_window_width = m_line_height * 25;
927 }
928 
render_text(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)929 void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
930 {
931 
932 	ImVec2      win_size(win_size_x, win_size_y);
933 	ImVec2      win_pos(win_pos_x, win_pos_y);
934 	float       x_offset = m_left_indentation;
935 	std::string fulltext = m_text1 + m_hypertext; //+ m_text2;
936 	ImVec2      text_size = ImGui::CalcTextSize(fulltext.c_str());
937 	// Lines are always at least two and m_multiline is always true for ExportFinishedNotification.
938 	// First line has "Export Finished" text and than hyper text open folder.
939 	// Following lines are path to gcode.
940 	int last_end = 0;
941 	float starting_y = m_line_height / 2;//10;
942 	float shift_y = m_line_height;// -m_line_height / 20;
943 	for (size_t i = 0; i < m_lines_count; i++) {
944 		if (m_text1.size() >= m_endlines[i]) {
945 			std::string line = m_text1.substr(last_end, m_endlines[i] - last_end);
946 			last_end = m_endlines[i];
947 			if (m_text1.size() > m_endlines[i])
948 				last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
949 			ImGui::SetCursorPosX(x_offset);
950 			ImGui::SetCursorPosY(starting_y + i * shift_y);
951 			imgui.text(line.c_str());
952 			//hyperlink text
953 			if ( i == 0 )  {
954 				render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x + ImGui::CalcTextSize("   ").x, starting_y, _u8L("Open Folder."));
955 			}
956 		}
957 	}
958 
959 }
960 
render_close_button(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)961 void NotificationManager::ExportFinishedNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
962 {
963 	PopNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
964 	if(m_to_removable)
965 		render_eject_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
966 }
967 
render_eject_button(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)968 void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
969 {
970 	ImVec2 win_size(win_size_x, win_size_y);
971 	ImVec2 win_pos(win_pos_x, win_pos_y);
972 	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
973 	orange_color.w = 0.8f;
974 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
975 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
976 	push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
977 	push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
978 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
979 
980 	std::string button_text;
981 	button_text = ImGui::EjectButton;
982 
983     if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
984 		ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y),
985 		true))
986 	{
987 		button_text = ImGui::EjectHoverButton;
988 		// tooltip
989 		long time_now = wxGetLocalTime();
990 		if (m_hover_time > 0 && m_hover_time < time_now) {
991 			ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
992 			ImGui::BeginTooltip();
993 			imgui.text(_u8L("Eject drive") + " " + GUI::shortkey_ctrl_prefix() + "T");
994 			ImGui::EndTooltip();
995 			ImGui::PopStyleColor();
996 		}
997 		if (m_hover_time == 0)
998 			m_hover_time = time_now;
999 	} else
1000 		m_hover_time = 0;
1001 
1002 	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
1003 	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
1004 	ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
1005 	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
1006 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
1007 	{
1008 		assert(m_evt_handler != nullptr);
1009 		if (m_evt_handler != nullptr)
1010 			wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
1011 		m_close_pending = true;
1012 	}
1013 
1014 	//invisible large button
1015 	ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
1016 	ImGui::SetCursorPosY(0);
1017 	if (imgui.button("  ", m_line_height * 2.f, win_size.y))
1018 	{
1019 		assert(m_evt_handler != nullptr);
1020 		if (m_evt_handler != nullptr)
1021 			wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
1022 		m_close_pending = true;
1023 	}
1024 	ImGui::PopStyleColor();
1025 	ImGui::PopStyleColor();
1026 	ImGui::PopStyleColor();
1027 	ImGui::PopStyleColor();
1028 	ImGui::PopStyleColor();
1029 }
on_text_click()1030 bool NotificationManager::ExportFinishedNotification::on_text_click()
1031 {
1032 	open_folder(m_export_dir_path);
1033 	return false;
1034 }
1035 //------ProgressBar----------------
init()1036 void NotificationManager::ProgressBarNotification::init()
1037 {
1038 	PopNotification::init();
1039 	m_lines_count++;
1040 	m_endlines.push_back(m_endlines.back());
1041 }
render_text(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)1042 void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
1043 {
1044 	PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
1045 	render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
1046 }
render_bar(ImGuiWrapper & imgui,const float win_size_x,const float win_size_y,const float win_pos_x,const float win_pos_y)1047 void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
1048 {
1049 	float bar_y = win_size_y / 2 - win_size_y / 6 + m_line_height;
1050 	ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);
1051 	float  invisible_length = 0;//((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x);
1052 	//invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame);
1053 	ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length - m_window_width_offset, win_pos_y + win_size_y/2 + m_line_height / 2);
1054 	ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y/2 + m_line_height / 2);
1055 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f);
1056 	/*
1057 	//countdown line
1058 	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
1059 	float  invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x);
1060 	invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame);
1061 	ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5);
1062 	ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5);
1063 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f);
1064 	if (!m_paused)
1065 		m_countdown_frame++;
1066 		*/
1067 }
1068 //------NotificationManager--------
NotificationManager(wxEvtHandler * evt_handler)1069 NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
1070 	m_evt_handler(evt_handler)
1071 {
1072 }
push_notification(const NotificationType type,int timestamp)1073 void NotificationManager::push_notification(const NotificationType type, int timestamp)
1074 {
1075 	auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications),
1076 		boost::bind(&NotificationData::type, boost::placeholders::_1) == type);
1077 	assert(it != std::end(basic_notifications));
1078 	if (it != std::end(basic_notifications))
1079 		push_notification_data(*it, timestamp);
1080 }
push_notification(const std::string & text,int timestamp)1081 void NotificationManager::push_notification(const std::string& text, int timestamp)
1082 {
1083 	push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, timestamp);
1084 }
1085 
push_notification(NotificationType type,NotificationLevel level,const std::string & text,const std::string & hypertext,std::function<bool (wxEvtHandler *)> callback,int timestamp)1086 void NotificationManager::push_notification(NotificationType type,
1087                                             NotificationLevel level,
1088                                             const std::string& text,
1089                                             const std::string& hypertext,
1090                                             std::function<bool(wxEvtHandler*)> callback,
1091                                             int timestamp)
1092 {
1093 	int duration = 0;
1094 	switch (level) {
1095 	case NotificationLevel::RegularNotification: 	duration = 10; break;
1096 	case NotificationLevel::ErrorNotification: 		break;
1097 	case NotificationLevel::ImportantNotification: 	break;
1098 	default:
1099 		assert(false);
1100 		return;
1101 	}
1102     push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp);
1103 }
push_slicing_error_notification(const std::string & text)1104 void NotificationManager::push_slicing_error_notification(const std::string& text)
1105 {
1106 	set_all_slicing_errors_gray(false);
1107 	push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, 0);
1108 	close_notification_of_type(NotificationType::SlicingComplete);
1109 }
push_slicing_warning_notification(const std::string & text,bool gray,ObjectID oid,int warning_step)1110 void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step)
1111 {
1112 	NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text };
1113 
1114 	auto notification = std::make_unique<NotificationManager::SlicingWarningNotification>(data, m_id_provider, m_evt_handler);
1115 	notification->object_id = oid;
1116 	notification->warning_step = warning_step;
1117 	if (push_notification_data(std::move(notification), 0)) {
1118 		m_pop_notifications.back()->set_gray(gray);
1119 	}
1120 }
push_plater_error_notification(const std::string & text)1121 void NotificationManager::push_plater_error_notification(const std::string& text)
1122 {
1123 	push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, 0);
1124 }
push_plater_warning_notification(const std::string & text)1125 void NotificationManager::push_plater_warning_notification(const std::string& text)
1126 {
1127 	push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }, 0);
1128 	// dissaper if in preview
1129 	set_in_preview(m_in_preview);
1130 }
close_plater_error_notification(const std::string & text)1131 void NotificationManager::close_plater_error_notification(const std::string& text)
1132 {
1133 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1134 		if (notification->get_type() == NotificationType::PlaterError && notification->compare_text(_u8L("ERROR:") + "\n" + text)) {
1135 			notification->close();
1136 		}
1137 	}
1138 }
close_plater_warning_notification(const std::string & text)1139 void NotificationManager::close_plater_warning_notification(const std::string& text)
1140 {
1141 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1142 		if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) {
1143 			notification->close();
1144 		}
1145 	}
1146 }
set_all_slicing_errors_gray(bool g)1147 void NotificationManager::set_all_slicing_errors_gray(bool g)
1148 {
1149 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1150 		if (notification->get_type() == NotificationType::SlicingError) {
1151 			notification->set_gray(g);
1152 		}
1153 	}
1154 }
set_all_slicing_warnings_gray(bool g)1155 void NotificationManager::set_all_slicing_warnings_gray(bool g)
1156 {
1157 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1158 		if (notification->get_type() == NotificationType::SlicingWarning) {
1159 			notification->set_gray(g);
1160 		}
1161 	}
1162 }
1163 /*
1164 void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g)
1165 {
1166 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1167 		if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) {
1168 			notification->set_gray(g);
1169 		}
1170 	}
1171 }
1172 */
close_slicing_errors_and_warnings()1173 void NotificationManager::close_slicing_errors_and_warnings()
1174 {
1175 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1176 		if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) {
1177 			notification->close();
1178 		}
1179 	}
1180 }
push_slicing_complete_notification(int timestamp,bool large)1181 void NotificationManager::push_slicing_complete_notification(int timestamp, bool large)
1182 {
1183 	std::string hypertext;
1184 	int         time = 10;
1185     if (has_slicing_error_notification())
1186         return;
1187 	if (large) {
1188 		hypertext = _u8L("Export G-Code.");
1189 		time = 0;
1190 	}
1191 	NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time,  _u8L("Slicing finished."), hypertext, [](wxEvtHandler* evnthndlr){
1192 		if (evnthndlr != nullptr) wxPostEvent(evnthndlr, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); return true; } };
1193 	push_notification_data(std::make_unique<NotificationManager::SlicingCompleteLargeNotification>(data, m_id_provider, m_evt_handler, large), timestamp);
1194 }
set_slicing_complete_print_time(const std::string & info)1195 void NotificationManager::set_slicing_complete_print_time(const std::string &info)
1196 {
1197 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1198 		if (notification->get_type() == NotificationType::SlicingComplete) {
1199 			dynamic_cast<SlicingCompleteLargeNotification*>(notification.get())->set_print_info(info);
1200 			break;
1201 		}
1202 	}
1203 }
set_slicing_complete_large(bool large)1204 void NotificationManager::set_slicing_complete_large(bool large)
1205 {
1206 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1207 		if (notification->get_type() == NotificationType::SlicingComplete) {
1208 			dynamic_cast<SlicingCompleteLargeNotification*>(notification.get())->set_large(large);
1209 			break;
1210 		}
1211 	}
1212 }
close_notification_of_type(const NotificationType type)1213 void NotificationManager::close_notification_of_type(const NotificationType type)
1214 {
1215 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1216 		if (notification->get_type() == type) {
1217 			notification->close();
1218 		}
1219 	}
1220 }
remove_slicing_warnings_of_released_objects(const std::vector<ObjectID> & living_oids)1221 void NotificationManager::remove_slicing_warnings_of_released_objects(const std::vector<ObjectID>& living_oids)
1222 {
1223 	for (std::unique_ptr<PopNotification> &notification : m_pop_notifications)
1224 		if (notification->get_type() == NotificationType::SlicingWarning) {
1225 			if (! std::binary_search(living_oids.begin(), living_oids.end(),
1226 				static_cast<SlicingWarningNotification*>(notification.get())->object_id))
1227 				notification->close();
1228 		}
1229 }
push_exporting_finished_notification(const std::string & path,const std::string & dir_path,bool on_removable)1230 void NotificationManager::push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable)
1231 {
1232 	close_notification_of_type(NotificationType::ExportFinished);
1233 	NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, on_removable ? 0 : 20,  _u8L("Exporting finished.") + "\n" + path };
1234 	push_notification_data(std::make_unique<NotificationManager::ExportFinishedNotification>(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0);
1235 }
push_progress_bar_notification(const std::string & text,float percentage)1236 void  NotificationManager::push_progress_bar_notification(const std::string& text, float percentage)
1237 {
1238 	NotificationData data{ NotificationType::ProgressBar, NotificationLevel::ProgressBarNotification, 0, text };
1239 	push_notification_data(std::make_unique<NotificationManager::ProgressBarNotification>(data, m_id_provider, m_evt_handler, 0), 0);
1240 }
set_progress_bar_percentage(const std::string & text,float percentage)1241 void NotificationManager::set_progress_bar_percentage(const std::string& text, float percentage)
1242 {
1243 	bool found = false;
1244 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
1245 		if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) {
1246 			dynamic_cast<ProgressBarNotification*>(notification.get())->set_percentage(percentage);
1247 			wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
1248 			found = true;
1249 		}
1250 	}
1251 	if (!found) {
1252 		push_progress_bar_notification(text, percentage);
1253 	}
1254 }
push_notification_data(const NotificationData & notification_data,int timestamp)1255 bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp)
1256 {
1257 	return push_notification_data(std::make_unique<PopNotification>(notification_data, m_id_provider, m_evt_handler), timestamp);
1258 }
push_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification,int timestamp)1259 bool NotificationManager::push_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, int timestamp)
1260 {
1261 /*
1262 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
1263 	m_requires_update = true;
1264 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
1265 */
1266 	// if timestamped notif, push only new one
1267 	if (timestamp != 0) {
1268 		if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) {
1269 			m_used_timestamps.insert(timestamp);
1270 		} else {
1271 			return false;
1272 		}
1273 	}
1274 
1275 	GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D();
1276 
1277 	if (this->activate_existing(notification.get())) {
1278 		m_pop_notifications.back()->update(notification->get_data());
1279 		canvas.request_extra_frame_delayed(33);
1280 		return false;
1281 	} else {
1282 		m_pop_notifications.emplace_back(std::move(notification));
1283 		canvas.request_extra_frame_delayed(33);
1284 		return true;
1285 	}
1286 }
1287 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
render_notifications(float overlay_width)1288 void NotificationManager::render_notifications(float overlay_width)
1289 {
1290 
1291 	sort_notifications();
1292 
1293 	GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D();
1294 	float last_y = 0.0f;
1295 
1296 	for (const auto& notification : m_pop_notifications) {
1297 		if (notification->get_state() != PopNotification::EState::Hidden) {
1298 			notification->render(canvas, last_y, m_move_from_overlay && !m_in_preview, overlay_width);
1299 			if (notification->get_state() != PopNotification::EState::Finished)
1300 				last_y = notification->get_top() + GAP_WIDTH;
1301 		}
1302 
1303 	}
1304 	update_notifications();
1305 }
1306 #else
render_notifications(float overlay_width)1307 void NotificationManager::render_notifications(float overlay_width)
1308 {
1309 	float    last_x = 0.0f;
1310 	float    current_height = 0.0f;
1311 	bool     request_next_frame = false;
1312 	bool     render_main = false;
1313 	bool     hovered = false;
1314 	sort_notifications();
1315 
1316 	GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D();
1317 
1318 	// iterate thru notifications and render them / erase them
1319 	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
1320 		if ((*it)->is_finished()) {
1321 			it = m_pop_notifications.erase(it);
1322 		} else {
1323 			(*it)->set_paused(m_hovered);
1324 			PopNotification::RenderResult res = (*it)->render(canvas, last_x, m_move_from_overlay && !m_in_preview, overlay_width);
1325 			if (res != PopNotification::RenderResult::Finished) {
1326 				last_x = (*it)->get_top() + GAP_WIDTH;
1327 				current_height = std::max(current_height, (*it)->get_current_top());
1328 				render_main = true;
1329 			}
1330 			if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished)
1331 				request_next_frame = true;
1332 			if (res == PopNotification::RenderResult::Hovered)
1333 				hovered = true;
1334 			++it;
1335 		}
1336 	}
1337 	m_hovered = hovered;
1338 
1339 	//actualizate timers and request frame if needed
1340 	wxWindow* p = dynamic_cast<wxWindow*> (wxGetApp().plater());
1341 	while (p->GetParent())
1342 		p = p->GetParent();
1343 	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
1344 	if (!top_level_wnd->IsActive())
1345 		return;
1346 
1347 	{
1348 		// Control the fade-out.
1349 		// time in seconds
1350 		long now = wxGetLocalTime();
1351 		// Pausing fade-out when the mouse is over some notification.
1352 		if (!m_hovered && m_last_time < now)
1353 		{
1354 			if (now - m_last_time == 1)
1355 			{
1356 				for (auto &notification : m_pop_notifications)
1357 				{
1358 					notification->substract_remaining_time();
1359 				}
1360 			}
1361 			m_last_time = now;
1362 		}
1363 	}
1364 
1365 	if (request_next_frame)
1366 		//FIXME this is very expensive for fade-out control.
1367 		// If any of the notifications is fading out, 100% of the CPU/GPU is consumed.
1368 		canvas.request_extra_frame();
1369 }
1370 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
1371 
sort_notifications()1372 void NotificationManager::sort_notifications()
1373 {
1374 	// Stable sorting, so that the order of equal ranges is stable.
1375 	std::stable_sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](const std::unique_ptr<PopNotification> &n1, const std::unique_ptr<PopNotification> &n2) {
1376 		int n1l = (int)n1->get_data().level;
1377 		int n2l = (int)n2->get_data().level;
1378 		if (n1l == n2l && n1->is_gray() && !n2->is_gray())
1379 			return true;
1380 		return (n1l < n2l);
1381 		});
1382 }
1383 
activate_existing(const NotificationManager::PopNotification * notification)1384 bool NotificationManager::activate_existing(const NotificationManager::PopNotification* notification)
1385 {
1386 	NotificationType   new_type = notification->get_type();
1387 	const std::string &new_text = notification->get_data().text1;
1388 	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) {
1389 		if ((*it)->get_type() == new_type && !(*it)->is_finished()) {
1390 			if (new_type == NotificationType::CustomNotification || new_type == NotificationType::PlaterWarning) {
1391 				if (!(*it)->compare_text(new_text))
1392 					continue;
1393 			} else if (new_type == NotificationType::SlicingWarning) {
1394 				auto w1 = dynamic_cast<const SlicingWarningNotification*>(notification);
1395 				auto w2 = dynamic_cast<const SlicingWarningNotification*>(it->get());
1396 				if (w1 != nullptr && w2 != nullptr) {
1397 					if (!(*it)->compare_text(new_text) || w1->object_id != w2->object_id) {
1398 						continue;
1399 					}
1400 				} else {
1401 					continue;
1402 				}
1403 			}
1404 
1405 			if (it != m_pop_notifications.end() - 1)
1406 				std::rotate(it, it + 1, m_pop_notifications.end());
1407 			return true;
1408 		}
1409 	}
1410 	return false;
1411 }
1412 
set_in_preview(bool preview)1413 void NotificationManager::set_in_preview(bool preview)
1414 {
1415     m_in_preview = preview;
1416     for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
1417         if (notification->get_type() == NotificationType::PlaterWarning)
1418             notification->hide(preview);
1419     }
1420 }
1421 
1422 #if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
update_notifications()1423 void NotificationManager::update_notifications()
1424 {
1425 	// no update if not top window
1426 	wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater());
1427 	while (p->GetParent() != nullptr)
1428 		p = p->GetParent();
1429 	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
1430 	if (!top_level_wnd->IsActive())
1431 		return;
1432 
1433 	//static size_t last_size = m_pop_notifications.size();
1434 
1435 	//request frames
1436 	int64_t next_render = std::numeric_limits<int64_t>::max();
1437 	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
1438 		std::unique_ptr<PopNotification>& notification = *it;
1439 		notification->set_paused(m_hovered);
1440 		notification->update_state();
1441 		next_render = std::min<int64_t>(next_render, notification->next_render());
1442 		if (notification->get_state() == PopNotification::EState::Finished)
1443 			it = m_pop_notifications.erase(it);
1444 		else {
1445 
1446 			++it;
1447 		}
1448 	}
1449 	/*
1450 	m_requires_update = false;
1451 	for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
1452 		if (notification->requires_update()) {
1453 			m_requires_update = true;
1454 			break;
1455 		}
1456 	}
1457 	*/
1458 	// update hovering state
1459 	m_hovered = false;
1460 	for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
1461 		if (notification->is_hovered()) {
1462 			m_hovered = true;
1463 			break;
1464 		}
1465 	}
1466 
1467 	/*
1468 	// Reuire render if some notification was just deleted.
1469 	size_t curr_size = m_pop_notifications.size();
1470 	m_requires_render = m_hovered || (last_size != curr_size);
1471 	last_size = curr_size;
1472 
1473 	// Ask notification if it needs render
1474 	if (!m_requires_render) {
1475 		for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
1476 			if (notification->requires_render()) {
1477 				m_requires_render = true;
1478 				break;
1479 			}
1480 		}
1481 	}
1482 	// Make sure there will be update after last notification erased
1483 	if (m_requires_render)
1484 		m_requires_update = true;
1485 	*/
1486 
1487 
1488 	if (next_render == 0)
1489 		wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(33); //few milliseconds to get from GLCanvas::render
1490 	else if (next_render < std::numeric_limits<int64_t>::max())
1491 		wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(int(next_render));
1492 
1493 	/*
1494 	// actualizate timers
1495 	wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater());
1496 	while (p->GetParent() != nullptr)
1497 		p = p->GetParent();
1498 	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
1499 	if (!top_level_wnd->IsActive())
1500 		return;
1501 
1502 	{
1503 		// Control the fade-out.
1504 		// time in seconds
1505 		long now = wxGetLocalTime();
1506 		// Pausing fade-out when the mouse is over some notification.
1507 		if (!m_hovered && m_last_time < now) {
1508 			if (now - m_last_time >= MAX_TIMEOUT_SECONDS) {
1509 				for (auto& notification : m_pop_notifications) {
1510 					//if (notification->get_state() != PopNotification::EState::Static)
1511 						notification->substract_remaining_time(MAX_TIMEOUT_SECONDS);
1512 				}
1513 				m_last_time = now;
1514 			}
1515 		}
1516 	}
1517 	*/
1518 }
1519 #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
1520 
has_slicing_error_notification()1521 bool NotificationManager::has_slicing_error_notification()
1522 {
1523 	return std::any_of(m_pop_notifications.begin(), m_pop_notifications.end(), [](auto &n) {
1524     	return n->get_type() == NotificationType::SlicingError;
1525     });
1526 }
1527 
new_export_began(bool on_removable)1528 void NotificationManager::new_export_began(bool on_removable)
1529 {
1530 	close_notification_of_type(NotificationType::ExportFinished);
1531 	// If we want to hold information of ejecting removable on later export finished notifications
1532 	/*
1533 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
1534 		if (notification->get_type() == NotificationType::ExportToRemovableFinished) {
1535 			if (!on_removable) {
1536 				const NotificationData old_data = notification->get_data();
1537 				notification->update( {old_data.type, old_data.level ,old_data.duration, std::string(), old_data.hypertext} );
1538 			} else {
1539 				notification->close();
1540 			}
1541 			return;
1542 		}
1543 	}
1544 	*/
1545 }
device_ejected()1546 void NotificationManager::device_ejected()
1547 {
1548 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
1549 		if (notification->get_type() == NotificationType::ExportFinished && dynamic_cast<ExportFinishedNotification*>(notification.get())->m_to_removable)
1550 			notification->close();
1551 	}
1552 }
1553 
1554 }//namespace GUI
1555 }//namespace Slic3r
1556