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