09dc9eb30dd4f99a00294da59cef9fcdb69fb361
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 {"python", "python-interpreter", "python" },
102 {"ruby", "ruby-interpreter", "ruby" },
103 {"shell", "shell-interpreter", "sh" },
104 { NULL, NULL, NULL }
105 };
109 /**
110 * Look up an interpreter name, and translate to something that
111 * is executable
112 */
113 static Glib::ustring
114 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
115 {
117 Glib::ustring interpName = interpNameArg;
119 interpreter_t const *interp;
120 bool foundInterp = false;
121 for (interp = interpreterTab ; interp->identity ; interp++ ){
122 if (interpName == interp->identity) {
123 foundInterp = true;
124 break;
125 }
126 }
128 // Do we have a supported interpreter type?
129 if (!foundInterp)
130 return "";
131 interpName = interp->defaultval;
133 // 1. Check preferences
134 gchar const *prefInterp = prefs_get_string_attribute("extensions", interp->prefstring);
136 if (prefInterp) {
137 interpName = prefInterp;
138 return interpName;
139 }
141 #ifdef _WIN32
143 // 2. Windows. Try looking relative to inkscape.exe
144 RegistryTool rt;
145 Glib::ustring fullPath;
146 Glib::ustring path;
147 Glib::ustring exeName;
148 if (rt.getExeInfo(fullPath, path, exeName)) {
149 Glib::ustring interpPath = path;
150 interpPath.append("\\");
151 interpPath.append(interpName);
152 interpPath.append("\\");
153 interpPath.append(interpName);
154 interpPath.append(".exe");
155 struct stat finfo;
156 if (stat(interpPath .c_str(), &finfo) ==0) {
157 g_message("Found local interpreter, '%s', Size: %d",
158 interpPath .c_str(),
159 (int)finfo.st_size);
160 return interpPath;
161 }
162 }
164 // 3. Try searching the path
165 char szExePath[MAX_PATH];
166 char szCurrentDir[MAX_PATH];
167 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
168 unsigned int ret = (unsigned int)FindExecutable(
169 interpName.c_str(), szCurrentDir, szExePath);
170 if (ret > 32) {
171 interpName = szExePath;
172 return interpName;
173 }
175 #endif // win32
178 return interpName;
179 }
183 /** \brief This function creates a script object and sets up the
184 variables.
185 \return A script object
187 This function just sets the command to NULL. It should get built
188 officially in the load function. This allows for less allocation
189 of memory in the unloaded state.
190 */
191 Script::Script() :
192 Implementation()
193 {
194 }
197 /**
198 * brief Destructor
199 */
200 Script::~Script()
201 {
202 }
206 /**
207 \return A string with the complete string with the relative directory expanded
208 \brief This function takes in a Repr that contains a reldir entry
209 and returns that data with the relative directory expanded.
210 Mostly it is here so that relative directories all get used
211 the same way.
212 \param reprin The Inkscape::XML::Node with the reldir in it.
214 Basically this function looks at an attribute of the Repr, and makes
215 a decision based on that. Currently, it is only working with the
216 'extensions' relative directory, but there will be more of them.
217 One thing to notice is that this function always returns an allocated
218 string. This means that the caller of this function can always
219 free what they are given (and should do it too!).
220 */
221 Glib::ustring
222 Script::solve_reldir(Inkscape::XML::Node *reprin) {
224 gchar const *s = reprin->attribute("reldir");
226 if (!s) {
227 Glib::ustring str = sp_repr_children(reprin)->content();
228 return str;
229 }
231 Glib::ustring reldir = s;
233 if (reldir == "extensions") {
235 for (unsigned int i=0;
236 i < Inkscape::Extension::Extension::search_path.size();
237 i++) {
239 gchar * fname = g_build_filename(
240 Inkscape::Extension::Extension::search_path[i],
241 sp_repr_children(reprin)->content(),
242 NULL);
243 Glib::ustring filename = fname;
244 g_free(fname);
246 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
247 return filename;
249 }
250 } else {
251 Glib::ustring str = sp_repr_children(reprin)->content();
252 return str;
253 }
255 return "";
256 }
260 /**
261 \return Whether the command given exists, including in the path
262 \brief This function is used to find out if something exists for
263 the check command. It can look in the path if required.
264 \param command The command or file that should be looked for
266 The first thing that this function does is check to see if the
267 incoming file name has a directory delimiter in it. This would
268 mean that it wants to control the directories, and should be
269 used directly.
271 If not, the path is used. Each entry in the path is stepped through,
272 attached to the string, and then tested. If the file is found
273 then a TRUE is returned. If we get all the way through the path
274 then a FALSE is returned, the command could not be found.
275 */
276 bool
277 Script::check_existance(const Glib::ustring &command)
278 {
280 // Check the simple case first
281 if (command.size() == 0) {
282 return false;
283 }
285 //Don't search when it contains a slash. */
286 if (command.find(G_DIR_SEPARATOR) != command.npos) {
287 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
288 return true;
289 else
290 return false;
291 }
294 Glib::ustring path;
295 gchar *s = (gchar *) g_getenv("PATH");
296 if (s)
297 path = s;
298 else
299 /* There is no `PATH' in the environment.
300 The default search path is the current directory */
301 path = G_SEARCHPATH_SEPARATOR_S;
303 std::string::size_type pos = 0;
304 std::string::size_type pos2 = 0;
305 while ( pos < path.size() ) {
307 Glib::ustring localPath;
309 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
310 if (pos2 == path.npos) {
311 localPath = path.substr(pos);
312 pos = path.size();
313 } else {
314 localPath = path.substr(pos, pos2-pos);
315 pos = pos2+1;
316 }
318 //printf("### %s\n", localPath.c_str());
319 Glib::ustring candidatePath =
320 Glib::build_filename(localPath, command);
322 if (Inkscape::IO::file_test(candidatePath .c_str(),
323 G_FILE_TEST_EXISTS)) {
324 return true;
325 }
327 }
329 return false;
330 }
336 /**
337 \return none
338 \brief This function 'loads' an extention, basically it determines
339 the full command for the extention and stores that.
340 \param module The extention to be loaded.
342 The most difficult part about this function is finding the actual
343 command through all of the Reprs. Basically it is hidden down a
344 couple of layers, and so the code has to move down too. When
345 the command is actually found, it has its relative directory
346 solved.
348 At that point all of the loops are exited, and there is an
349 if statement to make sure they didn't exit because of not finding
350 the command. If that's the case, the extention doesn't get loaded
351 and should error out at a higher level.
352 */
354 bool
355 Script::load(Inkscape::Extension::Extension *module)
356 {
357 if (module->loaded())
358 return TRUE;
360 helper_extension = "";
362 /* This should probably check to find the executable... */
363 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
364 while (child_repr != NULL) {
365 if (!strcmp(child_repr->name(), "script")) {
366 child_repr = sp_repr_children(child_repr);
367 while (child_repr != NULL) {
368 if (!strcmp(child_repr->name(), "command")) {
369 const gchar *interpretstr = child_repr->attribute("interpreter");
370 if (interpretstr != NULL) {
371 Glib::ustring interpString =
372 resolveInterpreterExecutable(interpretstr);
373 command.insert(command.end(), interpretstr);
374 }
376 command.insert(command.end(), solve_reldir(child_repr));
377 }
378 if (!strcmp(child_repr->name(), "helper_extension")) {
379 helper_extension = sp_repr_children(child_repr)->content();
380 }
381 child_repr = sp_repr_next(child_repr);
382 }
384 break;
385 }
386 child_repr = sp_repr_next(child_repr);
387 }
389 //g_return_val_if_fail(command.length() > 0, FALSE);
391 return true;
392 }
395 /**
396 \return None.
397 \brief Unload this puppy!
398 \param module Extension to be unloaded.
400 This function just sets the module to unloaded. It free's the
401 command if it has been allocated.
402 */
403 void
404 Script::unload(Inkscape::Extension::Extension */*module*/)
405 {
406 command.clear();
407 helper_extension = "";
408 }
413 /**
414 \return Whether the check passed or not
415 \brief Check every dependency that was given to make sure we should keep this extension
416 \param module The Extension in question
418 */
419 bool
420 Script::check(Inkscape::Extension::Extension *module)
421 {
422 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
423 while (child_repr != NULL) {
424 if (!strcmp(child_repr->name(), "script")) {
425 child_repr = sp_repr_children(child_repr);
426 while (child_repr != NULL) {
427 if (!strcmp(child_repr->name(), "check")) {
428 Glib::ustring command_text = solve_reldir(child_repr);
429 if (command_text.size() > 0) {
430 /* I've got the command */
431 bool existance = check_existance(command_text);
432 if (!existance)
433 return FALSE;
434 }
435 }
437 if (!strcmp(child_repr->name(), "helper_extension")) {
438 gchar const *helper = sp_repr_children(child_repr)->content();
439 if (Inkscape::Extension::db.get(helper) == NULL) {
440 return FALSE;
441 }
442 }
444 child_repr = sp_repr_next(child_repr);
445 }
447 break;
448 }
449 child_repr = sp_repr_next(child_repr);
450 }
452 return true;
453 }
455 class ScriptDocCache : public ImplementationDocumentCache {
456 friend class Script;
457 protected:
458 std::string _filename;
459 int _tempfd;
460 public:
461 ScriptDocCache (Inkscape::UI::View::View * view);
462 ~ScriptDocCache ( );
463 };
465 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
466 ImplementationDocumentCache(view),
467 _filename(""),
468 _tempfd(0)
469 {
470 try {
471 _tempfd = Glib::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
472 } catch (...) {
473 /// \todo Popup dialog here
474 return;
475 }
477 SPDesktop *desktop = (SPDesktop *) view;
478 sp_namedview_document_from_window(desktop);
480 Inkscape::Extension::save(
481 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
482 view->doc(), _filename.c_str(), FALSE, FALSE, FALSE);
484 return;
485 }
487 ScriptDocCache::~ScriptDocCache ( )
488 {
489 close(_tempfd);
490 unlink(_filename.c_str());
491 }
493 ImplementationDocumentCache *
494 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
495 return new ScriptDocCache(view);
496 }
499 /**
500 \return A dialog for preferences
501 \brief A stub funtion right now
502 \param module Module who's preferences need getting
503 \param filename Hey, the file you're getting might be important
505 This function should really do something, right now it doesn't.
506 */
507 Gtk::Widget *
508 Script::prefs_input(Inkscape::Extension::Input *module,
509 const gchar */*filename*/)
510 {
511 return module->autogui(NULL, NULL);
512 }
516 /**
517 \return A dialog for preferences
518 \brief A stub funtion right now
519 \param module Module whose preferences need getting
521 This function should really do something, right now it doesn't.
522 */
523 Gtk::Widget *
524 Script::prefs_output(Inkscape::Extension::Output *module)
525 {
526 return module->autogui(NULL, NULL);
527 }
531 /**
532 \return A dialog for preferences
533 \brief A stub funtion right now
534 \param module Module who's preferences need getting
536 This function should really do something, right now it doesn't.
537 */
538 Gtk::Widget *
539 Script::prefs_effect( Inkscape::Extension::Effect *module,
540 Inkscape::UI::View::View *view,
541 sigc::signal<void> * changeSignal,
542 ImplementationDocumentCache * /*docCache*/ )
543 {
544 SPDocument * current_document = view->doc();
546 using Inkscape::Util::GSListConstIterator;
547 GSListConstIterator<SPItem *> selected =
548 sp_desktop_selection((SPDesktop *)view)->itemList();
549 Inkscape::XML::Node * first_select = NULL;
550 if (selected != NULL) {
551 const SPItem * item = *selected;
552 first_select = SP_OBJECT_REPR(item);
553 }
555 return module->autogui(current_document, first_select, changeSignal);
556 }
561 /**
562 \return A new document that has been opened
563 \brief This function uses a filename that is put in, and calls
564 the extension's command to create an SVG file which is
565 returned.
566 \param module Extension to use.
567 \param filename File to open.
569 First things first, this function needs a temporary file name. To
570 create on of those the function g_file_open_tmp is used with
571 the header of ink_ext_.
573 The extension is then executed using the 'execute' function
574 with the filname coming in, and the temporary filename. After
575 That executing, the SVG should be in the temporary file.
577 Finally, the temporary file is opened using the SVG input module and
578 a document is returned. That document has its filename set to
579 the incoming filename (so that it's not the temporary filename).
580 That document is then returned from this function.
581 */
582 SPDocument *
583 Script::open(Inkscape::Extension::Input *module,
584 const gchar *filenameArg)
585 {
586 std::list<std::string> params;
587 module->paramListString(params);
589 std::string tempfilename_out;
590 int tempfd_out = 0;
591 try {
592 tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
593 } catch (...) {
594 /// \todo Popup dialog here
595 return NULL;
596 }
598 std::string lfilename = Glib::filename_from_utf8(filenameArg);
600 file_listener fileout;
601 int data_read = execute(command, params, lfilename, fileout);
602 fileout.toFile(tempfilename_out);
604 SPDocument * mydoc = NULL;
605 if (data_read > 10) {
606 if (helper_extension.size()==0) {
607 mydoc = Inkscape::Extension::open(
608 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
609 tempfilename_out.c_str());
610 } else {
611 mydoc = Inkscape::Extension::open(
612 Inkscape::Extension::db.get(helper_extension.c_str()),
613 tempfilename_out.c_str());
614 }
615 } // data_read
617 if (mydoc != NULL) {
618 sp_document_set_uri(mydoc, filenameArg);
619 }
621 // make sure we don't leak file descriptors from g_file_open_tmp
622 close(tempfd_out);
624 unlink(tempfilename_out.c_str());
626 return mydoc;
627 } // open
631 /**
632 \return none
633 \brief This function uses an extention to save a document. It first
634 creates an SVG file of the document, and then runs it through
635 the script.
636 \param module Extention to be used
637 \param doc Document to be saved
638 \param filename The name to save the final file as
640 Well, at some point people need to save - it is really what makes
641 the entire application useful. And, it is possible that someone
642 would want to use an extetion for this, so we need a function to
643 do that eh?
645 First things first, the document is saved to a temporary file that
646 is an SVG file. To get the temporary filename g_file_open_tmp is used with
647 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
648 end of the function.
650 After we have the SVG file, then extention_execute is called with
651 the temporary file name and the final output filename. This should
652 put the output of the script into the final output file. We then
653 delete the temporary file.
654 */
655 void
656 Script::save(Inkscape::Extension::Output *module,
657 SPDocument *doc,
658 const gchar *filenameArg)
659 {
660 std::list<std::string> params;
661 module->paramListString(params);
663 std::string tempfilename_in;
664 int tempfd_in = 0;
665 try {
666 tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
667 } catch (...) {
668 /// \todo Popup dialog here
669 return;
670 }
672 if (helper_extension.size() == 0) {
673 Inkscape::Extension::save(
674 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
675 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
676 } else {
677 Inkscape::Extension::save(
678 Inkscape::Extension::db.get(helper_extension.c_str()),
679 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
680 }
683 file_listener fileout;
684 execute(command, params, tempfilename_in, fileout);
686 std::string lfilename = Glib::filename_from_utf8(filenameArg);
687 fileout.toFile(lfilename);
689 // make sure we don't leak file descriptors from g_file_open_tmp
690 close(tempfd_in);
691 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
692 unlink(tempfilename_in.c_str());
694 return;
695 }
699 /**
700 \return none
701 \brief This function uses an extention as a effect on a document.
702 \param module Extention to effect with.
703 \param doc Document to run through the effect.
705 This function is a little bit trickier than the previous two. It
706 needs two temporary files to get it's work done. Both of these
707 files have random names created for them using the g_file_open_temp function
708 with the ink_ext_ prefix in the temporary directory. Like the other
709 functions, the temporary files are deleted at the end.
711 To save/load the two temporary documents (both are SVG) the internal
712 modules for SVG load and save are used. They are both used through
713 the module system function by passing their keys into the functions.
715 The command itself is built a little bit differently than in other
716 functions because the effect support selections. So on the command
717 line a list of all the ids that are selected is included. Currently,
718 this only works for a single selected object, but there will be more.
719 The command string is filled with the data, and then after the execution
720 it is freed.
722 The execute function is used at the core of this function
723 to execute the Script on the two SVG documents (actually only one
724 exists at the time, the other is created by that script). At that
725 point both should be full, and the second one is loaded.
726 */
727 void
728 Script::effect(Inkscape::Extension::Effect *module,
729 Inkscape::UI::View::View *doc,
730 ImplementationDocumentCache * docCache)
731 {
732 if (docCache == NULL) {
733 docCache = newDocCache(module, doc);
734 }
735 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
736 if (dc == NULL) {
737 printf("TOO BAD TO LIVE!!!");
738 exit(1);
739 }
741 SPDesktop *desktop = (SPDesktop *)doc;
742 sp_namedview_document_from_window(desktop);
744 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
746 std::list<std::string> params;
747 module->paramListString(params);
749 if (module->no_doc) {
750 // this is a no-doc extension, e.g. a Help menu command;
751 // just run the command without any files, ignoring errors
753 Glib::ustring empty;
754 file_listener outfile;
755 execute(command, params, empty, outfile);
757 return;
758 }
760 std::string tempfilename_out;
761 int tempfd_out = 0;
762 try {
763 tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
764 } catch (...) {
765 /// \todo Popup dialog here
766 return;
767 }
769 if (desktop != NULL) {
770 Inkscape::Util::GSListConstIterator<SPItem *> selected =
771 sp_desktop_selection(desktop)->itemList();
772 while ( selected != NULL ) {
773 Glib::ustring selected_id;
774 selected_id += "--id=";
775 selected_id += SP_OBJECT_ID(*selected);
776 params.insert(params.begin(), selected_id);
777 ++selected;
778 }
779 }
781 file_listener fileout;
782 int data_read = execute(command, params, dc->_filename, fileout);
783 fileout.toFile(tempfilename_out);
785 pump_events();
787 SPDocument * mydoc = NULL;
788 if (data_read > 10) {
789 mydoc = Inkscape::Extension::open(
790 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
791 tempfilename_out.c_str());
792 } // data_read
794 pump_events();
796 // make sure we don't leak file descriptors from g_file_open_tmp
797 close(tempfd_out);
799 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
800 unlink(tempfilename_out.c_str());
802 /* Do something with mydoc.... */
803 if (mydoc) {
804 doc->doc()->emitReconstructionStart();
805 copy_doc(doc->doc()->rroot, mydoc->rroot);
806 doc->doc()->emitReconstructionFinish();
807 mydoc->release();
808 sp_namedview_update_layers_from_document(desktop);
810 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
811 }
812 g_free(orig_output_extension);
814 return;
815 }
819 /**
820 \brief A function to take all the svg elements from one document
821 and put them in another.
822 \param oldroot The root node of the document to be replaced
823 \param newroot The root node of the document to replace it with
825 This function first deletes all of the data in the old document. It
826 does this by creating a list of what needs to be deleted, and then
827 goes through the list. This two pass approach removes issues with
828 the list being change while parsing through it. Lots of nasty bugs.
830 Then, it goes through the new document, duplicating all of the
831 elements and putting them into the old document. The copy
832 is then complete.
833 */
834 void
835 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
836 {
837 std::vector<Inkscape::XML::Node *> delete_list;
838 Inkscape::XML::Node * oldroot_namedview = NULL;
840 for (Inkscape::XML::Node * child = oldroot->firstChild();
841 child != NULL;
842 child = child->next()) {
843 if (!strcmp("sodipodi:namedview", child->name())) {
844 oldroot_namedview = child;
845 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
846 oldroot_namedview_child != NULL;
847 oldroot_namedview_child = oldroot_namedview_child->next()) {
848 delete_list.push_back(oldroot_namedview_child);
849 }
850 } else {
851 delete_list.push_back(child);
852 }
853 }
854 for (unsigned int i = 0; i < delete_list.size(); i++)
855 sp_repr_unparent(delete_list[i]);
857 for (Inkscape::XML::Node * child = newroot->firstChild();
858 child != NULL;
859 child = child->next()) {
860 if (!strcmp("sodipodi:namedview", child->name())) {
861 if (oldroot_namedview != NULL) {
862 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
863 newroot_namedview_child != NULL;
864 newroot_namedview_child = newroot_namedview_child->next()) {
865 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(child->document()));
866 }
867 }
868 } else {
869 oldroot->appendChild(child->duplicate(newroot->document()));
870 }
871 }
873 oldroot->setAttribute("width", newroot->attribute("width"));
874 oldroot->setAttribute("height", newroot->attribute("height"));
876 /** \todo Restore correct layer */
877 /** \todo Restore correct selection */
878 }
880 /** \brief This function checks the stderr file, and if it has data,
881 shows it in a warning dialog to the user
882 \param filename Filename of the stderr file
883 */
884 void
885 Script::checkStderr (const Glib::ustring &data,
886 Gtk::MessageType type,
887 const Glib::ustring &message)
888 {
889 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
890 warning.set_resizable(true);
891 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
892 sp_transientize(dlg);
894 Gtk::VBox * vbox = warning.get_vbox();
896 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
897 Gtk::TextView * textview = new Gtk::TextView();
898 textview->set_editable(false);
899 textview->set_wrap_mode(Gtk::WRAP_WORD);
900 textview->show();
902 textview->get_buffer()->set_text(data.c_str());
904 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
905 scrollwindow->add(*textview);
906 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
907 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
908 scrollwindow->show();
910 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
912 warning.run();
914 return;
915 }
917 bool
918 Script::cancelProcessing (void) {
919 _canceled = true;
920 _main_loop->quit();
921 Glib::spawn_close_pid(_pid);
923 return true;
924 }
927 /** \brief This is the core of the extension file as it actually does
928 the execution of the extension.
929 \param in_command The command to be executed
930 \param filein Filename coming in
931 \param fileout Filename of the out file
932 \return Number of bytes that were read into the output file.
934 The first thing that this function does is build the command to be
935 executed. This consists of the first string (in_command) and then
936 the filename for input (filein). This file is put on the command
937 line.
939 The next thing is that this function does is open a pipe to the
940 command and get the file handle in the ppipe variable. It then
941 opens the output file with the output file handle. Both of these
942 operations are checked extensively for errors.
944 After both are opened, then the data is copied from the output
945 of the pipe into the file out using fread and fwrite. These two
946 functions are used because of their primitive nature they make
947 no assumptions about the data. A buffer is used in the transfer,
948 but the output of fread is stored so the exact number of bytes
949 is handled gracefully.
951 At the very end (after the data has been copied) both of the files
952 are closed, and we return to what we were doing.
953 */
954 int
955 Script::execute (const std::list<std::string> &in_command,
956 const std::list<std::string> &in_params,
957 const Glib::ustring &filein,
958 file_listener &fileout)
959 {
960 g_return_val_if_fail(in_command.size() > 0, 0);
961 // printf("Executing\n");
963 std::vector <std::string> argv;
965 /*
966 for (std::list<std::string>::const_iterator i = in_command.begin();
967 i != in_command.end(); i++) {
968 argv.push_back(*i);
969 }
970 */
971 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
972 // we tokenize so that spwan does not need to quote over all params
973 for (std::list<std::string>::const_iterator i = in_command.begin();
974 i != in_command.end(); i++) {
975 std::string param_str = *i;
976 //std::cout << "params " << param_str << std::endl;
977 do {
978 //std::cout << "param " << param_str << std::endl;
979 size_t first_space = param_str.find_first_of(' ');
980 size_t first_quote = param_str.find_first_of('"');
981 //std::cout << "first space " << first_space << std::endl;
982 //std::cout << "first quote " << first_quote << std::endl;
984 if((first_quote != std::string::npos) && (first_quote == 0)) {
985 size_t next_quote = param_str.find_first_of('"', first_quote);
986 //std::cout << "next quote " << next_quote << std::endl;
988 if(next_quote != std::string::npos) {
989 //std::cout << "now split " << next_quote << std::endl;
990 //std::cout << "now split " << param_str.substr(1, next_quote) << std::endl;
991 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
992 std::string part_str = param_str.substr(1, next_quote);
993 if(part_str.size() > 0)
994 argv.push_back(part_str);
995 param_str = param_str.substr(next_quote + 1);
997 }
998 }
999 else if(first_space != std::string::npos) {
1000 //std::cout << "now split " << first_space << std::endl;
1001 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
1002 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
1003 std::string part_str = param_str.substr(0, first_space);
1004 if(part_str.size() > 0)
1005 argv.push_back(part_str);
1006 param_str = param_str.substr(first_space + 1);
1007 }
1008 else {
1009 if(param_str.size() > 0)
1010 argv.push_back(param_str);
1011 param_str = "";
1012 }
1013 } while(param_str.size() > 0);
1014 }
1016 if (!(filein.empty())) {
1017 // if(argv.size() == 1) {
1018 argv.push_back(filein);
1019 /* }
1020 else {
1021 std::string parameter_str = argv.back();
1022 argv.pop_back();
1023 //TODO: quote
1024 parameter_str += " " + filein;
1025 argv.push_back(parameter_str);
1026 }
1027 */
1028 }
1030 for (std::list<std::string>::const_iterator i = in_params.begin();
1031 i != in_params.end(); i++) {
1032 argv.push_back(*i);
1033 }
1035 /*
1036 for (std::vector<std::string>::const_iterator i = argv.begin();
1037 i != argv.end(); i++) {
1038 std::cout << "_" << *i << "_" << std::endl;
1039 }
1040 */
1042 int stdout_pipe, stderr_pipe;
1044 try {
1045 Glib::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1046 argv, // arg v
1047 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1048 sigc::slot<void>(),
1049 &_pid, // Pid
1050 NULL, // STDIN
1051 &stdout_pipe, // STDOUT
1052 &stderr_pipe); // STDERR
1053 } catch (Glib::SpawnError e) {
1054 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1055 return 0;
1056 }
1058 _main_loop = Glib::MainLoop::create(false);
1060 file_listener fileerr;
1061 fileout.init(stdout_pipe, _main_loop);
1062 fileerr.init(stderr_pipe, _main_loop);
1064 _canceled = false;
1065 _main_loop->run();
1067 // Ensure all the data is out of the pipe
1068 while (!fileout.isDead())
1069 fileout.read(Glib::IO_IN);
1070 while (!fileerr.isDead())
1071 fileerr.read(Glib::IO_IN);
1073 if (_canceled) {
1074 // std::cout << "Script Canceled" << std::endl;
1075 return 0;
1076 }
1078 Glib::ustring stderr_data = fileerr.string();
1079 if (stderr_data.length() != 0) {
1080 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1081 _("Inkscape has received additional data from the script executed. "
1082 "The script did not return an error, but this may indicate the results will not be as expected."));
1083 }
1085 Glib::ustring stdout_data = fileout.string();
1086 if (stdout_data.length() == 0) {
1087 return 0;
1088 }
1090 // std::cout << "Finishing Execution." << std::endl;
1091 return stdout_data.length();
1092 }
1097 } // namespace Implementation
1098 } // namespace Extension
1099 } // namespace Inkscape
1101 /*
1102 Local Variables:
1103 mode:c++
1104 c-file-style:"stroustrup"
1105 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1106 indent-tabs-mode:nil
1107 fill-column:99
1108 End:
1109 */
1110 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :