Code

A simple layout document as to what, why and how is cppification.
[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  *   Jon A. Cruz <jon@joncruz.org>
9  *
10  * Copyright (C) 2002-2005,2007 Authors
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
15 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
21 #include <unistd.h>
23 #include <errno.h>
24 #include <glib.h>
25 #include <glib/gstdio.h>
26 #include <gtkmm.h>
28 #include "ui/view/view.h"
29 #include "desktop-handles.h"
30 #include "desktop.h"
31 #include "selection.h"
32 #include "sp-namedview.h"
33 #include "io/sys.h"
34 #include "preferences.h"
35 #include "../system.h"
36 #include "extension/effect.h"
37 #include "extension/output.h"
38 #include "extension/input.h"
39 #include "extension/db.h"
40 #include "script.h"
41 #include "dialogs/dialog-events.h"
42 #include "application/application.h"
43 #include "xml/node.h"
44 #include "xml/attribute-record.h"
46 #include "util/glib-list-iterators.h"
50 #ifdef WIN32
51 #include <windows.h>
52 #include <sys/stat.h>
53 #include "registrytool.h"
54 #endif
58 /** This is the command buffer that gets allocated from the stack */
59 #define BUFSIZE (255)
63 /* Namespaces */
64 namespace Inkscape {
65 namespace Extension {
66 namespace Implementation {
68 /** \brief  Make GTK+ events continue to come through a little bit
70     This just keeps coming the events through so that we'll make the GUI
71     update and look pretty.
72 */
73 void Script::pump_events (void) {
74     while ( Gtk::Main::events_pending() ) {
75         Gtk::Main::iteration();
76     }
77     return;
78 }
81 /** \brief  A table of what interpreters to call for a given language
83     This table is used to keep track of all the programs to execute a
84     given script.  It also tracks the preference to use to overwrite
85     the given interpreter to a custom one per user.
86 */
87 Script::interpreter_t const Script::interpreterTab[] = {
88         {"perl",   "perl-interpreter",   "perl"   },
89 #ifdef WIN32
90         {"python", "python-interpreter", "pythonw" },
91 #else
92         {"python", "python-interpreter", "python" },
93 #endif
94         {"ruby",   "ruby-interpreter",   "ruby"   },
95         {"shell",  "shell-interpreter",  "sh"     },
96         { NULL,    NULL,                  NULL    }
97 };
101 /** \brief Look up an interpreter name, and translate to something that
102     is executable
103     \param interpNameArg  The name of the interpreter that we're looking
104     for, should be an entry in interpreterTab
105 */
106 std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
108     interpreter_t const *interp = 0;
109     bool foundInterp = false;
110     for (interp =  interpreterTab ; interp->identity ; interp++ ){
111         if (interpNameArg == interp->identity) {
112             foundInterp = true;
113             break;
114         }
115     }
117     // Do we have a supported interpreter type?
118     if (!foundInterp) {
119         return "";
120     }
121     std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval);
123     // 1.  Check preferences for an override.
124     // Note: this must be an absolute path.
125     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
126     Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
128     if (!prefInterp.empty()) {
129         interpreter_path = Glib::filename_from_utf8(prefInterp);
130     }
132     // 2. Search the path.
133     // Do this on all systems, for consistency.
134     // PATH is set up to contain the Python and Perl binary directories
135     // on Windows, so no extra code is necessary.
136     if (!Glib::path_is_absolute(interpreter_path)) {
137         interpreter_path = Glib::find_program_in_path(interpreter_path);
138     }
139     return interpreter_path;
142 /** \brief     This function creates a script object and sets up the
143                variables.
144     \return    A script object
146    This function just sets the command to NULL.  It should get built
147    officially in the load function.  This allows for less allocation
148    of memory in the unloaded state.
149 */
150 Script::Script() :
151     Implementation()
155 /**
156  *   brief     Destructor
157  */
158 Script::~Script()
164 /**
165     \return    A string with the complete string with the relative directory expanded
166     \brief     This function takes in a Repr that contains a reldir entry
167                and returns that data with the relative directory expanded.
168                Mostly it is here so that relative directories all get used
169                the same way.
170     \param     reprin   The Inkscape::XML::Node with the reldir in it.
172     Basically this function looks at an attribute of the Repr, and makes
173     a decision based on that.  Currently, it is only working with the
174     'extensions' relative directory, but there will be more of them.
175     One thing to notice is that this function always returns an allocated
176     string.  This means that the caller of this function can always
177     free what they are given (and should do it too!).
178 */
179 std::string
180 Script::solve_reldir(Inkscape::XML::Node *reprin) {
182     gchar const *s = reprin->attribute("reldir");
184     // right now the only recognized relative directory is "extensions"
185     if (!s || Glib::ustring(s) != "extensions") {
186         Glib::ustring str = sp_repr_children(reprin)->content();
187         return str;
188     }
190     Glib::ustring reldir = s;
192     for (unsigned int i=0;
193         i < Inkscape::Extension::Extension::search_path.size();
194         i++) {
196         gchar * fname = g_build_filename(
197            Inkscape::Extension::Extension::search_path[i],
198            sp_repr_children(reprin)->content(),
199            NULL);
200         Glib::ustring filename = fname;
201         g_free(fname);
203         if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) ) {
204             return Glib::filename_from_utf8(filename);
205         }
206     }
208     return "";
213 /**
214     \return   Whether the command given exists, including in the path
215     \brief    This function is used to find out if something exists for
216               the check command.  It can look in the path if required.
217     \param    command   The command or file that should be looked for
219     The first thing that this function does is check to see if the
220     incoming file name has a directory delimiter in it.  This would
221     mean that it wants to control the directories, and should be
222     used directly.
224     If not, the path is used.  Each entry in the path is stepped through,
225     attached to the string, and then tested.  If the file is found
226     then a TRUE is returned.  If we get all the way through the path
227     then a FALSE is returned, the command could not be found.
228 */
229 bool Script::check_existence(const std::string &command)
232     // Check the simple case first
233     if (command.empty()) {
234         return false;
235     }
237     //Don't search when it is an absolute path. */
238     if (!Glib::path_is_absolute(command)) {
239         if (Glib::file_test(command, Glib::FILE_TEST_EXISTS)) {
240             return true;
241         } else {
242             return false;
243         }
244     }
246     std::string path = Glib::getenv("PATH");
247     if (path.empty()) {
248        /* There is no `PATH' in the environment.
249            The default search path is the current directory */
250         path = G_SEARCHPATH_SEPARATOR_S;
251     }
253     std::string::size_type pos  = 0;
254     std::string::size_type pos2 = 0;
255     while ( pos < path.size() ) {
257         std::string localPath;
259         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
260         if (pos2 == path.npos) {
261             localPath = path.substr(pos);
262             pos = path.size();
263         } else {
264             localPath = path.substr(pos, pos2-pos);
265             pos = pos2+1;
266         }
268         //printf("### %s\n", localPath.c_str());
269         std::string candidatePath =
270                       Glib::build_filename(localPath, command);
272         if (Glib::file_test(candidatePath,
273                       Glib::FILE_TEST_EXISTS)) {
274             return true;
275         }
277     }
279     return false;
286 /**
287     \return   none
288     \brief    This function 'loads' an extention, basically it determines
289               the full command for the extention and stores that.
290     \param    module  The extention to be loaded.
292     The most difficult part about this function is finding the actual
293     command through all of the Reprs.  Basically it is hidden down a
294     couple of layers, and so the code has to move down too.  When
295     the command is actually found, it has its relative directory
296     solved.
298     At that point all of the loops are exited, and there is an
299     if statement to make sure they didn't exit because of not finding
300     the command.  If that's the case, the extention doesn't get loaded
301     and should error out at a higher level.
302 */
304 bool Script::load(Inkscape::Extension::Extension *module)
306     if (module->loaded()) {
307         return true;
308     }
310     helper_extension = "";
312     /* This should probably check to find the executable... */
313     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
314     while (child_repr != NULL) {
315         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
316             child_repr = sp_repr_children(child_repr);
317             while (child_repr != NULL) {
318                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
319                     const gchar *interpretstr = child_repr->attribute("interpreter");
320                     if (interpretstr != NULL) {
321                         std::string interpString = resolveInterpreterExecutable(interpretstr);
322                         command.insert(command.end(), interpString);
323                     }
324                     command.insert(command.end(), solve_reldir(child_repr));
325                 }
326                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
327                     helper_extension = sp_repr_children(child_repr)->content();
328                 }
329                 child_repr = sp_repr_next(child_repr);
330             }
332             break;
333         }
334         child_repr = sp_repr_next(child_repr);
335     }
337     //g_return_val_if_fail(command.length() > 0, false);
339     return true;
343 /**
344     \return   None.
345     \brief    Unload this puppy!
346     \param    module  Extension to be unloaded.
348     This function just sets the module to unloaded.  It free's the
349     command if it has been allocated.
350 */
351 void Script::unload(Inkscape::Extension::Extension */*module*/)
353     command.clear();
354     helper_extension = "";
360 /**
361     \return   Whether the check passed or not
362     \brief    Check every dependency that was given to make sure we should keep this extension
363     \param    module  The Extension in question
365 */
366 bool
367 Script::check(Inkscape::Extension::Extension *module)
369     int script_count = 0;
370     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
371     while (child_repr != NULL) {
372         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
373             script_count++;
374             child_repr = sp_repr_children(child_repr);
375             while (child_repr != NULL) {
376                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
377                     std::string command_text = solve_reldir(child_repr);
378                     if (!command_text.empty()) {
379                         /* I've got the command */
380                         bool existance = check_existence(command_text);
381                         if (!existance)
382                             return false;
383                     }
384                 }
386                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
387                     gchar const *helper = sp_repr_children(child_repr)->content();
388                     if (Inkscape::Extension::db.get(helper) == NULL) {
389                         return false;
390                     }
391                 }
393                 child_repr = sp_repr_next(child_repr);
394             }
396             break;
397         }
398         child_repr = sp_repr_next(child_repr);
399     }
401     if (script_count == 0) {
402         return false;
403     }
405     return true;
408 class ScriptDocCache : public ImplementationDocumentCache {
409     friend class Script;
410 protected:
411     std::string _filename;
412     int _tempfd;
413 public:
414     ScriptDocCache (Inkscape::UI::View::View * view);
415     ~ScriptDocCache ( );
416 };
418 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
419     ImplementationDocumentCache(view),
420     _filename(""),
421     _tempfd(0)
423     try {
424         _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
425     } catch (...) {
426         /// \todo Popup dialog here
427         return;
428     }
430     SPDesktop *desktop = (SPDesktop *) view;
431     sp_namedview_document_from_window(desktop);
433     Inkscape::Extension::save(
434               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
435               view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
437     return;
440 ScriptDocCache::~ScriptDocCache ( )
442     close(_tempfd);
443     unlink(_filename.c_str());
446 ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
447     return new ScriptDocCache(view);
451 /**
452     \return   A dialog for preferences
453     \brief    A stub funtion right now
454     \param    module    Module who's preferences need getting
455     \param    filename  Hey, the file you're getting might be important
457     This function should really do something, right now it doesn't.
458 */
459 Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
460                     const gchar */*filename*/)
462     return module->autogui(NULL, NULL);
467 /**
468     \return   A dialog for preferences
469     \brief    A stub funtion right now
470     \param    module    Module whose preferences need getting
472     This function should really do something, right now it doesn't.
473 */
474 Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
476     return module->autogui(NULL, NULL);
479 /**
480     \return  A new document that has been opened
481     \brief   This function uses a filename that is put in, and calls
482              the extension's command to create an SVG file which is
483              returned.
484     \param   module   Extension to use.
485     \param   filename File to open.
487     First things first, this function needs a temporary file name.  To
488     create on of those the function g_file_open_tmp is used with
489     the header of ink_ext_.
491     The extension is then executed using the 'execute' function
492     with the filname coming in, and the temporary filename.  After
493     That executing, the SVG should be in the temporary file.
495     Finally, the temporary file is opened using the SVG input module and
496     a document is returned.  That document has its filename set to
497     the incoming filename (so that it's not the temporary filename).
498     That document is then returned from this function.
499 */
500 SPDocument *Script::open(Inkscape::Extension::Input *module,
501              const gchar *filenameArg)
503     std::list<std::string> params;
504     module->paramListString(params);
506     std::string tempfilename_out;
507     int tempfd_out = 0;
508     try {
509         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
510     } catch (...) {
511         /// \todo Popup dialog here
512         return NULL;
513     }
515     std::string lfilename = Glib::filename_from_utf8(filenameArg);
517     file_listener fileout;
518     int data_read = execute(command, params, lfilename, fileout);
519     fileout.toFile(tempfilename_out);
521     SPDocument * mydoc = NULL;
522     if (data_read > 10) {
523         if (helper_extension.size()==0) {
524             mydoc = Inkscape::Extension::open(
525                   Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
526                   tempfilename_out.c_str());
527         } else {
528             mydoc = Inkscape::Extension::open(
529                   Inkscape::Extension::db.get(helper_extension.c_str()),
530                   tempfilename_out.c_str());
531         }
532     } // data_read
534     if (mydoc != NULL) {
535         g_free(mydoc->base);
536         mydoc->base = NULL;
537         mydoc->change_uri_and_hrefs(filenameArg);
538     }
540     // make sure we don't leak file descriptors from g_file_open_tmp
541     close(tempfd_out);
543     unlink(tempfilename_out.c_str());
545     return mydoc;
546 } // open
550 /**
551     \return   none
552     \brief    This function uses an extention to save a document.  It first
553               creates an SVG file of the document, and then runs it through
554               the script.
555     \param    module    Extention to be used
556     \param    doc       Document to be saved
557     \param    filename  The name to save the final file as
558     \return   false in case of any failure writing the file, otherwise true
560     Well, at some point people need to save - it is really what makes
561     the entire application useful.  And, it is possible that someone
562     would want to use an extetion for this, so we need a function to
563     do that eh?
565     First things first, the document is saved to a temporary file that
566     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
567     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
568     end of the function.
570     After we have the SVG file, then extention_execute is called with
571     the temporary file name and the final output filename.  This should
572     put the output of the script into the final output file.  We then
573     delete the temporary file.
574 */
575 void Script::save(Inkscape::Extension::Output *module,
576              SPDocument *doc,
577              const gchar *filenameArg)
579     std::list<std::string> params;
580     module->paramListString(params);
582     std::string tempfilename_in;
583     int tempfd_in = 0;
584     try {
585         tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
586     } catch (...) {
587         /// \todo Popup dialog here
588         throw Inkscape::Extension::Output::save_failed();
589     }
591     if (helper_extension.size() == 0) {
592         Inkscape::Extension::save(
593                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
594                    doc, tempfilename_in.c_str(), false, false, false,
595                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
596     } else {
597         Inkscape::Extension::save(
598                    Inkscape::Extension::db.get(helper_extension.c_str()),
599                    doc, tempfilename_in.c_str(), false, false, false,
600                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
601     }
604     file_listener fileout;
605     execute(command, params, tempfilename_in, fileout);
607     std::string lfilename = Glib::filename_from_utf8(filenameArg);
608     bool success = fileout.toFile(lfilename);
610     // make sure we don't leak file descriptors from g_file_open_tmp
611     close(tempfd_in);
612     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
613     unlink(tempfilename_in.c_str());
615     if (success == false) {
616         throw Inkscape::Extension::Output::save_failed();
617     }
619     return;
624 /**
625     \return    none
626     \brief     This function uses an extention as a effect on a document.
627     \param     module   Extention to effect with.
628     \param     doc      Document to run through the effect.
630     This function is a little bit trickier than the previous two.  It
631     needs two temporary files to get it's work done.  Both of these
632     files have random names created for them using the g_file_open_temp function
633     with the ink_ext_ prefix in the temporary directory.  Like the other
634     functions, the temporary files are deleted at the end.
636     To save/load the two temporary documents (both are SVG) the internal
637     modules for SVG load and save are used.  They are both used through
638     the module system function by passing their keys into the functions.
640     The command itself is built a little bit differently than in other
641     functions because the effect support selections.  So on the command
642     line a list of all the ids that are selected is included.  Currently,
643     this only works for a single selected object, but there will be more.
644     The command string is filled with the data, and then after the execution
645     it is freed.
647     The execute function is used at the core of this function
648     to execute the Script on the two SVG documents (actually only one
649     exists at the time, the other is created by that script).  At that
650     point both should be full, and the second one is loaded.
651 */
652 void Script::effect(Inkscape::Extension::Effect *module,
653                Inkscape::UI::View::View *doc,
654                ImplementationDocumentCache * docCache)
656     if (docCache == NULL) {
657         docCache = newDocCache(module, doc);
658     }
659     ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
660     if (dc == NULL) {
661         printf("TOO BAD TO LIVE!!!");
662         exit(1);
663     }
665     SPDesktop *desktop = (SPDesktop *)doc;
666     sp_namedview_document_from_window(desktop);
668     std::list<std::string> params;
669     module->paramListString(params);
671     if (module->no_doc) {
672         // this is a no-doc extension, e.g. a Help menu command;
673         // just run the command without any files, ignoring errors
675         Glib::ustring empty;
676         file_listener outfile;
677         execute(command, params, empty, outfile);
679         return;
680     }
682     std::string tempfilename_out;
683     int tempfd_out = 0;
684     try {
685         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
686     } catch (...) {
687         /// \todo Popup dialog here
688         return;
689     }
691     if (desktop != NULL) {
692         Inkscape::Util::GSListConstIterator<SPItem *> selected =
693              sp_desktop_selection(desktop)->itemList();
694         while ( selected != NULL ) {
695             Glib::ustring selected_id;
696             selected_id += "--id=";
697             selected_id += (*selected)->getId();
698             params.insert(params.begin(), selected_id);
699             ++selected;
700         }
701     }
703     file_listener fileout;
704     int data_read = execute(command, params, dc->_filename, fileout);
705     fileout.toFile(tempfilename_out);
707     pump_events();
709     SPDocument * mydoc = NULL;
710     if (data_read > 10) {
711         mydoc = Inkscape::Extension::open(
712               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
713               tempfilename_out.c_str());
714     } // data_read
716     pump_events();
718     // make sure we don't leak file descriptors from g_file_open_tmp
719     close(tempfd_out);
721     g_unlink(tempfilename_out.c_str());
723     /* Do something with mydoc.... */
724     if (mydoc) {
725         doc->doc()->emitReconstructionStart();
726         copy_doc(doc->doc()->rroot, mydoc->rroot);
727         doc->doc()->emitReconstructionFinish();
728         mydoc->release();
729         sp_namedview_update_layers_from_document(desktop);
730     }
732     return;
737 /**
738     \brief  A function to take all the svg elements from one document
739             and put them in another.
740     \param  oldroot  The root node of the document to be replaced
741     \param  newroot  The root node of the document to replace it with
743     This function first deletes all of the data in the old document.  It
744     does this by creating a list of what needs to be deleted, and then
745     goes through the list.  This two pass approach removes issues with
746     the list being change while parsing through it.  Lots of nasty bugs.
748     Then, it goes through the new document, duplicating all of the
749     elements and putting them into the old document.  The copy
750     is then complete.
751 */
752 void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
754     std::vector<Inkscape::XML::Node *> delete_list;
755     Inkscape::XML::Node * oldroot_namedview = NULL;
757     for (Inkscape::XML::Node * child = oldroot->firstChild();
758             child != NULL;
759             child = child->next()) {
760         if (!strcmp("sodipodi:namedview", child->name())) {
761             oldroot_namedview = child;
762             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
763                     oldroot_namedview_child != NULL;
764                     oldroot_namedview_child = oldroot_namedview_child->next()) {
765                 delete_list.push_back(oldroot_namedview_child);
766             }
767         } else {
768             delete_list.push_back(child);
769         }
770     }
771     for (unsigned int i = 0; i < delete_list.size(); i++) {
772         sp_repr_unparent(delete_list[i]);
773     }
775     for (Inkscape::XML::Node * child = newroot->firstChild();
776             child != NULL;
777             child = child->next()) {
778         if (!strcmp("sodipodi:namedview", child->name())) {
779             if (oldroot_namedview != NULL) {
780                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
781                         newroot_namedview_child != NULL;
782                         newroot_namedview_child = newroot_namedview_child->next()) {
783                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
784                 }
785             }
786         } else {
787             oldroot->appendChild(child->duplicate(oldroot->document()));
788         }
789     }
791     {
792         using Inkscape::Util::List;
793         using Inkscape::XML::AttributeRecord;
794         std::vector<gchar const *> attribs;
796         // Make a list of all attributes of the old root node.
797         for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
798             attribs.push_back(g_quark_to_string(iter->key));
799         }
801         // Delete the attributes of the old root nodes.
802         for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
803             oldroot->setAttribute(*it, NULL);
804         }
806         // Set the new attributes.
807         for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
808             gchar const *name = g_quark_to_string(iter->key);
809             oldroot->setAttribute(name, newroot->attribute(name));
810         }
811     }
813     /** \todo  Restore correct layer */
814     /** \todo  Restore correct selection */
817 /**  \brief  This function checks the stderr file, and if it has data,
818              shows it in a warning dialog to the user
819      \param  filename  Filename of the stderr file
820 */
821 void Script::checkStderr (const Glib::ustring &data,
822                            Gtk::MessageType type,
823                      const Glib::ustring &message)
825     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
826     warning.set_resizable(true);
827     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
828     sp_transientize(dlg);
830     Gtk::VBox * vbox = warning.get_vbox();
832     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
833     Gtk::TextView * textview = new Gtk::TextView();
834     textview->set_editable(false);
835     textview->set_wrap_mode(Gtk::WRAP_WORD);
836     textview->show();
838     // Remove the last character
839     char *errormsg = (char*) data.c_str();
840     while (*errormsg != '\0') errormsg++;
841     errormsg -= 1;
842     *errormsg = '\0';
844     textview->get_buffer()->set_text(_(data.c_str()));
846     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
847     scrollwindow->add(*textview);
848     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
849     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
850     scrollwindow->show();
852     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
854     warning.run();
856     return;
859 bool Script::cancelProcessing (void) {
860     _canceled = true;
861     _main_loop->quit();
862     Glib::spawn_close_pid(_pid);
864     return true;
868 /** \brief    This is the core of the extension file as it actually does
869               the execution of the extension.
870     \param    in_command  The command to be executed
871     \param    filein      Filename coming in
872     \param    fileout     Filename of the out file
873     \return   Number of bytes that were read into the output file.
875     The first thing that this function does is build the command to be
876     executed.  This consists of the first string (in_command) and then
877     the filename for input (filein).  This file is put on the command
878     line.
880     The next thing is that this function does is open a pipe to the
881     command and get the file handle in the ppipe variable.  It then
882     opens the output file with the output file handle.  Both of these
883     operations are checked extensively for errors.
885     After both are opened, then the data is copied from the output
886     of the pipe into the file out using fread and fwrite.  These two
887     functions are used because of their primitive nature they make
888     no assumptions about the data.  A buffer is used in the transfer,
889     but the output of fread is stored so the exact number of bytes
890     is handled gracefully.
892     At the very end (after the data has been copied) both of the files
893     are closed, and we return to what we were doing.
894 */
895 int Script::execute (const std::list<std::string> &in_command,
896                  const std::list<std::string> &in_params,
897                  const Glib::ustring &filein,
898                  file_listener &fileout)
900     g_return_val_if_fail(!in_command.empty(), 0);
901     // printf("Executing\n");
903     std::vector<std::string> argv;
905     bool interpreted = (in_command.size() == 2);
906     std::string program = in_command.front();
907     std::string script = interpreted ? in_command.back() : "";
908     std::string working_directory = "";
910     // Use Glib::find_program_in_path instead of the equivalent
911     // Glib::spawn_* functionality, because _wspawnp is broken on Windows:
912     // it doesn't work when PATH contains Unicode directories
913     if (!Glib::path_is_absolute(program)) {
914         program = Glib::find_program_in_path(program);
915     }
916     argv.push_back(program);
918     if (interpreted) {
919         // On Windows, Python garbles Unicode command line parameters
920         // in an useless way. This means extensions fail when Inkscape
921         // is run from an Unicode directory.
922         // As a workaround, we set the working directory to the one
923         // containing the script.
924         working_directory = Glib::path_get_dirname(script);
925         script = Glib::path_get_basename(script);
926         #ifdef G_OS_WIN32
927         // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8
928         gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data());
929         working_directory = workdir_s;
930         g_free(workdir_s);
931         #endif
933         argv.push_back(script);
934     }
936     // assemble the rest of argv
937     std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
938     if (!filein.empty()) {
939         argv.push_back(filein);
940     }
942     int stdout_pipe, stderr_pipe;
944     try {
945         Glib::spawn_async_with_pipes(working_directory, // working directory
946                                      argv,  // arg v
947                                      static_cast<Glib::SpawnFlags>(0), // no flags
948                                      sigc::slot<void>(),
949                                      &_pid,          // Pid
950                                      NULL,           // STDIN
951                                      &stdout_pipe,   // STDOUT
952                                      &stderr_pipe);  // STDERR
953     } catch (Glib::Error e) {
954         printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
955         return 0;
956     }
958     _main_loop = Glib::MainLoop::create(false);
960     file_listener fileerr;
961     fileout.init(stdout_pipe, _main_loop);
962     fileerr.init(stderr_pipe, _main_loop);
964     _canceled = false;
965     _main_loop->run();
967     // Ensure all the data is out of the pipe
968     while (!fileout.isDead()) {
969         fileout.read(Glib::IO_IN);
970     }
971     while (!fileerr.isDead()) {
972         fileerr.read(Glib::IO_IN);
973     }
975     if (_canceled) {
976         // std::cout << "Script Canceled" << std::endl;
977         return 0;
978     }
980     Glib::ustring stderr_data = fileerr.string();
981     if (stderr_data.length() != 0 &&
982         Inkscape::NSApplication::Application::getUseGui()
983        ) {
984         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
985                                  _("Inkscape has received additional data from the script executed.  "
986                                    "The script did not return an error, but this may indicate the results will not be as expected."));
987     }
989     Glib::ustring stdout_data = fileout.string();
990     if (stdout_data.length() == 0) {
991         return 0;
992     }
994     // std::cout << "Finishing Execution." << std::endl;
995     return stdout_data.length();
1001 }  // namespace Implementation
1002 }  // namespace Extension
1003 }  // namespace Inkscape
1005 /*
1006   Local Variables:
1007   mode:c++
1008   c-file-style:"stroustrup"
1009   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1010   indent-tabs-mode:nil
1011   fill-column:99
1012   End:
1013 */
1014 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :