Code

Refactored preferences handling into a new version of
[inkscape.git] / src / extension / implementation / script.cpp
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)
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;
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()
203 /**
204  *   brief     Destructor
205  */
206 Script::~Script()
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 "";
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)
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;
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)
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;
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*/)
416     command.clear();
417     helper_extension = "";
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)
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;
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)
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;
503 ScriptDocCache::~ScriptDocCache ( )
505     close(_tempfd);
506     unlink(_filename.c_str());
509 ImplementationDocumentCache *
510 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
511     return new ScriptDocCache(view);
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*/)
527     return module->autogui(NULL, NULL);
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)
542     return module->autogui(NULL, NULL);
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)
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)
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;
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)
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;
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)
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 */
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)
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;
901 bool
902 Script::cancelProcessing (void) {
903     _canceled = true;
904     _main_loop->quit();
905     Glib::spawn_close_pid(_pid);
907     return true;
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)
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();
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 :