Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
inkscape-application.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * The main Inkscape application.
4 *
5 * Copyright (C) 2018 Tavmjong Bah
6 *
7 * The contents of this file may be used under the GNU General Public License Version 2 or later.
8 */
9
10/* Application flow:
11 * main() -> InkscapeApplication::singleton().gio_app()->run(argc, argv);
12 *
13 * InkscapeApplication::InkscapeApplication
14 * Initialized: GC, Debug, Gettext, Autosave, Actions, Commandline
15 * InkscapeApplication::on_handle_local_options
16 * InkscapeApplication::parse_actions
17 *
18 * -- Switch to main instance if new Inkscape instance is merged with existing instance. --
19 * New instances are merged with existing instance unless app_id is changed, see below.
20 *
21 * InkscapeApplication::on_startup | Only called for main instance
22 *
23 * InkscapeApplication::on_activate (no file specified) OR InkscapeApplication::on_open (file specified)
24 * InkscapeApplication::process_document | Will use command-line actions from main instance!
25 *
26 * InkscapeApplication::create_window (document)
27 * Inkscape::Shortcuts
28 *
29 * InkscapeApplication::create_window (file) |
30 * InkscapeApplication::create_window (document) | Open/Close document
31 * InkscapeApplication::destroy_window |
32 *
33 * InkscapeApplication::on_quit
34 * InkscapeApplication::destroy_all
35 * InkscapeApplication::destroy_window
36 */
37
39
40#include <iostream>
41#include <fstream>
42#include <iomanip>
43#include <cerrno> // History file
44#include <regex>
45#include <numeric>
46#include <unistd.h>
47#include <chrono>
48#include <thread>
49
50#include <giomm/file.h>
51#include <glibmm/i18n.h> // Internationalization
52#include <gtkmm/application.h>
53#include <gtkmm/recentmanager.h>
54
56#include "inkscape-window.h"
57#include "auto-save.h" // Auto-save
58#include "desktop.h" // Access to window
59#include "document.h"
60#include "file.h" // sp_file_convert_dpi
61#include "inkscape.h" // Inkscape::Application
62#include "object/sp-namedview.h"
63#include "selection.h"
64#include "path-prefix.h" // Data directory
65
86#include "debug/logger.h" // INKSCAPE_DEBUG_LOG support
87#include "extension/db.h"
88#include "extension/effect.h"
89#include "extension/init.h"
90#include "extension/input.h"
91#include "helper/gettext.h" // gettext init
92#include "inkgc/gc-core.h" // Garbage Collecting init
93#include "io/file.h" // File open (command line).
94#include "io/fix-broken-links.h" // Fix up references.
95#include "io/resource.h" // TEMPLATE
96#include "object/sp-root.h" // Inkscape version.
97#include "ui/desktop/document-check.h" // Check for data loss on closing document window.
98#include "ui/dialog-run.h"
99#include "ui/dialog/dialog-manager.h" // Save state
100#include "ui/dialog/font-substitution.h" // Warn user about font substitution.
101#include "ui/dialog/startup.h"
102#include "ui/interface.h" // sp_ui_error_dialog
103#include "ui/tools/shortcuts.h"
105#include "util/scope_exit.h"
106
107#ifdef WITH_GNU_READLINE
108#include <readline/readline.h>
109#include <readline/history.h>
110#endif
111
113
114// This is a bit confusing as there are two ways to handle command line arguments and files
115// depending on if the Gio::Application::Flags::HANDLES_OPEN and/or Gio::Application::Flags::HANDLES_COMMAND_LINE
116// flags are set. If the open flag is set and the command line not, the all the remainng arguments
117// after calling on_handle_local_options() are assumed to be filenames.
118
119// Add document to app.
120SPDocument *InkscapeApplication::document_add(std::unique_ptr<SPDocument> document)
121{
122 assert(document);
123 auto [it, inserted] = _documents.try_emplace(std::move(document));
124 assert(inserted);
125 INKSCAPE.add_document(it->first.get());
126 return it->first.get();
127}
128
129// New document, add it to app. TODO: This should really be open_document with option to strip template data.
130SPDocument *InkscapeApplication::document_new(std::string const &template_filename)
131{
132 if (template_filename.empty()) {
134 if (!def.empty()) {
135 return document_new(def);
136 }
137 }
138
139 // Open file
140 auto doc_uniq = ink_file_new(template_filename);
141 if (!doc_uniq) {
142 std::cerr << "InkscapeApplication::new_document: failed to open new document!" << std::endl;
143 return nullptr;
144 }
145
146 auto doc = document_add(std::move(doc_uniq));
147
148 // Set viewBox if it doesn't exist.
149 if (!doc->getRoot()->viewBox_set) {
150 doc->setViewBox();
151 }
152
153 return doc;
154}
155
156// Open a document, add it to app.
157std::pair<SPDocument *, bool> InkscapeApplication::document_open(Glib::RefPtr<Gio::File> const &file)
158{
159 // Open file
160 auto [document, cancelled] = ink_file_open(file);
161 if (cancelled) {
162 return {nullptr, true};
163 }
164 if (!document) {
165 std::cerr << "InkscapeApplication::document_open: Failed to open: " << file->get_parse_name().raw() << std::endl;
166 return {nullptr, false};
167 }
168
169 document->setVirgin(false); // Prevents replacing document in same window during file open.
170
171 // Add/promote recent file; when we call add_item and file is on a recent list already,
172 // then apparently only "modified" time changes.
173 if (auto recentmanager = Gtk::RecentManager::get_default()) {
174 auto uri = file->get_uri();
175 auto path = file->get_path();
176 // Opening crash files, we can link them back using the recent files manager
177 // to get the original context for the file.
178 bool is_crash = false;
179 try {
180 auto orig = recentmanager->lookup_item(uri);
181 is_crash = orig->has_group("Crash");
182 if (is_crash) {
183 document->setModifiedSinceSave(true);
184 // Crash files store the original name in the display name field.
185 auto old_path = Inkscape::IO::find_original_file(path, Glib::filename_from_utf8(orig->get_display_name()));
186 document->setDocumentFilename(old_path.empty() ? nullptr : old_path.c_str());
187 // We don't want other programs to gain access to this crash file
188 recentmanager->remove_item(uri);
189 }
190 } catch (Glib::Error const &) {
191 // Nothing to do since lookup failed.
192 }
193 if (!is_crash) {
194 recentmanager->add_item(uri);
195 }
196 }
197
198 return {document_add(std::move(document)), false};
199}
200
201// Open a document, add it to app.
202SPDocument *InkscapeApplication::document_open(std::span<char const> buffer)
203{
204 // Open file
205 auto document = ink_file_open(buffer);
206
207 if (!document) {
208 std::cerr << "InkscapeApplication::document_open: Failed to open memory document." << std::endl;
209 return nullptr;
210 }
211
212 document->setVirgin(false); // Prevents replacing document in same window during file open.
213
214 return document_add(std::move(document));
215}
216
223{
224 if (!document || !desktop) {
225 std::cerr << "InkscapeAppliation::swap_document: Missing desktop or document!" << std::endl;
226 return false;
227 }
228
229 auto old_document = desktop->getDocument();
230 desktop->change_document(document);
231
232 // We need to move window from the old document to the new document.
233
234 // Find old document
235 auto doc_it = _documents.find(old_document);
236 if (doc_it == _documents.end()) {
237 std::cerr << "InkscapeApplication::swap_document: Old document not in map!" << std::endl;
238 return false;
239 }
240
241 // Remove desktop from document map.
242 auto dt_it = std::find_if(doc_it->second.begin(), doc_it->second.end(), [=] (auto &dt) { return dt.get() == desktop; });
243 if (dt_it == doc_it->second.end()) {
244 std::cerr << "InkscapeApplication::swap_document: Desktop not found!" << std::endl;
245 return false;
246 }
247
248 auto dt_uniq = std::move(*dt_it);
249 doc_it->second.erase(dt_it);
250
251 // Find new document
252 doc_it = _documents.find(document);
253 if (doc_it == _documents.end()) {
254 std::cerr << "InkscapeApplication::swap_document: New document not in map!" << std::endl;
255 return false;
256 }
257
258 doc_it->second.push_back(std::move(dt_uniq));
259
260 _active_document = document;
261 return true;
262}
263
267{
268 // Find saved document.
269 char const *path = document->getDocumentFilename();
270 if (!path) {
271 std::cerr << "InkscapeApplication::revert_document: Document never saved, cannot revert." << std::endl;
272 return false;
273 }
274
275 // Open saved document.
276 auto file = Gio::File::create_for_path(document->getDocumentFilename());
277 auto [new_document, cancelled] = document_open(file);
278 if (!new_document) {
279 if (!cancelled) {
280 std::cerr << "InkscapeApplication::revert_document: Cannot open saved document!" << std::endl;
281 }
282 return false;
283 }
284
285 // Allow overwriting current document.
286 document->setVirgin(true);
287
288 auto it = _documents.find(document);
289 if (it == _documents.end()) {
290 std::cerr << "InkscapeApplication::revert_document: Document not found!" << std::endl;
291 return false;
292 }
293
294 // Acquire list of desktops attached to old document. (They are about to get moved around.)
295 std::vector<SPDesktop *> desktops;
296 for (auto const &desktop : it->second) {
297 desktops.push_back(desktop.get());
298 }
299
300 // Swap reverted document in all windows.
301 for (auto const desktop : desktops) {
302 // Remember current zoom and view.
303 double zoom = desktop->current_zoom();
305
306 bool reverted = document_swap(desktop, new_document);
307
308 if (reverted) {
309 desktop->zoom_absolute(c, zoom, false);
310 // Update LPE and fix legacy LPE system.
312 } else {
313 std::cerr << "InkscapeApplication::revert_document: Revert failed!" << std::endl;
314 }
315 }
316
317 document_close(document);
318
319 return true;
320}
321
326{
327 if (!document) {
328 std::cerr << "InkscapeApplication::close_document: No document!" << std::endl;
329 return;
330 }
331
332 auto it = _documents.find(document);
333 if (it == _documents.end()) {
334 std::cerr << "InkscapeApplication::close_document: Document not registered with application." << std::endl;
335 return;
336 }
337
338 if (!it->second.empty()) {
339 std::cerr << "InkscapeApplication::close_document: Window vector not empty!" << std::endl;
340 }
341
342 INKSCAPE.remove_document(it->first.get());
343 _documents.erase(it);
344}
345
349{
350 // Most fixes are handled when document is opened in SPDocument::createDoc().
351 // But some require the GUI to be present. These are handled here.
352
353 if (_with_gui) {
354
355 auto document = desktop->getDocument();
356
357 // Perform a fixup pass for hrefs.
358 if (Inkscape::fixBrokenLinks(document)) {
359 desktop->showInfoDialog(_("Broken links have been changed to point to existing files."));
360 }
361
362 // Fix dpi (pre-92 files).
363 if (document->getRoot()->inkscape.getVersion().isInsideRangeInclusive({0, 1}, {0, 92})) {
364 sp_file_convert_dpi(document);
365 }
366
367 // Update LPE and fix legacy LPE system.
368 sp_file_fix_lpe(document);
369
370 // Check for font substitutions, requires text to have been rendered.
372 }
373}
374
378std::vector<SPDocument *> InkscapeApplication::get_documents()
379{
380 std::vector<SPDocument *> result;
381
382 for (auto const &[doc, _] : _documents) {
383 result.push_back(doc.get());
384 }
385
386 return result;
387}
388
389// Take an already open document and create a new window, adding window to document map.
391{
392 assert(document);
393 // Once we've removed Inkscape::Application (separating GUI from non-GUI stuff)
394 // it will be more easy to start up the GUI after-the-fact. Until then, prevent
395 // opening a window if GUI not selected at start-up time.
396 if (!_with_gui) {
397 std::cerr << "InkscapeApplication::window_open: Not in gui mode!" << std::endl;
398 return nullptr;
399 }
400
401 auto const doc_it = _documents.find(document);
402 if (doc_it == _documents.end()) {
403 std::cerr << "InkscapeApplication::window_open: Document not in map!" << std::endl;
404 return nullptr;
405 }
406
407 auto const desktop = doc_it->second.emplace_back(std::make_unique<SPDesktop>(document->getNamedView())).get();
408 INKSCAPE.add_desktop(desktop);
409
410 if (_active_window) { // Divert all opened documents to new tabs if possible.
412 } else {
413 auto const win = _windows.emplace_back(std::make_unique<InkscapeWindow>(desktop)).get();
414
415 _active_window = win;
416 assert(_active_desktop == desktop);
418 assert(_active_document == document);
419
420 // Resize the window to match the document properties
422
423 win->present();
424 }
425
426 document_fix(desktop); // May need flag to prevent this from being called more than once.
427
428 return desktop;
429}
430
431// Close a window. Does not delete document.
433{
434 if (!desktop) {
435 std::cerr << "InkscapeApplication::close_window: No desktop!" << std::endl;
436 return;
437 }
438
439 auto document = desktop->getDocument();
440 assert(document);
441
442 // Leave active document alone (maybe should find new active window and reset variables).
443 _active_selection = nullptr;
444 _active_desktop = nullptr;
445
446 // Remove desktop from document map.
447 auto doc_it = _documents.find(document);
448 if (doc_it == _documents.end()) {
449 std::cerr << "InkscapeApplication::close_window: document not in map!" << std::endl;
450 return;
451 }
452
453 auto dt_it = std::find_if(doc_it->second.begin(), doc_it->second.end(), [=] (auto &dt) { return dt.get() == desktop; });
454 if (dt_it == doc_it->second.end()) {
455 std::cerr << "InkscapeApplication::close_window: desktop not found!" << std::endl;
456 return;
457 }
458
459 if (get_number_of_windows() == 1) {
460 // Persist layout of docked and floating dialogs before deleting the last window.
462 }
463
464 auto win = desktop->getInkscapeWindow();
465
467
468 INKSCAPE.remove_desktop(desktop); // clears selection and event_context
469 doc_it->second.erase(dt_it); // Results in call to SPDesktop::destroy()
470}
471
472// Closes active window (useful for scripting).
474{
475 if (!_active_desktop) {
476 std::cerr << "InkscapeApplication::window_close_active: no active window!" << std::endl;
477 return;
478 }
480}
481
484{
485 std::cout << "InkscapeApplication::dump()" << std::endl;
486 std::cout << " Documents: " << _documents.size() << std::endl;
487 for (auto const &[doc, desktops] : _documents) {
488 std::cout << " Document: " << (doc->getDocumentName() ? doc->getDocumentName() : "unnamed") << std::endl;
489 for (auto const &dt : desktops) {
490 std::cout << " Desktop: " << dt.get() << std::endl;
491 }
492 }
493 std::cout << " Windows: " << _windows.size() << std::endl;
494 for (auto const &win : _windows) {
495 std::cout << " Window: " << win->get_title() << std::endl;
496 for (auto dt : win->get_desktop_widget()->get_desktops()) {
497 std::cout << " Desktop: " << dt << std::endl;
498 }
499 }
500}
501
503
508
509void InkscapeApplication::_start_main_option_section(Glib::ustring const &section_name)
510{
511#ifndef _WIN32
512 // Avoid outputting control characters to non-tty destinations.
513 //
514 // However, isatty() is not useful on Windows
515 // - it doesn't recognize mintty and similar terminals
516 // - it doesn't work in cmd.exe either, where we have to use the inkscape.com wrapper, connecting stdout to a pipe
517 if (!isatty(fileno(stdout))) {
518 return;
519 }
520#endif
521
522 auto *gapp = gio_app();
523
524 if (section_name.empty()) {
525 gapp->add_main_option_entry(Gio::Application::OptionType::BOOL, Glib::ustring("\b\b "));
526 } else {
527 gapp->add_main_option_entry(Gio::Application::OptionType::BOOL, Glib::ustring("\b\b \n") + section_name + ":");
528 }
529}
530
532{
533 if (_instance) {
534 std::cerr << "Multiple instances of InkscapeApplication" << std::endl;
535 std::terminate();
536 }
537 _instance = this;
538
539 using T = Gio::Application;
540
541 auto app_id = Glib::ustring("org.inkscape.Inkscape");
542 auto flags = Gio::Application::Flags::HANDLES_OPEN | // Use default file opening.
543 Gio::Application::Flags::CAN_OVERRIDE_APP_ID;
544 auto non_unique = false;
545
546 // Allow an independent instance of Inkscape to run. Will have matching DBus name and paths
547 // (e.g org.inkscape.Inkscape.tag, /org/inkscape/Inkscape/tag/window/1).
548 // If this flag isn't set, any new instance of Inkscape will be merged with the already running
549 // instance of Inkscape before on_open() or on_activate() is called.
550 if (Glib::getenv("INKSCAPE_APP_ID_TAG") != "") {
551 flags |= Gio::Application::Flags::CAN_OVERRIDE_APP_ID;
552 app_id += "." + Glib::getenv("INKSCAPE_APP_ID_TAG");
553 if (!Gio::Application::id_is_valid(app_id)) {
554 std::cerr << "InkscapeApplication: invalid application id: " << app_id.raw() << std::endl;
555 std::cerr << " tag must be ASCII and not start with a number." << std::endl;
556 }
557 non_unique = true;
558 } else if (Glib::getenv("SELF_CALL") == "") {
559 // Version protection attempts to refuse to merge with inkscape version
560 // that have a different build/revision hash. This is important for testing.
561 auto test_app = Gio::Application::create(app_id, flags);
562 test_app->register_application();
563 if (test_app->get_default()->is_remote()) {
564 bool enabled;
565 Glib::VariantBase hint;
566 if (!test_app->query_action(Inkscape::inkscape_revision(), enabled, hint)) {
567 app_id += "." + Inkscape::inkscape_revision();
568 non_unique = true;
569 }
570 }
571 // Run the application (a no-op) since there is no other way to unregister from D-Bus.
572 // Without this, a (wrong) warning is printed.
573 // See https://gitlab.gnome.org/GNOME/glib/-/issues/1857
574 test_app->run(0, nullptr);
575 Gio::Application::unset_default();
576 }
577
578 if (gtk_init_check()) {
579 g_set_prgname(app_id.c_str());
580 _gio_application = Gtk::Application::create(app_id, flags);
581 } else {
582 _gio_application = Gio::Application::create(app_id, flags);
583 _with_gui = false;
584 }
585
586 // Garbage Collector
588
589 auto *gapp = gio_app();
590
591 // Native Language Support
593
594 gapp->signal_startup().connect([this]() { this->on_startup(); });
595 gapp->signal_activate().connect([this]() { this->on_activate(); });
596 gapp->signal_open().connect(sigc::mem_fun(*this, &InkscapeApplication::on_open));
597
598 // ==================== Initializations =====================
599#ifndef NDEBUG
600 // Use environment variable INKSCAPE_DEBUG_LOG=log.txt for event logging
602#endif
603
604 // Don't set application name for now. We don't use it anywhere but
605 // it overrides the name used for adding recently opened files and breaks the Gtk::RecentFilter
606 // Glib::set_application_name(N_("Inkscape - A Vector Drawing Program")); // After gettext() init.
607
608 // ======================== Actions =========================
609 add_actions_base(this); // actions that are GUI independent
610 add_actions_edit(this); // actions for editing
611 add_actions_effect(this); // actions for Filters and Extensions
612 add_actions_element_a(this); // actions for the SVG a (anchor) element
613 add_actions_element_image(this); // actions for the SVG image element
614 add_actions_file(this); // actions for file handling
615 add_actions_hide_lock(this); // actions for hiding/locking items.
616 add_actions_object(this); // actions for object manipulation
617 add_actions_object_align(this); // actions for object alignment
618 add_actions_output(this); // actions for file export
619 add_actions_selection(this); // actions for object selection
620 add_actions_path(this); // actions for Paths
621 add_actions_selection_object(this); // actions for selected objects
622 add_actions_text(this); // actions for Text
623 add_actions_tutorial(this); // actions for opening tutorials (with GUI only)
624 add_actions_transform(this); // actions for transforming selected objects
625 add_actions_window(this); // actions for windows
626
627 // ====================== Command Line ======================
628
629 // Will automatically handle character conversions.
630 // Note: OptionType::FILENAME => std::string, OptionType::STRING => Glib::ustring.
631
632 // Additional informational strings for --help output
633 // TODO: Claims to be translated automatically, but seems broken, so pass already translated strings
634 gapp->set_option_context_parameter_string(_("file1 [file2 [fileN]]"));
635 gapp->set_option_context_summary(_("Process (or open) one or more files."));
636 gapp->set_option_context_description(Glib::ustring("\n") + _("Examples:") + '\n'
637 + " " + Glib::ustring::compose(_("Export input SVG (%1) to PDF (%2) format:"), "in.svg", "out.pdf") + '\n'
638 + '\t' + "inkscape --export-filename=out.pdf in.svg\n"
639 + " " + Glib::ustring::compose(_("Export input files (%1) to PNG format keeping original name (%2):"), "in1.svg, in2.svg", "in1.png, in2.png") + '\n'
640 + '\t' + "inkscape --export-type=png in1.svg in2.svg\n"
641 + " " + Glib::ustring::compose(_("See %1 and %2 for more details."), "'man inkscape'", "http://wiki.inkscape.org/wiki/index.php/Using_the_Command_Line"));
642
643 // clang-format off
644 // General
645 gapp->add_main_option_entry(T::OptionType::BOOL, "version", 'V', N_("Print Inkscape version"), "");
646 gapp->add_main_option_entry(T::OptionType::BOOL, "debug-info", '\0', N_("Print debugging information"), "");
647 gapp->add_main_option_entry(T::OptionType::BOOL, "system-data-directory", '\0', N_("Print system data directory"), "");
648 gapp->add_main_option_entry(T::OptionType::BOOL, "user-data-directory", '\0', N_("Print user data directory"), "");
649 gapp->add_main_option_entry(T::OptionType::BOOL, "list-input-types", '\0', N_("List all available input file extensions"), "");
650 gapp->add_main_option_entry(T::OptionType::STRING, "app-id-tag", '\0', N_("Create a unique instance of Inkscape with the application ID 'org.inkscape.Inkscape.TAG'"), "");
651
652 // Open/Import
653 _start_main_option_section(_("File import"));
654 gapp->add_main_option_entry(T::OptionType::BOOL, "pipe", 'p', N_("Read input file from standard input (stdin)"), "");
655 gapp->add_main_option_entry(T::OptionType::STRING, "pages", 'n', N_("Page numbers to import from multi-page document, i.e. PDF"), N_("PAGE[,PAGE]"));
656 gapp->add_main_option_entry(T::OptionType::BOOL, "pdf-poppler", '\0', N_("Use poppler when importing via commandline"), "");
657 gapp->add_main_option_entry(T::OptionType::STRING, "pdf-font-strategy", '\0', N_("How fonts are parsed in the internal PDF importer [draw-missing|draw-all|delete-missing|delete-all|substitute|keep]"), N_("STRATEGY")); // xSP
658 gapp->add_main_option_entry(T::OptionType::STRING, "convert-dpi-method", '\0', N_("Method used to convert pre-0.92 document dpi, if needed: [none|scale-viewbox|scale-document]"), N_("METHOD"));
659 gapp->add_main_option_entry(T::OptionType::BOOL, "no-convert-text-baseline-spacing", '\0', N_("Do not fix pre-0.92 document's text baseline spacing on opening"), "");
660
661 // Export - File and File Type
662 _start_main_option_section(_("File export"));
663 gapp->add_main_option_entry(T::OptionType::FILENAME, "export-filename", 'o', N_("Output file name (defaults to input filename; file type is guessed from extension if present; use '-' to write to stdout)"), N_("FILENAME"));
664 gapp->add_main_option_entry(T::OptionType::BOOL, "export-overwrite", '\0', N_("Overwrite input file (otherwise add '_out' suffix if type doesn't change)"), "");
665 gapp->add_main_option_entry(T::OptionType::STRING, "export-type", '\0', N_("File type(s) to export: [svg,png,ps,eps,pdf,emf,wmf,xaml]"), N_("TYPE[,TYPE]*"));
666 gapp->add_main_option_entry(T::OptionType::STRING, "export-extension", '\0', N_("Extension ID to use for exporting"), N_("EXTENSION-ID"));
667
668 // Export - Geometry
669 _start_main_option_section(_("Export geometry")); // B = PNG, S = SVG, P = PS/EPS/PDF
670 gapp->add_main_option_entry(T::OptionType::BOOL, "export-area-page", 'C', N_("Area to export is page"), ""); // BSP
671 gapp->add_main_option_entry(T::OptionType::BOOL, "export-area-drawing", 'D', N_("Area to export is whole drawing (ignoring page size)"), ""); // BSP
672 gapp->add_main_option_entry(T::OptionType::STRING, "export-area", 'a', N_("Area to export in SVG user units"), N_("x0:y0:x1:y1")); // BSP
673 gapp->add_main_option_entry(T::OptionType::BOOL, "export-area-snap", '\0', N_("Snap the bitmap export area outwards to the nearest integer values"), ""); // Bxx
674 gapp->add_main_option_entry(T::OptionType::DOUBLE, "export-dpi", 'd', N_("Resolution for bitmaps and rasterized filters; default is 96"), N_("DPI")); // BxP
675 gapp->add_main_option_entry(T::OptionType::INT, "export-width", 'w', N_("Bitmap width in pixels (overrides --export-dpi)"), N_("WIDTH")); // Bxx
676 gapp->add_main_option_entry(T::OptionType::INT, "export-height", 'h', N_("Bitmap height in pixels (overrides --export-dpi)"), N_("HEIGHT")); // Bxx
677 gapp->add_main_option_entry(T::OptionType::INT, "export-margin", '\0', N_("Margin around export area: units of page size for SVG, mm for PS/PDF"), N_("MARGIN")); // xSP
678
679 // Export - Options
680 _start_main_option_section(_("Export options"));
681 gapp->add_main_option_entry(T::OptionType::STRING, "export-page", '\0', N_("Page number to export"), N_("all|n[,a-b]"));
682 gapp->add_main_option_entry(T::OptionType::STRING, "export-id", 'i', N_("ID(s) of object(s) to export"), N_("OBJECT-ID[;OBJECT-ID]*")); // BSP
683 gapp->add_main_option_entry(T::OptionType::BOOL, "export-id-only", 'j', N_("Hide all objects except object with ID selected by export-id"), ""); // BSx
684 gapp->add_main_option_entry(T::OptionType::BOOL, "export-plain-svg", 'l', N_("Remove Inkscape-specific SVG attributes/properties"), ""); // xSx
685 gapp->add_main_option_entry(T::OptionType::INT, "export-ps-level", '\0', N_("Postscript level (2 or 3); default is 3"), N_("LEVEL")); // xxP
686 gapp->add_main_option_entry(T::OptionType::STRING, "export-pdf-version", '\0', N_("PDF version (1.4 or 1.5); default is 1.5"), N_("VERSION")); // xxP
687 gapp->add_main_option_entry(T::OptionType::BOOL, "export-text-to-path", 'T', N_("Convert text to paths (PS/EPS/PDF/SVG)"), ""); // xxP
688 gapp->add_main_option_entry(T::OptionType::BOOL, "export-latex", '\0', N_("Export text separately to LaTeX file (PS/EPS/PDF)"), ""); // xxP
689 gapp->add_main_option_entry(T::OptionType::BOOL, "export-ignore-filters", '\0', N_("Render objects without filters instead of rasterizing (PS/EPS/PDF)"), ""); // xxP
690 gapp->add_main_option_entry(T::OptionType::BOOL, "export-use-hints", 't', N_("Use stored filename and DPI hints when exporting object selected by --export-id"), ""); // Bxx
691 gapp->add_main_option_entry(T::OptionType::STRING, "export-background", 'b', N_("Background color for exported bitmaps (any SVG color string)"), N_("COLOR")); // Bxx
692 // FIXME: Opacity should really be a DOUBLE, but an upstream bug means 0.0 is detected as NULL
693 gapp->add_main_option_entry(T::OptionType::STRING, "export-background-opacity", 'y', N_("Background opacity for exported bitmaps (0.0 to 1.0, or 1 to 255)"), N_("VALUE")); // Bxx
694 gapp->add_main_option_entry(T::OptionType::STRING, "export-png-color-mode", '\0', N_("Color mode (bit depth and color type) for exported bitmaps (Gray_1/Gray_2/Gray_4/Gray_8/Gray_16/RGB_8/RGB_16/GrayAlpha_8/GrayAlpha_16/RGBA_8/RGBA_16)"), N_("COLOR-MODE")); // Bxx
695 gapp->add_main_option_entry(T::OptionType::STRING, "export-png-use-dithering", '\0', N_("Force dithering or disables it"), "false|true"); // Bxx
696 // FIXME: Compression should really be an INT, but an upstream bug means 0 is detected as NULL
697 gapp->add_main_option_entry(T::OptionType::STRING, "export-png-compression", '\0', N_("Compression level for PNG export (0 to 9); default is 6"), N_("LEVEL"));
698 // FIXME: Antialias should really be an INT, but an upstream bug means 0 is detected as NULL
699 gapp->add_main_option_entry(T::OptionType::STRING, "export-png-antialias", '\0', N_("Antialias level for PNG export (0 to 3); default is 2"), N_("LEVEL"));
700 gapp->add_main_option_entry(T::OptionType::BOOL, "export-make-paths", '\0', N_("Attempt to make the export directory if it doesn't exist."), ""); // Bxx
701
702 // Query - Geometry
703 _start_main_option_section(_("Query object/document geometry"));
704 gapp->add_main_option_entry(T::OptionType::STRING, "query-id", 'I', N_("ID(s) of object(s) to be queried"), N_("OBJECT-ID[,OBJECT-ID]*"));
705 gapp->add_main_option_entry(T::OptionType::BOOL, "query-all", 'S', N_("Print bounding boxes of all objects"), "");
706 gapp->add_main_option_entry(T::OptionType::BOOL, "query-x", 'X', N_("X coordinate of drawing or object (if specified by --query-id)"), "");
707 gapp->add_main_option_entry(T::OptionType::BOOL, "query-y", 'Y', N_("Y coordinate of drawing or object (if specified by --query-id)"), "");
708 gapp->add_main_option_entry(T::OptionType::BOOL, "query-width", 'W', N_("Width of drawing or object (if specified by --query-id)"), "");
709 gapp->add_main_option_entry(T::OptionType::BOOL, "query-height", 'H', N_("Height of drawing or object (if specified by --query-id)"), "");
710 gapp->add_main_option_entry(T::OptionType::BOOL, "query-pages", '\0', N_("Number of pages in the opened file."), "");
711
712 // Processing
713 _start_main_option_section(_("Advanced file processing"));
714 gapp->add_main_option_entry(T::OptionType::BOOL, "vacuum-defs", '\0', N_("Remove unused definitions from the <defs> section(s) of document"), "");
715 gapp->add_main_option_entry(T::OptionType::STRING, "select", '\0', N_("Select objects: comma-separated list of IDs"), N_("OBJECT-ID[,OBJECT-ID]*"));
716
717 // Actions
719 gapp->add_main_option_entry(T::OptionType::STRING, "actions", 'a', N_("List of actions (with optional arguments) to execute"), N_("ACTION(:ARG)[;ACTION(:ARG)]*"));
720 gapp->add_main_option_entry(T::OptionType::BOOL, "action-list", '\0', N_("List all available actions"), "");
721 gapp->add_main_option_entry(T::OptionType::FILENAME, "actions-file", '\0', N_("Use a file to input actions list"), N_("FILENAME"));
722
723 // Interface
724 _start_main_option_section(_("Interface"));
725 gapp->add_main_option_entry(T::OptionType::BOOL, "with-gui", 'g', N_("With graphical user interface (required by some actions)"), "");
726 gapp->add_main_option_entry(T::OptionType::BOOL, "batch-process", '\0', N_("Close GUI after executing all actions"), "");
728 gapp->add_main_option_entry(T::OptionType::BOOL, "shell", '\0', N_("Start Inkscape in interactive shell mode"), "");
729 gapp->add_main_option_entry(T::OptionType::BOOL, "active-window", 'q', N_("Use active window from commandline"), "");
730 // clang-format on
731
732 gapp->signal_handle_local_options().connect(sigc::mem_fun(*this, &InkscapeApplication::on_handle_local_options), true);
733
734 if (_with_gui && !non_unique) { // Will fail to register if not unique.
735 // On macOS, this enables:
736 // - DnD via dock icon
737 // - system menu "Quit"
738 gtk_app()->property_register_session() = true;
739 }
740}
741
746
751{
752 if (!gtk_app()) {
753 g_assert_not_reached();
754 return nullptr;
755 }
756
757 auto old_document = _active_document;
759
760 if (replace && old_document && desktop) {
761 document_swap(desktop, document);
762
763 // Delete old document if no longer attached to any window.
764 auto it = _documents.find(old_document);
765 if (it != _documents.end()) {
766 if (it->second.empty()) {
767 document_close(old_document);
768 }
769 }
770 } else {
771 desktop = desktopOpen(document);
772 }
773
774 return desktop;
775}
776
781void InkscapeApplication::create_window(Glib::RefPtr<Gio::File> const &file)
782{
783 if (!gtk_app()) {
784 g_assert_not_reached();
785 return;
786 }
787
789
790 SPDocument* document = nullptr;
791 SPDesktop *desktop = nullptr;
792 bool cancelled = false;
793
794 if (file) {
795 std::tie(document, cancelled) = document_open(file);
796 if (document) {
797 // Remember document so much that we'll add it to recent documents
798 auto recentmanager = Gtk::RecentManager::get_default();
799 recentmanager->add_item(file->get_uri());
800
801 auto old_document = _active_document;
802 bool replace = old_document && old_document->getVirgin();
803
804 desktop = createDesktop(document, replace);
806 } else if (!cancelled) {
807 std::cerr << "InkscapeApplication::create_window: Failed to load: "
808 << file->get_parse_name().raw() << std::endl;
809
810 gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), file->get_parse_name().c_str());
811 sp_ui_error_dialog(text);
812 g_free(text);
813 }
814
815 } else {
816 document = document_new();
817 if (document) {
818 desktop = desktopOpen(document);
819 } else {
820 std::cerr << "InkscapeApplication::create_window: Failed to open default document!" << std::endl;
821 }
822 }
823
824 _active_document = document;
826}
827
833{
834 if (!gtk_app()) {
835 g_assert_not_reached();
836 return false;
837 }
838
839 auto document = desktop->getDocument();
840
841 if (!document) {
842 std::cerr << "InkscapeApplication::destroy_window: window has no document!" << std::endl;
843 return false;
844 }
845
846 // Remove document if no desktop with document is left.
847 auto it = _documents.find(document);
848 if (it != _documents.end()) {
849 // If only one desktop for document:
850 if (it->second.size() == 1) {
851 // Check if document needs saving.
853 if (abort) {
854 return false;
855 }
856 }
857
858 if (get_number_of_windows() == 1 && keep_alive) {
859 // Last desktop, replace with new document.
860 auto new_document = document_new();
861 document_swap(desktop, new_document);
862 } else {
864 if (get_number_of_windows() == 0) {
865 // No Inkscape windows left, remove dialog windows.
866 for (auto const &window : gtk_app()->get_windows()) {
867 window->close();
868 }
869 }
870 }
871
872 if (it->second.size() == 0) {
873 // No window contains document so let's close it.
874 document_close (document);
875 }
876
877 } else {
878 std::cerr << "InkscapeApplication::destroy_window: Could not find document!" << std::endl;
879 }
880
881 return true;
882}
883
885{
886 // Remove from existing window.
887 auto old_win = desktop->getInkscapeWindow();
889
890 // Open in a new window.
891 auto new_win = _windows.emplace_back(std::make_unique<InkscapeWindow>(desktop)).get();
892 new_win->present();
893}
894
896{
897 if (!gtk_app()) {
898 g_assert_not_reached();
899 return false;
900 }
901
902 while (!_documents.empty()) {
903 auto &[doc, desktops] = *_documents.begin();
904 if (!desktops.empty()) {
905 if (!destroyDesktop(desktops.back().get())) {
906 return false; // If destroy aborted, we need to stop exit.
907 }
908 }
909 }
910
911 return true;
912}
913
916void InkscapeApplication::process_document(SPDocument *document, std::string output_path)
917{
918 // Are we doing one file at a time? In that case, we don't recreate new windows for each file.
919 bool replace = _use_pipe || _batch_process;
920
921 // Open window if needed (reuse window if we are doing one file at a time inorder to save overhead).
922 _active_document = document;
923 if (_with_gui) {
924 _active_desktop = createDesktop(document, replace);
926 } else {
927 _active_window = nullptr;
928 _active_desktop = nullptr;
929 _active_selection = document->getSelection();
930 }
931
932 document->ensureUpToDate(); // Or queries don't work!
933
934 // process_file
936
937 if (_use_shell) {
938 shell();
939 }
940 if (_with_gui && _active_window) {
942 }
943 // Only if --export-filename, --export-type --export-overwrite, or --export-use-hints are used.
944 if (_auto_export) {
945 // Save... can't use action yet.
946 _file_export.do_export(document, output_path);
947 }
948}
949
950/*
951 * Called on first Inkscape instance creation. Not called if a new Inkscape instance is merged
952 * with an existing instance.
953 */
955{
956 // Add the start/splash screen to the app as soon as possible
959 _start_screen = std::make_unique<Inkscape::UI::Dialog::StartScreen>();
960 _start_screen->show_now();
961 gtk_app()->add_window(*_start_screen);
962 }
963
964 // Autosave
966
967 // Deprecated...
969
970 // Extensions
972
973 // After extensions are loaded query effects to construct action data
975
976 // Command line execution. Must be after Extensions are initialized.
978
979 if (!_with_gui) {
980 return;
981 }
982
983 auto *gapp = gio_app();
984
985 // ======================= Actions (GUI) ======================
986 gapp->add_action("new", sigc::mem_fun(*this, &InkscapeApplication::on_new ));
987 gapp->add_action("quit", sigc::mem_fun(*this, &InkscapeApplication::on_quit ));
988
989 // ========================= GUI Init =========================
990 Gtk::Window::set_default_icon_name("org.inkscape.Inkscape");
991
992 // build_menu(); // Builds and adds menu to app. Used by all Inkscape windows. This can be done
993 // before all actions defined. * For the moment done by each window so we can add
994 // window action info to menu_label_to_tooltip map.
995
996 // Add tool based shortcut meta-data
998}
999
1000// Open document window with default document or pipe. Either this or on_open() is called.
1002{
1003 std::string output;
1004
1005 // Create new document, either from pipe or from template.
1006 SPDocument *document = nullptr;
1007
1008 if (_use_pipe) {
1009
1010 // Create document from pipe in.
1011 std::istreambuf_iterator<char> begin(std::cin), end;
1012 std::string s(begin, end);
1013 document = document_open(s);
1014 output = "-";
1015
1017 _start_screen->show_welcome();
1019 document = _start_screen->get_document();
1020 if (!document) {
1021 _start_screen.reset();
1022 return; // Start screen forcefully closed.
1023 }
1024 } else {
1025
1026 // Create a blank document from template
1027 document = document_new();
1028 }
1029 if (_start_screen) {
1030 _start_screen.reset();
1031 }
1032
1033 if (!document) {
1034 std::cerr << "InkscapeApplication::on_activate: failed to create document!" << std::endl;
1035 return;
1036 }
1037
1038 // Process document (command line actions, shell, create window)
1039 process_document (document, output);
1040
1041 if (_batch_process) {
1042 // If with_gui, we've reused a window for each file. We must quit to destroy it.
1043 gio_app()->quit();
1044 }
1045}
1046
1048{
1049 _start_screen.reset();
1050}
1051
1053{
1054 auto win_it = std::find_if(_windows.begin(), _windows.end(), [=] (auto &w) { return w.get() == window; });
1055 _windows.erase(win_it);
1056 if (window == _active_window) {
1057 _active_window = nullptr;
1058 }
1059}
1060
1061// Open document window for each file. Either this or on_activate() is called.
1062// type_vec_files == std::vector<Glib::RefPtr<Gio::File> >
1063void InkscapeApplication::on_open(Gio::Application::type_vec_files const &files, Glib::ustring const &hint)
1064{
1065 // on_activate isn't called in this instance
1066 _start_screen.reset();
1067
1068 if(_pdf_poppler)
1069 INKSCAPE.set_pdf_poppler(_pdf_poppler);
1070 if(!_pages.empty())
1071 INKSCAPE.set_pages(_pages);
1072
1073 INKSCAPE.set_pdf_font_strategy((int)_pdf_font_strategy);
1074
1075 if (files.size() > 1 && !_file_export.export_filename.empty()) {
1076 for (auto &file : files) {
1077 std::cerr << " * input-filename: '" << file->get_path().c_str() << "'\n";
1078 }
1079 std::cerr << "InkscapeApplication::on_open: "
1080 "Can't use '--export-filename' with multiple input files "
1081 "(output file would be overwritten for each input file). "
1082 "Please use '--export-type' instead and rename manually."
1083 << std::endl;
1084 return;
1085 }
1086
1087 for (auto file : files) {
1088
1089 // Open file
1090 auto [document, cancelled] = document_open(file);
1091 if (!document) {
1092 if (!cancelled) {
1093 std::cerr << "InkscapeApplication::on_open: failed to create document!" << std::endl;
1094 }
1095 continue;
1096 }
1097
1098 // Process document (command line actions, shell, create window)
1099 process_document(document, file->get_path());
1100 }
1101
1102 if (_batch_process) {
1103 // If with_gui, we've reused a window for each file. We must quit to destroy it.
1104 gio_app()->quit();
1105 }
1106}
1107
1108void InkscapeApplication::parse_actions(Glib::ustring const &input, action_vector_t &action_vector)
1109{
1110 auto const re_colon = Glib::Regex::create("\\s*:\\s*");
1111
1112 // Split action list
1113 std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*;\\s*", input);
1114 for (auto token : tokens) {
1115 // Note: split into 2 tokens max ("param:value"); allows value to contain colon (e.g. abs. paths on Windows)
1116 std::vector<Glib::ustring> tokens2 = re_colon->split(token, 0, static_cast<Glib::Regex::MatchFlags>(0), 2);
1117 Glib::ustring action;
1118 Glib::ustring value;
1119 if (tokens2.size() > 0) {
1120 action = tokens2[0];
1121 }
1122 if (action.find_first_not_of(" \f\n\r\t\v") == std::string::npos) {
1123 continue;
1124 }
1125 if (tokens2.size() > 1) {
1126 value = tokens2[1];
1127 }
1128
1129 Glib::RefPtr<Gio::Action> action_ptr = _gio_application->lookup_action(action);
1130 if (action_ptr) {
1131 // Doesn't seem to be a way to test this using the C++ binding without Glib-CRITICAL errors.
1132 const GVariantType* gtype = g_action_get_parameter_type(action_ptr->gobj());
1133 if (gtype) {
1134 // With value.
1135 Glib::VariantType type = action_ptr->get_parameter_type();
1136 if (type.get_string() == "b") {
1137 bool b = false;
1138 if (value == "1" || value == "true" || value.empty()) {
1139 b = true;
1140 } else if (value == "0" || value == "false") {
1141 b = false;
1142 } else {
1143 std::cerr << "InkscapeApplication::parse_actions: Invalid boolean value: " << action << ":" << value << std::endl;
1144 }
1145 action_vector.emplace_back(action, Glib::Variant<bool>::create(b));
1146 } else if (type.get_string() == "i") {
1147 action_vector.emplace_back(action, Glib::Variant<int>::create(std::stoi(value)));
1148 } else if (type.get_string() == "d") {
1149 action_vector.emplace_back(action, Glib::Variant<double>::create(std::stod(value)));
1150 } else if (type.get_string() == "s") {
1151 action_vector.emplace_back(action, Glib::Variant<Glib::ustring>::create(value));
1152 } else if (type.get_string() == "(dd)") {
1153 std::vector<Glib::ustring> tokens3 = Glib::Regex::split_simple(",", value.c_str());
1154 if (tokens3.size() != 2) {
1155 std::cerr << "InkscapeApplication::parse_actions: " << action << " requires two comma separated numbers" << std::endl;
1156 continue;
1157 }
1158
1159 double d0 = 0;
1160 double d1 = 0;
1161 try {
1162 d0 = std::stod(tokens3[0]);
1163 d1 = std::stod(tokens3[1]);
1164 } catch (...) {
1165 std::cerr << "InkscapeApplication::parse_actions: " << action << " requires two comma separated numbers" << std::endl;
1166 continue;
1167 }
1168
1169 action_vector.emplace_back(action, Glib::Variant<std::tuple<double, double>>::create({d0, d1}));
1170 } else {
1171 std::cerr << "InkscapeApplication::parse_actions: unhandled action value: "
1172 << action << ": " << type.get_string() << std::endl;
1173 }
1174 } else {
1175 // Stateless (i.e. no value).
1176 action_vector.emplace_back(action, Glib::VariantBase());
1177 }
1178 } else {
1179 std::cerr << "InkscapeApplication::parse_actions: could not find action for: " << action << std::endl;
1180 }
1181 }
1182}
1183
1184#ifdef WITH_GNU_READLINE
1185
1186// For use in shell mode. Command completion of action names.
1187char* readline_generator (const char* text, int state)
1188{
1189 static std::vector<Glib::ustring> actions;
1190
1191 // Fill the vector of action names.
1192 if (actions.size() == 0) {
1193 auto *app = InkscapeApplication::instance();
1194 actions = app->gio_app()->list_actions();
1195 std::sort(actions.begin(), actions.end());
1196 }
1197
1198 static int list_index = 0;
1199 static int len = 0;
1200
1201 if (!state) {
1202 list_index = 0;
1203 len = strlen(text);
1204 }
1205
1206 const char* name = nullptr;
1207 while (list_index < actions.size()) {
1208 name = actions[list_index].c_str();
1209 list_index++;
1210 if (strncmp (name, text, len) == 0) {
1211 return (strdup(name));
1212 }
1213 }
1214
1215 return ((char*)nullptr);
1216}
1217
1218char** readline_completion(const char* text, int start, int end)
1219{
1220 char **matches = (char**)nullptr;
1221
1222 // Match actions names, but only at start of line.
1223 // It would be nice to also match action names after a ';' but it's not possible as text won't include ';'.
1224 if (start == 0) {
1225 matches = rl_completion_matches (text, readline_generator);
1226 }
1227
1228 return (matches);
1229}
1230
1232{
1233 rl_readline_name = "inkscape";
1234 rl_attempted_completion_function = readline_completion;
1235}
1236
1237#endif // WITH_GNU_READLINE
1238
1239// Once we don't need to create a window just to process verbs!
1240void InkscapeApplication::shell(bool active_window)
1241{
1242 std::cout << "Inkscape interactive shell mode. Type 'action-list' to list all actions. "
1243 << "Type 'quit' to quit." << std::endl;
1244 std::cout << " Input of the form:" << std::endl;
1245 std::cout << " action1:arg1; action2:arg2; ..." << std::endl;
1246 if (!_with_gui && !active_window) {
1247 std::cout << "Only actions that don't require a desktop may be used." << std::endl;
1248 }
1249
1250#ifdef WITH_GNU_READLINE
1251 auto history_file = Glib::build_filename(Inkscape::IO::Resource::profile_path(), "shell.history");
1252
1253#ifdef _WIN32
1254 gchar *locale_filename = g_win32_locale_filename_from_utf8(history_file.c_str());
1255 if (locale_filename) {
1256 history_file = locale_filename;
1257 g_free(locale_filename);
1258 }
1259#endif
1260
1261 static bool init = false;
1262 if (!init) {
1263 readline_init();
1264 using_history();
1265 init = true;
1266
1267 int error = read_history(history_file.c_str());
1268 if (error && error != ENOENT) {
1269 std::cerr << "read_history error: " << std::strerror(error) << " " << history_file << std::endl;
1270 }
1271 }
1272#endif
1273
1274 while (std::cin.good()) {
1275 bool eof = false;
1276 std::string input;
1277
1278#ifdef WITH_GNU_READLINE
1279 char *readline_input = readline("> ");
1280 if (readline_input) {
1281 input = readline_input;
1282 if (input != "quit" && input != "q") {
1283 add_history(readline_input);
1284 }
1285 } else {
1286 eof = true;
1287 }
1288 free(readline_input);
1289#else
1290 std::cout << "> ";
1291 std::getline(std::cin, input);
1292#endif
1293
1294 // Remove trailing space
1295 input = std::regex_replace(input, std::regex(" +$"), "");
1296
1297 if (eof || input == "quit" || input == "q") {
1298 break;
1299 }
1300
1301 action_vector_t action_vector;
1302 if (active_window) {
1303 input = "active-window-start;" + input + ";active-window-end";
1304 unlink(get_active_desktop_commands_location().c_str());
1305 }
1306 parse_actions(input, action_vector);
1308 if (active_window) {
1310 } else {
1311 // This would allow displaying the results of actions on the fly... but it needs to be well
1312 // vetted first.
1313 auto context = Glib::MainContext::get_default();
1314 while (context->iteration(false)) {};
1315 }
1316 }
1317
1318#ifdef WITH_GNU_READLINE
1319 stifle_history(200); // ToDo: Make number a preference.
1320 int error = write_history(history_file.c_str());
1321 if (error) {
1322 std::cerr << "write_history error: " << std::strerror(error) << " " << history_file << std::endl;
1323 }
1324#endif
1325
1326 if (_with_gui) {
1327 _gio_application->quit(); // Force closing windows.
1328 }
1329}
1330
1331// Todo: Code can be improved by using proper IPC rather than temporary file polling.
1333{
1334 auto const tmpfile = get_active_desktop_commands_location();
1335
1336 for (int counter = 0; ; counter++) {
1337 if (Glib::file_test(tmpfile, Glib::FileTest::EXISTS)) {
1338 break;
1339 } else if (counter >= 300) { // 30 seconds exit
1340 std::cerr << "couldn't process response. File not found" << std::endl;
1341 return;
1342 } else {
1343 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1344 }
1345 }
1346
1347 auto tmpfile_delete_guard = scope_exit([&] {
1348 unlink(tmpfile.c_str());
1349 });
1350
1351 auto awo = std::ifstream(tmpfile);
1352 if (!awo) {
1353 std::cout << "couldn't process response. Couldn't read" << std::endl;
1354 return;
1355 }
1356
1357 auto const content = std::string(std::istreambuf_iterator<char>(awo), std::istreambuf_iterator<char>());
1358 awo.close();
1359
1360 auto doc = sp_repr_read_mem(content.c_str(), strlen(content.c_str()), nullptr);
1361 if (!doc) {
1362 std::cout << "couldn't process response. Wrong data" << std::endl;
1363 return;
1364 }
1365
1366 auto doc_delete_guard = scope_exit([&] {
1368 });
1369
1370 bool noout = true;
1371 for (auto child = doc->root()->firstChild(); child; child = child->next()) {
1372 auto grandchild = child->firstChild();
1373 auto res = grandchild ? grandchild->content() : nullptr;
1374 if (res) {
1375 if (!g_strcmp0(child->name(), "cerr")) {
1376 std::cerr << res << std::endl;
1377 } else {
1378 std::cout << res << std::endl;
1379 }
1380 noout = false;
1381 }
1382 }
1383
1384 if (noout) {
1385 std::cout << "no output" << std::endl;
1386 }
1387}
1388
1389// ========================= Callbacks ==========================
1390
1391/*
1392 * Handle command line options.
1393 *
1394 * Options are processed in the order they appear in this function.
1395 * We process in order: Print -> GUI -> Open -> Query -> Process -> Export.
1396 * For each file without GUI: Open -> Query -> Process -> Export
1397 * More flexible processing can be done via actions.
1398 */
1399int
1400InkscapeApplication::on_handle_local_options(const Glib::RefPtr<Glib::VariantDict>& options)
1401{
1402 auto prefs = Inkscape::Preferences::get();
1403 if (!options) {
1404 std::cerr << "InkscapeApplication::on_handle_local_options: options is null!" << std::endl;
1405 return -1; // Keep going
1406 }
1407
1408 // ===================== APP ID ====================
1409 if (options->contains("app-id-tag")) {
1410 Glib::ustring id_tag;
1411 options->lookup_value("app-id-tag", id_tag);
1412 Glib::ustring app_id = "org.inkscape.Inkscape." + id_tag;
1413 if (Gio::Application::id_is_valid(app_id)) {
1414 _gio_application->set_id(app_id);
1415 } else {
1416 std::cerr << "InkscapeApplication: invalid application id: " << app_id.raw() << std::endl;
1417 std::cerr << " tag must be ASCII and not start with a number." << std::endl;
1418 }
1419 }
1420
1421 // ===================== QUERY =====================
1422 // These are processed first as they result in immediate program termination.
1423 // Note: we cannot use actions here as the app has not been registered yet (registering earlier
1424 // causes problems with changing the app id).
1425 if (options->contains("version")) {
1426 std::cout << Inkscape::inkscape_version() << std::endl;
1427 return EXIT_SUCCESS;
1428 }
1429
1430 if (options->contains("debug-info")) {
1431 std::cout << Inkscape::debug_info() << std::endl;
1432 return EXIT_SUCCESS;
1433 }
1434
1435 if (options->contains("system-data-directory")) {
1436 std::cout << Glib::build_filename(get_inkscape_datadir(), "inkscape") << std::endl;
1437 return EXIT_SUCCESS;
1438 }
1439
1440 if (options->contains("user-data-directory")) {
1441 std::cout << Inkscape::IO::Resource::profile_path() << std::endl;
1442 return EXIT_SUCCESS;
1443 }
1444
1445 // Can't do this until after app is registered!
1446 // if (options->contains("action-list")) {
1447 // print_action_list();
1448 // return EXIT_SUCCESS;
1449 // }
1450
1451 // For options without arguments.
1452 auto base = Glib::VariantBase();
1453
1454 // ================== GUI and Shell ================
1455
1456 // Use of most command line options turns off use of gui unless explicitly requested!
1457 // Listed in order that they appear in constructor.
1458 if (options->contains("pipe") ||
1459
1460 options->contains("export-filename") ||
1461 options->contains("export-overwrite") ||
1462 options->contains("export-type") ||
1463 options->contains("export-page") ||
1464
1465 options->contains("export-area-page") ||
1466 options->contains("export-area-drawing") ||
1467 options->contains("export-area") ||
1468 options->contains("export-area-snap") ||
1469 options->contains("export-dpi") ||
1470 options->contains("export-width") ||
1471 options->contains("export-height") ||
1472 options->contains("export-margin") ||
1473 options->contains("export-height") ||
1474
1475 options->contains("export-id") ||
1476 options->contains("export-id-only") ||
1477 options->contains("export-plain-svg") ||
1478 options->contains("export-ps-level") ||
1479 options->contains("export-pdf-version") ||
1480 options->contains("export-text-to_path") ||
1481 options->contains("export-latex") ||
1482 options->contains("export-ignore-filters") ||
1483 options->contains("export-use-hints") ||
1484 options->contains("export-background") ||
1485 options->contains("export-background-opacity") ||
1486 options->contains("export-text-to_path") ||
1487 options->contains("export-png-color-mode") ||
1488 options->contains("export-png-use-dithering") ||
1489 options->contains("export-png-compression") ||
1490 options->contains("export-png-antialias") ||
1491 options->contains("export-make-paths") ||
1492
1493 options->contains("query-id") ||
1494 options->contains("query-x") ||
1495 options->contains("query-all") ||
1496 options->contains("query-y") ||
1497 options->contains("query-width") ||
1498 options->contains("query-height") ||
1499 options->contains("query-pages") ||
1500
1501 options->contains("vacuum-defs") ||
1502 options->contains("select") ||
1503 options->contains("list-input-types") ||
1504 options->contains("action-list") ||
1505 options->contains("actions") ||
1506 options->contains("actions-file") ||
1507 options->contains("shell")
1508 ) {
1509 _with_gui = false;
1510 }
1511
1512 if (options->contains("with-gui") ||
1513 options->contains("batch-process")
1514 ) {
1515 _with_gui = bool(gtk_app()); // Override turning GUI off
1516 if (!_with_gui)
1517 std::cerr << "No GUI available, some actions may fail" << std::endl;
1518 }
1519
1520 if (options->contains("batch-process")) _batch_process = true;
1521 if (options->contains("shell")) _use_shell = true;
1522 if (options->contains("pipe")) _use_pipe = true;
1523
1524 // Enable auto-export
1525 if (options->contains("export-filename") ||
1526 options->contains("export-type") ||
1527 options->contains("export-overwrite") ||
1528 options->contains("export-use-hints")
1529 ) {
1530 _auto_export = true;
1531 }
1532
1533 // 1. If we are running in command-line mode (without gui) and we haven't explicitly changed the app_id,
1534 // change it here so that this instance of Inkscape is not merged with an existing instance (otherwise
1535 // unwanted windows will pop up and the command-line arguments will be ignored).
1536 // 2. Set a new app id if we are calling a new inkscape instance (with a gui) through an extension
1537 // when the extension author hasn't already done so
1538 bool use_active_window = options->contains("active-window");
1539 if (!options->contains("app-id-tag") && (_with_gui ? Glib::getenv("SELF_CALL") != "" : !use_active_window)) {
1540 Glib::ustring app_id = "org.inkscape.Inkscape.p" + std::to_string(getpid());
1541 _gio_application->set_id(app_id);
1542 }
1543
1544 // ==================== ACTIONS ====================
1545 // Actions as an argument string: e.g.: --actions="query-id:rect1;query-x".
1546 // Actions will be processed in order that they are given in argument.
1547 Glib::ustring actions;
1548 if (options->contains("actions-file")) {
1549 std::string fileactions;
1550 options->lookup_value("actions-file", fileactions);
1551 if (!fileactions.empty()) {
1552 std::ifstream awo(fileactions);
1553 if (awo) {
1554 std::string content((std::istreambuf_iterator<char>(awo)), (std::istreambuf_iterator<char>()));
1555 _command_line_actions_input = content + ";";
1556 }
1557 }
1558 } else if (options->contains("actions")) {
1559 options->lookup_value("actions", _command_line_actions_input);
1560 }
1561
1562 // This must be done after the app has been registered!
1563 if (options->contains("action-list")) {
1564 _command_line_actions.emplace_back("action-list", base);
1565 }
1566
1567 if (options->contains("list-input-types")) {
1568 _command_line_actions.emplace_back("list-input-types", base);
1569 }
1570
1571 // ================= OPEN/IMPORT ===================
1572
1573 if (options->contains("pages")) {
1574 options->lookup_value("pages", _pages);
1575 }
1576
1577 if (options->contains("pdf-poppler")) {
1578 _pdf_poppler = true;
1579 }
1580
1581 if (options->contains("pdf-font-strategy")) {
1582 Glib::ustring strategy;
1583 options->lookup_value("pdf-font-strategy", strategy);
1585 if (strategy == "delete-all") {
1587 }
1588 if (strategy == "delete-missing") {
1590 }
1591 if (strategy == "draw-all") {
1593 }
1594 if (strategy == "keep") {
1596 }
1597 if (strategy == "substitute") {
1599 }
1600 }
1601
1602 if (options->contains("convert-dpi-method")) {
1603 Glib::ustring method;
1604 options->lookup_value("convert-dpi-method", method);
1605 if (!method.empty()) {
1606 _command_line_actions.emplace_back("convert-dpi-method", Glib::Variant<Glib::ustring>::create(method));
1607 }
1608 }
1609
1610 if (options->contains("no-convert-text-baseline-spacing")) {
1611 _command_line_actions.emplace_back("no-convert-baseline", base);
1612 }
1613
1614 // ===================== QUERY =====================
1615
1616 // 'query-id' should be processed first! Can be a comma-separated list.
1617 if (options->contains("query-id")) {
1618 Glib::ustring query_id;
1619 options->lookup_value("query-id", query_id);
1620 if (!query_id.empty()) {
1621 _command_line_actions.emplace_back("select-by-id", Glib::Variant<Glib::ustring>::create(query_id));
1622 }
1623 }
1624
1625 if (options->contains("query-all")) _command_line_actions.emplace_back("query-all", base);
1626 if (options->contains("query-x")) _command_line_actions.emplace_back("query-x", base);
1627 if (options->contains("query-y")) _command_line_actions.emplace_back("query-y", base);
1628 if (options->contains("query-width")) _command_line_actions.emplace_back("query-width", base);
1629 if (options->contains("query-height")) _command_line_actions.emplace_back("query-height",base);
1630 if (options->contains("query-pages")) _command_line_actions.emplace_back("query-pages", base);
1631
1632 // =================== PROCESS =====================
1633
1634 if (options->contains("vacuum-defs")) _command_line_actions.emplace_back("vacuum-defs", base);
1635
1636 if (options->contains("select")) {
1637 Glib::ustring select;
1638 options->lookup_value("select", select);
1639 if (!select.empty()) {
1640 _command_line_actions.emplace_back("select", Glib::Variant<Glib::ustring>::create(select));
1641 }
1642 }
1643
1644 // ==================== EXPORT =====================
1645 if (options->contains("export-filename")) {
1646 options->lookup_value("export-filename", _file_export.export_filename);
1647 }
1648
1649 if (options->contains("export-type")) {
1650 options->lookup_value("export-type", _file_export.export_type);
1651 }
1652 if (options->contains("export-extension")) {
1653 options->lookup_value("export-extension", _file_export.export_extension);
1655 }
1656
1657 if (options->contains("export-overwrite")) _file_export.export_overwrite = true;
1658
1659 if (options->contains("export-page")) {
1660 options->lookup_value("export-page", _file_export.export_page);
1661 }
1662
1663 // Export - Geometry
1664 if (options->contains("export-area")) {
1665 Glib::ustring area{};
1666 options->lookup_value("export-area", area);
1668 }
1669
1670 if (options->contains("export-area-drawing")) {
1672 }
1673 if (options->contains("export-area-page")) {
1675 }
1676
1677 if (options->contains("export-margin")) {
1678 options->lookup_value("export-margin", _file_export.export_margin);
1679 }
1680
1681 if (options->contains("export-area-snap")) _file_export.export_area_snap = true;
1682
1683 if (options->contains("export-width")) {
1684 options->lookup_value("export-width", _file_export.export_width);
1685 }
1686
1687 if (options->contains("export-height")) {
1688 options->lookup_value("export-height", _file_export.export_height);
1689 }
1690
1691 // Export - Options
1692 if (options->contains("export-id")) {
1693 options->lookup_value("export-id", _file_export.export_id);
1694 }
1695
1696 if (options->contains("export-id-only")) _file_export.export_id_only = true;
1697 if (options->contains("export-plain-svg")) _file_export.export_plain_svg = true;
1698
1699 if (options->contains("export-dpi")) {
1700 options->lookup_value("export-dpi", _file_export.export_dpi);
1701 }
1702
1703 if (options->contains("export-ignore-filters")) _file_export.export_ignore_filters = true;
1704 if (options->contains("export-text-to-path")) _file_export.export_text_to_path = true;
1705
1706 if (options->contains("export-ps-level")) {
1707 options->lookup_value("export-ps-level", _file_export.export_ps_level);
1708 }
1709
1710 if (options->contains("export-pdf-version")) {
1711 options->lookup_value("export-pdf-version", _file_export.export_pdf_level);
1712 }
1713
1714 if (options->contains("export-latex")) _file_export.export_latex = true;
1715 if (options->contains("export-use-hints")) _file_export.export_use_hints = true;
1716 if (options->contains("export-make-paths")) _file_export.make_paths = true;
1717
1718 if (options->contains("export-background")) {
1719 options->lookup_value("export-background",_file_export.export_background);
1720 }
1721
1722 // FIXME: Upstream bug means DOUBLE is ignored if set to 0.0 so doesn't exist in options
1723 if (options->contains("export-background-opacity")) {
1724 Glib::ustring opacity;
1725 options->lookup_value("export-background-opacity", opacity);
1726 _file_export.export_background_opacity = Glib::Ascii::strtod(opacity);
1727 }
1728
1729 if (options->contains("export-png-color-mode")) {
1730 options->lookup_value("export-png-color-mode", _file_export.export_png_color_mode);
1731 }
1732
1733 if (options->contains("export-png-use-dithering")) {
1734 Glib::ustring val;
1735 options->lookup_value("export-png-use-dithering", val);
1736 if (val == "true") {
1738#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 18, 0)
1739 std::cerr << "Your cairo version does not support dithering! Option will be ignored." << std::endl;
1740#endif
1741 }
1742 else if (val == "false") _file_export.export_png_use_dithering = false;
1743 else std::cerr << "invalid value for export-png-use-dithering. Ignoring." << std::endl;
1744 } else {
1745 _file_export.export_png_use_dithering = prefs->getBool("/options/dithering/value", true);
1746 }
1747
1748 // FIXME: Upstream bug means INT is ignored if set to 0 so doesn't exist in options
1749 if (options->contains("export-png-compression")) {
1750 Glib::ustring compression;
1751 options->lookup_value("export-png-compression", compression);
1752 const char *begin = compression.raw().c_str();
1753 char *end;
1754 long ival = strtol(begin, &end, 10);
1755 if (end == begin || *end != '\0' || errno == ERANGE) {
1756 std::cerr << "Cannot parse integer value "
1757 << compression
1758 << " for --export-png-compression; the default value "
1760 << " will be used"
1761 << std::endl;
1762 }
1763 else {
1765 }
1766 }
1767
1768 // FIXME: Upstream bug means INT is ignored if set to 0 so doesn't exist in options
1769 if (options->contains("export-png-antialias")) {
1770 Glib::ustring antialias;
1771 options->lookup_value("export-png-antialias", antialias);
1772 const char *begin = antialias.raw().c_str();
1773 char *end;
1774 long ival = strtol(begin, &end, 10);
1775 if (end == begin || *end != '\0' || errno == ERANGE) {
1776 std::cerr << "Cannot parse integer value "
1777 << antialias
1778 << " for --export-png-antialias; the default value "
1780 << " will be used"
1781 << std::endl;
1782 }
1783 else {
1785 }
1786 }
1787
1788 if (use_active_window) {
1789 _gio_application->register_application();
1790 if (!_gio_application->get_default()->is_remote()) {
1791#ifdef __APPLE__
1792 std::cerr << "Active window is not available on macOS" << std::endl;
1793#else
1794 std::cerr << "No active desktop to run" << std::endl;
1795#endif
1796 return EXIT_SUCCESS;
1797 }
1798 if (_use_shell) {
1799 shell(true);
1800 } else {
1801 _command_line_actions.emplace(_command_line_actions.begin(), "active-window-start", base);
1803 unlink(get_active_desktop_commands_location().c_str());
1807 }
1808 return EXIT_SUCCESS;
1809 }
1810 GVariantDict *options_copy = options->gobj_copy();
1811 GVariant *options_var = g_variant_dict_end(options_copy);
1812 if (g_variant_get_size(options_var) != 0) {
1814 }
1815 g_variant_dict_unref(options_copy);
1816 g_variant_unref(options_var);
1817
1818 return -1; // Keep going
1819}
1820
1821// ======================== Actions =========================
1822
1827
1829{
1830 if (gtk_app()) {
1831 if (!destroy_all()) return; // Quit aborted.
1832 // For mac, ensure closing the gtk_app windows
1833 for (auto window : gtk_app()->get_windows()) {
1834 window->close();
1835 }
1836 }
1837
1838 gio_app()->quit();
1839}
1840
1841/*
1842 * Quit without checking for data loss.
1843 */
1844void
1849
1851{
1853 if (desktop) {
1854 INKSCAPE.activate_desktop(desktop);
1855 }
1856}
1857
1858void
1860{
1861 auto const *gapp = gio_app();
1862
1863 auto actions = gapp->list_actions();
1864 std::sort(actions.begin(), actions.end());
1865 for (auto const &action : actions) {
1866 Glib::ustring fullname("app." + action);
1867 std::cout << std::left << std::setw(20) << action
1868 << ": " << _action_extra_data.get_tooltip_for_action(fullname) << std::endl;
1869 }
1870}
1871
1876{
1879
1880 for (auto *imod : extension_list) {
1881 auto suffix = imod->get_extension();
1882 if (suffix[0] == '.') {
1883 ++suffix;
1884 }
1885 std::cout << suffix << std::endl;
1886 }
1887}
1888
1893 if (_with_gui) {
1894 return std::accumulate(_documents.begin(), _documents.end(), 0,
1895 [&](int sum, auto& v){ return sum + static_cast<int>(v.second.size()); });
1896 }
1897 return 0;
1898}
1899
1906void action_effect(Inkscape::Extension::Effect* effect, bool show_prefs) {
1908 if (effect->_workingDialog && show_prefs && desktop) {
1909 effect->prefs(desktop);
1910 } else {
1912 effect->effect(desktop, document);
1913 }
1914}
1915
1916// Modifying string to get submenu id
1917std::string action_menu_name(std::string menu) {
1918 transform(menu.begin(), menu.end(), menu.begin(), ::tolower);
1919 for (auto &x:menu) {
1920 if (x==' ') {
1921 x = '-';
1922 }
1923 }
1924 return menu;
1925}
1926
1928 for (auto effect : Inkscape::Extension::db.get_effect_list()) {
1929
1930 std::string aid = effect->get_sanitized_id();
1931 std::string action_id = "app." + aid;
1932
1933 auto app = this;
1934 if (auto gapp = gtk_app()) {
1935 auto action = gapp->add_action(aid, [effect](){ action_effect(effect, true); });
1936 auto action_noprefs = gapp->add_action(aid + ".noprefs", [effect](){ action_effect(effect, false); });
1937 _effect_actions.emplace_back(action);
1938 _effect_actions.emplace_back(action_noprefs);
1939 }
1940
1941 if (effect->hidden_from_menu()) continue;
1942
1943 // Submenu retrieval as a list of strings (to handle nested menus).
1944 auto sub_menu_list = effect->get_menu_list();
1945
1946 // Setting initial value of description to name of action in case there is no description
1947 auto description = effect->get_menu_tip();
1948 if (description.empty()) description = effect->get_name();
1949
1950 if (effect->is_filter_effect()) {
1951 std::vector<std::vector<Glib::ustring>>raw_data_filter =
1952 {{ action_id, effect->get_name(), "Filters", description },
1953 { action_id + ".noprefs", Glib::ustring(effect->get_name()) + " " + _("(No preferences)"), "Filters (no prefs)", description }};
1954 app->get_action_extra_data().add_data(raw_data_filter);
1955 } else {
1956 std::vector<std::vector<Glib::ustring>>raw_data_effect =
1957 {{ action_id, effect->get_name(), "Extensions", description },
1958 { action_id + ".noprefs", Glib::ustring(effect->get_name()) + " " + _("(No preferences)"), "Extensions (no prefs)", description }};
1959 app->get_action_extra_data().add_data(raw_data_effect);
1960 }
1961
1962#if false // enable to see all the loaded effects
1963 std::cout << " Effect: name: " << effect->get_name();
1964 std::cout << " id: " << aid.c_str();
1965 std::cout << " menu: ";
1966 for (auto sub_menu : sub_menu_list) {
1967 std::cout << "|" << sub_menu.raw(); // Must use raw() as somebody has messed up encoding.
1968 }
1969 std::cout << "| icon: " << effect->find_icon_file();
1970 std::cout << std::endl;
1971#endif
1972
1973 // Add submenu to effect data
1974 gchar *ellipsized_name = effect->takes_input() ? g_strdup_printf(_("%s..."), effect->get_name()) : nullptr;
1975 Glib::ustring menu_name = ellipsized_name ? ellipsized_name : effect->get_name();
1976 bool is_filter = effect->is_filter_effect();
1977 app->get_action_effect_data().add_data(aid, is_filter, sub_menu_list, menu_name);
1978 g_free(ellipsized_name);
1979 }
1980}
1981
1982/*
1983 Local Variables:
1984 mode:c++
1985 c-file-style:"stroustrup"
1986 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1987 indent-tabs-mode:nil
1988 fill-column:99
1989 End:
1990*/
1991// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
void add_actions_base(InkscapeApplication *app)
void add_actions_edit(InkscapeApplication *app)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_effect(InkscapeApplication *app)
std::vector< std::vector< Glib::ustring > > raw_data_effect
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_element_a(InkscapeApplication *app)
void add_actions_element_image(InkscapeApplication *app)
void add_actions_file(InkscapeApplication *app)
void activate_any_actions(action_vector_t const &actions, Glib::RefPtr< Gio::Application > app, InkscapeWindow *win, SPDocument *doc)
This activates all actions, which are called even if you don't know or care about which action group ...
static bool use_active_window
std::string get_active_desktop_commands_location()
void add_actions_hide_lock(InkscapeApplication *app)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_object_align(InkscapeApplication *app)
void add_actions_object(InkscapeApplication *app)
void add_actions_output(InkscapeApplication *app)
void add_actions_path(InkscapeApplication *app)
void add_actions_selection_object(InkscapeApplication *app)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_selection(InkscapeApplication *app)
void add_actions_text(InkscapeApplication *app)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_transform(InkscapeApplication *app)
void add_actions_tutorial(InkscapeApplication *app)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_window(InkscapeApplication *app)
Two-dimensional point that doubles as a vector.
Definition point.h:66
Glib::ustring get_tooltip_for_action(Glib::ustring const &action_name, bool translated=true, bool expanded=false) const
Glib::ustring export_id
void set_export_area(const Glib::ustring &area)
void set_export_area_type(ExportAreaType type)
Glib::ustring export_pdf_level
Glib::ustring export_page
Glib::ustring export_type
double export_background_opacity
Glib::ustring export_png_color_mode
void do_export(SPDocument *doc, std::string filename_in="")
Glib::ustring export_background
std::string export_filename
Glib::ustring export_extension
void on_open(const Gio::Application::type_vec_files &files, const Glib::ustring &hint)
void set_active_desktop(SPDesktop *desktop)
void document_fix(SPDesktop *desktop)
Fix up a document if necessary (Only fixes that require GUI).
std::vector< Glib::RefPtr< Gio::SimpleAction > > _effect_actions
void process_document(SPDocument *document, std::string output_path)
Common processing for documents.
void dump()
Debug function.
bool destroyDesktop(SPDesktop *desktop, bool keep_alive=false)
Destroy a window and close the document it contains.
bool document_swap(SPDesktop *desktop, SPDocument *document)
Swap out one document for another in a tab.
InkFileExportCmd _file_export
std::pair< SPDocument *, bool > document_open(Glib::RefPtr< Gio::File > const &file)
void _start_main_option_section(const Glib::ustring &section_name="")
Gio::Application * gio_app()
The Gio application instance, never NULL.
std::unique_ptr< Inkscape::UI::Dialog::StartScreen > _start_screen
void shell(bool active_window=false)
bool document_revert(SPDocument *document)
Revert document: open saved document and swap it for each window.
SPDesktop * get_active_desktop()
Glib::RefPtr< Gio::Application > _gio_application
Glib::ustring _command_line_actions_input
std::map< std::unique_ptr< SPDocument >, std::vector< std::unique_ptr< SPDesktop > >, TransparentPtrLess< SPDocument > > _documents
void windowClose(InkscapeWindow *window)
SPDocument * get_active_document()
SPDesktop * desktopOpen(SPDocument *document)
SPDocument * document_new(std::string const &template_filename={})
InkActionExtraData _action_extra_data
void parse_actions(const Glib::ustring &input, action_vector_t &action_vector)
static InkscapeApplication * instance()
Singleton instance.
int on_handle_local_options(const Glib::RefPtr< Glib::VariantDict > &options)
Gtk::Application * gtk_app()
The Gtk application instance, or NULL if running headless without display.
void print_input_type_list() const
Prints file type extensions (without leading dot) of input formats.
InkscapeApplication()
Exclusively for the creation of the singleton instance inside main().
Inkscape::Selection * _active_selection
void detachDesktopToNewWindow(SPDesktop *desktop)
void create_window(Glib::RefPtr< Gio::File > const &file={})
Create a window given a Gio::File.
SPDocument * document_add(std::unique_ptr< SPDocument > document)
std::vector< std::unique_ptr< InkscapeWindow > > _windows
void document_close(SPDocument *document)
Close a document, remove from app.
std::vector< SPDocument * > get_documents()
Get a list of open documents (from document map).
SPDesktop * createDesktop(SPDocument *document, bool replace)
Create a desktop given a document.
int get_number_of_windows() const
Return number of open Inkscape Windows (irrespective of number of documents)
action_vector_t _command_line_actions
void desktopClose(SPDesktop *desktop)
InkscapeWindow * _active_window
SPDesktopWidget * get_desktop_widget()
static void create(bool use_gui)
Creates a new Inkscape::Application global object.
Definition inkscape.cpp:151
void init(InkscapeApplication *app)
Definition auto-save.cpp:43
static AutoSave & getInstance()
Definition auto-save.h:30
static void init()
Definition logger.cpp:145
std::vector< Effect * > get_effect_list()
Creates a list of all the Effect extensions.
Definition db.cpp:266
std::list< Input * > InputList
Definition db.h:59
InputList & get_input_list(InputList &ou_list)
Creates a list of all the Input extensions.
Definition db.cpp:242
Effects are extensions that take a document and do something to it in place.
Definition effect.h:39
void effect(SPDesktop *desktop, SPDocument *document=nullptr)
The function that 'does' the effect itself.
Definition effect.cpp:233
bool prefs(SPDesktop *desktop)
Definition effect.cpp:200
bool _workingDialog
Whether a working dialog should be shown.
Definition effect.h:61
static Preferences * get()
Access the singleton Preferences object.
static DialogManager & singleton()
void save_dialogs_state(DialogContainer *docking_container)
static int get_start_mode()
Get the preference for the startup mode.
Definition startup.cpp:579
Inkscape::UI::Dialog::DialogContainer * getDialogContainer()
void removeDesktop(SPDesktop *desktop)
void addDesktop(SPDesktop *desktop, int pos=-1)
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
Geom::Point current_center() const
Definition desktop.cpp:660
SPDocument * getDocument() const
Definition desktop.h:189
InkscapeWindow const * getInkscapeWindow() const
Definition desktop.cpp:975
void showInfoDialog(Glib::ustring const &message)
Definition desktop.cpp:991
SPDesktopWidget * getDesktopWidget() const
Definition desktop.h:192
void change_document(SPDocument *document)
Make desktop switch documents.
Definition desktop.cpp:281
Inkscape::Selection * getSelection() const
Definition desktop.h:188
void zoom_absolute(Geom::Point const &c, double zoom, bool keep_point=true)
Zoom to the given absolute zoom level.
Definition desktop.cpp:538
Typed SVG document implementation.
Definition document.h:101
Inkscape::Selection * getSelection()
Definition document.h:220
char const * getDocumentFilename() const
Definition document.h:229
void setVirgin(bool Virgin)
Definition document.h:134
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
SPNamedView * getNamedView()
Get the namedview for this document, creates it if it's not found.
Definition document.cpp:234
bool getVirgin()
Definition document.h:135
const double w
Definition conic-4.cpp:19
Css & result
double c[8][4]
Geom::Point orig
A class to hold:
Editable view implementation.
bool document_check_for_data_loss(SPDesktop *desktop)
Check if closing document associated with window will cause data loss, and if so opens a dialog that ...
void sp_file_fix_lpe(SPDocument *doc)
void sp_file_convert_dpi(SPDocument *doc)
FontSubstitution dialog.
Wrapper for Boehm GC.
helper functions for gettext
char ** readline_completion(const char *text, int start, int end)
std::string action_menu_name(std::string menu)
char * readline_generator(const char *text, int state)
void action_effect(Inkscape::Extension::Effect *effect, bool show_prefs)
Adds effect to Gio::Actions.
static InkscapeApplication * _instance
void readline_init()
std::vector< std::pair< std::string, Glib::VariantBase > > action_vector_t
Consolidates version info for Inkscape, its various dependencies and the OS we're running on.
Inkscape - An SVG editor.
void sp_ui_error_dialog(char const *message)
Definition interface.cpp:56
std::unique_ptr< SPDocument > ink_file_open(std::span< char const > buffer)
Open a document from memory.
Definition file.cpp:73
std::unique_ptr< SPDocument > ink_file_new(std::string const &Template)
Create a blank document, remove any template data.
Definition file.cpp:34
Geom::Point start
Geom::Point end
DB db
This is the actual database object.
Definition db.cpp:32
void init()
Invokes the init routines for internal modules.
Definition init.cpp:160
void init()
Definition gc-core.h:119
static R & release(R &r)
Decrements the reference count of a anchored object.
std::string get_filename(Type type, char const *filename, bool localized, bool silent)
Definition resource.cpp:170
std::string profile_path()
Definition resource.cpp:415
std::string find_original_file(Glib::StdStringView const filepath, Glib::StdStringView const name)
Takes an absolute file path and returns a second file at the same directory location,...
Definition file.cpp:150
void checkFontSubstitutions(SPDocument *doc)
int dialog_run(Gtk::Dialog &dialog)
This is a GTK4 porting aid meant to replace the removal of the Gtk::Dialog synchronous API.
std::string inkscape_revision()
Return Inkscape repository revision string.
bool fixBrokenLinks(SPDocument *doc)
std::string debug_info()
Return full debug info.
std::string inkscape_version()
Return Inkscape version string.
void initialize_gettext()
does all required gettext initialization and takes care of the respective locale directory paths
Definition gettext.cpp:30
static gint counter
Definition box3d.cpp:39
char const * get_inkscape_datadir()
Determine the location of the Inkscape data directory (typically the share/ folder from where Inkscap...
TODO: insert short description here.
Ocnode * child[8]
Definition quantize.cpp:33
Document * sp_repr_read_mem(const gchar *buffer, gint length, const gchar *default_ns)
Reads and parses XML from a buffer, returning it as an Document.
Definition repr-io.cpp:324
Inkscape::IO::Resource - simple resource API.
auto len
Definition safe-printf.h:21
Run code on scope exit.
double sum(const double alpha[16], const double &x, const double &y)
void sp_namedview_window_from_document(SPDesktop *desktop)
SPRoot: SVG <svg> implementation.
A dialog for the start screen.
SPDesktop * desktop
std::unique_ptr< Toolbar >(* create)()
Definition toolbars.cpp:56
Glib::ustring name
Definition toolbars.cpp:55
void init_tool_shortcuts(InkscapeApplication *app)
Add a bunch of tool specific names to the action data which the tool will handle manually and aren't ...
Definition shortcuts.cpp:34
void init(int argc, char **argv, Toy *t, int width=600, int height=600)