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 "preferences.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"
56 #include "application/application.h"
58 #include "util/glib-list-iterators.h"
62 #ifdef WIN32
63 #include <windows.h>
64 #include <sys/stat.h>
65 #include "registrytool.h"
66 #endif
70 /** This is the command buffer that gets allocated from the stack */
71 #define BUFSIZE (255)
75 /* Namespaces */
76 namespace Inkscape {
77 namespace Extension {
78 namespace Implementation {
80 void pump_events (void) {
81 while( Gtk::Main::events_pending() )
82 Gtk::Main::iteration();
83 return;
84 }
86 //Interpreter lookup table
87 struct interpreter_t {
88 gchar const *identity;
89 gchar const *prefstring;
90 gchar const *defaultval;
91 };
94 /** \brief A table of what interpreters to call for a given language
96 This table is used to keep track of all the programs to execute a
97 given script. It also tracks the preference to use to overwrite
98 the given interpreter to a custom one per user.
99 */
100 static interpreter_t const interpreterTab[] = {
101 {"perl", "perl-interpreter", "perl" },
102 #ifdef WIN32
103 {"python", "python-interpreter", "pythonw" },
104 #else
105 {"python", "python-interpreter", "python" },
106 #endif
107 {"ruby", "ruby-interpreter", "ruby" },
108 {"shell", "shell-interpreter", "sh" },
109 { NULL, NULL, NULL }
110 };
114 /**
115 * Look up an interpreter name, and translate to something that
116 * is executable
117 */
118 static Glib::ustring
119 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
120 {
122 Glib::ustring interpName = interpNameArg;
124 interpreter_t const *interp;
125 bool foundInterp = false;
126 for (interp = interpreterTab ; interp->identity ; interp++ ){
127 if (interpName == interp->identity) {
128 foundInterp = true;
129 break;
130 }
131 }
133 // Do we have a supported interpreter type?
134 if (!foundInterp)
135 return "";
136 interpName = interp->defaultval;
138 // 1. Check preferences
139 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
140 Glib::ustring prefInterp = prefs->getString("extensions", interp->prefstring);
142 if (!prefInterp.empty()) {
143 interpName = prefInterp;
144 return interpName;
145 }
147 #ifdef WIN32
149 // 2. Windows. Try looking relative to inkscape.exe
150 RegistryTool rt;
151 Glib::ustring fullPath;
152 Glib::ustring path;
153 Glib::ustring exeName;
154 if (rt.getExeInfo(fullPath, path, exeName)) {
155 Glib::ustring interpPath = path;
156 interpPath.append("\\");
157 interpPath.append(interpNameArg);
158 interpPath.append("\\");
159 interpPath.append(interpName);
160 interpPath.append(".exe");
161 struct stat finfo;
162 if (stat(interpPath .c_str(), &finfo) ==0) {
163 g_message("Found local interpreter, '%s', Size: %d",
164 interpPath .c_str(),
165 (int)finfo.st_size);
166 return interpPath;
167 }
168 }
170 // 3. Try searching the path
171 char szExePath[MAX_PATH];
172 char szCurrentDir[MAX_PATH];
173 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
174 unsigned int ret = (unsigned int)FindExecutable(
175 interpName.c_str(), szCurrentDir, szExePath);
176 if (ret > 32) {
177 interpName = szExePath;
178 return interpName;
179 }
181 #endif // win32
184 return interpName;
185 }
189 /** \brief This function creates a script object and sets up the
190 variables.
191 \return A script object
193 This function just sets the command to NULL. It should get built
194 officially in the load function. This allows for less allocation
195 of memory in the unloaded state.
196 */
197 Script::Script() :
198 Implementation()
199 {
200 }
203 /**
204 * brief Destructor
205 */
206 Script::~Script()
207 {
208 }
212 /**
213 \return A string with the complete string with the relative directory expanded
214 \brief This function takes in a Repr that contains a reldir entry
215 and returns that data with the relative directory expanded.
216 Mostly it is here so that relative directories all get used
217 the same way.
218 \param reprin The Inkscape::XML::Node with the reldir in it.
220 Basically this function looks at an attribute of the Repr, and makes
221 a decision based on that. Currently, it is only working with the
222 'extensions' relative directory, but there will be more of them.
223 One thing to notice is that this function always returns an allocated
224 string. This means that the caller of this function can always
225 free what they are given (and should do it too!).
226 */
227 Glib::ustring
228 Script::solve_reldir(Inkscape::XML::Node *reprin) {
230 gchar const *s = reprin->attribute("reldir");
232 if (!s) {
233 Glib::ustring str = sp_repr_children(reprin)->content();
234 return str;
235 }
237 Glib::ustring reldir = s;
239 if (reldir == "extensions") {
241 for (unsigned int i=0;
242 i < Inkscape::Extension::Extension::search_path.size();
243 i++) {
245 gchar * fname = g_build_filename(
246 Inkscape::Extension::Extension::search_path[i],
247 sp_repr_children(reprin)->content(),
248 NULL);
249 Glib::ustring filename = fname;
250 g_free(fname);
252 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
253 return filename;
255 }
256 } else {
257 Glib::ustring str = sp_repr_children(reprin)->content();
258 return str;
259 }
261 return "";
262 }
266 /**
267 \return Whether the command given exists, including in the path
268 \brief This function is used to find out if something exists for
269 the check command. It can look in the path if required.
270 \param command The command or file that should be looked for
272 The first thing that this function does is check to see if the
273 incoming file name has a directory delimiter in it. This would
274 mean that it wants to control the directories, and should be
275 used directly.
277 If not, the path is used. Each entry in the path is stepped through,
278 attached to the string, and then tested. If the file is found
279 then a TRUE is returned. If we get all the way through the path
280 then a FALSE is returned, the command could not be found.
281 */
282 bool
283 Script::check_existance(const Glib::ustring &command)
284 {
286 // Check the simple case first
287 if (command.size() == 0) {
288 return false;
289 }
291 //Don't search when it contains a slash. */
292 if (command.find(G_DIR_SEPARATOR) != command.npos) {
293 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
294 return true;
295 else
296 return false;
297 }
300 Glib::ustring path;
301 gchar *s = (gchar *) g_getenv("PATH");
302 if (s)
303 path = s;
304 else
305 /* There is no `PATH' in the environment.
306 The default search path is the current directory */
307 path = G_SEARCHPATH_SEPARATOR_S;
309 std::string::size_type pos = 0;
310 std::string::size_type pos2 = 0;
311 while ( pos < path.size() ) {
313 Glib::ustring localPath;
315 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
316 if (pos2 == path.npos) {
317 localPath = path.substr(pos);
318 pos = path.size();
319 } else {
320 localPath = path.substr(pos, pos2-pos);
321 pos = pos2+1;
322 }
324 //printf("### %s\n", localPath.c_str());
325 Glib::ustring candidatePath =
326 Glib::build_filename(localPath, command);
328 if (Inkscape::IO::file_test(candidatePath .c_str(),
329 G_FILE_TEST_EXISTS)) {
330 return true;
331 }
333 }
335 return false;
336 }
342 /**
343 \return none
344 \brief This function 'loads' an extention, basically it determines
345 the full command for the extention and stores that.
346 \param module The extention to be loaded.
348 The most difficult part about this function is finding the actual
349 command through all of the Reprs. Basically it is hidden down a
350 couple of layers, and so the code has to move down too. When
351 the command is actually found, it has its relative directory
352 solved.
354 At that point all of the loops are exited, and there is an
355 if statement to make sure they didn't exit because of not finding
356 the command. If that's the case, the extention doesn't get loaded
357 and should error out at a higher level.
358 */
360 bool
361 Script::load(Inkscape::Extension::Extension *module)
362 {
363 if (module->loaded())
364 return true;
366 helper_extension = "";
368 /* This should probably check to find the executable... */
369 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
370 while (child_repr != NULL) {
371 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
372 child_repr = sp_repr_children(child_repr);
373 while (child_repr != NULL) {
374 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
375 const gchar *interpretstr = child_repr->attribute("interpreter");
376 if (interpretstr != NULL) {
377 Glib::ustring interpString =
378 resolveInterpreterExecutable(interpretstr);
379 //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
380 command.insert(command.end(), interpretstr);
381 }
382 Glib::ustring tmp = "\"";
383 tmp += solve_reldir(child_repr);
384 tmp += "\"";
386 command.insert(command.end(), tmp);
387 }
388 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
389 helper_extension = sp_repr_children(child_repr)->content();
390 }
391 child_repr = sp_repr_next(child_repr);
392 }
394 break;
395 }
396 child_repr = sp_repr_next(child_repr);
397 }
399 //g_return_val_if_fail(command.length() > 0, false);
401 return true;
402 }
405 /**
406 \return None.
407 \brief Unload this puppy!
408 \param module Extension to be unloaded.
410 This function just sets the module to unloaded. It free's the
411 command if it has been allocated.
412 */
413 void
414 Script::unload(Inkscape::Extension::Extension */*module*/)
415 {
416 command.clear();
417 helper_extension = "";
418 }
423 /**
424 \return Whether the check passed or not
425 \brief Check every dependency that was given to make sure we should keep this extension
426 \param module The Extension in question
428 */
429 bool
430 Script::check(Inkscape::Extension::Extension *module)
431 {
432 int script_count = 0;
433 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
434 while (child_repr != NULL) {
435 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
436 script_count++;
437 child_repr = sp_repr_children(child_repr);
438 while (child_repr != NULL) {
439 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
440 Glib::ustring command_text = solve_reldir(child_repr);
441 if (command_text.size() > 0) {
442 /* I've got the command */
443 bool existance = check_existance(command_text);
444 if (!existance)
445 return false;
446 }
447 }
449 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
450 gchar const *helper = sp_repr_children(child_repr)->content();
451 if (Inkscape::Extension::db.get(helper) == NULL) {
452 return false;
453 }
454 }
456 child_repr = sp_repr_next(child_repr);
457 }
459 break;
460 }
461 child_repr = sp_repr_next(child_repr);
462 }
464 if (script_count == 0) {
465 return false;
466 }
468 return true;
469 }
471 class ScriptDocCache : public ImplementationDocumentCache {
472 friend class Script;
473 protected:
474 std::string _filename;
475 int _tempfd;
476 public:
477 ScriptDocCache (Inkscape::UI::View::View * view);
478 ~ScriptDocCache ( );
479 };
481 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
482 ImplementationDocumentCache(view),
483 _filename(""),
484 _tempfd(0)
485 {
486 try {
487 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
488 } catch (...) {
489 /// \todo Popup dialog here
490 return;
491 }
493 SPDesktop *desktop = (SPDesktop *) view;
494 sp_namedview_document_from_window(desktop);
496 Inkscape::Extension::save(
497 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
498 view->doc(), _filename.c_str(), false, false, false);
500 return;
501 }
503 ScriptDocCache::~ScriptDocCache ( )
504 {
505 close(_tempfd);
506 unlink(_filename.c_str());
507 }
509 ImplementationDocumentCache *
510 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
511 return new ScriptDocCache(view);
512 }
515 /**
516 \return A dialog for preferences
517 \brief A stub funtion right now
518 \param module Module who's preferences need getting
519 \param filename Hey, the file you're getting might be important
521 This function should really do something, right now it doesn't.
522 */
523 Gtk::Widget *
524 Script::prefs_input(Inkscape::Extension::Input *module,
525 const gchar */*filename*/)
526 {
527 return module->autogui(NULL, NULL);
528 }
532 /**
533 \return A dialog for preferences
534 \brief A stub funtion right now
535 \param module Module whose preferences need getting
537 This function should really do something, right now it doesn't.
538 */
539 Gtk::Widget *
540 Script::prefs_output(Inkscape::Extension::Output *module)
541 {
542 return module->autogui(NULL, NULL);
543 }
545 /**
546 \return A new document that has been opened
547 \brief This function uses a filename that is put in, and calls
548 the extension's command to create an SVG file which is
549 returned.
550 \param module Extension to use.
551 \param filename File to open.
553 First things first, this function needs a temporary file name. To
554 create on of those the function g_file_open_tmp is used with
555 the header of ink_ext_.
557 The extension is then executed using the 'execute' function
558 with the filname coming in, and the temporary filename. After
559 That executing, the SVG should be in the temporary file.
561 Finally, the temporary file is opened using the SVG input module and
562 a document is returned. That document has its filename set to
563 the incoming filename (so that it's not the temporary filename).
564 That document is then returned from this function.
565 */
566 SPDocument *
567 Script::open(Inkscape::Extension::Input *module,
568 const gchar *filenameArg)
569 {
570 std::list<std::string> params;
571 module->paramListString(params);
573 std::string tempfilename_out;
574 int tempfd_out = 0;
575 try {
576 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
577 } catch (...) {
578 /// \todo Popup dialog here
579 return NULL;
580 }
582 std::string lfilename = Glib::filename_from_utf8(filenameArg);
584 file_listener fileout;
585 int data_read = execute(command, params, lfilename, fileout);
586 fileout.toFile(tempfilename_out);
588 SPDocument * mydoc = NULL;
589 if (data_read > 10) {
590 if (helper_extension.size()==0) {
591 mydoc = Inkscape::Extension::open(
592 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
593 tempfilename_out.c_str());
594 } else {
595 mydoc = Inkscape::Extension::open(
596 Inkscape::Extension::db.get(helper_extension.c_str()),
597 tempfilename_out.c_str());
598 }
599 } // data_read
601 if (mydoc != NULL) {
602 sp_document_set_uri(mydoc, filenameArg);
603 }
605 // make sure we don't leak file descriptors from g_file_open_tmp
606 close(tempfd_out);
608 unlink(tempfilename_out.c_str());
610 return mydoc;
611 } // open
615 /**
616 \return none
617 \brief This function uses an extention to save a document. It first
618 creates an SVG file of the document, and then runs it through
619 the script.
620 \param module Extention to be used
621 \param doc Document to be saved
622 \param filename The name to save the final file as
624 Well, at some point people need to save - it is really what makes
625 the entire application useful. And, it is possible that someone
626 would want to use an extetion for this, so we need a function to
627 do that eh?
629 First things first, the document is saved to a temporary file that
630 is an SVG file. To get the temporary filename g_file_open_tmp is used with
631 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
632 end of the function.
634 After we have the SVG file, then extention_execute is called with
635 the temporary file name and the final output filename. This should
636 put the output of the script into the final output file. We then
637 delete the temporary file.
638 */
639 void
640 Script::save(Inkscape::Extension::Output *module,
641 SPDocument *doc,
642 const gchar *filenameArg)
643 {
644 std::list<std::string> params;
645 module->paramListString(params);
647 std::string tempfilename_in;
648 int tempfd_in = 0;
649 try {
650 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
651 } catch (...) {
652 /// \todo Popup dialog here
653 return;
654 }
656 if (helper_extension.size() == 0) {
657 Inkscape::Extension::save(
658 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
659 doc, tempfilename_in.c_str(), false, false, false);
660 } else {
661 Inkscape::Extension::save(
662 Inkscape::Extension::db.get(helper_extension.c_str()),
663 doc, tempfilename_in.c_str(), false, false, false);
664 }
667 file_listener fileout;
668 execute(command, params, tempfilename_in, fileout);
670 std::string lfilename = Glib::filename_from_utf8(filenameArg);
671 fileout.toFile(lfilename);
673 // make sure we don't leak file descriptors from g_file_open_tmp
674 close(tempfd_in);
675 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
676 unlink(tempfilename_in.c_str());
678 return;
679 }
683 /**
684 \return none
685 \brief This function uses an extention as a effect on a document.
686 \param module Extention to effect with.
687 \param doc Document to run through the effect.
689 This function is a little bit trickier than the previous two. It
690 needs two temporary files to get it's work done. Both of these
691 files have random names created for them using the g_file_open_temp function
692 with the ink_ext_ prefix in the temporary directory. Like the other
693 functions, the temporary files are deleted at the end.
695 To save/load the two temporary documents (both are SVG) the internal
696 modules for SVG load and save are used. They are both used through
697 the module system function by passing their keys into the functions.
699 The command itself is built a little bit differently than in other
700 functions because the effect support selections. So on the command
701 line a list of all the ids that are selected is included. Currently,
702 this only works for a single selected object, but there will be more.
703 The command string is filled with the data, and then after the execution
704 it is freed.
706 The execute function is used at the core of this function
707 to execute the Script on the two SVG documents (actually only one
708 exists at the time, the other is created by that script). At that
709 point both should be full, and the second one is loaded.
710 */
711 void
712 Script::effect(Inkscape::Extension::Effect *module,
713 Inkscape::UI::View::View *doc,
714 ImplementationDocumentCache * docCache)
715 {
716 if (docCache == NULL) {
717 docCache = newDocCache(module, doc);
718 }
719 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
720 if (dc == NULL) {
721 printf("TOO BAD TO LIVE!!!");
722 exit(1);
723 }
725 SPDesktop *desktop = (SPDesktop *)doc;
726 sp_namedview_document_from_window(desktop);
728 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
730 std::list<std::string> params;
731 module->paramListString(params);
733 if (module->no_doc) {
734 // this is a no-doc extension, e.g. a Help menu command;
735 // just run the command without any files, ignoring errors
737 Glib::ustring empty;
738 file_listener outfile;
739 execute(command, params, empty, outfile);
741 return;
742 }
744 std::string tempfilename_out;
745 int tempfd_out = 0;
746 try {
747 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
748 } catch (...) {
749 /// \todo Popup dialog here
750 return;
751 }
753 if (desktop != NULL) {
754 Inkscape::Util::GSListConstIterator<SPItem *> selected =
755 sp_desktop_selection(desktop)->itemList();
756 while ( selected != NULL ) {
757 Glib::ustring selected_id;
758 selected_id += "--id=";
759 selected_id += SP_OBJECT_ID(*selected);
760 params.insert(params.begin(), selected_id);
761 ++selected;
762 }
763 }
765 file_listener fileout;
766 int data_read = execute(command, params, dc->_filename, fileout);
767 fileout.toFile(tempfilename_out);
769 pump_events();
771 SPDocument * mydoc = NULL;
772 if (data_read > 10) {
773 mydoc = Inkscape::Extension::open(
774 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
775 tempfilename_out.c_str());
776 } // data_read
778 pump_events();
780 // make sure we don't leak file descriptors from g_file_open_tmp
781 close(tempfd_out);
783 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
784 unlink(tempfilename_out.c_str());
786 /* Do something with mydoc.... */
787 if (mydoc) {
788 doc->doc()->emitReconstructionStart();
789 copy_doc(doc->doc()->rroot, mydoc->rroot);
790 doc->doc()->emitReconstructionFinish();
791 mydoc->release();
792 sp_namedview_update_layers_from_document(desktop);
794 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
795 }
796 g_free(orig_output_extension);
798 return;
799 }
803 /**
804 \brief A function to take all the svg elements from one document
805 and put them in another.
806 \param oldroot The root node of the document to be replaced
807 \param newroot The root node of the document to replace it with
809 This function first deletes all of the data in the old document. It
810 does this by creating a list of what needs to be deleted, and then
811 goes through the list. This two pass approach removes issues with
812 the list being change while parsing through it. Lots of nasty bugs.
814 Then, it goes through the new document, duplicating all of the
815 elements and putting them into the old document. The copy
816 is then complete.
817 */
818 void
819 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
820 {
821 std::vector<Inkscape::XML::Node *> delete_list;
822 Inkscape::XML::Node * oldroot_namedview = NULL;
824 for (Inkscape::XML::Node * child = oldroot->firstChild();
825 child != NULL;
826 child = child->next()) {
827 if (!strcmp("sodipodi:namedview", child->name())) {
828 oldroot_namedview = child;
829 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
830 oldroot_namedview_child != NULL;
831 oldroot_namedview_child = oldroot_namedview_child->next()) {
832 delete_list.push_back(oldroot_namedview_child);
833 }
834 } else {
835 delete_list.push_back(child);
836 }
837 }
838 for (unsigned int i = 0; i < delete_list.size(); i++)
839 sp_repr_unparent(delete_list[i]);
841 for (Inkscape::XML::Node * child = newroot->firstChild();
842 child != NULL;
843 child = child->next()) {
844 if (!strcmp("sodipodi:namedview", child->name())) {
845 if (oldroot_namedview != NULL) {
846 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
847 newroot_namedview_child != NULL;
848 newroot_namedview_child = newroot_namedview_child->next()) {
849 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
850 }
851 }
852 } else {
853 oldroot->appendChild(child->duplicate(oldroot->document()));
854 }
855 }
857 oldroot->setAttribute("width", newroot->attribute("width"));
858 oldroot->setAttribute("height", newroot->attribute("height"));
860 /** \todo Restore correct layer */
861 /** \todo Restore correct selection */
862 }
864 /** \brief This function checks the stderr file, and if it has data,
865 shows it in a warning dialog to the user
866 \param filename Filename of the stderr file
867 */
868 void
869 Script::checkStderr (const Glib::ustring &data,
870 Gtk::MessageType type,
871 const Glib::ustring &message)
872 {
873 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
874 warning.set_resizable(true);
875 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
876 sp_transientize(dlg);
878 Gtk::VBox * vbox = warning.get_vbox();
880 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
881 Gtk::TextView * textview = new Gtk::TextView();
882 textview->set_editable(false);
883 textview->set_wrap_mode(Gtk::WRAP_WORD);
884 textview->show();
886 textview->get_buffer()->set_text(data.c_str());
888 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
889 scrollwindow->add(*textview);
890 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
891 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
892 scrollwindow->show();
894 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
896 warning.run();
898 return;
899 }
901 bool
902 Script::cancelProcessing (void) {
903 _canceled = true;
904 _main_loop->quit();
905 Glib::spawn_close_pid(_pid);
907 return true;
908 }
911 /** \brief This is the core of the extension file as it actually does
912 the execution of the extension.
913 \param in_command The command to be executed
914 \param filein Filename coming in
915 \param fileout Filename of the out file
916 \return Number of bytes that were read into the output file.
918 The first thing that this function does is build the command to be
919 executed. This consists of the first string (in_command) and then
920 the filename for input (filein). This file is put on the command
921 line.
923 The next thing is that this function does is open a pipe to the
924 command and get the file handle in the ppipe variable. It then
925 opens the output file with the output file handle. Both of these
926 operations are checked extensively for errors.
928 After both are opened, then the data is copied from the output
929 of the pipe into the file out using fread and fwrite. These two
930 functions are used because of their primitive nature they make
931 no assumptions about the data. A buffer is used in the transfer,
932 but the output of fread is stored so the exact number of bytes
933 is handled gracefully.
935 At the very end (after the data has been copied) both of the files
936 are closed, and we return to what we were doing.
937 */
938 int
939 Script::execute (const std::list<std::string> &in_command,
940 const std::list<std::string> &in_params,
941 const Glib::ustring &filein,
942 file_listener &fileout)
943 {
944 g_return_val_if_fail(in_command.size() > 0, 0);
945 // printf("Executing\n");
947 std::vector <std::string> argv;
949 /*
950 for (std::list<std::string>::const_iterator i = in_command.begin();
951 i != in_command.end(); i++) {
952 argv.push_back(*i);
953 }
954 */
955 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
956 // we tokenize so that spwan does not need to quote over all params
957 for (std::list<std::string>::const_iterator i = in_command.begin();
958 i != in_command.end(); i++) {
959 std::string param_str = *i;
960 do {
961 //g_message("param: %s", param_str.c_str());
962 size_t first_space = param_str.find_first_of(' ');
963 size_t first_quote = param_str.find_first_of('"');
964 //std::cout << "first space " << first_space << std::endl;
965 //std::cout << "first quote " << first_quote << std::endl;
967 if((first_quote != std::string::npos) && (first_quote == 0)) {
968 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
969 //std::cout << "next quote " << next_quote << std::endl;
971 if(next_quote != std::string::npos) {
972 //std::cout << "now split " << next_quote << std::endl;
973 //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
974 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
975 std::string part_str = param_str.substr(1, next_quote - 1);
976 if(part_str.size() > 0)
977 argv.push_back(part_str);
978 param_str = param_str.substr(next_quote + 1);
980 }
981 else {
982 if(param_str.size() > 0)
983 argv.push_back(param_str);
984 param_str = "";
985 }
987 }
988 else if(first_space != std::string::npos) {
989 //std::cout << "now split " << first_space << std::endl;
990 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
991 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
992 std::string part_str = param_str.substr(0, first_space);
993 if(part_str.size() > 0)
994 argv.push_back(part_str);
995 param_str = param_str.substr(first_space + 1);
996 }
997 else {
998 if(param_str.size() > 0)
999 argv.push_back(param_str);
1000 param_str = "";
1001 }
1002 } while(param_str.size() > 0);
1003 }
1005 for (std::list<std::string>::const_iterator i = in_params.begin();
1006 i != in_params.end(); i++) {
1007 //g_message("Script parameter: %s",(*i)g.c_str());
1008 argv.push_back(*i);
1009 }
1011 if (!(filein.empty())) {
1012 argv.push_back(filein);
1013 }
1015 int stdout_pipe, stderr_pipe;
1017 try {
1018 Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1019 argv, // arg v
1020 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1021 sigc::slot<void>(),
1022 &_pid, // Pid
1023 NULL, // STDIN
1024 &stdout_pipe, // STDOUT
1025 &stderr_pipe); // STDERR
1026 } catch (Glib::SpawnError e) {
1027 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1028 return 0;
1029 }
1031 _main_loop = Glib::MainLoop::create(false);
1033 file_listener fileerr;
1034 fileout.init(stdout_pipe, _main_loop);
1035 fileerr.init(stderr_pipe, _main_loop);
1037 _canceled = false;
1038 _main_loop->run();
1040 // Ensure all the data is out of the pipe
1041 while (!fileout.isDead())
1042 fileout.read(Glib::IO_IN);
1043 while (!fileerr.isDead())
1044 fileerr.read(Glib::IO_IN);
1046 if (_canceled) {
1047 // std::cout << "Script Canceled" << std::endl;
1048 return 0;
1049 }
1051 Glib::ustring stderr_data = fileerr.string();
1052 if (stderr_data.length() != 0 &&
1053 Inkscape::NSApplication::Application::getUseGui()
1054 ) {
1055 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1056 _("Inkscape has received additional data from the script executed. "
1057 "The script did not return an error, but this may indicate the results will not be as expected."));
1058 }
1060 Glib::ustring stdout_data = fileout.string();
1061 if (stdout_data.length() == 0) {
1062 return 0;
1063 }
1065 // std::cout << "Finishing Execution." << std::endl;
1066 return stdout_data.length();
1067 }
1072 } // namespace Implementation
1073 } // namespace Extension
1074 } // namespace Inkscape
1076 /*
1077 Local Variables:
1078 mode:c++
1079 c-file-style:"stroustrup"
1080 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1081 indent-tabs-mode:nil
1082 fill-column:99
1083 End:
1084 */
1085 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :