1 /** \file
2 * Code for handling extensions (i.e.\ scripts).
3 */
4 /*
5 * Authors:
6 * Bryce Harrington <bryce@osdl.org>
7 * Ted Gould <ted@gould.cx>
8 *
9 * Copyright (C) 2002-2005,2007 Authors
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 /*
15 TODO:
16 FIXME:
17 After Inkscape makes a formal requirement for a GTK version above 2.11.4, please
18 replace all the instances of ink_ext_XXXXXX in this file that represent
19 svg files with ink_ext_XXXXXX.svg . Doing so will prevent errors in extensions
20 that call inkscape to manipulate the file.
22 "** (inkscape:5848): WARNING **: Format autodetect failed. The file is being opened as SVG."
24 references:
25 http://www.gtk.org/api/2.6/glib/glib-File-Utilities.html#g-mkstemp
26 http://ftp.gnome.org/pub/gnome/sources/glib/2.11/glib-2.11.4.changes
27 http://developer.gnome.org/doc/API/2.0/glib/glib-File-Utilities.html#g-mkstemp
29 --Aaron Spike
30 */
31 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
33 #ifdef HAVE_CONFIG_H
34 # include <config.h>
35 #endif
37 #include <unistd.h>
39 #include <errno.h>
40 #include <gtkmm.h>
42 #include "ui/view/view.h"
43 #include "desktop-handles.h"
44 #include "desktop.h"
45 #include "selection.h"
46 #include "sp-namedview.h"
47 #include "io/sys.h"
48 #include "prefs-utils.h"
49 #include "../system.h"
50 #include "extension/effect.h"
51 #include "extension/output.h"
52 #include "extension/input.h"
53 #include "extension/db.h"
54 #include "script.h"
55 #include "dialogs/dialog-events.h"
57 #include "util/glib-list-iterators.h"
61 #ifdef WIN32
62 #include <windows.h>
63 #include <sys/stat.h>
64 #include "registrytool.h"
65 #endif
69 /** This is the command buffer that gets allocated from the stack */
70 #define BUFSIZE (255)
74 /* Namespaces */
75 namespace Inkscape {
76 namespace Extension {
77 namespace Implementation {
79 void pump_events (void) {
80 while( Gtk::Main::events_pending() )
81 Gtk::Main::iteration();
82 return;
83 }
85 //Interpreter lookup table
86 struct interpreter_t {
87 gchar const *identity;
88 gchar const *prefstring;
89 gchar const *defaultval;
90 };
93 /** \brief A table of what interpreters to call for a given language
95 This table is used to keep track of all the programs to execute a
96 given script. It also tracks the preference to use to overwrite
97 the given interpreter to a custom one per user.
98 */
99 static interpreter_t const interpreterTab[] = {
100 {"perl", "perl-interpreter", "perl" },
101 #ifdef _WIN32
102 {"python", "python-interpreter", "pythonw" },
103 #else
104 {"python", "python-interpreter", "python" },
105 #endif
106 {"ruby", "ruby-interpreter", "ruby" },
107 {"shell", "shell-interpreter", "sh" },
108 { NULL, NULL, NULL }
109 };
113 /**
114 * Look up an interpreter name, and translate to something that
115 * is executable
116 */
117 static Glib::ustring
118 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
119 {
121 Glib::ustring interpName = interpNameArg;
123 interpreter_t const *interp;
124 bool foundInterp = false;
125 for (interp = interpreterTab ; interp->identity ; interp++ ){
126 if (interpName == interp->identity) {
127 foundInterp = true;
128 break;
129 }
130 }
132 // Do we have a supported interpreter type?
133 if (!foundInterp)
134 return "";
135 interpName = interp->defaultval;
137 // 1. Check preferences
138 gchar const *prefInterp = prefs_get_string_attribute("extensions", interp->prefstring);
140 if (prefInterp) {
141 interpName = prefInterp;
142 return interpName;
143 }
145 #ifdef _WIN32
147 // 2. Windows. Try looking relative to inkscape.exe
148 RegistryTool rt;
149 Glib::ustring fullPath;
150 Glib::ustring path;
151 Glib::ustring exeName;
152 if (rt.getExeInfo(fullPath, path, exeName)) {
153 Glib::ustring interpPath = path;
154 interpPath.append("\\");
155 interpPath.append(interpNameArg);
156 interpPath.append("\\");
157 interpPath.append(interpName);
158 interpPath.append(".exe");
159 struct stat finfo;
160 if (stat(interpPath .c_str(), &finfo) ==0) {
161 g_message("Found local interpreter, '%s', Size: %d",
162 interpPath .c_str(),
163 (int)finfo.st_size);
164 return interpPath;
165 }
166 }
168 // 3. Try searching the path
169 char szExePath[MAX_PATH];
170 char szCurrentDir[MAX_PATH];
171 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
172 unsigned int ret = (unsigned int)FindExecutable(
173 interpName.c_str(), szCurrentDir, szExePath);
174 if (ret > 32) {
175 interpName = szExePath;
176 return interpName;
177 }
179 #endif // win32
182 return interpName;
183 }
187 /** \brief This function creates a script object and sets up the
188 variables.
189 \return A script object
191 This function just sets the command to NULL. It should get built
192 officially in the load function. This allows for less allocation
193 of memory in the unloaded state.
194 */
195 Script::Script() :
196 Implementation()
197 {
198 }
201 /**
202 * brief Destructor
203 */
204 Script::~Script()
205 {
206 }
210 /**
211 \return A string with the complete string with the relative directory expanded
212 \brief This function takes in a Repr that contains a reldir entry
213 and returns that data with the relative directory expanded.
214 Mostly it is here so that relative directories all get used
215 the same way.
216 \param reprin The Inkscape::XML::Node with the reldir in it.
218 Basically this function looks at an attribute of the Repr, and makes
219 a decision based on that. Currently, it is only working with the
220 'extensions' relative directory, but there will be more of them.
221 One thing to notice is that this function always returns an allocated
222 string. This means that the caller of this function can always
223 free what they are given (and should do it too!).
224 */
225 Glib::ustring
226 Script::solve_reldir(Inkscape::XML::Node *reprin) {
228 gchar const *s = reprin->attribute("reldir");
230 if (!s) {
231 Glib::ustring str = sp_repr_children(reprin)->content();
232 return str;
233 }
235 Glib::ustring reldir = s;
237 if (reldir == "extensions") {
239 for (unsigned int i=0;
240 i < Inkscape::Extension::Extension::search_path.size();
241 i++) {
243 gchar * fname = g_build_filename(
244 Inkscape::Extension::Extension::search_path[i],
245 sp_repr_children(reprin)->content(),
246 NULL);
247 Glib::ustring filename = fname;
248 g_free(fname);
250 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
251 return filename;
253 }
254 } else {
255 Glib::ustring str = sp_repr_children(reprin)->content();
256 return str;
257 }
259 return "";
260 }
264 /**
265 \return Whether the command given exists, including in the path
266 \brief This function is used to find out if something exists for
267 the check command. It can look in the path if required.
268 \param command The command or file that should be looked for
270 The first thing that this function does is check to see if the
271 incoming file name has a directory delimiter in it. This would
272 mean that it wants to control the directories, and should be
273 used directly.
275 If not, the path is used. Each entry in the path is stepped through,
276 attached to the string, and then tested. If the file is found
277 then a TRUE is returned. If we get all the way through the path
278 then a FALSE is returned, the command could not be found.
279 */
280 bool
281 Script::check_existance(const Glib::ustring &command)
282 {
284 // Check the simple case first
285 if (command.size() == 0) {
286 return false;
287 }
289 //Don't search when it contains a slash. */
290 if (command.find(G_DIR_SEPARATOR) != command.npos) {
291 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
292 return true;
293 else
294 return false;
295 }
298 Glib::ustring path;
299 gchar *s = (gchar *) g_getenv("PATH");
300 if (s)
301 path = s;
302 else
303 /* There is no `PATH' in the environment.
304 The default search path is the current directory */
305 path = G_SEARCHPATH_SEPARATOR_S;
307 std::string::size_type pos = 0;
308 std::string::size_type pos2 = 0;
309 while ( pos < path.size() ) {
311 Glib::ustring localPath;
313 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
314 if (pos2 == path.npos) {
315 localPath = path.substr(pos);
316 pos = path.size();
317 } else {
318 localPath = path.substr(pos, pos2-pos);
319 pos = pos2+1;
320 }
322 //printf("### %s\n", localPath.c_str());
323 Glib::ustring candidatePath =
324 Glib::build_filename(localPath, command);
326 if (Inkscape::IO::file_test(candidatePath .c_str(),
327 G_FILE_TEST_EXISTS)) {
328 return true;
329 }
331 }
333 return false;
334 }
340 /**
341 \return none
342 \brief This function 'loads' an extention, basically it determines
343 the full command for the extention and stores that.
344 \param module The extention to be loaded.
346 The most difficult part about this function is finding the actual
347 command through all of the Reprs. Basically it is hidden down a
348 couple of layers, and so the code has to move down too. When
349 the command is actually found, it has its relative directory
350 solved.
352 At that point all of the loops are exited, and there is an
353 if statement to make sure they didn't exit because of not finding
354 the command. If that's the case, the extention doesn't get loaded
355 and should error out at a higher level.
356 */
358 bool
359 Script::load(Inkscape::Extension::Extension *module)
360 {
361 if (module->loaded())
362 return TRUE;
364 helper_extension = "";
366 /* This should probably check to find the executable... */
367 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
368 while (child_repr != NULL) {
369 if (!strcmp(child_repr->name(), "script")) {
370 child_repr = sp_repr_children(child_repr);
371 while (child_repr != NULL) {
372 if (!strcmp(child_repr->name(), "command")) {
373 const gchar *interpretstr = child_repr->attribute("interpreter");
374 if (interpretstr != NULL) {
375 Glib::ustring interpString =
376 resolveInterpreterExecutable(interpretstr);
377 command.insert(command.end(), interpretstr);
378 }
380 command.insert(command.end(), solve_reldir(child_repr));
381 }
382 if (!strcmp(child_repr->name(), "helper_extension")) {
383 helper_extension = sp_repr_children(child_repr)->content();
384 }
385 child_repr = sp_repr_next(child_repr);
386 }
388 break;
389 }
390 child_repr = sp_repr_next(child_repr);
391 }
393 //g_return_val_if_fail(command.length() > 0, FALSE);
395 return true;
396 }
399 /**
400 \return None.
401 \brief Unload this puppy!
402 \param module Extension to be unloaded.
404 This function just sets the module to unloaded. It free's the
405 command if it has been allocated.
406 */
407 void
408 Script::unload(Inkscape::Extension::Extension */*module*/)
409 {
410 command.clear();
411 helper_extension = "";
412 }
417 /**
418 \return Whether the check passed or not
419 \brief Check every dependency that was given to make sure we should keep this extension
420 \param module The Extension in question
422 */
423 bool
424 Script::check(Inkscape::Extension::Extension *module)
425 {
426 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
427 while (child_repr != NULL) {
428 if (!strcmp(child_repr->name(), "script")) {
429 child_repr = sp_repr_children(child_repr);
430 while (child_repr != NULL) {
431 if (!strcmp(child_repr->name(), "check")) {
432 Glib::ustring command_text = solve_reldir(child_repr);
433 if (command_text.size() > 0) {
434 /* I've got the command */
435 bool existance = check_existance(command_text);
436 if (!existance)
437 return FALSE;
438 }
439 }
441 if (!strcmp(child_repr->name(), "helper_extension")) {
442 gchar const *helper = sp_repr_children(child_repr)->content();
443 if (Inkscape::Extension::db.get(helper) == NULL) {
444 return FALSE;
445 }
446 }
448 child_repr = sp_repr_next(child_repr);
449 }
451 break;
452 }
453 child_repr = sp_repr_next(child_repr);
454 }
456 return true;
457 }
459 class ScriptDocCache : public ImplementationDocumentCache {
460 friend class Script;
461 protected:
462 std::string _filename;
463 int _tempfd;
464 public:
465 ScriptDocCache (Inkscape::UI::View::View * view);
466 ~ScriptDocCache ( );
467 };
469 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
470 ImplementationDocumentCache(view),
471 _filename(""),
472 _tempfd(0)
473 {
474 try {
475 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
476 } catch (...) {
477 /// \todo Popup dialog here
478 return;
479 }
481 SPDesktop *desktop = (SPDesktop *) view;
482 sp_namedview_document_from_window(desktop);
484 Inkscape::Extension::save(
485 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
486 view->doc(), _filename.c_str(), FALSE, FALSE, FALSE);
488 return;
489 }
491 ScriptDocCache::~ScriptDocCache ( )
492 {
493 close(_tempfd);
494 unlink(_filename.c_str());
495 }
497 ImplementationDocumentCache *
498 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
499 return new ScriptDocCache(view);
500 }
503 /**
504 \return A dialog for preferences
505 \brief A stub funtion right now
506 \param module Module who's preferences need getting
507 \param filename Hey, the file you're getting might be important
509 This function should really do something, right now it doesn't.
510 */
511 Gtk::Widget *
512 Script::prefs_input(Inkscape::Extension::Input *module,
513 const gchar */*filename*/)
514 {
515 return module->autogui(NULL, NULL);
516 }
520 /**
521 \return A dialog for preferences
522 \brief A stub funtion right now
523 \param module Module whose preferences need getting
525 This function should really do something, right now it doesn't.
526 */
527 Gtk::Widget *
528 Script::prefs_output(Inkscape::Extension::Output *module)
529 {
530 return module->autogui(NULL, NULL);
531 }
535 /**
536 \return A dialog for preferences
537 \brief A stub funtion right now
538 \param module Module who's preferences need getting
540 This function should really do something, right now it doesn't.
541 */
542 Gtk::Widget *
543 Script::prefs_effect( Inkscape::Extension::Effect *module,
544 Inkscape::UI::View::View *view,
545 sigc::signal<void> * changeSignal,
546 ImplementationDocumentCache * /*docCache*/ )
547 {
548 SPDocument * current_document = view->doc();
550 using Inkscape::Util::GSListConstIterator;
551 GSListConstIterator<SPItem *> selected =
552 sp_desktop_selection((SPDesktop *)view)->itemList();
553 Inkscape::XML::Node * first_select = NULL;
554 if (selected != NULL) {
555 const SPItem * item = *selected;
556 first_select = SP_OBJECT_REPR(item);
557 }
559 return module->autogui(current_document, first_select, changeSignal);
560 }
565 /**
566 \return A new document that has been opened
567 \brief This function uses a filename that is put in, and calls
568 the extension's command to create an SVG file which is
569 returned.
570 \param module Extension to use.
571 \param filename File to open.
573 First things first, this function needs a temporary file name. To
574 create on of those the function g_file_open_tmp is used with
575 the header of ink_ext_.
577 The extension is then executed using the 'execute' function
578 with the filname coming in, and the temporary filename. After
579 That executing, the SVG should be in the temporary file.
581 Finally, the temporary file is opened using the SVG input module and
582 a document is returned. That document has its filename set to
583 the incoming filename (so that it's not the temporary filename).
584 That document is then returned from this function.
585 */
586 SPDocument *
587 Script::open(Inkscape::Extension::Input *module,
588 const gchar *filenameArg)
589 {
590 std::list<std::string> params;
591 module->paramListString(params);
593 std::string tempfilename_out;
594 int tempfd_out = 0;
595 try {
596 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
597 } catch (...) {
598 /// \todo Popup dialog here
599 return NULL;
600 }
602 std::string lfilename = Glib::filename_from_utf8(filenameArg);
604 file_listener fileout;
605 int data_read = execute(command, params, lfilename, fileout);
606 fileout.toFile(tempfilename_out);
608 SPDocument * mydoc = NULL;
609 if (data_read > 10) {
610 if (helper_extension.size()==0) {
611 mydoc = Inkscape::Extension::open(
612 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
613 tempfilename_out.c_str());
614 } else {
615 mydoc = Inkscape::Extension::open(
616 Inkscape::Extension::db.get(helper_extension.c_str()),
617 tempfilename_out.c_str());
618 }
619 } // data_read
621 if (mydoc != NULL) {
622 sp_document_set_uri(mydoc, filenameArg);
623 }
625 // make sure we don't leak file descriptors from g_file_open_tmp
626 close(tempfd_out);
628 unlink(tempfilename_out.c_str());
630 return mydoc;
631 } // open
635 /**
636 \return none
637 \brief This function uses an extention to save a document. It first
638 creates an SVG file of the document, and then runs it through
639 the script.
640 \param module Extention to be used
641 \param doc Document to be saved
642 \param filename The name to save the final file as
644 Well, at some point people need to save - it is really what makes
645 the entire application useful. And, it is possible that someone
646 would want to use an extetion for this, so we need a function to
647 do that eh?
649 First things first, the document is saved to a temporary file that
650 is an SVG file. To get the temporary filename g_file_open_tmp is used with
651 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
652 end of the function.
654 After we have the SVG file, then extention_execute is called with
655 the temporary file name and the final output filename. This should
656 put the output of the script into the final output file. We then
657 delete the temporary file.
658 */
659 void
660 Script::save(Inkscape::Extension::Output *module,
661 SPDocument *doc,
662 const gchar *filenameArg)
663 {
664 std::list<std::string> params;
665 module->paramListString(params);
667 std::string tempfilename_in;
668 int tempfd_in = 0;
669 try {
670 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
671 } catch (...) {
672 /// \todo Popup dialog here
673 return;
674 }
676 if (helper_extension.size() == 0) {
677 Inkscape::Extension::save(
678 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
679 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
680 } else {
681 Inkscape::Extension::save(
682 Inkscape::Extension::db.get(helper_extension.c_str()),
683 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
684 }
687 file_listener fileout;
688 execute(command, params, tempfilename_in, fileout);
690 std::string lfilename = Glib::filename_from_utf8(filenameArg);
691 fileout.toFile(lfilename);
693 // make sure we don't leak file descriptors from g_file_open_tmp
694 close(tempfd_in);
695 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
696 unlink(tempfilename_in.c_str());
698 return;
699 }
703 /**
704 \return none
705 \brief This function uses an extention as a effect on a document.
706 \param module Extention to effect with.
707 \param doc Document to run through the effect.
709 This function is a little bit trickier than the previous two. It
710 needs two temporary files to get it's work done. Both of these
711 files have random names created for them using the g_file_open_temp function
712 with the ink_ext_ prefix in the temporary directory. Like the other
713 functions, the temporary files are deleted at the end.
715 To save/load the two temporary documents (both are SVG) the internal
716 modules for SVG load and save are used. They are both used through
717 the module system function by passing their keys into the functions.
719 The command itself is built a little bit differently than in other
720 functions because the effect support selections. So on the command
721 line a list of all the ids that are selected is included. Currently,
722 this only works for a single selected object, but there will be more.
723 The command string is filled with the data, and then after the execution
724 it is freed.
726 The execute function is used at the core of this function
727 to execute the Script on the two SVG documents (actually only one
728 exists at the time, the other is created by that script). At that
729 point both should be full, and the second one is loaded.
730 */
731 void
732 Script::effect(Inkscape::Extension::Effect *module,
733 Inkscape::UI::View::View *doc,
734 ImplementationDocumentCache * docCache)
735 {
736 if (docCache == NULL) {
737 docCache = newDocCache(module, doc);
738 }
739 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
740 if (dc == NULL) {
741 printf("TOO BAD TO LIVE!!!");
742 exit(1);
743 }
745 SPDesktop *desktop = (SPDesktop *)doc;
746 sp_namedview_document_from_window(desktop);
748 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
750 std::list<std::string> params;
751 module->paramListString(params);
753 if (module->no_doc) {
754 // this is a no-doc extension, e.g. a Help menu command;
755 // just run the command without any files, ignoring errors
757 Glib::ustring empty;
758 file_listener outfile;
759 execute(command, params, empty, outfile);
761 return;
762 }
764 std::string tempfilename_out;
765 int tempfd_out = 0;
766 try {
767 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
768 } catch (...) {
769 /// \todo Popup dialog here
770 return;
771 }
773 if (desktop != NULL) {
774 Inkscape::Util::GSListConstIterator<SPItem *> selected =
775 sp_desktop_selection(desktop)->itemList();
776 while ( selected != NULL ) {
777 Glib::ustring selected_id;
778 selected_id += "--id=";
779 selected_id += SP_OBJECT_ID(*selected);
780 params.insert(params.begin(), selected_id);
781 ++selected;
782 }
783 }
785 file_listener fileout;
786 int data_read = execute(command, params, dc->_filename, fileout);
787 fileout.toFile(tempfilename_out);
789 pump_events();
791 SPDocument * mydoc = NULL;
792 if (data_read > 10) {
793 mydoc = Inkscape::Extension::open(
794 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
795 tempfilename_out.c_str());
796 } // data_read
798 pump_events();
800 // make sure we don't leak file descriptors from g_file_open_tmp
801 close(tempfd_out);
803 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
804 unlink(tempfilename_out.c_str());
806 /* Do something with mydoc.... */
807 if (mydoc) {
808 doc->doc()->emitReconstructionStart();
809 copy_doc(doc->doc()->rroot, mydoc->rroot);
810 doc->doc()->emitReconstructionFinish();
811 mydoc->release();
812 sp_namedview_update_layers_from_document(desktop);
814 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
815 }
816 g_free(orig_output_extension);
818 return;
819 }
823 /**
824 \brief A function to take all the svg elements from one document
825 and put them in another.
826 \param oldroot The root node of the document to be replaced
827 \param newroot The root node of the document to replace it with
829 This function first deletes all of the data in the old document. It
830 does this by creating a list of what needs to be deleted, and then
831 goes through the list. This two pass approach removes issues with
832 the list being change while parsing through it. Lots of nasty bugs.
834 Then, it goes through the new document, duplicating all of the
835 elements and putting them into the old document. The copy
836 is then complete.
837 */
838 void
839 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
840 {
841 std::vector<Inkscape::XML::Node *> delete_list;
842 Inkscape::XML::Node * oldroot_namedview = NULL;
844 for (Inkscape::XML::Node * child = oldroot->firstChild();
845 child != NULL;
846 child = child->next()) {
847 if (!strcmp("sodipodi:namedview", child->name())) {
848 oldroot_namedview = child;
849 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
850 oldroot_namedview_child != NULL;
851 oldroot_namedview_child = oldroot_namedview_child->next()) {
852 delete_list.push_back(oldroot_namedview_child);
853 }
854 } else {
855 delete_list.push_back(child);
856 }
857 }
858 for (unsigned int i = 0; i < delete_list.size(); i++)
859 sp_repr_unparent(delete_list[i]);
861 for (Inkscape::XML::Node * child = newroot->firstChild();
862 child != NULL;
863 child = child->next()) {
864 if (!strcmp("sodipodi:namedview", child->name())) {
865 if (oldroot_namedview != NULL) {
866 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
867 newroot_namedview_child != NULL;
868 newroot_namedview_child = newroot_namedview_child->next()) {
869 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(child->document()));
870 }
871 }
872 } else {
873 oldroot->appendChild(child->duplicate(newroot->document()));
874 }
875 }
877 oldroot->setAttribute("width", newroot->attribute("width"));
878 oldroot->setAttribute("height", newroot->attribute("height"));
880 /** \todo Restore correct layer */
881 /** \todo Restore correct selection */
882 }
884 /** \brief This function checks the stderr file, and if it has data,
885 shows it in a warning dialog to the user
886 \param filename Filename of the stderr file
887 */
888 void
889 Script::checkStderr (const Glib::ustring &data,
890 Gtk::MessageType type,
891 const Glib::ustring &message)
892 {
893 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
894 warning.set_resizable(true);
895 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
896 sp_transientize(dlg);
898 Gtk::VBox * vbox = warning.get_vbox();
900 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
901 Gtk::TextView * textview = new Gtk::TextView();
902 textview->set_editable(false);
903 textview->set_wrap_mode(Gtk::WRAP_WORD);
904 textview->show();
906 textview->get_buffer()->set_text(data.c_str());
908 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
909 scrollwindow->add(*textview);
910 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
911 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
912 scrollwindow->show();
914 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
916 warning.run();
918 return;
919 }
921 bool
922 Script::cancelProcessing (void) {
923 _canceled = true;
924 _main_loop->quit();
925 Glib::spawn_close_pid(_pid);
927 return true;
928 }
931 /** \brief This is the core of the extension file as it actually does
932 the execution of the extension.
933 \param in_command The command to be executed
934 \param filein Filename coming in
935 \param fileout Filename of the out file
936 \return Number of bytes that were read into the output file.
938 The first thing that this function does is build the command to be
939 executed. This consists of the first string (in_command) and then
940 the filename for input (filein). This file is put on the command
941 line.
943 The next thing is that this function does is open a pipe to the
944 command and get the file handle in the ppipe variable. It then
945 opens the output file with the output file handle. Both of these
946 operations are checked extensively for errors.
948 After both are opened, then the data is copied from the output
949 of the pipe into the file out using fread and fwrite. These two
950 functions are used because of their primitive nature they make
951 no assumptions about the data. A buffer is used in the transfer,
952 but the output of fread is stored so the exact number of bytes
953 is handled gracefully.
955 At the very end (after the data has been copied) both of the files
956 are closed, and we return to what we were doing.
957 */
958 int
959 Script::execute (const std::list<std::string> &in_command,
960 const std::list<std::string> &in_params,
961 const Glib::ustring &filein,
962 file_listener &fileout)
963 {
964 g_return_val_if_fail(in_command.size() > 0, 0);
965 // printf("Executing\n");
967 std::vector <std::string> argv;
969 /*
970 for (std::list<std::string>::const_iterator i = in_command.begin();
971 i != in_command.end(); i++) {
972 argv.push_back(*i);
973 }
974 */
975 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
976 // we tokenize so that spwan does not need to quote over all params
977 for (std::list<std::string>::const_iterator i = in_command.begin();
978 i != in_command.end(); i++) {
979 std::string param_str = *i;
980 //std::cout << "params " << param_str << std::endl;
981 do {
982 //std::cout << "param " << param_str << std::endl;
983 size_t first_space = param_str.find_first_of(' ');
984 size_t first_quote = param_str.find_first_of('"');
985 //std::cout << "first space " << first_space << std::endl;
986 //std::cout << "first quote " << first_quote << std::endl;
988 if((first_quote != std::string::npos) && (first_quote == 0)) {
989 size_t next_quote = param_str.find_first_of('"', first_quote);
990 //std::cout << "next quote " << next_quote << std::endl;
992 if(next_quote != std::string::npos) {
993 //std::cout << "now split " << next_quote << std::endl;
994 //std::cout << "now split " << param_str.substr(1, next_quote) << std::endl;
995 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
996 std::string part_str = param_str.substr(1, next_quote);
997 if(part_str.size() > 0)
998 argv.push_back(part_str);
999 param_str = param_str.substr(next_quote + 1);
1001 }
1002 }
1003 else if(first_space != std::string::npos) {
1004 //std::cout << "now split " << first_space << std::endl;
1005 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
1006 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
1007 std::string part_str = param_str.substr(0, first_space);
1008 if(part_str.size() > 0)
1009 argv.push_back(part_str);
1010 param_str = param_str.substr(first_space + 1);
1011 }
1012 else {
1013 if(param_str.size() > 0)
1014 argv.push_back(param_str);
1015 param_str = "";
1016 }
1017 } while(param_str.size() > 0);
1018 }
1020 for (std::list<std::string>::const_iterator i = in_params.begin();
1021 i != in_params.end(); i++) {
1022 argv.push_back(*i);
1023 }
1025 if (!(filein.empty())) {
1026 argv.push_back(filein);
1027 }
1029 int stdout_pipe, stderr_pipe;
1031 try {
1032 Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1033 argv, // arg v
1034 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1035 sigc::slot<void>(),
1036 &_pid, // Pid
1037 NULL, // STDIN
1038 &stdout_pipe, // STDOUT
1039 &stderr_pipe); // STDERR
1040 } catch (Glib::SpawnError e) {
1041 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1042 return 0;
1043 }
1045 _main_loop = Glib::MainLoop::create(false);
1047 file_listener fileerr;
1048 fileout.init(stdout_pipe, _main_loop);
1049 fileerr.init(stderr_pipe, _main_loop);
1051 _canceled = false;
1052 _main_loop->run();
1054 // Ensure all the data is out of the pipe
1055 while (!fileout.isDead())
1056 fileout.read(Glib::IO_IN);
1057 while (!fileerr.isDead())
1058 fileerr.read(Glib::IO_IN);
1060 if (_canceled) {
1061 // std::cout << "Script Canceled" << std::endl;
1062 return 0;
1063 }
1065 Glib::ustring stderr_data = fileerr.string();
1066 if (stderr_data.length() != 0) {
1067 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1068 _("Inkscape has received additional data from the script executed. "
1069 "The script did not return an error, but this may indicate the results will not be as expected."));
1070 }
1072 Glib::ustring stdout_data = fileout.string();
1073 if (stdout_data.length() == 0) {
1074 return 0;
1075 }
1077 // std::cout << "Finishing Execution." << std::endl;
1078 return stdout_data.length();
1079 }
1084 } // namespace Implementation
1085 } // namespace Extension
1086 } // namespace Inkscape
1088 /*
1089 Local Variables:
1090 mode:c++
1091 c-file-style:"stroustrup"
1092 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1093 indent-tabs-mode:nil
1094 fill-column:99
1095 End:
1096 */
1097 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :