Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
script.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
16#include "script.h"
17
18#include <memory>
19#include <boost/range/adaptor/reversed.hpp>
20#include <glib/gstdio.h>
21#include <glibmm/convert.h>
22#include <glibmm/fileutils.h>
23#include <glibmm/main.h>
24#include <glibmm/miscutils.h>
25#include <gtkmm/enums.h>
26#include <gtkmm/messagedialog.h>
27#include <gtkmm/scrolledwindow.h>
28#include <gtkmm/textview.h>
29#include <gtkmm/window.h>
30
31#include "desktop.h"
32#include "event.h"
33#include "extension/db.h"
34#include "extension/effect.h"
36#include "extension/init.h"
37#include "extension/input.h"
38#include "extension/output.h"
39#include "extension/system.h"
40#include "extension/template.h"
41#include "inkscape-window.h"
42#include "inkscape.h"
43#include "io/dir-util.h"
44#include "io/file.h"
45#include "io/resource.h"
46#include "layer-manager.h"
47#include "object/sp-namedview.h"
48#include "object/sp-page.h"
49#include "object/sp-path.h"
50#include "object/sp-root.h"
51#include "path-prefix.h"
52#include "preferences.h"
53#include "selection.h"
54#include "ui/desktop/menubar.h"
55#include "ui/dialog-events.h"
56#include "ui/dialog-run.h"
57#include "ui/pack.h"
61#include "ui/tools/node-tool.h"
62#include "ui/util.h"
64#include "xml/event.h"
65#include "xml/rebase-hrefs.h"
66#include "xml/repr.h"
67#include "xml/simple-document.h"
68
69#ifdef _WIN32
70#include <windows.h>
71#define KILL_PROCESS(pid) TerminateProcess(pid, 0)
72#else
73#define KILL_PROCESS(pid) kill(pid, SIGTERM)
74#endif
75
77
84 auto main_context = Glib::MainContext::get_default();
85 while (main_context->iteration(false)) {
86 }
87}
88
89
96const std::map<std::string, Script::interpreter_t> Script::interpreterTab = {
97 // clang-format off
98#ifdef _WIN32
99 { "perl", {"perl-interpreter", {"wperl" }}},
100 { "python", {"python-interpreter", {"pythonw" }}},
101#elif defined __APPLE__
102 { "perl", {"perl-interpreter", {"perl" }}},
103 { "python", {"python-interpreter", {"python3" }}},
104#else
105 { "perl", {"perl-interpreter", {"perl" }}},
106 { "python", {"python-interpreter", {"python3", "python" }}},
107#endif
108 { "python2", {"python2-interpreter", {"python2", "python" }}},
109 { "ruby", {"ruby-interpreter", {"ruby" }}},
110 { "shell", {"shell-interpreter", {"sh" }}},
111 // clang-format on
112};
113
114
115
121std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
122{
123 // 0. Do we have a supported interpreter type?
124 auto interp = interpreterTab.find(interpNameArg);
125 if (interp == interpreterTab.end()) {
126 g_critical("Script::resolveInterpreterExecutable(): unknown script interpreter '%s'", interpNameArg.c_str());
127 return "";
128 }
129
130 std::list<Glib::ustring> searchList;
131 std::copy(interp->second.defaultvals.begin(), interp->second.defaultvals.end(), std::back_inserter(searchList));
132
133 // 1. Check preferences for an override.
134 auto prefs = Inkscape::Preferences::get();
135 auto prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->second.prefstring));
136
137 if (!prefInterp.empty()) {
138 searchList.push_front(prefInterp);
139 }
140
141 // 2. Search for things in the path if they're there or an absolute
142 for (const auto& binname : searchList) {
143 auto interpreter_path = Glib::filename_from_utf8(binname);
144
145 if (!Glib::path_is_absolute(interpreter_path)) {
146 auto found_path = Glib::find_program_in_path(interpreter_path);
147 if (!found_path.empty()) {
148 return found_path;
149 }
150 } else {
151 return interpreter_path;
152 }
153 }
154
155 // 3. Error
156 g_critical("Script::resolveInterpreterExecutable(): failed to locate script interpreter '%s'", interpNameArg.c_str());
157 return "";
158}
159
170 , _canceled(false)
171 , parent_window(nullptr)
172{
173}
174
179= default;
180
181
201{
202 if (module->loaded()) {
203 return true;
204 }
205
206 helper_extension = "";
207
208 /* This should probably check to find the executable... */
209 Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
210 while (child_repr != nullptr) {
211 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
212 for (child_repr = child_repr->firstChild(); child_repr != nullptr; child_repr = child_repr->next()) {
213 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
214 const gchar *interpretstr = child_repr->attribute("interpreter");
215 if (interpretstr != nullptr) {
216 std::string interpString = resolveInterpreterExecutable(interpretstr);
217 if (interpString.empty()) {
218 continue; // can't have a script extension with empty interpreter
219 }
220 command.push_back(interpString);
221 }
222 // TODO: we already parse commands as dependencies in extension.cpp
223 // can can we optimize this to be less indirect?
224 const char *script_name = child_repr->firstChild()->content();
225 std::string script_location = module->get_dependency_location(script_name);
226 command.push_back(std::move(script_location));
227 } else if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
228 helper_extension = child_repr->firstChild()->content();
229 }
230 }
231
232 break;
233 }
234 child_repr = child_repr->next();
235 }
236
237 // TODO: Currently this causes extensions to fail silently, see comment in Extension::set_state()
238 g_return_val_if_fail(command.size() > 0, false);
239
240 return true;
241}
242
243
253{
254 command.clear();
255 helper_extension = "";
256}
257
258
259
260
268{
269 int script_count = 0;
270 Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
271 while (child_repr != nullptr) {
272 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
273 script_count++;
274
275 // check if all helper_extensions attached to this script were registered
276 child_repr = child_repr->firstChild();
277 while (child_repr != nullptr) {
278 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
279 gchar const *helper = child_repr->firstChild()->content();
280 if (Inkscape::Extension::db.get(helper) == nullptr) {
281 return false;
282 }
283 }
284
285 child_repr = child_repr->next();
286 }
287
288 break;
289 }
290 child_repr = child_repr->next();
291 }
292
293 if (script_count == 0) {
294 return false;
295 }
296
297 return true;
298}
299
304{
305 std::list<std::string> params;
306 module->paramListString(params);
307 module->set_environment();
308
309 if (auto in_file = module->get_template_filename()) {
310 file_listener fileout;
311 execute(command, params, in_file->get_path(), fileout);
312 auto svg = fileout.string();
313 auto rdoc = sp_repr_read_mem(svg.c_str(), svg.length(), SP_SVG_NS_URI);
314 if (rdoc) {
315 auto name = Glib::ustring::compose(_("New document %1"), SPDocument::get_new_doc_number());
316 return SPDocument::createDoc(rdoc, nullptr, nullptr, name.c_str(), false);
317 }
318 }
319
320 return nullptr;
321}
322
327{
328 std::list<std::string> params;
329 {
330 std::string param = "--page=";
331 if (page) {
332 param += page->getId();
333 } else {
334 // This means 'resize the svg document'
335 param += doc->getRoot()->getId();
336 }
337 params.push_back(param);
338 }
339 _change_extension(tmod, nullptr, doc, params, true);
340}
341
363std::unique_ptr<SPDocument> Script::open(Inkscape::Extension::Input *module, char const *filenameArg, bool)
364{
365 std::list<std::string> params;
366 module->paramListString(params);
367 module->set_environment();
368
369 std::string tempfilename_out;
370 int tempfd_out = 0;
371 try {
372 tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
373 } catch (...) {
375 return nullptr;
376 }
377
378 std::string lfilename = Glib::filename_from_utf8(filenameArg);
379
380 file_listener fileout;
381 int data_read = execute(command, params, lfilename, fileout);
382 fileout.toFile(tempfilename_out);
383
384 std::unique_ptr<SPDocument> mydoc;
385 if (data_read > 10) {
386 if (helper_extension.size()==0) {
388 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
389 tempfilename_out.c_str());
390 } else {
393 tempfilename_out.c_str());
394 }
395 } // data_read
396
397 if (mydoc) {
398 mydoc->setDocumentBase(nullptr);
399 mydoc->changeFilenameAndHrefs(filenameArg);
400 }
401
402 // make sure we don't leak file descriptors from Glib::file_open_tmp
403 close(tempfd_out);
404
405 unlink(tempfilename_out.c_str());
406
407 return mydoc;
408} // open
409
410
411
438 SPDocument *doc,
439 const gchar *filenameArg)
440{
441 std::list<std::string> params;
442 module->paramListString(params);
443 module->set_environment(doc);
444
445 std::string tempfilename_in;
446 int tempfd_in = 0;
447 try {
448 tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
449 } catch (...) {
452 }
453
454 if (helper_extension.size() == 0) {
456 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
457 doc, tempfilename_in.c_str(), false, false,
459 } else {
462 doc, tempfilename_in.c_str(), false, false,
464 }
465
466
467 file_listener fileout;
468 int data_read = execute(command, params, tempfilename_in, fileout);
469
470 bool success = false;
471
472 if (data_read > 0) {
473 std::string lfilename = Glib::filename_from_utf8(filenameArg);
474 success = fileout.toFile(lfilename);
475 }
476
477 // make sure we don't leak file descriptors from Glib::file_open_tmp
478 close(tempfd_in);
479 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
480 unlink(tempfilename_in.c_str());
481
482 if (success == false) {
484 }
485}
486
487
489 const SPDocument *doc,
490 const std::string &png_file,
491 const gchar *filenameArg)
492{
493 if(!module->is_raster()) {
494 g_error("Can not export raster to non-raster extension.");
495 return;
496 }
497
498 std::list<std::string> params;
499 module->paramListString(params);
500 module->set_environment(doc);
501
502 file_listener fileout;
503 int data_read = execute(command, params, png_file, fileout);
504
505 bool success = false;
506 if (data_read > 0) {
507 std::string lfilename = Glib::filename_from_utf8(filenameArg);
508 success = fileout.toFile(lfilename);
509 }
510 if (success == false) {
512 }
513}
514
547{
548 if (desktop == nullptr)
549 {
550 g_warning("Script::effect: Desktop not defined");
551 return;
552 }
553
555
556 if (module->no_doc) {
557 // this is a no-doc extension, e.g. a Help menu command;
558 // just run the command without any files, ignoring errors
559
560 std::list<std::string> params;
561 module->paramListString(params);
562 module->set_environment(desktop->getDocument());
563
564 Glib::ustring empty;
565 file_listener outfile;
566 execute(command, {}, empty, outfile, false, module->pipe_diffs);
567
568 // Hack to allow for extension manager to reload extensions
569 // TODO: Find a better way to do this, e.g. implement an action and have extensions (or users)
570 // call that instead when there's a change that requires extensions to reload
571 if (!g_strcmp0(module->get_id(), "org.inkscape.extension.manager")) {
572 Inkscape::Extension::refresh_user_extensions();
573 build_menu(); // Rebuild main menubar.
574 }
575
576 return;
577 }
578
579 std::list<std::string> params;
580 if (desktop) {
581 if (auto selection = desktop->getSelection()) {
582 // Get current selection state
583 auto state = selection->getState();
584
585 // Add selected object IDs
586 for (auto const &id : state.selected_ids) {
587 std::string selected_id = "--id=";
588 selected_id += id;
589 params.push_back(std::move(selected_id));
590 }
591
592 // Add selected nodes
593 for (auto const &node : state.selected_nodes) {
594 params.push_back(Glib::ustring::compose("--selected-nodes=%1:%2:%3", node.path_id, node.subpath_index,
595 node.node_index));
596 }
597 }
598 }
599 _change_extension(module, executionEnv, desktop->getDocument(), params, module->ignore_stderr, module->pipe_diffs);
600}
601
606{
607 std::list<std::string> params;
608 _change_extension(mod, executionEnv, document, params, mod->ignore_stderr);
609}
610
615 std::list<std::string> &params, bool ignore_stderr, bool pipe_diffs)
616{
617 module->paramListString(params);
618 module->set_environment(doc);
619
620 if (executionEnv) {
621 parent_window = executionEnv->get_working_dialog();
622 }
623
624 auto tempfile_out = Inkscape::IO::TempFilename("ink_ext_XXXXXX.svg");
625 auto tempfile_in = Inkscape::IO::TempFilename("ink_ext_XXXXXX.svg");
626
627 // Save current document to a temporary file we can send to the extension
629 prefs->setBool("/options/svgoutput/disable_optimizations", true);
631 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
632 doc, tempfile_in.get_filename().c_str(), false, false,
634 prefs->setBool("/options/svgoutput/disable_optimizations", false);
635
636 file_listener fileout;
637 int data_read = execute(command, params, tempfile_in.get_filename(), fileout, ignore_stderr, pipe_diffs);
638 if (data_read == 0) {
639 return;
640 }
641 fileout.toFile(tempfile_out.get_filename());
642
643 pump_events();
644 Inkscape::XML::Document *new_xmldoc = nullptr;
645 if (data_read > 10) {
646 new_xmldoc = sp_repr_read_file(tempfile_out.get_filename().c_str(), SP_SVG_NS_URI);
647 } // data_read
648
649 pump_events();
650
651 if (new_xmldoc) {
652 //uncomment if issues on ref extensions links (with previous function)
653 //sp_change_hrefs(new_xmldoc, tempfile_out.get_filename().c_str(), doc->getDocumentFilename());
654 doc->rebase(new_xmldoc);
655 } else {
656 Inkscape::UI::gui_warning(_("The output from the extension could not be parsed."), parent_window);
657 }
658}
659
664void Script::showPopupError (const Glib::ustring &data,
665 Gtk::MessageType type,
666 const Glib::ustring &message)
667{
668 Gtk::MessageDialog warning(message, false, type, Gtk::ButtonsType::OK, true);
669 warning.set_resizable(true);
670 if (parent_window) {
671 warning.set_transient_for(*parent_window);
672 } else {
673 sp_transientize(warning);
674 }
675
676 auto const textview = Gtk::make_managed<Gtk::TextView>();
677 textview->set_editable(false);
678 textview->set_wrap_mode(Gtk::WrapMode::WORD);
679 textview->get_buffer()->set_text(data);
680
681 auto const scrollwindow = Gtk::make_managed<Gtk::ScrolledWindow>();
682 scrollwindow->set_child(*textview);
683
684 scrollwindow->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
685 scrollwindow->set_has_frame(true);
686 scrollwindow->set_size_request(0, 60);
687
688 auto const vbox = warning.get_content_area();
689 UI::pack_start(*vbox, *scrollwindow, true, true, 5 /* fix these */);
690
692}
693
695 _canceled = true;
696 if (_main_loop) {
697 _main_loop->quit();
698 }
699 Glib::spawn_close_pid(_pid);
700
701 return true;
702}
703
731int Script::execute(std::list<std::string> const &in_command, std::list<std::string> const &in_params,
732 Glib::ustring const &filein, file_listener &fileout, bool ignore_stderr, bool pipe_diffs)
733{
734 g_return_val_if_fail(!in_command.empty(), 0);
735
736 std::vector<std::string> argv;
737
738 bool interpreted = (in_command.size() == 2);
739 std::string program = in_command.front();
740 std::string script = interpreted ? in_command.back() : "";
741 std::string working_directory = "";
742
743 auto const desktop = SP_ACTIVE_DESKTOP;
744 auto const document = desktop ? desktop->doc() : nullptr;
745 if (!document) {
746 pipe_diffs = false; // pipe_diffs mode requires a desktop and document to attach to
747 }
748
749 // We should always have an absolute path here:
750 // - For interpreted scripts, see Script::resolveInterpreterExecutable()
751 // - For "normal" scripts this should be done as part of the dependency checking, see Dependency::check()
752 if (!Glib::path_is_absolute(program)) {
753 g_critical("Script::execute(): Got unexpected relative path '%s'. Please report a bug.", program.c_str());
754 return 0;
755 }
756 argv.push_back(program);
757
758 if (interpreted) {
759 // On Windows, Python garbles Unicode command line parameters
760 // in an useless way. This means extensions fail when Inkscape
761 // is run from an Unicode directory.
762 // As a workaround, we set the working directory to the one
763 // containing the script.
764 working_directory = Glib::path_get_dirname(script);
765 script = Glib::path_get_basename(script);
766 argv.push_back(script);
767 }
768
769 // assemble the rest of argv
770 std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
771 if (!filein.empty()) {
772 auto filein_native = Glib::filename_from_utf8(filein);
773 if (!Glib::path_is_absolute(filein_native))
774 filein_native = Glib::build_filename(Glib::get_current_dir(), filein_native);
775 argv.push_back(filein_native);
776 }
777
778 //for(int i=0;i<argv.size(); ++i){printf("%s ",argv[i].c_str());}printf("\n");
779
780 int stdout_pipe, stderr_pipe, stdin_pipe;
781
782 try {
783 auto spawn_flags = Glib::SpawnFlags::DEFAULT;
784 if (Glib::getenv("SNAP") != "") {
785 // If we are running within the Linux "snap" package format,
786 // we need different spawn flags to avoid that Inkscape hangs when
787 // starting an extension.
788 spawn_flags = Glib::SpawnFlags::LEAVE_DESCRIPTORS_OPEN;
789 }
790 Glib::spawn_async_with_pipes(working_directory, // working directory
791 argv, // arg v
792 spawn_flags, // spawn flags
793 sigc::slot<void()>(),
794 &_pid, // Pid
795 &stdin_pipe, // STDIN
796 &stdout_pipe, // STDOUT
797 &stderr_pipe); // STDERR
798 } catch (Glib::Error const &e) {
799 g_critical("Script::execute(): failed to execute program '%s'.\n\tReason: %s", program.c_str(), e.what());
800 return 0;
801 }
802
803 // Save the pid. (This function is reentrant, so _pid could be overwritten.)
804 auto const local_pid = _pid;
805
806 // Create a new MainContext for the loop so that the original context sources are not run here,
807 // this enforces that only the file_listeners should be read in this new MainLoop
808 // Unless in pipe_diffs mode, in which case use the application-wide main loop
809 auto const main_context = !pipe_diffs
810 ? Glib::MainContext::create()
811 : Glib::MainContext::get_default();
812
813 _main_loop = Glib::MainLoop::create(main_context, false);
814
815 file_listener fileerr;
816 fileout.init(stdout_pipe, _main_loop);
817 fileerr.init(stderr_pipe, _main_loop);
818
819 std::optional<PreviewObserver> watch;
820 bool lost_document = false;
821 std::vector<sigc::scoped_connection> conns;
822
823 if (pipe_diffs) {
824 auto stdin_channel = Glib::IOChannel::create_from_fd(stdin_pipe);
825 stdin_channel->set_close_on_unref(false);
826 stdin_channel->set_encoding();
827#ifndef _WIN32
828 // does not seem to be needed on Windows
829 stdin_channel->set_flags(static_cast<Glib::IOFlags>(G_IO_FLAG_NONBLOCK));
830#endif
831 stdin_channel->set_buffered(false);
832
833 watch.emplace(std::move(stdin_channel));
834 (*watch).connect(desktop, document);
835 auto on_lose_document = [&] {
836 KILL_PROCESS(local_pid);
837 (*watch).disconnect(document);
838 lost_document = true;
839 conns.clear();
840 };
841 conns.emplace_back(desktop->connectDestroy([=] (auto...) { on_lose_document(); }));
842 conns.emplace_back(desktop->connectDocumentReplaced([=] (auto...) { on_lose_document(); }));
843 conns.emplace_back(document->connectDestroy(on_lose_document));
844 }
845
846 _canceled = false;
847 _main_loop->run();
848
849 if (pipe_diffs && !lost_document) {
850 (*watch).disconnect(document);
851 }
852
853 // Ensure all the data is out of the pipe
854 while (!fileout.isDead()) {
855 fileout.read(Glib::IOCondition::IO_IN);
856 }
857 while (!fileerr.isDead()) {
858 fileerr.read(Glib::IOCondition::IO_IN);
859 }
860
861 _main_loop.reset();
862
863 if (pipe_diffs && lost_document) {
865 }
866
867 if (_canceled) {
868 // std::cout << "Script Canceled" << std::endl;
869 return 0;
870 }
871
872 Glib::ustring stderr_data = fileerr.string();
873 if (!stderr_data.empty() && !ignore_stderr) {
874 if (INKSCAPE.use_gui()) {
875 showPopupError(stderr_data, Gtk::MessageType::INFO,
876 _("Inkscape has received additional data from the script executed. "
877 "The script did not return an error, but this may indicate the results will not be as expected."));
878 } else {
879 std::cerr << "Script Error\n----\n" << stderr_data.c_str() << "\n----\n";
880 }
881 }
882
883 Glib::ustring stdout_data = fileout.string();
884 return stdout_data.length();
885}
886
888
889void Script::file_listener::init(int fd, Glib::RefPtr<Glib::MainLoop> main) {
890 _channel = Glib::IOChannel::create_from_fd(fd);
891 _channel->set_close_on_unref(true);
892 _channel->set_encoding();
893 _conn = main->get_context()->signal_io().connect(sigc::mem_fun(*this, &file_listener::read), _channel, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR);
895}
896
897bool Script::file_listener::read(Glib::IOCondition condition) {
898 if (condition != Glib::IOCondition::IO_IN) {
899 _main_loop->quit();
900 return false;
901 }
902
903 Glib::IOStatus status;
904 Glib::ustring out;
905 status = _channel->read_line(out);
906 _string += out;
907
908 if (status != Glib::IOStatus::NORMAL) {
909 _main_loop->quit();
910 _dead = true;
911 return false;
912 }
913
914 return true;
915}
916
921bool Script::file_listener::toFile(const Glib::ustring &name) {
922 return toFile(Glib::filename_from_utf8(name));
923}
924
929bool Script::file_listener::toFile(const std::string &name) {
930 try {
931 Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w");
932 stdout_file->set_encoding();
933 stdout_file->write(_string);
934 } catch (Glib::FileError &e) {
935 return false;
936 }
937 return true;
938}
939
940Script::PreviewObserver::PreviewObserver(Glib::RefPtr<Glib::IOChannel> channel)
941 : _channel{std::move(channel)}
942{}
943
945{
946 document->addUndoObserver(*this);
947 auto selection = desktop->getSelection();
948 _select_changed =
949 selection->connectChanged([this](Inkscape::Selection *selection) { selectionChanged(selection); });
950 // We don't want to spam deselect / select events
951 // while document reconstruction is ongoing.
952 // The selection is restored after the reconstruction, so
953 // we will emit an event there anyway.
954 _reconstruction_start_connection =
955 document->connectReconstructionStart([this]() { _pause_select_events = true; }, true);
956 _reconstruction_finish_connection =
957 document->connectReconstructionFinish([this]() { _pause_select_events = false; });
958}
959
961{
962 document->removeUndoObserver(*this);
963 _select_changed.disconnect();
964 _reconstruction_start_connection.disconnect();
965 _reconstruction_finish_connection.disconnect();
966}
967
969 std::function<void(Inkscape::XML::Document *, Inkscape::XML::Node *)> const &eventPopulator)
970{
972 Inkscape::XML::Node *event_node = doc->createElement("event");
973 doc->addChildAtPos(event_node, 0);
974 Inkscape::GC::release(event_node);
975
976 eventPopulator(doc, event_node);
977
978 Glib::ustring xml_output = sp_repr_write_buf(event_node, 0, true, GQuark(0), 0, 0);
979 _channel->write(xml_output + "\n");
980
982 delete doc;
983}
984
986{
987 if (_pause_select_events) {
988 return;
989 }
990 createAndSendEvent([&](Inkscape::XML::Document *doc, Inkscape::XML::Node *event_node) {
991 event_node->setAttribute("type", "updateSelection");
992 for (auto objsel : selection->objects()) {
993 Inkscape::XML::Node *item = event_node->document()->createElement("selObj");
994 item->setAttribute("id", objsel->getId());
995 event_node->appendChild(item);
997 }
998 });
999}
1000
1002{
1003 std::vector<XML::Event *> events;
1004
1005 // First collect all events
1006 for (auto e = ee->event; e; e = e->next) {
1007 events.push_back(e);
1008 }
1009
1010 // Process events in reverse order (chronological order)
1011 for (auto e : events | boost::adaptors::reversed) {
1012 createAndSendEvent([&](Inkscape::XML::Document *doc, Inkscape::XML::Node *event_node) {
1013 if (auto eadd = dynamic_cast<XML::EventAdd *>(e)) {
1014 event_node->setAttribute("type", "add");
1015 if (eadd->ref) {
1016 event_node->setAttribute("after", eadd->ref->attribute("id"));
1017 }
1018 if (eadd->child) {
1019 Inkscape::XML::Node *new_child = eadd->child->duplicate(doc);
1020
1021 event_node->appendChild(new_child);
1022 Inkscape::GC::release(new_child);
1023 }
1024 if (eadd->repr) {
1025 event_node->setAttribute("parent", eadd->repr->attribute("id"));
1026 }
1027 } else if (auto edel = dynamic_cast<XML::EventDel *>(e)) {
1028 event_node->setAttribute("type", "delete");
1029 if (edel->repr && edel->repr->attribute("id")) {
1030 event_node->setAttribute("parent", edel->repr->attribute("id"));
1031 }
1032 if (edel->ref) {
1033 event_node->setAttribute("after", edel->ref->attribute("id"));
1034 }
1035 if (edel->child) {
1036 event_node->setAttribute("child", edel->child->attribute("id"));
1037 }
1038 } else if (auto echga = dynamic_cast<XML::EventChgAttr *>(e)) {
1039 event_node->setAttribute("type", "attribute_change");
1040 if (echga->repr && e->repr->attribute("id")) {
1041 event_node->setAttribute("element-id", echga->repr->attribute("id"));
1042 }
1043 event_node->setAttribute("attribute-name", g_quark_to_string(echga->key));
1044 event_node->setAttribute("old-value", &*(echga->oldval));
1045 event_node->setAttribute("new-value", &*(echga->newval));
1046 } else if (auto echgc = dynamic_cast<XML::EventChgContent *>(e)) {
1047 event_node->setAttribute("type", "content_change");
1048 if (e->repr && e->repr->attribute("id")) {
1049 event_node->setAttribute("element-id", e->repr->attribute("id"));
1050 }
1051 event_node->setAttribute("old-content", &*(echgc->oldval));
1052 event_node->setAttribute("new-content", &*(echgc->newval));
1053 } else if (auto echgo = dynamic_cast<XML::EventChgOrder *>(e)) {
1054 event_node->setAttribute("type", "order_change");
1055 if (echgo->repr && echgo->repr->attribute("id")) {
1056 event_node->setAttribute("element-id", e->repr->attribute("id"));
1057 }
1058 event_node->setAttribute("child", echgo->child->attribute("id"));
1059 if (echgo->oldref) {
1060 event_node->setAttribute("old-ref", echgo->oldref->attribute("id"));
1061 }
1062 if (echgo->newref) {
1063 event_node->setAttribute("new-ref", echgo->newref->attribute("id"));
1064 }
1065 } else if (auto echgn = dynamic_cast<XML::EventChgElementName *>(e)) {
1066 event_node->setAttribute("type", "element_name_change");
1067 if (echgn->repr && echgn->repr->attribute("id")) {
1068 event_node->setAttribute("element-id", e->repr->attribute("id"));
1069 }
1070 event_node->setAttribute("old-name", g_quark_to_string(echgn->old_name));
1071 event_node->setAttribute("new-name", g_quark_to_string(echgn->new_name));
1072 } else {
1073 event_node->setAttribute("type", "unknown");
1074 }
1075 });
1076 }
1077}
1078
1080{
1081 notifyUndoCommitEvent(e);
1082}
1083
1085{
1086 notifyUndoCommitEvent(e);
1087}
1088
1090{
1091 // do nothing
1092}
1093
1095{
1096 // do nothing
1097}
1098
1100{
1101 // do nothing
1102}
1103
1104} // namespace Inkscape::Extension::Implementation
1105
1106/*
1107 Local Variables:
1108 mode:c++
1109 c-file-style:"stroustrup"
1110 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1111 indent-tabs-mode:nil
1112 fill-column:99
1113 End:
1114*/
1115// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
TODO: insert short description here.
int main()
uint64_t page
Definition canvas.cpp:171
XML::Event * event
Definition event.h:41
Effects are extensions that take a document and do something to it in place.
Definition effect.h:39
bool ignore_stderr
If stderr log should be shown, when process return code is 0.
Definition effect.h:64
bool pipe_diffs
If changesets should be piped in via stdin.
Definition effect.h:67
Gtk::Dialog * get_working_dialog()
Return reference to working dialog (if any)
The object that is the basis for the Extension system.
Definition extension.h:133
char const * get_id() const
Get the ID of this extension - not a copy don't delete!
bool loaded()
A quick function to test the state of the extension.
A cache for the document and this implementation.
Base class for all implementations of modules.
void notifyUndoCommitEvent(Event *log) override
Triggered when a set of transactions is committed to the undo log.
Definition script.cpp:1001
void selectionChanged(Inkscape::Selection *selection)
Definition script.cpp:985
void createAndSendEvent(std::function< void(Inkscape::XML::Document *doc, Inkscape::XML::Node *)> const &eventPopulator)
Definition script.cpp:968
void notifyClearRedoEvent() override
Triggered when the redo log is cleared.
Definition script.cpp:1094
void notifyUndoExpired(Event *log) override
Triggered when undo items are removed from the back of the log (expired)
Definition script.cpp:1099
void notifyRedoEvent(Event *log) override
Triggered when the user issues a redo command.
Definition script.cpp:1084
void connect(SPDesktop const *desktop, SPDocument *document)
Definition script.cpp:944
PreviewObserver(Glib::RefPtr< Glib::IOChannel > channel)
Definition script.cpp:940
void notifyClearUndoEvent() override
Triggered when the undo log is cleared.
Definition script.cpp:1089
void notifyUndoEvent(Event *log) override
Triggered when the user issues an undo command.
Definition script.cpp:1079
void init(int fd, Glib::RefPtr< Glib::MainLoop > main)
Definition script.cpp:889
std::unique_ptr< SPDocument > new_from_template(Inkscape::Extension::Template *module) override
Create a new document based on the given template.
Definition script.cpp:303
Script()
This function creates a script object and sets up the variables.
Definition script.cpp:168
bool check(Inkscape::Extension::Extension *module) override
Check every dependency that was given to make sure we should keep this extension.
Definition script.cpp:267
void resize_to_template(Inkscape::Extension::Template *tmod, SPDocument *doc, SPPage *page) override
Take an existing document and selected page and resize or add items as needed.
Definition script.cpp:326
void pump_events()
Make GTK+ events continue to come through a little bit.
Definition script.cpp:83
Glib::ustring helper_extension
This is the extension that will be used as the helper to read in or write out the data.
Definition script.h:92
void save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) override
This function uses an extension to save a document. It first creates an SVG file of the document,...
Definition script.cpp:437
std::string resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
Look up an interpreter name, and translate to something that is executable.
Definition script.cpp:121
void export_raster(Inkscape::Extension::Output *module, const SPDocument *doc, std::string const &png_file, gchar const *filename) override
Convert from PNG to raster format.
Definition script.cpp:488
Glib::RefPtr< Glib::MainLoop > _main_loop
Definition script.h:76
void unload(Inkscape::Extension::Extension *module) override
Unload this puppy!
Definition script.cpp:252
int execute(std::list< std::string > const &in_command, std::list< std::string > const &in_params, Glib::ustring const &filein, file_listener &fileout, bool ignore_stderr=false, bool pipe_diffs=false)
This is the core of the extension file as it actually does the execution of the extension.
Definition script.cpp:731
std::list< std::string > command
The command that has been derived from the configuration file with appropriate directories.
Definition script.h:85
std::unique_ptr< SPDocument > open(Inkscape::Extension::Input *module, char const *filename, bool is_importing) override
This function uses a filename that is put in, and calls the extension's command to create an SVG file...
Definition script.cpp:363
Gtk::Window * parent_window
The window which should be considered as "parent window" of the script execution, e....
Definition script.h:100
void _change_extension(Inkscape::Extension::Extension *mod, ExecutionEnv *executionEnv, SPDocument *doc, std::list< std::string > &params, bool ignore_stderr, bool pipe_diffs=false)
Internally, any modification of an existing document, used by effect and resize_page extensions.
Definition script.cpp:614
void effect(Inkscape::Extension::Effect *module, ExecutionEnv *executionEnv, SPDesktop *desktop, ImplementationDocumentCache *docCache) override
This function uses an extension as an effect on a document.
Definition script.cpp:545
void showPopupError(Glib::ustring const &filename, Gtk::MessageType type, Glib::ustring const &message)
This function checks the stderr file, and if it has data, shows it in a warning dialog to the user.
Definition script.cpp:664
static const std::map< std::string, interpreter_t > interpreterTab
A table of what interpreters to call for a given language.
Definition script.h:159
bool load(Inkscape::Extension::Extension *module) override
This function 'loads' an extension, basically it determines the full command for the extension and st...
Definition script.cpp:200
Generic failure for an undescribed reason.
Definition output.h:34
bool is_raster() const
Definition output.h:63
Glib::RefPtr< Gio::File > get_template_filename() const
Get the template filename, or return the default template.
Definition template.cpp:384
SPObjectRange objects()
Returns the list of selected objects.
Preference storage class.
Definition preferences.h:61
static Preferences * get()
Access the singleton Preferences object.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
Definition selection.h:179
Object representing child addition.
Definition event.h:124
Object representing attribute change.
Definition event.h:162
Object representing content change.
Definition event.h:187
Object representing element name change.
Definition event.h:231
Object representing child order change.
Definition event.h:209
Object representing child removal.
Definition event.h:143
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * next()=0
Get the next sibling of this node.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
virtual char const * name() const =0
Get the name of the element node.
void addChildAtPos(Node *child, unsigned pos)
Insert another node as a child of this node.
Definition node.h:430
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * firstChild()=0
Get the first child of this node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual char const * content() const =0
Get the content of a text or comment node.
virtual Document * document()=0
Get the node's associated document.
To do: update description of desktop.
Definition desktop.h:149
sigc::connection connectDocumentReplaced(F &&slot)
Definition desktop.h:257
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::Selection * getSelection() const
Definition desktop.h:188
sigc::connection connectDestroy(F &&slot)
Definition desktop.h:253
SPDocument * doc() const
Definition desktop.h:159
Typed SVG document implementation.
Definition document.h:101
static std::unique_ptr< SPDocument > createDoc(Inkscape::XML::Document *rdoc, char const *filename, char const *base, char const *name, bool keepalive, SPDocument *parent=nullptr)
Definition document.cpp:342
static int get_new_doc_number()
Definition document.cpp:220
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:200
void addUndoObserver(Inkscape::UndoStackObserver &observer)
Add the observer to the document's undo listener The caller is in charge of freeing any memory alloca...
void removeUndoObserver(Inkscape::UndoStackObserver &observer)
sigc::connection connectReconstructionFinish(ReconstructionFinish::slot_type slot)
void rebase(Inkscape::XML::Document *new_xmldoc, bool keep_namedview=true)
Definition document.cpp:556
sigc::connection connectReconstructionStart(ReconstructionStart::slot_type slot, bool first=false)
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
char const * getId() const
Returns the objects current ID string.
Control point selection - stores a set of control points and applies transformations to them.
Editable view implementation.
void sp_transientize(Gtk::Window &window)
Make the argument dialog transient to the currently active document window.
Event handler for dialog windows.
TODO: insert short description here.
SPItem * item
Inkscape::XML::Node * node
Inkscape - An SVG editor.
Desktop main menu bar code.
Multi path manipulator - a tool component that edits multiple paths at once.
DB db
This is the actual database object.
Definition db.cpp:32
std::unique_ptr< SPDocument > open(Extension *key, char const *filename, bool is_importing)
This is a generic function to use the open function of a module (including Autodetect)
Definition system.cpp:66
void save(Extension *key, SPDocument *doc, gchar const *filename, bool check_overwrite, bool official, Inkscape::Extension::FileSaveMethod save_method)
This is a generic function to use the save function of a module (including Autodetect)
Definition system.cpp:162
@ FILE_SAVE_METHOD_TEMPORARY
Definition system.h:42
static R & release(R &r)
Decrements the reference count of a anchored object.
void gui_warning(const std::string &msg, Gtk::Window *parent_window)
Definition util.cpp:119
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
int dialog_run(Gtk::Dialog &dialog)
This is a GTK4 porting aid meant to replace the removal of the Gtk::Dialog synchronous API.
STL namespace.
New node tool with support for multiple path editing.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Path manipulator - a component that edits a single path on-canvas.
TODO: insert short description here.
Singleton class to access the preferences file in a convenient way.
TODO: insert short description here.
Document * sp_repr_read_file(const gchar *filename, const gchar *default_ns, bool xinclude)
Reads XML from a file, and returns the Document.
Definition repr-io.cpp:274
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
Glib::ustring sp_repr_write_buf(Node *repr, int indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent, char const *old_href_base, char const *new_href_base)
Definition repr-io.cpp:934
C facade to Inkscape::XML::Node.
Inkscape::IO::Resource - simple resource API.
Inkscape::XML::SimpleDocument - generic XML document implementation.
void sp_namedview_document_from_window(SPDesktop *desktop)
guint32 GQuark
SPPage – a page object.
SPRoot: SVG <svg> implementation.
static const Point data[]
Document was closed during execution of async extension.
Definition output.h:43
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
SPDesktop * desktop
Glib::ustring name
Definition toolbars.cpp:55
Event object representing a change of the XML document.