Code

9475b479623ed6e98865d48cd5baf03c8bf5a6e2
[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 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <unistd.h>
22 #include <errno.h>
23 #include <gtkmm.h>
25 #include "ui/view/view.h"
26 #include "desktop-handles.h"
27 #include "desktop.h"
28 #include "selection.h"
29 #include "sp-namedview.h"
30 #include "io/sys.h"
31 #include "preferences.h"
32 #include "../system.h"
33 #include "extension/effect.h"
34 #include "extension/output.h"
35 #include "extension/input.h"
36 #include "extension/db.h"
37 #include "script.h"
38 #include "dialogs/dialog-events.h"
39 #include "application/application.h"
41 #include "util/glib-list-iterators.h"
45 #ifdef WIN32
46 #include <windows.h>
47 #include <sys/stat.h>
48 #include "registrytool.h"
49 #endif
53 /** This is the command buffer that gets allocated from the stack */
54 #define BUFSIZE (255)
58 /* Namespaces */
59 namespace Inkscape {
60 namespace Extension {
61 namespace Implementation {
63 /** \brief  Make GTK+ events continue to come through a little bit
64         
65         This just keeps coming the events through so that we'll make the GUI
66         update and look pretty.
67 */
68 void
69 Script::pump_events (void) {
70     while( Gtk::Main::events_pending() )
71         Gtk::Main::iteration();
72     return;
73 }
76 /** \brief  A table of what interpreters to call for a given language
78     This table is used to keep track of all the programs to execute a
79     given script.  It also tracks the preference to use to overwrite
80     the given interpreter to a custom one per user.
81 */
82 Script::interpreter_t const Script::interpreterTab[] = {
83         {"perl",   "perl-interpreter",   "perl"   },
84 #ifdef WIN32
85         {"python", "python-interpreter", "pythonw" },
86 #else
87         {"python", "python-interpreter", "python" },
88 #endif
89         {"ruby",   "ruby-interpreter",   "ruby"   },
90         {"shell",  "shell-interpreter",  "sh"     },
91         { NULL,    NULL,                  NULL    }
92 };
96 /** \brief Look up an interpreter name, and translate to something that
97            is executable
98     \param interpNameArg  The name of the interpreter that we're looking
99                               for, should be an entry in interpreterTab
100 */
101 Glib::ustring
102 Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
105     Glib::ustring interpName = interpNameArg;
107     interpreter_t const *interp;
108     bool foundInterp = false;
109     for (interp =  interpreterTab ; interp->identity ; interp++ ){
110         if (interpName == interp->identity) {
111             foundInterp = true;
112             break;
113         }
114     }
116     // Do we have a supported interpreter type?
117     if (!foundInterp)
118         return "";
119     interpName = interp->defaultval;
121     // 1.  Check preferences
122     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
123     Glib::ustring prefInterp = prefs->getString("extensions", interp->prefstring);
125     if (!prefInterp.empty()) {
126         interpName = prefInterp;
127         return interpName;
128     }
130 #ifdef WIN32
132     // 2.  Windows.  Try looking relative to inkscape.exe
133     RegistryTool rt;
134     Glib::ustring fullPath;
135     Glib::ustring path;
136     Glib::ustring exeName;
137     if (rt.getExeInfo(fullPath, path, exeName)) {
138         Glib::ustring interpPath = path;
139         interpPath.append("\\");
140         interpPath.append(interpNameArg);
141         interpPath.append("\\");
142         interpPath.append(interpName);
143         interpPath.append(".exe");
144         struct stat finfo;
145         if (stat(interpPath .c_str(), &finfo) ==0) {
146             g_message("Found local interpreter, '%s',  Size: %d",
147                       interpPath .c_str(),
148                       (int)finfo.st_size);
149             return interpPath;
150         }
151     }
153     // 3. Try searching the path
154     char szExePath[MAX_PATH];
155     char szCurrentDir[MAX_PATH];
156     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
157     unsigned int ret = (unsigned int)FindExecutable(
158                   interpName.c_str(), szCurrentDir, szExePath);
159     if (ret > 32) {
160         interpName = szExePath;
161         return interpName;
162     }
164 #endif // win32
167     return interpName;
170 /** \brief     This function creates a script object and sets up the
171                variables.
172     \return    A script object
174    This function just sets the command to NULL.  It should get built
175    officially in the load function.  This allows for less allocation
176    of memory in the unloaded state.
177 */
178 Script::Script() :
179     Implementation()
183 /**
184  *   brief     Destructor
185  */
186 Script::~Script()
192 /**
193     \return    A string with the complete string with the relative directory expanded
194     \brief     This function takes in a Repr that contains a reldir entry
195                and returns that data with the relative directory expanded.
196                Mostly it is here so that relative directories all get used
197                the same way.
198     \param     reprin   The Inkscape::XML::Node with the reldir in it.
200     Basically this function looks at an attribute of the Repr, and makes
201     a decision based on that.  Currently, it is only working with the
202     'extensions' relative directory, but there will be more of them.
203     One thing to notice is that this function always returns an allocated
204     string.  This means that the caller of this function can always
205     free what they are given (and should do it too!).
206 */
207 Glib::ustring
208 Script::solve_reldir(Inkscape::XML::Node *reprin) {
210     gchar const *s = reprin->attribute("reldir");
212     if (!s) {
213         Glib::ustring str = sp_repr_children(reprin)->content();
214         return str;
215     }
217     Glib::ustring reldir = s;
219     if (reldir == "extensions") {
221         for (unsigned int i=0;
222             i < Inkscape::Extension::Extension::search_path.size();
223             i++) {
225             gchar * fname = g_build_filename(
226                Inkscape::Extension::Extension::search_path[i],
227                sp_repr_children(reprin)->content(),
228                NULL);
229             Glib::ustring filename = fname;
230             g_free(fname);
232             if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
233                 return filename;
235         }
236     } else {
237         Glib::ustring str = sp_repr_children(reprin)->content();
238         return str;
239     }
241     return "";
246 /**
247     \return   Whether the command given exists, including in the path
248     \brief    This function is used to find out if something exists for
249               the check command.  It can look in the path if required.
250     \param    command   The command or file that should be looked for
252     The first thing that this function does is check to see if the
253     incoming file name has a directory delimiter in it.  This would
254     mean that it wants to control the directories, and should be
255     used directly.
257     If not, the path is used.  Each entry in the path is stepped through,
258     attached to the string, and then tested.  If the file is found
259     then a TRUE is returned.  If we get all the way through the path
260     then a FALSE is returned, the command could not be found.
261 */
262 bool
263 Script::check_existance(const Glib::ustring &command)
266     // Check the simple case first
267     if (command.size() == 0) {
268         return false;
269     }
271     //Don't search when it contains a slash. */
272     if (command.find(G_DIR_SEPARATOR) != command.npos) {
273         if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
274             return true;
275         else
276             return false;
277     }
280     Glib::ustring path;
281     gchar *s = (gchar *) g_getenv("PATH");
282     if (s)
283         path = s;
284     else
285        /* There is no `PATH' in the environment.
286            The default search path is the current directory */
287         path = G_SEARCHPATH_SEPARATOR_S;
289     std::string::size_type pos  = 0;
290     std::string::size_type pos2 = 0;
291     while ( pos < path.size() ) {
293         Glib::ustring localPath;
295         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
296         if (pos2 == path.npos) {
297             localPath = path.substr(pos);
298             pos = path.size();
299         } else {
300             localPath = path.substr(pos, pos2-pos);
301             pos = pos2+1;
302         }
304         //printf("### %s\n", localPath.c_str());
305         Glib::ustring candidatePath =
306                       Glib::build_filename(localPath, command);
308         if (Inkscape::IO::file_test(candidatePath .c_str(),
309                       G_FILE_TEST_EXISTS)) {
310             return true;
311         }
313     }
315     return false;
322 /**
323     \return   none
324     \brief    This function 'loads' an extention, basically it determines
325               the full command for the extention and stores that.
326     \param    module  The extention to be loaded.
328     The most difficult part about this function is finding the actual
329     command through all of the Reprs.  Basically it is hidden down a
330     couple of layers, and so the code has to move down too.  When
331     the command is actually found, it has its relative directory
332     solved.
334     At that point all of the loops are exited, and there is an
335     if statement to make sure they didn't exit because of not finding
336     the command.  If that's the case, the extention doesn't get loaded
337     and should error out at a higher level.
338 */
340 bool
341 Script::load(Inkscape::Extension::Extension *module)
343     if (module->loaded())
344         return true;
346     helper_extension = "";
348     /* This should probably check to find the executable... */
349     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
350     while (child_repr != NULL) {
351         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
352             child_repr = sp_repr_children(child_repr);
353             while (child_repr != NULL) {
354                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
355                     const gchar *interpretstr = child_repr->attribute("interpreter");
356                     if (interpretstr != NULL) {
357                         Glib::ustring interpString =
358                             resolveInterpreterExecutable(interpretstr);
359                         //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
360                         command.insert(command.end(), interpretstr);
361                     }
362                     Glib::ustring tmp = "\"";
363                     tmp += solve_reldir(child_repr);
364                     tmp += "\"";
366                     command.insert(command.end(), tmp);
367                 }
368                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
369                     helper_extension = sp_repr_children(child_repr)->content();
370                 }
371                 child_repr = sp_repr_next(child_repr);
372             }
374             break;
375         }
376         child_repr = sp_repr_next(child_repr);
377     }
379     //g_return_val_if_fail(command.length() > 0, false);
381     return true;
385 /**
386     \return   None.
387     \brief    Unload this puppy!
388     \param    module  Extension to be unloaded.
390     This function just sets the module to unloaded.  It free's the
391     command if it has been allocated.
392 */
393 void
394 Script::unload(Inkscape::Extension::Extension */*module*/)
396     command.clear();
397     helper_extension = "";
403 /**
404     \return   Whether the check passed or not
405     \brief    Check every dependency that was given to make sure we should keep this extension
406     \param    module  The Extension in question
408 */
409 bool
410 Script::check(Inkscape::Extension::Extension *module)
412         int script_count = 0;
413     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
414     while (child_repr != NULL) {
415         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
416                         script_count++;
417             child_repr = sp_repr_children(child_repr);
418             while (child_repr != NULL) {
419                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
420                     Glib::ustring command_text = solve_reldir(child_repr);
421                     if (command_text.size() > 0) {
422                         /* I've got the command */
423                         bool existance = check_existance(command_text);
424                         if (!existance)
425                             return false;
426                     }
427                 }
429                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
430                     gchar const *helper = sp_repr_children(child_repr)->content();
431                     if (Inkscape::Extension::db.get(helper) == NULL) {
432                         return false;
433                     }
434                 }
436                 child_repr = sp_repr_next(child_repr);
437             }
439             break;
440         }
441         child_repr = sp_repr_next(child_repr);
442     }
444         if (script_count == 0) {
445                 return false;
446         }
448     return true;
451 class ScriptDocCache : public ImplementationDocumentCache {
452     friend class Script;
453 protected:
454     std::string _filename;
455     int _tempfd;
456 public:
457     ScriptDocCache (Inkscape::UI::View::View * view);
458     ~ScriptDocCache ( );
459 };
461 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
462     ImplementationDocumentCache(view),
463     _filename(""),
464     _tempfd(0)
466     try {
467         _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
468     } catch (...) {
469         /// \todo Popup dialog here
470         return;
471     }
473     SPDesktop *desktop = (SPDesktop *) view;
474     sp_namedview_document_from_window(desktop);
476     Inkscape::Extension::save(
477               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
478               view->doc(), _filename.c_str(), false, false, false);
480     return;
483 ScriptDocCache::~ScriptDocCache ( )
485     close(_tempfd);
486     unlink(_filename.c_str());
489 ImplementationDocumentCache *
490 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
491     return new ScriptDocCache(view);
495 /**
496     \return   A dialog for preferences
497     \brief    A stub funtion right now
498     \param    module    Module who's preferences need getting
499     \param    filename  Hey, the file you're getting might be important
501     This function should really do something, right now it doesn't.
502 */
503 Gtk::Widget *
504 Script::prefs_input(Inkscape::Extension::Input *module,
505                     const gchar */*filename*/)
507     return module->autogui(NULL, NULL);
512 /**
513     \return   A dialog for preferences
514     \brief    A stub funtion right now
515     \param    module    Module whose preferences need getting
517     This function should really do something, right now it doesn't.
518 */
519 Gtk::Widget *
520 Script::prefs_output(Inkscape::Extension::Output *module)
522     return module->autogui(NULL, NULL);
525 /**
526     \return  A new document that has been opened
527     \brief   This function uses a filename that is put in, and calls
528              the extension's command to create an SVG file which is
529              returned.
530     \param   module   Extension to use.
531     \param   filename File to open.
533     First things first, this function needs a temporary file name.  To
534     create on of those the function g_file_open_tmp is used with
535     the header of ink_ext_.
537     The extension is then executed using the 'execute' function
538     with the filname coming in, and the temporary filename.  After
539     That executing, the SVG should be in the temporary file.
541     Finally, the temporary file is opened using the SVG input module and
542     a document is returned.  That document has its filename set to
543     the incoming filename (so that it's not the temporary filename).
544     That document is then returned from this function.
545 */
546 SPDocument *
547 Script::open(Inkscape::Extension::Input *module,
548              const gchar *filenameArg)
550     std::list<std::string> params;
551     module->paramListString(params);
553     std::string tempfilename_out;
554     int tempfd_out = 0;
555     try {
556         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
557     } catch (...) {
558         /// \todo Popup dialog here
559         return NULL;
560     }
562     std::string lfilename = Glib::filename_from_utf8(filenameArg);
564     file_listener fileout;
565     int data_read = execute(command, params, lfilename, fileout);
566     fileout.toFile(tempfilename_out);
568     SPDocument * mydoc = NULL;
569     if (data_read > 10) {
570         if (helper_extension.size()==0) {
571             mydoc = Inkscape::Extension::open(
572                   Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
573                   tempfilename_out.c_str());
574         } else {
575             mydoc = Inkscape::Extension::open(
576                   Inkscape::Extension::db.get(helper_extension.c_str()),
577                   tempfilename_out.c_str());
578         }
579     } // data_read
581     if (mydoc != NULL) {
582         sp_document_set_uri(mydoc, filenameArg);
583     }
585     // make sure we don't leak file descriptors from g_file_open_tmp
586     close(tempfd_out);
588     unlink(tempfilename_out.c_str());
590     return mydoc;
591 } // open
595 /**
596     \return   none
597     \brief    This function uses an extention to save a document.  It first
598               creates an SVG file of the document, and then runs it through
599               the script.
600     \param    module    Extention to be used
601     \param    doc       Document to be saved
602     \param    filename  The name to save the final file as
604     Well, at some point people need to save - it is really what makes
605     the entire application useful.  And, it is possible that someone
606     would want to use an extetion for this, so we need a function to
607     do that eh?
609     First things first, the document is saved to a temporary file that
610     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
611     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
612     end of the function.
614     After we have the SVG file, then extention_execute is called with
615     the temporary file name and the final output filename.  This should
616     put the output of the script into the final output file.  We then
617     delete the temporary file.
618 */
619 void
620 Script::save(Inkscape::Extension::Output *module,
621              SPDocument *doc,
622              const gchar *filenameArg)
624     std::list<std::string> params;
625     module->paramListString(params);
627     std::string tempfilename_in;
628     int tempfd_in = 0;
629     try {
630         tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
631     } catch (...) {
632         /// \todo Popup dialog here
633         return;
634     }
636     if (helper_extension.size() == 0) {
637         Inkscape::Extension::save(
638                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
639                    doc, tempfilename_in.c_str(), false, false, false);
640     } else {
641         Inkscape::Extension::save(
642                    Inkscape::Extension::db.get(helper_extension.c_str()),
643                    doc, tempfilename_in.c_str(), false, false, false);
644     }
647     file_listener fileout;
648     execute(command, params, tempfilename_in, fileout);
650     std::string lfilename = Glib::filename_from_utf8(filenameArg);
651     fileout.toFile(lfilename);
653     // make sure we don't leak file descriptors from g_file_open_tmp
654     close(tempfd_in);
655     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
656     unlink(tempfilename_in.c_str());
658     return;
663 /**
664     \return    none
665     \brief     This function uses an extention as a effect on a document.
666     \param     module   Extention to effect with.
667     \param     doc      Document to run through the effect.
669     This function is a little bit trickier than the previous two.  It
670     needs two temporary files to get it's work done.  Both of these
671     files have random names created for them using the g_file_open_temp function
672     with the ink_ext_ prefix in the temporary directory.  Like the other
673     functions, the temporary files are deleted at the end.
675     To save/load the two temporary documents (both are SVG) the internal
676     modules for SVG load and save are used.  They are both used through
677     the module system function by passing their keys into the functions.
679     The command itself is built a little bit differently than in other
680     functions because the effect support selections.  So on the command
681     line a list of all the ids that are selected is included.  Currently,
682     this only works for a single selected object, but there will be more.
683     The command string is filled with the data, and then after the execution
684     it is freed.
686     The execute function is used at the core of this function
687     to execute the Script on the two SVG documents (actually only one
688     exists at the time, the other is created by that script).  At that
689     point both should be full, and the second one is loaded.
690 */
691 void
692 Script::effect(Inkscape::Extension::Effect *module,
693                Inkscape::UI::View::View *doc,
694                ImplementationDocumentCache * docCache)
696     if (docCache == NULL) {
697         docCache = newDocCache(module, doc);
698     }
699     ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
700     if (dc == NULL) {
701         printf("TOO BAD TO LIVE!!!");
702         exit(1);
703     }
705     SPDesktop *desktop = (SPDesktop *)doc;
706     sp_namedview_document_from_window(desktop);
708     gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
710     std::list<std::string> params;
711     module->paramListString(params);
713     if (module->no_doc) {
714         // this is a no-doc extension, e.g. a Help menu command;
715         // just run the command without any files, ignoring errors
717         Glib::ustring empty;
718         file_listener outfile;
719         execute(command, params, empty, outfile);
721         return;
722     }
724     std::string tempfilename_out;
725     int tempfd_out = 0;
726     try {
727         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
728     } catch (...) {
729         /// \todo Popup dialog here
730         return;
731     }
733     if (desktop != NULL) {
734         Inkscape::Util::GSListConstIterator<SPItem *> selected =
735              sp_desktop_selection(desktop)->itemList();
736         while ( selected != NULL ) {
737             Glib::ustring selected_id;
738             selected_id += "--id=";
739             selected_id += SP_OBJECT_ID(*selected);
740             params.insert(params.begin(), selected_id);
741             ++selected;
742         }
743     }
745     file_listener fileout;
746     int data_read = execute(command, params, dc->_filename, fileout);
747     fileout.toFile(tempfilename_out);
749     pump_events();
751     SPDocument * mydoc = NULL;
752     if (data_read > 10) {
753         mydoc = Inkscape::Extension::open(
754               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
755               tempfilename_out.c_str());
756     } // data_read
758     pump_events();
760     // make sure we don't leak file descriptors from g_file_open_tmp
761     close(tempfd_out);
763     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
764     unlink(tempfilename_out.c_str());
766     /* Do something with mydoc.... */
767     if (mydoc) {
768         doc->doc()->emitReconstructionStart();
769         copy_doc(doc->doc()->rroot, mydoc->rroot);
770         doc->doc()->emitReconstructionFinish();
771         mydoc->release();
772         sp_namedview_update_layers_from_document(desktop);
774         sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
775     }
776     g_free(orig_output_extension);
778     return;
783 /**
784     \brief  A function to take all the svg elements from one document
785             and put them in another.
786     \param  oldroot  The root node of the document to be replaced
787     \param  newroot  The root node of the document to replace it with
789     This function first deletes all of the data in the old document.  It
790     does this by creating a list of what needs to be deleted, and then
791     goes through the list.  This two pass approach removes issues with
792     the list being change while parsing through it.  Lots of nasty bugs.
794     Then, it goes through the new document, duplicating all of the
795     elements and putting them into the old document.  The copy
796     is then complete.
797 */
798 void
799 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
801     std::vector<Inkscape::XML::Node *> delete_list;
802     Inkscape::XML::Node * oldroot_namedview = NULL;
804     for (Inkscape::XML::Node * child = oldroot->firstChild();
805             child != NULL;
806             child = child->next()) {
807         if (!strcmp("sodipodi:namedview", child->name())) {
808             oldroot_namedview = child;
809             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
810                     oldroot_namedview_child != NULL;
811                     oldroot_namedview_child = oldroot_namedview_child->next()) {
812                 delete_list.push_back(oldroot_namedview_child);
813             }
814         } else {
815             delete_list.push_back(child);
816         }
817     }
818     for (unsigned int i = 0; i < delete_list.size(); i++)
819         sp_repr_unparent(delete_list[i]);
821     for (Inkscape::XML::Node * child = newroot->firstChild();
822             child != NULL;
823             child = child->next()) {
824         if (!strcmp("sodipodi:namedview", child->name())) {
825             if (oldroot_namedview != NULL) {
826                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
827                         newroot_namedview_child != NULL;
828                         newroot_namedview_child = newroot_namedview_child->next()) {
829                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
830                 }
831             }
832         } else {
833             oldroot->appendChild(child->duplicate(oldroot->document()));
834         }
835     }
837     oldroot->setAttribute("width", newroot->attribute("width"));
838     oldroot->setAttribute("height", newroot->attribute("height"));
840     /** \todo  Restore correct layer */
841     /** \todo  Restore correct selection */
844 /**  \brief  This function checks the stderr file, and if it has data,
845              shows it in a warning dialog to the user
846      \param  filename  Filename of the stderr file
847 */
848 void
849 Script::checkStderr (const Glib::ustring &data,
850                            Gtk::MessageType type,
851                      const Glib::ustring &message)
853     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
854     warning.set_resizable(true);
855     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
856     sp_transientize(dlg);
858     Gtk::VBox * vbox = warning.get_vbox();
860     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
861     Gtk::TextView * textview = new Gtk::TextView();
862     textview->set_editable(false);
863     textview->set_wrap_mode(Gtk::WRAP_WORD);
864     textview->show();
866     textview->get_buffer()->set_text(data.c_str());
868     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
869     scrollwindow->add(*textview);
870     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
871     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
872     scrollwindow->show();
874     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
876     warning.run();
878     return;
881 bool
882 Script::cancelProcessing (void) {
883     _canceled = true;
884     _main_loop->quit();
885     Glib::spawn_close_pid(_pid);
887     return true;
891 /** \brief    This is the core of the extension file as it actually does
892               the execution of the extension.
893     \param    in_command  The command to be executed
894     \param    filein      Filename coming in
895     \param    fileout     Filename of the out file
896     \return   Number of bytes that were read into the output file.
898     The first thing that this function does is build the command to be
899     executed.  This consists of the first string (in_command) and then
900     the filename for input (filein).  This file is put on the command
901     line.
903     The next thing is that this function does is open a pipe to the
904     command and get the file handle in the ppipe variable.  It then
905     opens the output file with the output file handle.  Both of these
906     operations are checked extensively for errors.
908     After both are opened, then the data is copied from the output
909     of the pipe into the file out using fread and fwrite.  These two
910     functions are used because of their primitive nature they make
911     no assumptions about the data.  A buffer is used in the transfer,
912     but the output of fread is stored so the exact number of bytes
913     is handled gracefully.
915     At the very end (after the data has been copied) both of the files
916     are closed, and we return to what we were doing.
917 */
918 int
919 Script::execute (const std::list<std::string> &in_command,
920                  const std::list<std::string> &in_params,
921                  const Glib::ustring &filein,
922                  file_listener &fileout)
924     g_return_val_if_fail(in_command.size() > 0, 0);
925     // printf("Executing\n");
927     std::vector <std::string> argv;
929 /*
930     for (std::list<std::string>::const_iterator i = in_command.begin();
931             i != in_command.end(); i++) {
932         argv.push_back(*i);
933     }
934 */
935     // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
936     // we tokenize so that spwan does not need to quote over all params
937     for (std::list<std::string>::const_iterator i = in_command.begin();
938             i != in_command.end(); i++) {
939         std::string param_str = *i;
940         do {
941             //g_message("param: %s", param_str.c_str());
942             size_t first_space = param_str.find_first_of(' ');
943             size_t first_quote = param_str.find_first_of('"');
944             //std::cout << "first space " << first_space << std::endl;
945             //std::cout << "first quote " << first_quote << std::endl;
947             if((first_quote != std::string::npos) && (first_quote == 0)) {
948                 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
949                 //std::cout << "next quote " << next_quote << std::endl;
951                 if(next_quote != std::string::npos) {
952                     //std::cout << "now split " << next_quote << std::endl;
953                     //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
954                     //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
955                     std::string part_str = param_str.substr(1, next_quote - 1);
956                     if(part_str.size() > 0)
957                         argv.push_back(part_str);
958                     param_str = param_str.substr(next_quote + 1);
960                 }
961                 else {
962                     if(param_str.size() > 0)
963                         argv.push_back(param_str);
964                     param_str = "";
965                 }
967             }
968             else if(first_space != std::string::npos) {
969                 //std::cout << "now split " << first_space << std::endl;
970                 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
971                 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
972                 std::string part_str = param_str.substr(0, first_space);
973                 if(part_str.size() > 0)
974                     argv.push_back(part_str);
975                 param_str = param_str.substr(first_space + 1);
976             }
977             else {
978                 if(param_str.size() > 0)
979                     argv.push_back(param_str);
980                 param_str = "";
981             }
982         } while(param_str.size() > 0);
983     }
985     for (std::list<std::string>::const_iterator i = in_params.begin();
986             i != in_params.end(); i++) {
987         //g_message("Script parameter: %s",(*i)g.c_str());
988         argv.push_back(*i);        
989     }
991     if (!(filein.empty())) {
992                 argv.push_back(filein);
993     }
995     int stdout_pipe, stderr_pipe;
997     try {
998         Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
999                                      argv,  // arg v
1000                                      Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1001                                      sigc::slot<void>(),
1002                                      &_pid,          // Pid
1003                                      NULL,           // STDIN
1004                                      &stdout_pipe,   // STDOUT
1005                                      &stderr_pipe);  // STDERR
1006     } catch (Glib::SpawnError e) {
1007         printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1008         return 0;
1009     }
1011     _main_loop = Glib::MainLoop::create(false);
1013     file_listener fileerr;
1014     fileout.init(stdout_pipe, _main_loop);
1015     fileerr.init(stderr_pipe, _main_loop);
1017     _canceled = false;
1018     _main_loop->run();
1020     // Ensure all the data is out of the pipe
1021     while (!fileout.isDead())
1022         fileout.read(Glib::IO_IN);
1023     while (!fileerr.isDead())
1024         fileerr.read(Glib::IO_IN);
1026     if (_canceled) {
1027         // std::cout << "Script Canceled" << std::endl;
1028         return 0;
1029     }
1031     Glib::ustring stderr_data = fileerr.string();
1032     if (stderr_data.length() != 0 &&
1033         Inkscape::NSApplication::Application::getUseGui()
1034        ) {
1035         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1036                                  _("Inkscape has received additional data from the script executed.  "
1037                                    "The script did not return an error, but this may indicate the results will not be as expected."));
1038     }
1040     Glib::ustring stdout_data = fileout.string();
1041     if (stdout_data.length() == 0) {
1042         return 0;
1043     }
1045     // std::cout << "Finishing Execution." << std::endl;
1046     return stdout_data.length();
1052 }  // namespace Implementation
1053 }  // namespace Extension
1054 }  // namespace Inkscape
1056 /*
1057   Local Variables:
1058   mode:c++
1059   c-file-style:"stroustrup"
1060   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1061   indent-tabs-mode:nil
1062   fill-column:99
1063   End:
1064 */
1065 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :