summaryrefslogtreecommitdiffstats
path: root/src/inkscape.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/inkscape.cpp
parentInitial commit. (diff)
downloadinkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz
inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/inkscape.cpp849
1 files changed, 849 insertions, 0 deletions
diff --git a/src/inkscape.cpp b/src/inkscape.cpp
new file mode 100644
index 0000000..c20af1b
--- /dev/null
+++ b/src/inkscape.cpp
@@ -0,0 +1,849 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Interface to main application.
+ */
+/* Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Liam P. White <inkscapebrony@gmail.com>
+ *
+ * Copyright (C) 1999-2014 authors
+ * c++ port Copyright (C) 2003 Nathan Hurst
+ * c++ification Copyright (C) 2014 Liam P. White
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <unistd.h>
+
+#include <map>
+
+#include <glibmm/regex.h>
+
+#include <gtkmm/icontheme.h>
+#include <gtkmm/messagedialog.h>
+
+#include <glibmm/i18n.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/convert.h>
+
+#include "desktop.h"
+#include "device-manager.h"
+#include "document.h"
+#include "inkscape.h"
+#include "message-stack.h"
+#include "path-prefix.h"
+
+#include "debug/simple-event.h"
+#include "debug/event-tracker.h"
+
+#include "io/resource.h"
+#include "io/sys.h"
+
+#include "libnrtype/FontFactory.h"
+
+#include "object/sp-item-group.h"
+#include "object/sp-root.h"
+
+#include "ui/themes.h"
+#include "ui/dialog/debug.h"
+#include "ui/tools/tool-base.h"
+
+#include <fstream>
+
+// Inkscape::Application static members
+Inkscape::Application * Inkscape::Application::_S_inst = nullptr;
+bool Inkscape::Application::_crashIsHappening = false;
+
+#define DESKTOP_IS_ACTIVE(d) (INKSCAPE._desktops != nullptr && !INKSCAPE._desktops->empty() && ((d) == INKSCAPE._desktops->front()))
+
+static void (* segv_handler) (int) = SIG_DFL;
+static void (* abrt_handler) (int) = SIG_DFL;
+static void (* fpe_handler) (int) = SIG_DFL;
+static void (* ill_handler) (int) = SIG_DFL;
+#ifndef _WIN32
+static void (* bus_handler) (int) = SIG_DFL;
+#endif
+
+#define SP_INDENT 8
+
+/** C++ification TODO list
+ * - _S_inst should NOT need to be assigned inside the constructor, but if it isn't the Filters+Extensions menus break.
+ * - 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(),
+ * but if it's a pointer we can stop and return NULL in Application::active_desktop()
+ * - These functions are calling Application::create for no good reason I can determine:
+ *
+ * Inkscape::UI::Dialog::SVGPreview::SVGPreview()
+ * src/ui/dialog/filedialogimpl-gtkmm.cpp:542:9
+ */
+
+
+class InkErrorHandler : public Inkscape::ErrorReporter {
+public:
+ InkErrorHandler(bool useGui) : Inkscape::ErrorReporter(),
+ _useGui(useGui)
+ {}
+ ~InkErrorHandler() override = default;
+
+ void handleError( Glib::ustring const& primary, Glib::ustring const& secondary ) const override
+ {
+ if (_useGui) {
+ Gtk::MessageDialog err(primary, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
+ err.set_secondary_text(secondary);
+ err.run();
+ } else {
+ g_message("%s", primary.data());
+ g_message("%s", secondary.data());
+ }
+ }
+
+private:
+ bool _useGui;
+};
+
+void inkscape_ref(Inkscape::Application & in)
+{
+ in.refCount++;
+}
+
+void inkscape_unref(Inkscape::Application & in)
+{
+ in.refCount--;
+
+ if (&in == Inkscape::Application::_S_inst) {
+ if (in.refCount <= 0) {
+ delete Inkscape::Application::_S_inst;
+ }
+ } else {
+ g_error("Attempt to unref an Application (=%p) not the current instance (=%p) (maybe it's already been destroyed?)",
+ &in, Inkscape::Application::_S_inst);
+ }
+}
+
+namespace Inkscape {
+
+/**
+ * Defined only for debugging purposes. If we are certain the bugs are gone we can remove this
+ * and the references in inkscape_ref and inkscape_unref.
+ */
+Application*
+Application::operator &() const
+{
+ return const_cast<Application*>(this);
+}
+/**
+ * Creates a new Inkscape::Application global object.
+ */
+void
+Application::create(bool use_gui)
+{
+ if (!Application::exists()) {
+ new Application(use_gui);
+ } else {
+ // g_assert_not_reached(); Can happen with InkscapeApplication
+ }
+}
+
+
+/**
+ * Checks whether the current Inkscape::Application global object exists.
+ */
+bool
+Application::exists()
+{
+ return Application::_S_inst != nullptr;
+}
+
+/**
+ * Returns the current Inkscape::Application global object.
+ * \pre Application::_S_inst != NULL
+ */
+Application&
+Application::instance()
+{
+ if (!exists()) {
+ g_error("Inkscape::Application does not yet exist.");
+ }
+ return *Application::_S_inst;
+}
+
+/* \brief Constructor for the application.
+ * Creates a new Inkscape::Application.
+ *
+ * \pre Application::_S_inst == NULL
+ */
+
+Application::Application(bool use_gui) :
+ _use_gui(use_gui)
+{
+ using namespace Inkscape::IO::Resource;
+ /* fixme: load application defaults */
+
+ segv_handler = signal (SIGSEGV, Application::crash_handler);
+ abrt_handler = signal (SIGABRT, Application::crash_handler);
+ fpe_handler = signal (SIGFPE, Application::crash_handler);
+ ill_handler = signal (SIGILL, Application::crash_handler);
+#ifndef _WIN32
+ bus_handler = signal (SIGBUS, Application::crash_handler);
+#endif
+
+ // \TODO: this belongs to Application::init but if it isn't here
+ // then the Filters and Extensions menus don't work.
+ _S_inst = this;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ InkErrorHandler* handler = new InkErrorHandler(use_gui);
+ prefs->setErrorHandler(handler);
+ {
+ Glib::ustring msg;
+ Glib::ustring secondary;
+ if (prefs->getLastError( msg, secondary )) {
+ handler->handleError(msg, secondary);
+ }
+ }
+
+ if (use_gui) {
+ using namespace Inkscape::IO::Resource;
+ auto icon_theme = Gtk::IconTheme::get_default();
+ icon_theme->prepend_search_path(get_path_ustring(SYSTEM, ICONS));
+ icon_theme->prepend_search_path(get_path_ustring(USER, ICONS));
+ themecontext = new Inkscape::UI::ThemeContext();
+ themecontext->add_gtk_css(false);
+ auto scale = prefs->getDoubleLimited("/theme/fontscale", 100, 50, 150);
+ themecontext->adjust_global_font_scale(scale / 100.0);
+ Inkscape::DeviceManager::getManager().loadConfig();
+ }
+
+ /* set language for user interface according setting in preferences */
+ Glib::ustring ui_language = prefs->getString("/ui/language");
+ if(!ui_language.empty())
+ {
+ setenv("LANGUAGE", ui_language, true);
+#ifdef _WIN32
+ // locale may be set to C with some Windows Region Formats (like English(Europe)).
+ // forcing the LANGUAGE variable to be ignored
+ // see :guess_category_value:gettext-runtime/intl/dcigettext.c,
+ // and :gl_locale_name_from_win32_LANGID:gettext-runtime/gnulib-lib/localename.c
+ setenv("LANG", ui_language, true);
+#endif
+ }
+
+ /* DebugDialog redirection. On Linux, default to OFF, on Win32, default to ON.
+ * Use only if use_gui is enabled
+ */
+#ifdef _WIN32
+#define DEFAULT_LOG_REDIRECT true
+#else
+#define DEFAULT_LOG_REDIRECT false
+#endif
+
+ if (use_gui && prefs->getBool("/dialogs/debug/redirect", DEFAULT_LOG_REDIRECT))
+ {
+ Inkscape::UI::Dialog::DebugDialog::getInstance()->captureLogMessages();
+ }
+
+ if (use_gui)
+ {
+ Inkscape::UI::Tools::init_latin_keys_group();
+ /* Check for global remapping of Alt key */
+ mapalt(guint(prefs->getInt("/options/mapalt/value", 0)));
+ trackalt(guint(prefs->getInt("/options/trackalt/value", 0)));
+
+ /* update highlight colors when theme changes */
+ themecontext->getChangeThemeSignal().connect([=](){
+ if (auto desktop = active_desktop()) {
+ set_default_highlight_colors(themecontext->getHighlightColors(desktop->getToplevel()));
+ }
+ });
+ }
+
+ /* Initialize font factory */
+ font_factory *factory = font_factory::Default();
+ if (prefs->getBool("/options/font/use_fontsdir_system", true)) {
+ char const *fontsdir = get_path(SYSTEM, FONTS);
+ factory->AddFontsDir(fontsdir);
+ }
+ if (prefs->getBool("/options/font/use_fontsdir_user", true)) {
+ char const *fontsdir = get_path(USER, FONTS);
+ factory->AddFontsDir(fontsdir);
+ }
+ Glib::ustring fontdirs_pref = prefs->getString("/options/font/custom_fontdirs");
+ std::vector<Glib::ustring> fontdirs = Glib::Regex::split_simple("\\|", fontdirs_pref);
+ for (auto &fontdir : fontdirs) {
+ factory->AddFontsDir(fontdir.c_str());
+ }
+}
+
+Application::~Application()
+{
+ if (_desktops) {
+ g_error("FATAL: desktops still in list on application destruction!");
+ }
+
+ Inkscape::Preferences::unload();
+
+ _S_inst = nullptr; // this will probably break things
+
+ refCount = 0;
+ // gtk_main_quit ();
+}
+
+/** Sets the keyboard modifier to map to Alt.
+ *
+ * Zero switches off mapping, as does '1', which is the default.
+ */
+void Application::mapalt(guint maskvalue)
+{
+ if ( maskvalue < 2 || maskvalue > 5 ) { // MOD5 is the highest defined in gdktypes.h
+ _mapalt = 0;
+ } else {
+ _mapalt = (GDK_MOD1_MASK << (maskvalue-1));
+ }
+}
+
+void
+Application::crash_handler (int /*signum*/)
+{
+ using Inkscape::Debug::SimpleEvent;
+ using Inkscape::Debug::EventTracker;
+ using Inkscape::Debug::Logger;
+
+ static bool recursion = false;
+
+ /*
+ * reset all signal handlers: any further crashes should just be allowed
+ * to crash normally.
+ * */
+ signal (SIGSEGV, segv_handler );
+ signal (SIGABRT, abrt_handler );
+ signal (SIGFPE, fpe_handler );
+ signal (SIGILL, ill_handler );
+#ifndef _WIN32
+ signal (SIGBUS, bus_handler );
+#endif
+
+ /* Stop bizarre loops */
+ if (recursion) {
+ abort ();
+ }
+ recursion = true;
+
+ _crashIsHappening = true;
+
+ EventTracker<SimpleEvent<Inkscape::Debug::Event::CORE> > tracker("crash");
+ tracker.set<SimpleEvent<> >("emergency-save");
+
+ fprintf(stderr, "\nEmergency save activated!\n");
+
+ time_t sptime = time (nullptr);
+ struct tm *sptm = localtime (&sptime);
+ gchar sptstr[256];
+ strftime(sptstr, 256, "%Y_%m_%d_%H_%M_%S", sptm);
+
+ gint count = 0;
+ gchar *curdir = g_get_current_dir(); // This one needs to be freed explicitly
+ std::vector<gchar *> savednames;
+ std::vector<gchar *> failednames;
+ for (std::map<SPDocument*,int>::iterator iter = INKSCAPE._document_set.begin(), e = INKSCAPE._document_set.end();
+ iter != e;
+ ++iter) {
+ SPDocument *doc = iter->first;
+ Inkscape::XML::Node *repr;
+ repr = doc->getReprRoot();
+ if (doc->isModifiedSinceSave()) {
+ const gchar *docname;
+ char n[64];
+
+ /* originally, the document name was retrieved from
+ * the sodipod:docname attribute */
+ docname = doc->getDocumentName();
+ if (docname) {
+ /* Removes an emergency save suffix if present: /(.*)\.[0-9_]*\.[0-9_]*\.[~\.]*$/\1/ */
+ const char* d0 = strrchr ((char*)docname, '.');
+ if (d0 && (d0 > docname)) {
+ const char* d = d0;
+ unsigned int dots = 0;
+ while ((isdigit (*d) || *d=='_' || *d=='.') && d>docname && dots<2) {
+ d -= 1;
+ if (*d=='.') dots++;
+ }
+ if (*d=='.' && d>docname && dots==2) {
+ size_t len = MIN (d - docname, 63);
+ memcpy (n, docname, len);
+ n[len] = '\0';
+ docname = n;
+ }
+ }
+ }
+ if (!docname || !*docname) docname = "emergency";
+
+ // Emergency filename
+ char c[1024];
+ g_snprintf (c, 1024, "%.256s.%s.%d.svg", docname, sptstr, count);
+
+ const char* document_filename = doc->getDocumentFilename();
+ char* document_base = nullptr;
+ if (document_filename) {
+ document_base = g_path_get_dirname(document_filename);
+ }
+
+ // Find a location
+ const char* locations[] = {
+ // Don't use getDocumentBase as that also can be unsaved template locations.
+ document_base,
+ g_get_home_dir(),
+ g_get_tmp_dir(),
+ curdir,
+ };
+ FILE *file = nullptr;
+ for(auto & location : locations) {
+ if (!location) continue; // It seems to be okay, but just in case
+ gchar * filename = g_build_filename(location, c, nullptr);
+ Inkscape::IO::dump_fopen_call(filename, "E");
+ file = Inkscape::IO::fopen_utf8name(filename, "w");
+ if (file) {
+ g_snprintf (c, 1024, "%s", filename); // we want the complete path to be stored in c (for reporting purposes)
+ break;
+ }
+ }
+ if (document_base) {
+ g_free(document_base);
+ }
+
+ // Save
+ if (file) {
+ sp_repr_save_stream (repr->document(), file, SP_SVG_NS_URI);
+ savednames.push_back(g_strdup (c));
+ fclose (file);
+ } else {
+ failednames.push_back((doc->getDocumentName()) ? g_strdup(doc->getDocumentName()) : g_strdup (_("Untitled document")));
+ }
+ count++;
+ }
+ }
+ g_free(curdir);
+
+ if (!savednames.empty()) {
+ fprintf (stderr, "\nEmergency save document locations:\n");
+ for (auto i:savednames) {
+ fprintf (stderr, " %s\n", i);
+ }
+ }
+ if (!failednames.empty()) {
+ fprintf (stderr, "\nFailed to do emergency save for documents:\n");
+ for (auto i:failednames) {
+ fprintf (stderr, " %s\n", i);
+ }
+ }
+
+ // do not save the preferences since they can be in a corrupted state
+ Inkscape::Preferences::unload(false);
+
+ fprintf (stderr, "Emergency save completed. Inkscape will close now.\n");
+ fprintf (stderr, "If you can reproduce this crash, please file a bug at https://inkscape.org/report\n");
+ fprintf (stderr, "with a detailed description of the steps leading to the crash, so we can fix it.\n");
+
+ /* Show nice dialog box */
+
+ char const *istr = _("Inkscape encountered an internal error and will close now.\n");
+ char const *sstr = _("Automatic backups of unsaved documents were done to the following locations:\n");
+ char const *fstr = _("Automatic backup of the following documents failed:\n");
+ gint nllen = strlen ("\n");
+ gint len = strlen (istr) + strlen (sstr) + strlen (fstr);
+ for (auto i:savednames) {
+ len = len + SP_INDENT + strlen (i) + nllen;
+ }
+ for (auto i:failednames) {
+ len = len + SP_INDENT + strlen (i) + nllen;
+ }
+ len += 1;
+ gchar *b = g_new (gchar, len);
+ gint pos = 0;
+ len = strlen (istr);
+ memcpy (b + pos, istr, len);
+ pos += len;
+ if (!savednames.empty()) {
+ len = strlen (sstr);
+ memcpy (b + pos, sstr, len);
+ pos += len;
+ for (auto i:savednames) {
+ memset (b + pos, ' ', SP_INDENT);
+ pos += SP_INDENT;
+ len = strlen(i);
+ memcpy (b + pos, i, len);
+ pos += len;
+ memcpy (b + pos, "\n", nllen);
+ pos += nllen;
+ }
+ }
+ if (!failednames.empty()) {
+ len = strlen (fstr);
+ memcpy (b + pos, fstr, len);
+ pos += len;
+ for (auto i:failednames) {
+ memset (b + pos, ' ', SP_INDENT);
+ pos += SP_INDENT;
+ len = strlen(i);
+ memcpy (b + pos, i, len);
+ pos += len;
+ memcpy (b + pos, "\n", nllen);
+ pos += nllen;
+ }
+ }
+ *(b + pos) = '\0';
+
+ if ( exists() && instance().use_gui() ) {
+ GtkWidget *msgbox = gtk_message_dialog_new (nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", b);
+ gtk_dialog_run (GTK_DIALOG (msgbox));
+ gtk_widget_destroy (msgbox);
+ }
+ else
+ {
+ g_message( "Error: %s", b );
+ }
+ g_free (b);
+
+ tracker.clear();
+ Logger::shutdown();
+
+ fflush(stderr); // make sure buffers are empty before crashing (otherwise output might be suppressed)
+
+ /* on exit, allow restored signal handler to take over and crash us */
+}
+
+
+void
+Application::add_desktop (SPDesktop * desktop)
+{
+ g_return_if_fail (desktop != nullptr);
+ if (_desktops == nullptr) {
+ _desktops = new std::vector<SPDesktop*>;
+ }
+
+ if (std::find(_desktops->begin(), _desktops->end(), desktop) != _desktops->end()) {
+ g_error("Attempted to add desktop already in list.");
+ }
+
+ _desktops->insert(_desktops->begin(), desktop);
+
+ signal_activate_desktop.emit(desktop);
+ signal_selection_set.emit(desktop->getSelection());
+ signal_selection_changed.emit(desktop->getSelection());
+}
+
+
+
+void
+Application::remove_desktop (SPDesktop * desktop)
+{
+ g_return_if_fail (desktop != nullptr);
+
+ if (std::find (_desktops->begin(), _desktops->end(), desktop) == _desktops->end() ) {
+ g_error("Attempted to remove desktop not in list.");
+ }
+
+
+ if (DESKTOP_IS_ACTIVE (desktop)) {
+ signal_deactivate_desktop.emit(desktop);
+ if (_desktops->size() > 1) {
+ SPDesktop * new_desktop = *(++_desktops->begin());
+ _desktops->erase(std::find(_desktops->begin(), _desktops->end(), new_desktop));
+ _desktops->insert(_desktops->begin(), new_desktop);
+
+ signal_activate_desktop.emit(new_desktop);
+ signal_selection_set.emit(new_desktop->getSelection());
+ signal_selection_changed.emit(new_desktop->getSelection());
+ } else {
+ if (desktop->getSelection())
+ desktop->getSelection()->clear();
+ }
+ }
+
+ _desktops->erase(std::find(_desktops->begin(), _desktops->end(), desktop));
+
+ // if this was the last desktop, shut down the program
+ if (_desktops->empty()) {
+ this->exit();
+ delete _desktops;
+ _desktops = nullptr;
+ }
+}
+
+
+
+void
+Application::activate_desktop (SPDesktop * desktop)
+{
+ g_return_if_fail (desktop != nullptr);
+
+ if (DESKTOP_IS_ACTIVE (desktop)) {
+ return;
+ }
+
+ std::vector<SPDesktop*>::iterator i;
+
+ if ((i = std::find (_desktops->begin(), _desktops->end(), desktop)) == _desktops->end()) {
+ g_error("Tried to activate desktop not added to list.");
+ }
+
+ SPDesktop *current = _desktops->front();
+
+ signal_deactivate_desktop.emit(current);
+
+ _desktops->erase (i);
+ _desktops->insert (_desktops->begin(), desktop);
+
+ signal_activate_desktop.emit(desktop);
+ signal_selection_set(desktop->getSelection());
+ signal_selection_changed(desktop->getSelection());
+}
+
+
+/**
+ * Resends ACTIVATE_DESKTOP for current desktop; needed when a new desktop has got its window that dialogs will transientize to
+ */
+void
+Application::reactivate_desktop (SPDesktop * desktop)
+{
+ g_return_if_fail (desktop != nullptr);
+
+ if (DESKTOP_IS_ACTIVE (desktop)) {
+ signal_activate_desktop.emit(desktop);
+ }
+}
+
+
+
+SPDesktop *
+Application::find_desktop_by_dkey (unsigned int dkey)
+{
+ for (auto & _desktop : *_desktops) {
+ if (_desktop->dkey == dkey){
+ return _desktop;
+ }
+ }
+ return nullptr;
+}
+
+
+unsigned int
+Application::maximum_dkey()
+{
+ unsigned int dkey = 0;
+
+ for (auto & _desktop : *_desktops) {
+ if (_desktop->dkey > dkey){
+ dkey = _desktop->dkey;
+ }
+ }
+ return dkey;
+}
+
+
+
+SPDesktop *
+Application::next_desktop ()
+{
+ SPDesktop *d = nullptr;
+ unsigned int dkey_current = (_desktops->front())->dkey;
+
+ if (dkey_current < maximum_dkey()) {
+ // find next existing
+ for (unsigned int i = dkey_current + 1; i <= maximum_dkey(); ++i) {
+ d = find_desktop_by_dkey (i);
+ if (d) {
+ break;
+ }
+ }
+ } else {
+ // find first existing
+ for (unsigned int i = 0; i <= maximum_dkey(); ++i) {
+ d = find_desktop_by_dkey (i);
+ if (d) {
+ break;
+ }
+ }
+ }
+
+ g_assert (d);
+ return d;
+}
+
+
+
+SPDesktop *
+Application::prev_desktop ()
+{
+ SPDesktop *d = nullptr;
+ unsigned int dkey_current = (_desktops->front())->dkey;
+
+ if (dkey_current > 0) {
+ // find prev existing
+ for (signed int i = dkey_current - 1; i >= 0; --i) {
+ d = find_desktop_by_dkey (i);
+ if (d) {
+ break;
+ }
+ }
+ }
+ if (!d) {
+ // find last existing
+ d = find_desktop_by_dkey (maximum_dkey());
+ }
+
+ g_assert (d);
+ return d;
+}
+
+
+
+void
+Application::switch_desktops_next ()
+{
+ next_desktop()->presentWindow();
+}
+
+void
+Application::switch_desktops_prev()
+{
+ prev_desktop()->presentWindow();
+}
+
+void
+Application::external_change()
+{
+ signal_external_change.emit();
+}
+
+/**
+ * fixme: These need probably signals too
+ */
+void
+Application::add_document (SPDocument *document)
+{
+ g_return_if_fail (document != nullptr);
+
+ // try to insert the pair into the list
+ if (!(_document_set.insert(std::make_pair(document, 1)).second)) {
+ //insert failed, this key (document) is already in the list
+ for (auto & iter : _document_set) {
+ if (iter.first == document) {
+ // found this document in list, increase its count
+ iter.second ++;
+ }
+ }
+ }
+}
+
+
+// returns true if this was last reference to this document, so you can delete it
+bool
+Application::remove_document (SPDocument *document)
+{
+ g_return_val_if_fail (document != nullptr, false);
+
+ for (std::map<SPDocument *,int>::iterator iter = _document_set.begin();
+ iter != _document_set.end();
+ ++iter) {
+ if (iter->first == document) {
+ // found this document in list, decrease its count
+ iter->second --;
+ if (iter->second < 1) {
+ // this was the last one, remove the pair from list
+ _document_set.erase (iter);
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return false;
+}
+
+SPDesktop *
+Application::active_desktop()
+{
+ if (!_desktops || _desktops->empty()) {
+ return nullptr;
+ }
+
+ return _desktops->front();
+}
+
+SPDocument *
+Application::active_document()
+{
+ if (SP_ACTIVE_DESKTOP) {
+ return SP_ACTIVE_DESKTOP->getDocument();
+ } else if (!_document_set.empty()) {
+ // If called from the command line there will be no desktop
+ // So 'fall back' to take the first listed document in the Inkscape instance
+ return _document_set.begin()->first;
+ }
+
+ return nullptr;
+}
+
+bool
+Application::sole_desktop_for_document(SPDesktop const &desktop) {
+ SPDocument const* document = desktop.doc();
+ if (!document) {
+ return false;
+ }
+ for (auto other_desktop : *_desktops) {
+ SPDocument *other_document = other_desktop->doc();
+ if ( other_document == document && other_desktop != &desktop ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*#####################
+# HELPERS
+#####################*/
+
+void
+Application::refresh_display ()
+{
+ for (auto & _desktop : *_desktops) {
+ _desktop->requestRedraw();
+ }
+}
+
+
+/**
+ * Handler for Inkscape's Exit verb. This emits the shutdown signal,
+ * saves the preferences if appropriate, and quits.
+ */
+void
+Application::exit ()
+{
+ //emit shutdown signal so that dialogs could remember layout
+ signal_shut_down.emit();
+
+ Inkscape::Preferences::unload();
+ //gtk_main_quit ();
+}
+
+void
+Application::get_all_desktops(std::list< SPDesktop* >& listbuf)
+{
+ listbuf.insert(listbuf.end(), _desktops->begin(), _desktops->end());
+}
+
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :