1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3 * @file
4 * Interface to main application.
5 */
6 /* Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 * Liam P. White <inkscapebrony@gmail.com>
10 *
11 * Copyright (C) 1999-2014 authors
12 * c++ port Copyright (C) 2003 Nathan Hurst
13 * c++ification Copyright (C) 2014 Liam P. White
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18 #include <cerrno>
19 #include <unistd.h>
20
21 #include <map>
22
23 #include <glibmm/fileutils.h>
24 #include <glibmm/regex.h>
25
26 #include <gtkmm/icontheme.h>
27 #include <gtkmm/messagedialog.h>
28
29 #include <glib/gstdio.h>
30 #include <glibmm/i18n.h>
31 #include <glibmm/miscutils.h>
32 #include <glibmm/convert.h>
33 #include <regex>
34
35 #include "desktop.h"
36 #include "device-manager.h"
37 #include "document.h"
38 #include "inkscape.h"
39 #include "message-stack.h"
40 #include "path-prefix.h"
41
42 #include "debug/simple-event.h"
43 #include "debug/event-tracker.h"
44
45 #include "extension/db.h"
46 #include "extension/init.h"
47 #include "extension/system.h"
48
49 #include "helper/action-context.h"
50
51 #include "io/resource.h"
52 #include "io/resource-manager.h"
53 #include "io/sys.h"
54
55 #include "libnrtype/FontFactory.h"
56
57 #include "object/sp-root.h"
58 #include "object/sp-style-elem.h"
59
60 #include "svg/svg-color.h"
61
62 #include "object/sp-root.h"
63 #include "object/sp-style-elem.h"
64
65 #include "ui/dialog/debug.h"
66 #include "ui/tools/tool-base.h"
67
68 /* Backbones of configuration xml data */
69 #include "menus-skeleton.h"
70
71 #include <fstream>
72
73 // Inkscape::Application static members
74 Inkscape::Application * Inkscape::Application::_S_inst = nullptr;
75 bool Inkscape::Application::_crashIsHappening = false;
76
77 #define DESKTOP_IS_ACTIVE(d) (INKSCAPE._desktops != nullptr && !INKSCAPE._desktops->empty() && ((d) == INKSCAPE._desktops->front()))
78
79 static void (* segv_handler) (int) = SIG_DFL;
80 static void (* abrt_handler) (int) = SIG_DFL;
81 static void (* fpe_handler) (int) = SIG_DFL;
82 static void (* ill_handler) (int) = SIG_DFL;
83 #ifndef _WIN32
84 static void (* bus_handler) (int) = SIG_DFL;
85 #endif
86
87 #define MENUS_FILE "menus.xml"
88
89 #define SP_INDENT 8
90
91 /** C++ification TODO list
92 * - _S_inst should NOT need to be assigned inside the constructor, but if it isn't the Filters+Extensions menus break.
93 * - Application::_deskops has to be a pointer because of a signal bug somewhere else. Basically, it will attempt to access a deleted object in sp_ui_close_all(),
94 * but if it's a pointer we can stop and return NULL in Application::active_desktop()
95 * - These functions are calling Application::create for no good reason I can determine:
96 *
97 * Inkscape::UI::Dialog::SVGPreview::SVGPreview()
98 * src/ui/dialog/filedialogimpl-gtkmm.cpp:542:9
99 */
100
101
102 class InkErrorHandler : public Inkscape::ErrorReporter {
103 public:
InkErrorHandler(bool useGui)104 InkErrorHandler(bool useGui) : Inkscape::ErrorReporter(),
105 _useGui(useGui)
106 {}
107 ~InkErrorHandler() override = default;
108
handleError(Glib::ustring const & primary,Glib::ustring const & secondary) const109 void handleError( Glib::ustring const& primary, Glib::ustring const& secondary ) const override
110 {
111 if (_useGui) {
112 Gtk::MessageDialog err(primary, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
113 err.set_secondary_text(secondary);
114 err.run();
115 } else {
116 g_message("%s", primary.data());
117 g_message("%s", secondary.data());
118 }
119 }
120
121 private:
122 bool _useGui;
123 };
124
inkscape_ref(Inkscape::Application & in)125 void inkscape_ref(Inkscape::Application & in)
126 {
127 in.refCount++;
128 }
129
inkscape_unref(Inkscape::Application & in)130 void inkscape_unref(Inkscape::Application & in)
131 {
132 in.refCount--;
133
134 if (&in == Inkscape::Application::_S_inst) {
135 if (in.refCount <= 0) {
136 delete Inkscape::Application::_S_inst;
137 }
138 } else {
139 g_error("Attempt to unref an Application (=%p) not the current instance (=%p) (maybe it's already been destroyed?)",
140 &in, Inkscape::Application::_S_inst);
141 }
142 }
143
144 namespace Inkscape {
145
146 /**
147 * Defined only for debugging purposes. If we are certain the bugs are gone we can remove this
148 * and the references in inkscape_ref and inkscape_unref.
149 */
150 Application*
operator &() const151 Application::operator &() const
152 {
153 return const_cast<Application*>(this);
154 }
155 /**
156 * Creates a new Inkscape::Application global object.
157 */
158 void
create(bool use_gui)159 Application::create(bool use_gui)
160 {
161 if (!Application::exists()) {
162 new Application(use_gui);
163 } else {
164 // g_assert_not_reached(); Can happen with InkscapeApplication
165 }
166 }
167
168
169 /**
170 * Checks whether the current Inkscape::Application global object exists.
171 */
172 bool
exists()173 Application::exists()
174 {
175 return Application::_S_inst != nullptr;
176 }
177
178 /**
179 * Returns the current Inkscape::Application global object.
180 * \pre Application::_S_inst != NULL
181 */
182 Application&
instance()183 Application::instance()
184 {
185 if (!exists()) {
186 g_error("Inkscape::Application does not yet exist.");
187 }
188 return *Application::_S_inst;
189 }
190
191 /* \brief Constructor for the application.
192 * Creates a new Inkscape::Application.
193 *
194 * \pre Application::_S_inst == NULL
195 */
196
Application(bool use_gui)197 Application::Application(bool use_gui) :
198 _use_gui(use_gui)
199 {
200 using namespace Inkscape::IO::Resource;
201 /* fixme: load application defaults */
202
203 segv_handler = signal (SIGSEGV, Application::crash_handler);
204 abrt_handler = signal (SIGABRT, Application::crash_handler);
205 fpe_handler = signal (SIGFPE, Application::crash_handler);
206 ill_handler = signal (SIGILL, Application::crash_handler);
207 #ifndef _WIN32
208 bus_handler = signal (SIGBUS, Application::crash_handler);
209 #endif
210
211 // \TODO: this belongs to Application::init but if it isn't here
212 // then the Filters and Extensions menus don't work.
213 _S_inst = this;
214
215 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
216 InkErrorHandler* handler = new InkErrorHandler(use_gui);
217 prefs->setErrorHandler(handler);
218 {
219 Glib::ustring msg;
220 Glib::ustring secondary;
221 if (prefs->getLastError( msg, secondary )) {
222 handler->handleError(msg, secondary);
223 }
224 }
225
226 if (use_gui) {
227 using namespace Inkscape::IO::Resource;
228 auto icon_theme = Gtk::IconTheme::get_default();
229 icon_theme->prepend_search_path(get_path_ustring(SYSTEM, ICONS));
230 icon_theme->prepend_search_path(get_path_ustring(USER, ICONS));
231 add_gtk_css(false);
232 /* Load the preferences and menus */
233 load_menus();
234 Inkscape::DeviceManager::getManager().loadConfig();
235 }
236
237 Inkscape::ResourceManager::getManager();
238
239 /* set language for user interface according setting in preferences */
240 Glib::ustring ui_language = prefs->getString("/ui/language");
241 if(!ui_language.empty())
242 {
243 setenv("LANGUAGE", ui_language, true);
244 }
245
246 /* DebugDialog redirection. On Linux, default to OFF, on Win32, default to ON.
247 * Use only if use_gui is enabled
248 */
249 #ifdef _WIN32
250 #define DEFAULT_LOG_REDIRECT true
251 #else
252 #define DEFAULT_LOG_REDIRECT false
253 #endif
254
255 if (use_gui && prefs->getBool("/dialogs/debug/redirect", DEFAULT_LOG_REDIRECT))
256 {
257 Inkscape::UI::Dialog::DebugDialog::getInstance()->captureLogMessages();
258 }
259
260 if (use_gui)
261 {
262 Inkscape::UI::Tools::init_latin_keys_group();
263 /* Check for global remapping of Alt key */
264 mapalt(guint(prefs->getInt("/options/mapalt/value", 0)));
265 trackalt(guint(prefs->getInt("/options/trackalt/value", 0)));
266 }
267
268 /* Initialize the extensions */
269 Inkscape::Extension::init();
270
271 /* Initialize font factory */
272 font_factory *factory = font_factory::Default();
273 if (prefs->getBool("/options/font/use_fontsdir_system", true)) {
274 char const *fontsdir = get_path(SYSTEM, FONTS);
275 factory->AddFontsDir(fontsdir);
276 }
277 if (prefs->getBool("/options/font/use_fontsdir_user", true)) {
278 char const *fontsdir = get_path(USER, FONTS);
279 factory->AddFontsDir(fontsdir);
280 }
281 Glib::ustring fontdirs_pref = prefs->getString("/options/font/custom_fontdirs");
282 std::vector<Glib::ustring> fontdirs = Glib::Regex::split_simple("\\|", fontdirs_pref);
283 for (auto &fontdir : fontdirs) {
284 factory->AddFontsDir(fontdir.c_str());
285 }
286 }
287
~Application()288 Application::~Application()
289 {
290 if (_desktops) {
291 g_error("FATAL: desktops still in list on application destruction!");
292 }
293
294 Inkscape::Preferences::unload();
295
296 if (_menus) {
297 Inkscape::GC::release(_menus);
298 _menus = nullptr;
299 }
300
301 _S_inst = nullptr; // this will probably break things
302
303 refCount = 0;
304 // gtk_main_quit ();
305 }
306
307
get_symbolic_colors()308 Glib::ustring Application::get_symbolic_colors()
309 {
310 Glib::ustring css_str;
311 gchar colornamed[64];
312 gchar colornamedsuccess[64];
313 gchar colornamedwarning[64];
314 gchar colornamederror[64];
315 gchar colornamed_inverse[64];
316 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
317 Glib::ustring themeiconname = prefs->getString("/theme/iconTheme", prefs->getString("/theme/defaultIconTheme", ""));
318 guint32 colorsetbase = 0x2E3436ff;
319 guint32 colorsetbase_inverse = colorsetbase ^ 0xffffff00;
320 guint32 colorsetsuccess = 0x4AD589ff;
321 guint32 colorsetwarning = 0xF57900ff;
322 guint32 colorseterror = 0xCC0000ff;
323 colorsetbase = prefs->getUInt("/theme/" + themeiconname + "/symbolicBaseColor", colorsetbase);
324 colorsetsuccess = prefs->getUInt("/theme/" + themeiconname + "/symbolicSuccessColor", colorsetsuccess);
325 colorsetwarning = prefs->getUInt("/theme/" + themeiconname + "/symbolicWarningColor", colorsetwarning);
326 colorseterror = prefs->getUInt("/theme/" + themeiconname + "/symbolicErrorColor", colorseterror);
327 sp_svg_write_color(colornamed, sizeof(colornamed), colorsetbase);
328 sp_svg_write_color(colornamedsuccess, sizeof(colornamedsuccess), colorsetsuccess);
329 sp_svg_write_color(colornamedwarning, sizeof(colornamedwarning), colorsetwarning);
330 sp_svg_write_color(colornamederror, sizeof(colornamederror), colorseterror);
331 colorsetbase_inverse = colorsetbase ^ 0xffffff00;
332 sp_svg_write_color(colornamed_inverse, sizeof(colornamed_inverse), colorsetbase_inverse);
333 css_str += "@define-color warning_color " + Glib::ustring(colornamedwarning) + ";\n";
334 css_str += "@define-color error_color " + Glib::ustring(colornamederror) + ";\n";
335 css_str += "@define-color success_color " + Glib::ustring(colornamedsuccess) + ";\n";
336 /* ":not(.rawstyle) > image" works only on images in first level of widget container
337 if in the future we use a complex widget with more levels and we dont want to tweak the color
338 here, retaining default we can add more lines like ":not(.rawstyle) > > image"
339 if we not override the color we use defautt theme colors*/
340 bool overridebasecolor = !prefs->getBool("/theme/symbolicDefaultBaseColors", true);
341 if (overridebasecolor) {
342 css_str += "#InkRuler,";
343 css_str += ":not(.rawstyle) > image";
344 css_str += "{color:";
345 css_str += colornamed;
346 css_str += ";}";
347 }
348 css_str += ".dark .forcebright :not(.rawstyle) > image,";
349 css_str += ".dark .forcebright image:not(.rawstyle),";
350 css_str += ".bright .forcedark :not(.rawstyle) > image,";
351 css_str += ".bright .forcedark image:not(.rawstyle),";
352 css_str += ".dark :not(.rawstyle) > image.forcebright,";
353 css_str += ".dark image.forcebright:not(.rawstyle),";
354 css_str += ".bright :not(.rawstyle) > image.forcedark,";
355 css_str += ".bright image.forcedark:not(.rawstyle),";
356 css_str += ".inverse :not(.rawstyle) > image,";
357 css_str += ".inverse image:not(.rawstyle)";
358 css_str += "{color:";
359 if (overridebasecolor) {
360 css_str += colornamed_inverse;
361 } else {
362 // we override base color in this special cases using inverse color
363 css_str += "@theme_bg_color";
364 }
365 css_str += ";}";
366 return css_str;
367 }
368
sp_get_contrasted_color(std::string cssstring,std::string define,std::string define_b,double contrast)369 std::string sp_get_contrasted_color(std::string cssstring, std::string define, std::string define_b,
370 double contrast)
371 {
372 std::smatch m;
373 std::regex e("@define-color " + define + " ([^;]*)");
374 std::regex_search(cssstring, m, e);
375 std::smatch n;
376 std::regex f("@define-color " + define_b + " ([^;]*)");
377 std::regex_search(cssstring, n, f);
378 std::string out = "";
379 if (m.size() >= 1 && n.size() >= 1) {
380 out = "@define-color " + define + " mix(" + m[1].str() + ", " + n[1].str() + ", " + Glib::ustring::format(contrast) + ");\n";
381 }
382 return out;
383 }
384
sp_tweak_background_colors(std::string cssstring,double crossfade)385 std::string sp_tweak_background_colors(std::string cssstring, double crossfade)
386 {
387 static std::regex re_no_affect("(inherit|unset|initial|none|url)");
388 static std::regex re_background_color("background-color( ){0,3}:(.*?);");
389 static std::regex re_background_image("background-image( ){0,3}:(.*?\\)) *?;");
390 std::string sub = "";
391 std::smatch m;
392 std::regex_search(cssstring, m, re_no_affect);
393 if (m.size() == 0) {
394 if (cssstring.find("background-color") != std::string::npos) {
395 sub = "background-color:shade($2," + Glib::ustring::format(crossfade) + ");";
396 cssstring = std::regex_replace(cssstring, re_background_color, sub);
397 } else if (cssstring.find("background-image") != std::string::npos) {
398 if (crossfade > 1) {
399 crossfade = std::clamp((int)((2 - crossfade) * 80), 0, 100);
400 sub = "background-image:cross-fade(" + Glib::ustring::format(crossfade) + "% image($2), image(@theme_bg_color));";
401 } else {
402 crossfade = std::clamp((int)((1 - crossfade) * 80), 0 , 100);
403 sub = "background-image:cross-fade(" + Glib::ustring::format(crossfade) + "% image(@theme_bg_color), image($2));";
404 }
405 cssstring = std::regex_replace(cssstring, re_background_image, sub);
406 }
407 } else {
408 cssstring = "";
409 }
410 return cssstring;
411 }
412
413 static void
show_parsing_error(const Glib::RefPtr<const Gtk::CssSection> & section,const Glib::Error & error)414 show_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section, const Glib::Error& error)
415 {
416 #ifndef NDEBUG
417 g_warning("There is a warning parsing theme CSS:: %s", error.what().c_str());
418 #endif
419 }
420
421 /**
422 * \brief Add our CSS style sheets
423 * @param only_providers: Apply only the providers part, from inkscape preferences::theme change, no need to reaply
424 */
add_gtk_css(bool only_providers)425 void Application::add_gtk_css(bool only_providers)
426 {
427 using namespace Inkscape::IO::Resource;
428 // Add style sheet (GTK3)
429 auto const screen = Gdk::Screen::get_default();
430 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
431 gchar *gtkThemeName = nullptr;
432 gchar *gtkIconThemeName = nullptr;
433 Glib::ustring themeiconname;
434 gboolean gtkApplicationPreferDarkTheme;
435 GtkSettings *settings = gtk_settings_get_default();
436 if (settings && !only_providers) {
437 g_object_get(settings, "gtk-icon-theme-name", >kIconThemeName, NULL);
438 g_object_get(settings, "gtk-theme-name", >kThemeName, NULL);
439 g_object_get(settings, "gtk-application-prefer-dark-theme", >kApplicationPreferDarkTheme, NULL);
440 prefs->setBool("/theme/defaultPreferDarkTheme", gtkApplicationPreferDarkTheme);
441 prefs->setString("/theme/defaultGtkTheme", Glib::ustring(gtkThemeName));
442 prefs->setString("/theme/defaultIconTheme", Glib::ustring(gtkIconThemeName));
443 Glib::ustring gtkthemename = prefs->getString("/theme/gtkTheme");
444 if (gtkthemename != "") {
445 g_object_set(settings, "gtk-theme-name", gtkthemename.c_str(), NULL);
446 } else {
447 Glib::RefPtr<Gdk::Display> display = Gdk::Display::get_default();
448 Glib::RefPtr<Gdk::Screen> screen = display->get_default_screen();
449 Glib::RefPtr<Gtk::IconTheme> icon_theme = Gtk::IconTheme::get_for_screen(screen);
450 Gtk::IconInfo iconinfo = icon_theme->lookup_icon("tool-pointer", 22, Gtk::ICON_LOOKUP_FORCE_SIZE);
451 prefs->setBool("/theme/symbolicIcons", iconinfo.is_symbolic());
452 }
453 bool preferdarktheme = prefs->getBool("/theme/preferDarkTheme", false);
454 g_object_set(settings, "gtk-application-prefer-dark-theme", preferdarktheme, NULL);
455 themeiconname = prefs->getString("/theme/iconTheme");
456 // legacy cleanup
457 if (themeiconname == prefs->getString("/theme/defaultIconTheme")) {
458 prefs->setString("/theme/iconTheme", "");
459 } else if (themeiconname != "") {
460 g_object_set(settings, "gtk-icon-theme-name", themeiconname.c_str(), NULL);
461 }
462 }
463
464 g_free(gtkThemeName);
465 g_free(gtkIconThemeName);
466
467 int themecontrast = prefs->getInt("/theme/contrast", 10);
468 if (!contrastthemeprovider) {
469 contrastthemeprovider = Gtk::CssProvider::create();
470 // We can uncoment this line to remove warnings and errors on the theme
471 contrastthemeprovider->signal_parsing_error().connect(sigc::ptr_fun(show_parsing_error));
472 }
473 // we use contast only if is setup (!= 10)
474 if (themecontrast < 10) {
475 Glib::ustring css_contrast = "";
476 double contrast = (10 - themecontrast) / 40.0;
477 double shade = 1 - contrast;
478 const gchar *variant = nullptr;
479 if (prefs->getBool("/theme/preferDarkTheme", false)) {
480 variant = "dark";
481 }
482 if (prefs->getBool("/theme/darkTheme", false)) {
483 contrast *= 2.5;
484 shade = 1 + contrast;
485 }
486 Glib::ustring current_theme = prefs->getString("/theme/gtkTheme", prefs->getString("/theme/defaultGtkTheme", ""));
487 GtkCssProvider *currentthemeprovider =
488 gtk_css_provider_get_named(current_theme.c_str(), variant);
489 std::string cssstring = gtk_css_provider_to_string(currentthemeprovider);
490 if (contrast) {
491 std::string cssdefined = "";
492 std::string appenddefined = "";
493 std::string colorsdefined = "";
494 // we do this way to fix issue Inkscape#2345
495 // windows seem crash if text length > 2000;
496 std::istringstream f(cssstring);
497 std::string line;
498 while (std::getline(f, line)) {
499 if (line.find("@define-color") != std::string::npos) {
500 colorsdefined += line;
501 colorsdefined += "\n";
502 }
503 // here we ignore most of class to parse because is in additive mode
504 // so stiles not applyed are set on previous context style
505 if (line.find(";") != std::string::npos &&
506 line.find("background-image") == std::string::npos &&
507 line.find("background-color") == std::string::npos)
508 {
509 continue;
510 }
511 cssdefined += sp_tweak_background_colors(line, shade);
512 cssdefined += "\n";
513 }
514 appenddefined = sp_get_contrasted_color(colorsdefined, "theme_bg_color", "theme_fg_color", contrast);
515 appenddefined += sp_get_contrasted_color(colorsdefined, "theme_base_color", "theme_text_color", contrast);
516 appenddefined += sp_get_contrasted_color(colorsdefined, "theme_selected_bg_color", "theme_selected_fg_color", contrast);
517 cssstring = cssdefined + appenddefined;
518 }
519 if (!cssstring.empty()) {
520 // Use c format allow parse with errors or warnings
521 gtk_css_provider_load_from_data (contrastthemeprovider->gobj(), cssstring.c_str(), -1, nullptr);
522 Gtk::StyleContext::add_provider_for_screen(screen, contrastthemeprovider, GTK_STYLE_PROVIDER_PRIORITY_SETTINGS);
523 }
524 } else if (contrastthemeprovider) {
525 Gtk::StyleContext::remove_provider_for_screen(screen, contrastthemeprovider);
526 }
527 Glib::ustring style = get_filename(UIS, "style.css");
528 if (!style.empty()) {
529 if (styleprovider) {
530 Gtk::StyleContext::remove_provider_for_screen(screen, styleprovider);
531 }
532 if (!styleprovider) {
533 styleprovider = Gtk::CssProvider::create();
534 }
535 try {
536 styleprovider->load_from_path(style);
537 } catch (const Gtk::CssProviderError &ex) {
538 g_critical("CSSProviderError::load_from_path(): failed to load '%s'\n(%s)", style.c_str(),
539 ex.what().c_str());
540 }
541 Gtk::StyleContext::add_provider_for_screen(screen, styleprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
542 }
543 Glib::ustring gtkthemename = prefs->getString("/theme/gtkTheme", prefs->getString("/theme/defaultGtkTheme", ""));
544 gtkthemename += ".css";
545 style = get_filename(UIS, gtkthemename.c_str(), false, true);
546 if (!style.empty()) {
547 if (themeprovider) {
548 Gtk::StyleContext::remove_provider_for_screen(screen, themeprovider);
549 }
550 if (!themeprovider) {
551 themeprovider = Gtk::CssProvider::create();
552 }
553 try {
554 themeprovider->load_from_path(style);
555 } catch (const Gtk::CssProviderError &ex) {
556 g_critical("CSSProviderError::load_from_path(): failed to load '%s'\n(%s)", style.c_str(),
557 ex.what().c_str());
558 }
559 Gtk::StyleContext::add_provider_for_screen(screen, themeprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
560 }
561
562 if (!colorizeprovider) {
563 colorizeprovider = Gtk::CssProvider::create();
564 }
565 Glib::ustring css_str = "";
566 if (prefs->getBool("/theme/symbolicIcons", false)) {
567 css_str = get_symbolic_colors();
568 }
569 try {
570 colorizeprovider->load_from_data(css_str);
571 } catch (const Gtk::CssProviderError &ex) {
572 g_critical("CSSProviderError::load_from_data(): failed to load '%s'\n(%s)", css_str.c_str(), ex.what().c_str());
573 }
574 Gtk::StyleContext::add_provider_for_screen(screen, colorizeprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
575 }
576
577 /** Sets the keyboard modifier to map to Alt.
578 *
579 * Zero switches off mapping, as does '1', which is the default.
580 */
mapalt(guint maskvalue)581 void Application::mapalt(guint maskvalue)
582 {
583 if ( maskvalue < 2 || maskvalue > 5 ) { // MOD5 is the highest defined in gdktypes.h
584 _mapalt = 0;
585 } else {
586 _mapalt = (GDK_MOD1_MASK << (maskvalue-1));
587 }
588 }
589
590 void
crash_handler(int)591 Application::crash_handler (int /*signum*/)
592 {
593 using Inkscape::Debug::SimpleEvent;
594 using Inkscape::Debug::EventTracker;
595 using Inkscape::Debug::Logger;
596
597 static bool recursion = false;
598
599 /*
600 * reset all signal handlers: any further crashes should just be allowed
601 * to crash normally.
602 * */
603 signal (SIGSEGV, segv_handler );
604 signal (SIGABRT, abrt_handler );
605 signal (SIGFPE, fpe_handler );
606 signal (SIGILL, ill_handler );
607 #ifndef _WIN32
608 signal (SIGBUS, bus_handler );
609 #endif
610
611 /* Stop bizarre loops */
612 if (recursion) {
613 abort ();
614 }
615 recursion = true;
616
617 _crashIsHappening = true;
618
619 EventTracker<SimpleEvent<Inkscape::Debug::Event::CORE> > tracker("crash");
620 tracker.set<SimpleEvent<> >("emergency-save");
621
622 fprintf(stderr, "\nEmergency save activated!\n");
623
624 time_t sptime = time (nullptr);
625 struct tm *sptm = localtime (&sptime);
626 gchar sptstr[256];
627 strftime(sptstr, 256, "%Y_%m_%d_%H_%M_%S", sptm);
628
629 gint count = 0;
630 gchar *curdir = g_get_current_dir(); // This one needs to be freed explicitly
631 std::vector<gchar *> savednames;
632 std::vector<gchar *> failednames;
633 for (std::map<SPDocument*,int>::iterator iter = INKSCAPE._document_set.begin(), e = INKSCAPE._document_set.end();
634 iter != e;
635 ++iter) {
636 SPDocument *doc = iter->first;
637 Inkscape::XML::Node *repr;
638 repr = doc->getReprRoot();
639 if (doc->isModifiedSinceSave()) {
640 const gchar *docname;
641 char n[64];
642
643 /* originally, the document name was retrieved from
644 * the sodipod:docname attribute */
645 docname = doc->getDocumentName();
646 if (docname) {
647 /* Removes an emergency save suffix if present: /(.*)\.[0-9_]*\.[0-9_]*\.[~\.]*$/\1/ */
648 const char* d0 = strrchr ((char*)docname, '.');
649 if (d0 && (d0 > docname)) {
650 const char* d = d0;
651 unsigned int dots = 0;
652 while ((isdigit (*d) || *d=='_' || *d=='.') && d>docname && dots<2) {
653 d -= 1;
654 if (*d=='.') dots++;
655 }
656 if (*d=='.' && d>docname && dots==2) {
657 size_t len = MIN (d - docname, 63);
658 memcpy (n, docname, len);
659 n[len] = '\0';
660 docname = n;
661 }
662 }
663 }
664 if (!docname || !*docname) docname = "emergency";
665
666 // Emergency filename
667 char c[1024];
668 g_snprintf (c, 1024, "%.256s.%s.%d.svg", docname, sptstr, count);
669
670 const char* document_uri = doc->getDocumentURI();
671 char* document_base = nullptr;
672 if (document_uri) {
673 document_base = g_path_get_dirname(document_uri);
674 }
675
676 // Find a location
677 const char* locations[] = {
678 // Don't use getDocumentBase as that also can be unsaved template locations.
679 document_base,
680 g_get_home_dir(),
681 g_get_tmp_dir(),
682 curdir,
683 };
684 FILE *file = nullptr;
685 for(auto & location : locations) {
686 if (!location) continue; // It seems to be okay, but just in case
687 gchar * filename = g_build_filename(location, c, NULL);
688 Inkscape::IO::dump_fopen_call(filename, "E");
689 file = Inkscape::IO::fopen_utf8name(filename, "w");
690 if (file) {
691 g_snprintf (c, 1024, "%s", filename); // we want the complete path to be stored in c (for reporting purposes)
692 break;
693 }
694 }
695 if (document_base) {
696 g_free(document_base);
697 }
698
699 // Save
700 if (file) {
701 sp_repr_save_stream (repr->document(), file, SP_SVG_NS_URI);
702 savednames.push_back(g_strdup (c));
703 fclose (file);
704 } else {
705 failednames.push_back((doc->getDocumentName()) ? g_strdup(doc->getDocumentName()) : g_strdup (_("Untitled document")));
706 }
707 count++;
708 }
709 }
710 g_free(curdir);
711
712 if (!savednames.empty()) {
713 fprintf (stderr, "\nEmergency save document locations:\n");
714 for (auto i:savednames) {
715 fprintf (stderr, " %s\n", i);
716 }
717 }
718 if (!failednames.empty()) {
719 fprintf (stderr, "\nFailed to do emergency save for documents:\n");
720 for (auto i:failednames) {
721 fprintf (stderr, " %s\n", i);
722 }
723 }
724
725 // do not save the preferences since they can be in a corrupted state
726 Inkscape::Preferences::unload(false);
727
728 fprintf (stderr, "Emergency save completed. Inkscape will close now.\n");
729 fprintf (stderr, "If you can reproduce this crash, please file a bug at https://inkscape.org/report\n");
730 fprintf (stderr, "with a detailed description of the steps leading to the crash, so we can fix it.\n");
731
732 /* Show nice dialog box */
733
734 char const *istr = _("Inkscape encountered an internal error and will close now.\n");
735 char const *sstr = _("Automatic backups of unsaved documents were done to the following locations:\n");
736 char const *fstr = _("Automatic backup of the following documents failed:\n");
737 gint nllen = strlen ("\n");
738 gint len = strlen (istr) + strlen (sstr) + strlen (fstr);
739 for (auto i:savednames) {
740 len = len + SP_INDENT + strlen (i) + nllen;
741 }
742 for (auto i:failednames) {
743 len = len + SP_INDENT + strlen (i) + nllen;
744 }
745 len += 1;
746 gchar *b = g_new (gchar, len);
747 gint pos = 0;
748 len = strlen (istr);
749 memcpy (b + pos, istr, len);
750 pos += len;
751 if (!savednames.empty()) {
752 len = strlen (sstr);
753 memcpy (b + pos, sstr, len);
754 pos += len;
755 for (auto i:savednames) {
756 memset (b + pos, ' ', SP_INDENT);
757 pos += SP_INDENT;
758 len = strlen(i);
759 memcpy (b + pos, i, len);
760 pos += len;
761 memcpy (b + pos, "\n", nllen);
762 pos += nllen;
763 }
764 }
765 if (!failednames.empty()) {
766 len = strlen (fstr);
767 memcpy (b + pos, fstr, len);
768 pos += len;
769 for (auto i:failednames) {
770 memset (b + pos, ' ', SP_INDENT);
771 pos += SP_INDENT;
772 len = strlen(i);
773 memcpy (b + pos, i, len);
774 pos += len;
775 memcpy (b + pos, "\n", nllen);
776 pos += nllen;
777 }
778 }
779 *(b + pos) = '\0';
780
781 if ( exists() && instance().use_gui() ) {
782 GtkWidget *msgbox = gtk_message_dialog_new (nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", b);
783 gtk_dialog_run (GTK_DIALOG (msgbox));
784 gtk_widget_destroy (msgbox);
785 }
786 else
787 {
788 g_message( "Error: %s", b );
789 }
790 g_free (b);
791
792 tracker.clear();
793 Logger::shutdown();
794
795 fflush(stderr); // make sure buffers are empty before crashing (otherwise output might be suppressed)
796
797 /* on exit, allow restored signal handler to take over and crash us */
798 }
799
800 /**
801 * Menus management
802 *
803 */
load_menus()804 bool Application::load_menus()
805 {
806 using namespace Inkscape::IO::Resource;
807 Glib::ustring filename = get_filename(UIS, MENUS_FILE);
808
809 _menus = sp_repr_read_file(filename.c_str(), nullptr);
810 if ( !_menus ) {
811 _menus = sp_repr_read_mem(menus_skeleton, MENUS_SKELETON_SIZE, nullptr);
812 }
813 return (_menus != nullptr);
814 }
815
816
817 void
selection_modified(Inkscape::Selection * selection,guint flags)818 Application::selection_modified (Inkscape::Selection *selection, guint flags)
819 {
820 g_return_if_fail (selection != nullptr);
821
822 if (DESKTOP_IS_ACTIVE (selection->desktop())) {
823 signal_selection_modified.emit(selection, flags);
824 }
825 }
826
827
828 void
selection_changed(Inkscape::Selection * selection)829 Application::selection_changed (Inkscape::Selection * selection)
830 {
831 g_return_if_fail (selection != nullptr);
832
833 if (DESKTOP_IS_ACTIVE (selection->desktop())) {
834 signal_selection_changed.emit(selection);
835 }
836 }
837
838 void
subselection_changed(SPDesktop * desktop)839 Application::subselection_changed (SPDesktop *desktop)
840 {
841 g_return_if_fail (desktop != nullptr);
842
843 if (DESKTOP_IS_ACTIVE (desktop)) {
844 signal_subselection_changed.emit(desktop);
845 }
846 }
847
848
849 void
selection_set(Inkscape::Selection * selection)850 Application::selection_set (Inkscape::Selection * selection)
851 {
852 g_return_if_fail (selection != nullptr);
853
854 if (DESKTOP_IS_ACTIVE (selection->desktop())) {
855 signal_selection_set.emit(selection);
856 signal_selection_changed.emit(selection);
857 }
858 }
859
860
861 void
eventcontext_set(Inkscape::UI::Tools::ToolBase * eventcontext)862 Application::eventcontext_set (Inkscape::UI::Tools::ToolBase * eventcontext)
863 {
864 g_return_if_fail (eventcontext != nullptr);
865
866 if (DESKTOP_IS_ACTIVE (eventcontext->getDesktop())) {
867 signal_eventcontext_set.emit(eventcontext);
868 }
869 }
870
871
872 void
add_desktop(SPDesktop * desktop)873 Application::add_desktop (SPDesktop * desktop)
874 {
875 g_return_if_fail (desktop != nullptr);
876 if (_desktops == nullptr) {
877 _desktops = new std::vector<SPDesktop*>;
878 }
879
880 if (std::find(_desktops->begin(), _desktops->end(), desktop) != _desktops->end()) {
881 g_error("Attempted to add desktop already in list.");
882 }
883
884 _desktops->insert(_desktops->begin(), desktop);
885
886 signal_activate_desktop.emit(desktop);
887 signal_eventcontext_set.emit(desktop->getEventContext());
888 signal_selection_set.emit(desktop->getSelection());
889 signal_selection_changed.emit(desktop->getSelection());
890 }
891
892
893
894 void
remove_desktop(SPDesktop * desktop)895 Application::remove_desktop (SPDesktop * desktop)
896 {
897 g_return_if_fail (desktop != nullptr);
898
899 if (std::find (_desktops->begin(), _desktops->end(), desktop) == _desktops->end() ) {
900 g_error("Attempted to remove desktop not in list.");
901 }
902
903
904 if (DESKTOP_IS_ACTIVE (desktop)) {
905 signal_deactivate_desktop.emit(desktop);
906 if (_desktops->size() > 1) {
907 SPDesktop * new_desktop = *(++_desktops->begin());
908 _desktops->erase(std::find(_desktops->begin(), _desktops->end(), new_desktop));
909 _desktops->insert(_desktops->begin(), new_desktop);
910
911 signal_activate_desktop.emit(new_desktop);
912 signal_eventcontext_set.emit(new_desktop->getEventContext());
913 signal_selection_set.emit(new_desktop->getSelection());
914 signal_selection_changed.emit(new_desktop->getSelection());
915 } else {
916 signal_eventcontext_set.emit(nullptr);
917 if (desktop->getSelection())
918 desktop->getSelection()->clear();
919 }
920 }
921 desktop->setEventContext("");
922
923 _desktops->erase(std::find(_desktops->begin(), _desktops->end(), desktop));
924
925 // if this was the last desktop, shut down the program
926 if (_desktops->empty()) {
927 this->exit();
928 delete _desktops;
929 _desktops = nullptr;
930 }
931 }
932
933
934
935 void
activate_desktop(SPDesktop * desktop)936 Application::activate_desktop (SPDesktop * desktop)
937 {
938 g_return_if_fail (desktop != nullptr);
939
940 if (DESKTOP_IS_ACTIVE (desktop)) {
941 return;
942 }
943
944 std::vector<SPDesktop*>::iterator i;
945
946 if ((i = std::find (_desktops->begin(), _desktops->end(), desktop)) == _desktops->end()) {
947 g_error("Tried to activate desktop not added to list.");
948 }
949
950 SPDesktop *current = _desktops->front();
951
952 signal_deactivate_desktop.emit(current);
953
954 _desktops->erase (i);
955 _desktops->insert (_desktops->begin(), desktop);
956
957 signal_activate_desktop.emit(desktop);
958 signal_eventcontext_set.emit(desktop->getEventContext());
959 signal_selection_set(desktop->getSelection());
960 signal_selection_changed(desktop->getSelection());
961 }
962
963
964 /**
965 * Resends ACTIVATE_DESKTOP for current desktop; needed when a new desktop has got its window that dialogs will transientize to
966 */
967 void
reactivate_desktop(SPDesktop * desktop)968 Application::reactivate_desktop (SPDesktop * desktop)
969 {
970 g_return_if_fail (desktop != nullptr);
971
972 if (DESKTOP_IS_ACTIVE (desktop)) {
973 signal_activate_desktop.emit(desktop);
974 }
975 }
976
977
978
979 SPDesktop *
find_desktop_by_dkey(unsigned int dkey)980 Application::find_desktop_by_dkey (unsigned int dkey)
981 {
982 for (auto & _desktop : *_desktops) {
983 if (_desktop->dkey == dkey){
984 return _desktop;
985 }
986 }
987 return nullptr;
988 }
989
990
991 unsigned int
maximum_dkey()992 Application::maximum_dkey()
993 {
994 unsigned int dkey = 0;
995
996 for (auto & _desktop : *_desktops) {
997 if (_desktop->dkey > dkey){
998 dkey = _desktop->dkey;
999 }
1000 }
1001 return dkey;
1002 }
1003
1004
1005
1006 SPDesktop *
next_desktop()1007 Application::next_desktop ()
1008 {
1009 SPDesktop *d = nullptr;
1010 unsigned int dkey_current = (_desktops->front())->dkey;
1011
1012 if (dkey_current < maximum_dkey()) {
1013 // find next existing
1014 for (unsigned int i = dkey_current + 1; i <= maximum_dkey(); ++i) {
1015 d = find_desktop_by_dkey (i);
1016 if (d) {
1017 break;
1018 }
1019 }
1020 } else {
1021 // find first existing
1022 for (unsigned int i = 0; i <= maximum_dkey(); ++i) {
1023 d = find_desktop_by_dkey (i);
1024 if (d) {
1025 break;
1026 }
1027 }
1028 }
1029
1030 g_assert (d);
1031 return d;
1032 }
1033
1034
1035
1036 SPDesktop *
prev_desktop()1037 Application::prev_desktop ()
1038 {
1039 SPDesktop *d = nullptr;
1040 unsigned int dkey_current = (_desktops->front())->dkey;
1041
1042 if (dkey_current > 0) {
1043 // find prev existing
1044 for (signed int i = dkey_current - 1; i >= 0; --i) {
1045 d = find_desktop_by_dkey (i);
1046 if (d) {
1047 break;
1048 }
1049 }
1050 }
1051 if (!d) {
1052 // find last existing
1053 d = find_desktop_by_dkey (maximum_dkey());
1054 }
1055
1056 g_assert (d);
1057 return d;
1058 }
1059
1060
1061
1062 void
switch_desktops_next()1063 Application::switch_desktops_next ()
1064 {
1065 next_desktop()->presentWindow();
1066 }
1067
1068 void
switch_desktops_prev()1069 Application::switch_desktops_prev()
1070 {
1071 prev_desktop()->presentWindow();
1072 }
1073
1074 void
external_change()1075 Application::external_change()
1076 {
1077 signal_external_change.emit();
1078 }
1079
1080 /**
1081 * fixme: These need probably signals too
1082 */
1083 void
add_document(SPDocument * document)1084 Application::add_document (SPDocument *document)
1085 {
1086 g_return_if_fail (document != nullptr);
1087
1088 // try to insert the pair into the list
1089 if (!(_document_set.insert(std::make_pair(document, 1)).second)) {
1090 //insert failed, this key (document) is already in the list
1091 for (auto & iter : _document_set) {
1092 if (iter.first == document) {
1093 // found this document in list, increase its count
1094 iter.second ++;
1095 }
1096 }
1097 } else {
1098 // insert succeeded, this document is new.
1099
1100 // Create a selection model tied to the document for running without a GUI.
1101 // We create the model even if there is a GUI as there might not be a window
1102 // tied to the document (which would have its own selection model) as in the
1103 // case where a verb requires a GUI where it's not really needed (conversion
1104 // of verbs to actions will eliminate this need).
1105 g_assert(_selection_models.find(document) == _selection_models.end());
1106 _selection_models[document] = new AppSelectionModel(document);
1107 }
1108 }
1109
1110
1111 // returns true if this was last reference to this document, so you can delete it
1112 bool
remove_document(SPDocument * document)1113 Application::remove_document (SPDocument *document)
1114 {
1115 g_return_val_if_fail (document != nullptr, false);
1116
1117 for (std::map<SPDocument *,int>::iterator iter = _document_set.begin();
1118 iter != _document_set.end();
1119 ++iter) {
1120 if (iter->first == document) {
1121 // found this document in list, decrease its count
1122 iter->second --;
1123 if (iter->second < 1) {
1124 // this was the last one, remove the pair from list
1125 _document_set.erase (iter);
1126
1127 // also remove the selection model
1128 std::map<SPDocument *, AppSelectionModel *>::iterator sel_iter = _selection_models.find(document);
1129 if (sel_iter != _selection_models.end()) {
1130 _selection_models.erase(sel_iter);
1131 }
1132
1133 return true;
1134 } else {
1135 return false;
1136 }
1137 }
1138 }
1139
1140 return false;
1141 }
1142
1143 SPDesktop *
active_desktop()1144 Application::active_desktop()
1145 {
1146 if (!_desktops || _desktops->empty()) {
1147 return nullptr;
1148 }
1149
1150 return _desktops->front();
1151 }
1152
1153 SPDocument *
active_document()1154 Application::active_document()
1155 {
1156 if (SP_ACTIVE_DESKTOP) {
1157 return SP_ACTIVE_DESKTOP->getDocument();
1158 } else if (!_document_set.empty()) {
1159 // If called from the command line there will be no desktop
1160 // So 'fall back' to take the first listed document in the Inkscape instance
1161 return _document_set.begin()->first;
1162 }
1163
1164 return nullptr;
1165 }
1166
1167 bool
sole_desktop_for_document(SPDesktop const & desktop)1168 Application::sole_desktop_for_document(SPDesktop const &desktop) {
1169 SPDocument const* document = desktop.doc();
1170 if (!document) {
1171 return false;
1172 }
1173 for (auto other_desktop : *_desktops) {
1174 SPDocument *other_document = other_desktop->doc();
1175 if ( other_document == document && other_desktop != &desktop ) {
1176 return false;
1177 }
1178 }
1179 return true;
1180 }
1181
1182 Inkscape::UI::Tools::ToolBase *
active_event_context()1183 Application::active_event_context ()
1184 {
1185 if (SP_ACTIVE_DESKTOP) {
1186 return SP_ACTIVE_DESKTOP->getEventContext();
1187 }
1188
1189 return nullptr;
1190 }
1191
1192 Inkscape::ActionContext
active_action_context()1193 Application::active_action_context()
1194 {
1195 if (SP_ACTIVE_DESKTOP) {
1196 return Inkscape::ActionContext(SP_ACTIVE_DESKTOP);
1197 }
1198
1199 SPDocument *doc = active_document();
1200 if (!doc) {
1201 return Inkscape::ActionContext();
1202 }
1203
1204 return action_context_for_document(doc);
1205 }
1206
1207 Inkscape::ActionContext
action_context_for_document(SPDocument * doc)1208 Application::action_context_for_document(SPDocument *doc)
1209 {
1210 // If there are desktops, check them first to see if the document is bound to one of them
1211 if (_desktops != nullptr) {
1212 for (auto desktop : *_desktops) {
1213 if (desktop->doc() == doc) {
1214 return Inkscape::ActionContext(desktop);
1215 }
1216 }
1217 }
1218
1219 // Document is not associated with any desktops - maybe we're in command-line mode
1220 std::map<SPDocument *, AppSelectionModel *>::iterator sel_iter = _selection_models.find(doc);
1221 if (sel_iter == _selection_models.end()) {
1222 std::cout << "Application::action_context_for_document: no selection model" << std::endl;
1223 return Inkscape::ActionContext();
1224 }
1225 return Inkscape::ActionContext(sel_iter->second->getSelection());
1226 }
1227
1228
1229 /*#####################
1230 # HELPERS
1231 #####################*/
1232
1233 void
refresh_display()1234 Application::refresh_display ()
1235 {
1236 for (auto & _desktop : *_desktops) {
1237 _desktop->requestRedraw();
1238 }
1239 }
1240
1241
1242 /**
1243 * Handler for Inkscape's Exit verb. This emits the shutdown signal,
1244 * saves the preferences if appropriate, and quits.
1245 */
1246 void
exit()1247 Application::exit ()
1248 {
1249 //emit shutdown signal so that dialogs could remember layout
1250 signal_shut_down.emit();
1251
1252 Inkscape::Preferences::unload();
1253 //gtk_main_quit ();
1254 }
1255
1256
1257
1258
1259 Inkscape::XML::Node *
get_menus()1260 Application::get_menus()
1261 {
1262 Inkscape::XML::Node *repr = _menus->root();
1263 g_assert (!(strcmp (repr->name(), "inkscape")));
1264 return repr->firstChild();
1265 }
1266
1267 void
get_all_desktops(std::list<SPDesktop * > & listbuf)1268 Application::get_all_desktops(std::list< SPDesktop* >& listbuf)
1269 {
1270 listbuf.insert(listbuf.end(), _desktops->begin(), _desktops->end());
1271 }
1272
1273 } // namespace Inkscape
1274
1275 /*
1276 Local Variables:
1277 mode:c++
1278 c-file-style:"stroustrup"
1279 c-file-offsets:((innamespace . 0)(inline-open . 0))
1280 indent-tabs-mode:nil
1281 fill-column:99
1282 End:
1283 */
1284 // vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
1285