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 }
379 Glib::ustring tmp = "\"";
380 tmp += solve_reldir(child_repr);
381 tmp += "\"";
383 command.insert(command.end(), tmp);
384 }
385 if (!strcmp(child_repr->name(), "helper_extension")) {
386 helper_extension = sp_repr_children(child_repr)->content();
387 }
388 child_repr = sp_repr_next(child_repr);
389 }
391 break;
392 }
393 child_repr = sp_repr_next(child_repr);
394 }
396 //g_return_val_if_fail(command.length() > 0, FALSE);
398 return true;
399 }
402 /**
403 \return None.
404 \brief Unload this puppy!
405 \param module Extension to be unloaded.
407 This function just sets the module to unloaded. It free's the
408 command if it has been allocated.
409 */
410 void
411 Script::unload(Inkscape::Extension::Extension */*module*/)
412 {
413 command.clear();
414 helper_extension = "";
415 }
420 /**
421 \return Whether the check passed or not
422 \brief Check every dependency that was given to make sure we should keep this extension
423 \param module The Extension in question
425 */
426 bool
427 Script::check(Inkscape::Extension::Extension *module)
428 {
429 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
430 while (child_repr != NULL) {
431 if (!strcmp(child_repr->name(), "script")) {
432 child_repr = sp_repr_children(child_repr);
433 while (child_repr != NULL) {
434 if (!strcmp(child_repr->name(), "check")) {
435 Glib::ustring command_text = solve_reldir(child_repr);
436 if (command_text.size() > 0) {
437 /* I've got the command */
438 bool existance = check_existance(command_text);
439 if (!existance)
440 return FALSE;
441 }
442 }
444 if (!strcmp(child_repr->name(), "helper_extension")) {
445 gchar const *helper = sp_repr_children(child_repr)->content();
446 if (Inkscape::Extension::db.get(helper) == NULL) {
447 return FALSE;
448 }
449 }
451 child_repr = sp_repr_next(child_repr);
452 }
454 break;
455 }
456 child_repr = sp_repr_next(child_repr);
457 }
459 return true;
460 }
462 class ScriptDocCache : public ImplementationDocumentCache {
463 friend class Script;
464 protected:
465 std::string _filename;
466 int _tempfd;
467 public:
468 ScriptDocCache (Inkscape::UI::View::View * view);
469 ~ScriptDocCache ( );
470 };
472 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
473 ImplementationDocumentCache(view),
474 _filename(""),
475 _tempfd(0)
476 {
477 try {
478 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
479 } catch (...) {
480 /// \todo Popup dialog here
481 return;
482 }
484 SPDesktop *desktop = (SPDesktop *) view;
485 sp_namedview_document_from_window(desktop);
487 Inkscape::Extension::save(
488 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
489 view->doc(), _filename.c_str(), FALSE, FALSE, FALSE);
491 return;
492 }
494 ScriptDocCache::~ScriptDocCache ( )
495 {
496 close(_tempfd);
497 unlink(_filename.c_str());
498 }
500 ImplementationDocumentCache *
501 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
502 return new ScriptDocCache(view);
503 }
506 /**
507 \return A dialog for preferences
508 \brief A stub funtion right now
509 \param module Module who's preferences need getting
510 \param filename Hey, the file you're getting might be important
512 This function should really do something, right now it doesn't.
513 */
514 Gtk::Widget *
515 Script::prefs_input(Inkscape::Extension::Input *module,
516 const gchar */*filename*/)
517 {
518 return module->autogui(NULL, NULL);
519 }
523 /**
524 \return A dialog for preferences
525 \brief A stub funtion right now
526 \param module Module whose preferences need getting
528 This function should really do something, right now it doesn't.
529 */
530 Gtk::Widget *
531 Script::prefs_output(Inkscape::Extension::Output *module)
532 {
533 return module->autogui(NULL, NULL);
534 }
538 /**
539 \return A dialog for preferences
540 \brief A stub funtion right now
541 \param module Module who's preferences need getting
543 This function should really do something, right now it doesn't.
544 */
545 Gtk::Widget *
546 Script::prefs_effect( Inkscape::Extension::Effect *module,
547 Inkscape::UI::View::View *view,
548 sigc::signal<void> * changeSignal,
549 ImplementationDocumentCache * /*docCache*/ )
550 {
551 SPDocument * current_document = view->doc();
553 using Inkscape::Util::GSListConstIterator;
554 GSListConstIterator<SPItem *> selected =
555 sp_desktop_selection((SPDesktop *)view)->itemList();
556 Inkscape::XML::Node * first_select = NULL;
557 if (selected != NULL) {
558 const SPItem * item = *selected;
559 first_select = SP_OBJECT_REPR(item);
560 }
562 return module->autogui(current_document, first_select, changeSignal);
563 }
568 /**
569 \return A new document that has been opened
570 \brief This function uses a filename that is put in, and calls
571 the extension's command to create an SVG file which is
572 returned.
573 \param module Extension to use.
574 \param filename File to open.
576 First things first, this function needs a temporary file name. To
577 create on of those the function g_file_open_tmp is used with
578 the header of ink_ext_.
580 The extension is then executed using the 'execute' function
581 with the filname coming in, and the temporary filename. After
582 That executing, the SVG should be in the temporary file.
584 Finally, the temporary file is opened using the SVG input module and
585 a document is returned. That document has its filename set to
586 the incoming filename (so that it's not the temporary filename).
587 That document is then returned from this function.
588 */
589 SPDocument *
590 Script::open(Inkscape::Extension::Input *module,
591 const gchar *filenameArg)
592 {
593 std::list<std::string> params;
594 module->paramListString(params);
596 std::string tempfilename_out;
597 int tempfd_out = 0;
598 try {
599 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
600 } catch (...) {
601 /// \todo Popup dialog here
602 return NULL;
603 }
605 std::string lfilename = Glib::filename_from_utf8(filenameArg);
607 file_listener fileout;
608 int data_read = execute(command, params, lfilename, fileout);
609 fileout.toFile(tempfilename_out);
611 SPDocument * mydoc = NULL;
612 if (data_read > 10) {
613 if (helper_extension.size()==0) {
614 mydoc = Inkscape::Extension::open(
615 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
616 tempfilename_out.c_str());
617 } else {
618 mydoc = Inkscape::Extension::open(
619 Inkscape::Extension::db.get(helper_extension.c_str()),
620 tempfilename_out.c_str());
621 }
622 } // data_read
624 if (mydoc != NULL) {
625 sp_document_set_uri(mydoc, filenameArg);
626 }
628 // make sure we don't leak file descriptors from g_file_open_tmp
629 close(tempfd_out);
631 unlink(tempfilename_out.c_str());
633 return mydoc;
634 } // open
638 /**
639 \return none
640 \brief This function uses an extention to save a document. It first
641 creates an SVG file of the document, and then runs it through
642 the script.
643 \param module Extention to be used
644 \param doc Document to be saved
645 \param filename The name to save the final file as
647 Well, at some point people need to save - it is really what makes
648 the entire application useful. And, it is possible that someone
649 would want to use an extetion for this, so we need a function to
650 do that eh?
652 First things first, the document is saved to a temporary file that
653 is an SVG file. To get the temporary filename g_file_open_tmp is used with
654 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
655 end of the function.
657 After we have the SVG file, then extention_execute is called with
658 the temporary file name and the final output filename. This should
659 put the output of the script into the final output file. We then
660 delete the temporary file.
661 */
662 void
663 Script::save(Inkscape::Extension::Output *module,
664 SPDocument *doc,
665 const gchar *filenameArg)
666 {
667 std::list<std::string> params;
668 module->paramListString(params);
670 std::string tempfilename_in;
671 int tempfd_in = 0;
672 try {
673 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
674 } catch (...) {
675 /// \todo Popup dialog here
676 return;
677 }
679 if (helper_extension.size() == 0) {
680 Inkscape::Extension::save(
681 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
682 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
683 } else {
684 Inkscape::Extension::save(
685 Inkscape::Extension::db.get(helper_extension.c_str()),
686 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
687 }
690 file_listener fileout;
691 execute(command, params, tempfilename_in, fileout);
693 std::string lfilename = Glib::filename_from_utf8(filenameArg);
694 fileout.toFile(lfilename);
696 // make sure we don't leak file descriptors from g_file_open_tmp
697 close(tempfd_in);
698 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
699 unlink(tempfilename_in.c_str());
701 return;
702 }
706 /**
707 \return none
708 \brief This function uses an extention as a effect on a document.
709 \param module Extention to effect with.
710 \param doc Document to run through the effect.
712 This function is a little bit trickier than the previous two. It
713 needs two temporary files to get it's work done. Both of these
714 files have random names created for them using the g_file_open_temp function
715 with the ink_ext_ prefix in the temporary directory. Like the other
716 functions, the temporary files are deleted at the end.
718 To save/load the two temporary documents (both are SVG) the internal
719 modules for SVG load and save are used. They are both used through
720 the module system function by passing their keys into the functions.
722 The command itself is built a little bit differently than in other
723 functions because the effect support selections. So on the command
724 line a list of all the ids that are selected is included. Currently,
725 this only works for a single selected object, but there will be more.
726 The command string is filled with the data, and then after the execution
727 it is freed.
729 The execute function is used at the core of this function
730 to execute the Script on the two SVG documents (actually only one
731 exists at the time, the other is created by that script). At that
732 point both should be full, and the second one is loaded.
733 */
734 void
735 Script::effect(Inkscape::Extension::Effect *module,
736 Inkscape::UI::View::View *doc,
737 ImplementationDocumentCache * docCache)
738 {
739 if (docCache == NULL) {
740 docCache = newDocCache(module, doc);
741 }
742 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
743 if (dc == NULL) {
744 printf("TOO BAD TO LIVE!!!");
745 exit(1);
746 }
748 SPDesktop *desktop = (SPDesktop *)doc;
749 sp_namedview_document_from_window(desktop);
751 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
753 std::list<std::string> params;
754 module->paramListString(params);
756 if (module->no_doc) {
757 // this is a no-doc extension, e.g. a Help menu command;
758 // just run the command without any files, ignoring errors
760 Glib::ustring empty;
761 file_listener outfile;
762 execute(command, params, empty, outfile);
764 return;
765 }
767 std::string tempfilename_out;
768 int tempfd_out = 0;
769 try {
770 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
771 } catch (...) {
772 /// \todo Popup dialog here
773 return;
774 }
776 if (desktop != NULL) {
777 Inkscape::Util::GSListConstIterator<SPItem *> selected =
778 sp_desktop_selection(desktop)->itemList();
779 while ( selected != NULL ) {
780 Glib::ustring selected_id;
781 selected_id += "--id=";
782 selected_id += SP_OBJECT_ID(*selected);
783 params.insert(params.begin(), selected_id);
784 ++selected;
785 }
786 }
788 file_listener fileout;
789 int data_read = execute(command, params, dc->_filename, fileout);
790 fileout.toFile(tempfilename_out);
792 pump_events();
794 SPDocument * mydoc = NULL;
795 if (data_read > 10) {
796 mydoc = Inkscape::Extension::open(
797 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
798 tempfilename_out.c_str());
799 } // data_read
801 pump_events();
803 // make sure we don't leak file descriptors from g_file_open_tmp
804 close(tempfd_out);
806 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
807 unlink(tempfilename_out.c_str());
809 /* Do something with mydoc.... */
810 if (mydoc) {
811 doc->doc()->emitReconstructionStart();
812 copy_doc(doc->doc()->rroot, mydoc->rroot);
813 doc->doc()->emitReconstructionFinish();
814 mydoc->release();
815 sp_namedview_update_layers_from_document(desktop);
817 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
818 }
819 g_free(orig_output_extension);
821 return;
822 }
826 /**
827 \brief A function to take all the svg elements from one document
828 and put them in another.
829 \param oldroot The root node of the document to be replaced
830 \param newroot The root node of the document to replace it with
832 This function first deletes all of the data in the old document. It
833 does this by creating a list of what needs to be deleted, and then
834 goes through the list. This two pass approach removes issues with
835 the list being change while parsing through it. Lots of nasty bugs.
837 Then, it goes through the new document, duplicating all of the
838 elements and putting them into the old document. The copy
839 is then complete.
840 */
841 void
842 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
843 {
844 std::vector<Inkscape::XML::Node *> delete_list;
845 Inkscape::XML::Node * oldroot_namedview = NULL;
847 for (Inkscape::XML::Node * child = oldroot->firstChild();
848 child != NULL;
849 child = child->next()) {
850 if (!strcmp("sodipodi:namedview", child->name())) {
851 oldroot_namedview = child;
852 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
853 oldroot_namedview_child != NULL;
854 oldroot_namedview_child = oldroot_namedview_child->next()) {
855 delete_list.push_back(oldroot_namedview_child);
856 }
857 } else {
858 delete_list.push_back(child);
859 }
860 }
861 for (unsigned int i = 0; i < delete_list.size(); i++)
862 sp_repr_unparent(delete_list[i]);
864 for (Inkscape::XML::Node * child = newroot->firstChild();
865 child != NULL;
866 child = child->next()) {
867 if (!strcmp("sodipodi:namedview", child->name())) {
868 if (oldroot_namedview != NULL) {
869 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
870 newroot_namedview_child != NULL;
871 newroot_namedview_child = newroot_namedview_child->next()) {
872 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(child->document()));
873 }
874 }
875 } else {
876 oldroot->appendChild(child->duplicate(newroot->document()));
877 }
878 }
880 oldroot->setAttribute("width", newroot->attribute("width"));
881 oldroot->setAttribute("height", newroot->attribute("height"));
883 /** \todo Restore correct layer */
884 /** \todo Restore correct selection */
885 }
887 /** \brief This function checks the stderr file, and if it has data,
888 shows it in a warning dialog to the user
889 \param filename Filename of the stderr file
890 */
891 void
892 Script::checkStderr (const Glib::ustring &data,
893 Gtk::MessageType type,
894 const Glib::ustring &message)
895 {
896 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
897 warning.set_resizable(true);
898 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
899 sp_transientize(dlg);
901 Gtk::VBox * vbox = warning.get_vbox();
903 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
904 Gtk::TextView * textview = new Gtk::TextView();
905 textview->set_editable(false);
906 textview->set_wrap_mode(Gtk::WRAP_WORD);
907 textview->show();
909 textview->get_buffer()->set_text(data.c_str());
911 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
912 scrollwindow->add(*textview);
913 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
914 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
915 scrollwindow->show();
917 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
919 warning.run();
921 return;
922 }
924 bool
925 Script::cancelProcessing (void) {
926 _canceled = true;
927 _main_loop->quit();
928 Glib::spawn_close_pid(_pid);
930 return true;
931 }
934 /** \brief This is the core of the extension file as it actually does
935 the execution of the extension.
936 \param in_command The command to be executed
937 \param filein Filename coming in
938 \param fileout Filename of the out file
939 \return Number of bytes that were read into the output file.
941 The first thing that this function does is build the command to be
942 executed. This consists of the first string (in_command) and then
943 the filename for input (filein). This file is put on the command
944 line.
946 The next thing is that this function does is open a pipe to the
947 command and get the file handle in the ppipe variable. It then
948 opens the output file with the output file handle. Both of these
949 operations are checked extensively for errors.
951 After both are opened, then the data is copied from the output
952 of the pipe into the file out using fread and fwrite. These two
953 functions are used because of their primitive nature they make
954 no assumptions about the data. A buffer is used in the transfer,
955 but the output of fread is stored so the exact number of bytes
956 is handled gracefully.
958 At the very end (after the data has been copied) both of the files
959 are closed, and we return to what we were doing.
960 */
961 int
962 Script::execute (const std::list<std::string> &in_command,
963 const std::list<std::string> &in_params,
964 const Glib::ustring &filein,
965 file_listener &fileout)
966 {
967 g_return_val_if_fail(in_command.size() > 0, 0);
968 // printf("Executing\n");
970 std::vector <std::string> argv;
972 /*
973 for (std::list<std::string>::const_iterator i = in_command.begin();
974 i != in_command.end(); i++) {
975 argv.push_back(*i);
976 }
977 */
978 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
979 // we tokenize so that spwan does not need to quote over all params
980 for (std::list<std::string>::const_iterator i = in_command.begin();
981 i != in_command.end(); i++) {
982 std::string param_str = *i;
983 //std::cout << "params " << param_str << std::endl;
984 do {
985 //std::cout << "param " << param_str << std::endl;
986 size_t first_space = param_str.find_first_of(' ');
987 size_t first_quote = param_str.find_first_of('"');
988 //std::cout << "first space " << first_space << std::endl;
989 //std::cout << "first quote " << first_quote << std::endl;
991 if((first_quote != std::string::npos) && (first_quote == 0)) {
992 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
993 //std::cout << "next quote " << next_quote << std::endl;
995 if(next_quote != std::string::npos) {
996 //std::cout << "now split " << next_quote << std::endl;
997 //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
998 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
999 std::string part_str = param_str.substr(1, next_quote - 1);
1000 if(part_str.size() > 0)
1001 argv.push_back(part_str);
1002 param_str = param_str.substr(next_quote + 1);
1004 }
1005 else {
1006 if(param_str.size() > 0)
1007 argv.push_back(param_str);
1008 param_str = "";
1009 }
1011 }
1012 else if(first_space != std::string::npos) {
1013 //std::cout << "now split " << first_space << std::endl;
1014 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
1015 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
1016 std::string part_str = param_str.substr(0, first_space);
1017 if(part_str.size() > 0)
1018 argv.push_back(part_str);
1019 param_str = param_str.substr(first_space + 1);
1020 }
1021 else {
1022 if(param_str.size() > 0)
1023 argv.push_back(param_str);
1024 param_str = "";
1025 }
1026 } while(param_str.size() > 0);
1027 }
1029 for (std::list<std::string>::const_iterator i = in_params.begin();
1030 i != in_params.end(); i++) {
1031 argv.push_back(*i);
1032 }
1034 if (!(filein.empty())) {
1035 argv.push_back(filein);
1036 }
1038 int stdout_pipe, stderr_pipe;
1040 try {
1041 Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1042 argv, // arg v
1043 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1044 sigc::slot<void>(),
1045 &_pid, // Pid
1046 NULL, // STDIN
1047 &stdout_pipe, // STDOUT
1048 &stderr_pipe); // STDERR
1049 } catch (Glib::SpawnError e) {
1050 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1051 return 0;
1052 }
1054 _main_loop = Glib::MainLoop::create(false);
1056 file_listener fileerr;
1057 fileout.init(stdout_pipe, _main_loop);
1058 fileerr.init(stderr_pipe, _main_loop);
1060 _canceled = false;
1061 _main_loop->run();
1063 // Ensure all the data is out of the pipe
1064 while (!fileout.isDead())
1065 fileout.read(Glib::IO_IN);
1066 while (!fileerr.isDead())
1067 fileerr.read(Glib::IO_IN);
1069 if (_canceled) {
1070 // std::cout << "Script Canceled" << std::endl;
1071 return 0;
1072 }
1074 Glib::ustring stderr_data = fileerr.string();
1075 if (stderr_data.length() != 0) {
1076 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1077 _("Inkscape has received additional data from the script executed. "
1078 "The script did not return an error, but this may indicate the results will not be as expected."));
1079 }
1081 Glib::ustring stdout_data = fileout.string();
1082 if (stdout_data.length() == 0) {
1083 return 0;
1084 }
1086 // std::cout << "Finishing Execution." << std::endl;
1087 return stdout_data.length();
1088 }
1093 } // namespace Implementation
1094 } // namespace Extension
1095 } // namespace Inkscape
1097 /*
1098 Local Variables:
1099 mode:c++
1100 c-file-style:"stroustrup"
1101 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1102 indent-tabs-mode:nil
1103 fill-column:99
1104 End:
1105 */
1106 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :