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 "selection.h"
45 #include "sp-namedview.h"
46 #include "io/sys.h"
47 #include "prefs-utils.h"
48 #include "../system.h"
49 #include "extension/effect.h"
50 #include "extension/output.h"
51 #include "extension/input.h"
52 #include "extension/db.h"
53 #include "script.h"
54 #include "dialogs/dialog-events.h"
56 #include "util/glib-list-iterators.h"
60 #ifdef WIN32
61 #include <windows.h>
62 #include <sys/stat.h>
63 #include "registrytool.h"
64 #endif
68 /** This is the command buffer that gets allocated from the stack */
69 #define BUFSIZE (255)
73 /* Namespaces */
74 namespace Inkscape {
75 namespace Extension {
76 namespace Implementation {
80 //Interpreter lookup table
81 struct interpreter_t {
82 gchar * identity;
83 gchar * prefstring;
84 gchar * defaultval;
85 };
88 /** \brief A table of what interpreters to call for a given language
90 This table is used to keep track of all the programs to execute a
91 given script. It also tracks the preference to use to overwrite
92 the given interpreter to a custom one per user.
93 */
94 static interpreter_t interpreterTab[] = {
95 {"perl", "perl-interpreter", "perl" },
96 {"python", "python-interpreter", "python" },
97 {"ruby", "ruby-interpreter", "ruby" },
98 {"shell", "shell-interpreter", "sh" },
99 { NULL, NULL, NULL }
100 };
104 /**
105 * Look up an interpreter name, and translate to something that
106 * is executable
107 */
108 static Glib::ustring
109 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
110 {
112 Glib::ustring interpName = interpNameArg;
114 interpreter_t *interp;
115 bool foundInterp = false;
116 for (interp = interpreterTab ; interp->identity ; interp++ ){
117 if (interpName == interp->identity) {
118 foundInterp = true;
119 break;
120 }
121 }
123 // Do we have a supported interpreter type?
124 if (!foundInterp)
125 return "";
126 interpName = interp->defaultval;
128 // 1. Check preferences
129 gchar *prefInterp = (gchar *)prefs_get_string_attribute(
130 "extensions", interp->prefstring);
132 if (prefInterp) {
133 interpName = prefInterp;
134 return interpName;
135 }
137 #ifdef _WIN32
139 // 2. Windows. Try looking relative to inkscape.exe
140 RegistryTool rt;
141 Glib::ustring fullPath;
142 Glib::ustring path;
143 Glib::ustring exeName;
144 if (rt.getExeInfo(fullPath, path, exeName)) {
145 Glib::ustring interpPath = path;
146 interpPath.append("\\");
147 interpPath.append(interpName);
148 interpPath.append("\\");
149 interpPath.append(interpName);
150 interpPath.append(".exe");
151 struct stat finfo;
152 if (stat(interpPath .c_str(), &finfo) ==0) {
153 g_message("Found local interpreter, '%s', Size: %d",
154 interpPath .c_str(),
155 (int)finfo.st_size);
156 return interpPath;
157 }
158 }
160 // 3. Try searching the path
161 char szExePath[MAX_PATH];
162 char szCurrentDir[MAX_PATH];
163 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
164 unsigned int ret = (unsigned int)FindExecutable(
165 interpName.c_str(), szCurrentDir, szExePath);
166 if (ret > 32) {
167 interpName = szExePath;
168 return interpName;
169 }
171 #endif // win32
174 return interpName;
175 }
178 class file_listener {
179 Glib::ustring _string;
180 sigc::connection _conn;
181 Glib::RefPtr<Glib::IOChannel> _channel;
182 Glib::RefPtr<Glib::MainLoop> _main_loop;
184 public:
185 file_listener () { };
186 ~file_listener () {
187 _conn.disconnect();
188 };
190 void init (int fd, Glib::RefPtr<Glib::MainLoop> main) {
191 _channel = Glib::IOChannel::create_from_fd(fd);
192 _channel->set_encoding();
193 _conn = Glib::signal_io().connect(sigc::mem_fun(*this, &file_listener::read), _channel, Glib::IO_IN | Glib::IO_HUP | Glib::IO_ERR);
194 _main_loop = main;
196 return;
197 };
199 bool read (Glib::IOCondition condition) {
200 if (condition != Glib::IO_IN) {
201 _main_loop->quit();
202 return false;
203 }
205 Glib::IOStatus status;
206 Glib::ustring out;
207 status = _channel->read_to_end(out);
209 if (status != Glib::IO_STATUS_NORMAL) {
210 _main_loop->quit();
211 return false;
212 }
214 _string += out;
215 return true;
216 };
218 // Note, doing a copy here, on purpose
219 Glib::ustring string (void) { return _string; };
221 void toFile (const Glib::ustring &name) {
222 Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w");
223 stdout_file->write(_string);
224 return;
225 };
226 };
228 int execute (const std::list<std::string> &in_command,
229 const std::list<std::string> &in_params,
230 const Glib::ustring &filein,
231 file_listener &fileout);
232 void checkStderr (const Glib::ustring &data,
233 Gtk::MessageType type,
234 const Glib::ustring &message);
237 /** \brief This function creates a script object and sets up the
238 variables.
239 \return A script object
241 This function just sets the command to NULL. It should get built
242 officially in the load function. This allows for less allocation
243 of memory in the unloaded state.
244 */
245 Script::Script() :
246 Implementation()
247 {
248 }
251 /**
252 * brief Destructor
253 */
254 Script::~Script()
255 {
256 }
260 /**
261 \return A string with the complete string with the relative directory expanded
262 \brief This function takes in a Repr that contains a reldir entry
263 and returns that data with the relative directory expanded.
264 Mostly it is here so that relative directories all get used
265 the same way.
266 \param reprin The Inkscape::XML::Node with the reldir in it.
268 Basically this function looks at an attribute of the Repr, and makes
269 a decision based on that. Currently, it is only working with the
270 'extensions' relative directory, but there will be more of them.
271 One thing to notice is that this function always returns an allocated
272 string. This means that the caller of this function can always
273 free what they are given (and should do it too!).
274 */
275 Glib::ustring
276 Script::solve_reldir(Inkscape::XML::Node *reprin) {
278 gchar const *s = reprin->attribute("reldir");
280 if (!s) {
281 Glib::ustring str = sp_repr_children(reprin)->content();
282 return str;
283 }
285 Glib::ustring reldir = s;
287 if (reldir == "extensions") {
289 for (unsigned int i=0;
290 i < Inkscape::Extension::Extension::search_path.size();
291 i++) {
293 gchar * fname = g_build_filename(
294 Inkscape::Extension::Extension::search_path[i],
295 sp_repr_children(reprin)->content(),
296 NULL);
297 Glib::ustring filename = fname;
298 g_free(fname);
300 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
301 return filename;
303 }
304 } else {
305 Glib::ustring str = sp_repr_children(reprin)->content();
306 return str;
307 }
309 return "";
310 }
314 /**
315 \return Whether the command given exists, including in the path
316 \brief This function is used to find out if something exists for
317 the check command. It can look in the path if required.
318 \param command The command or file that should be looked for
320 The first thing that this function does is check to see if the
321 incoming file name has a directory delimiter in it. This would
322 mean that it wants to control the directories, and should be
323 used directly.
325 If not, the path is used. Each entry in the path is stepped through,
326 attached to the string, and then tested. If the file is found
327 then a TRUE is returned. If we get all the way through the path
328 then a FALSE is returned, the command could not be found.
329 */
330 bool
331 Script::check_existance(const Glib::ustring &command)
332 {
334 // Check the simple case first
335 if (command.size() == 0) {
336 return false;
337 }
339 //Don't search when it contains a slash. */
340 if (command.find(G_DIR_SEPARATOR) != command.npos) {
341 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
342 return true;
343 else
344 return false;
345 }
348 Glib::ustring path;
349 gchar *s = (gchar *) g_getenv("PATH");
350 if (s)
351 path = s;
352 else
353 /* There is no `PATH' in the environment.
354 The default search path is the current directory */
355 path = G_SEARCHPATH_SEPARATOR_S;
357 std::string::size_type pos = 0;
358 std::string::size_type pos2 = 0;
359 while ( pos < path.size() ) {
361 Glib::ustring localPath;
363 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
364 if (pos2 == path.npos) {
365 localPath = path.substr(pos);
366 pos = path.size();
367 } else {
368 localPath = path.substr(pos, pos2-pos);
369 pos = pos2+1;
370 }
372 //printf("### %s\n", localPath.c_str());
373 Glib::ustring candidatePath =
374 Glib::build_filename(localPath, command);
376 if (Inkscape::IO::file_test(candidatePath .c_str(),
377 G_FILE_TEST_EXISTS)) {
378 return true;
379 }
381 }
383 return false;
384 }
390 /**
391 \return none
392 \brief This function 'loads' an extention, basically it determines
393 the full command for the extention and stores that.
394 \param module The extention to be loaded.
396 The most difficult part about this function is finding the actual
397 command through all of the Reprs. Basically it is hidden down a
398 couple of layers, and so the code has to move down too. When
399 the command is actually found, it has its relative directory
400 solved.
402 At that point all of the loops are exited, and there is an
403 if statement to make sure they didn't exit because of not finding
404 the command. If that's the case, the extention doesn't get loaded
405 and should error out at a higher level.
406 */
408 bool
409 Script::load(Inkscape::Extension::Extension *module)
410 {
411 if (module->loaded())
412 return TRUE;
414 helper_extension = "";
416 /* This should probably check to find the executable... */
417 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
418 while (child_repr != NULL) {
419 if (!strcmp(child_repr->name(), "script")) {
420 child_repr = sp_repr_children(child_repr);
421 while (child_repr != NULL) {
422 if (!strcmp(child_repr->name(), "command")) {
423 const gchar *interpretstr = child_repr->attribute("interpreter");
424 if (interpretstr != NULL) {
425 Glib::ustring interpString =
426 resolveInterpreterExecutable(interpretstr);
427 command.insert(command.end(), interpretstr);
428 }
430 command.insert(command.end(), solve_reldir(child_repr));
431 }
432 if (!strcmp(child_repr->name(), "helper_extension")) {
433 helper_extension = sp_repr_children(child_repr)->content();
434 }
435 child_repr = sp_repr_next(child_repr);
436 }
438 break;
439 }
440 child_repr = sp_repr_next(child_repr);
441 }
443 //g_return_val_if_fail(command.length() > 0, FALSE);
445 return true;
446 }
449 /**
450 \return None.
451 \brief Unload this puppy!
452 \param module Extension to be unloaded.
454 This function just sets the module to unloaded. It free's the
455 command if it has been allocated.
456 */
457 void
458 Script::unload(Inkscape::Extension::Extension *module)
459 {
460 command.clear();
461 helper_extension = "";
462 }
467 /**
468 \return Whether the check passed or not
469 \brief Check every dependency that was given to make sure we should keep this extension
470 \param module The Extension in question
472 */
473 bool
474 Script::check(Inkscape::Extension::Extension *module)
475 {
476 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
477 while (child_repr != NULL) {
478 if (!strcmp(child_repr->name(), "script")) {
479 child_repr = sp_repr_children(child_repr);
480 while (child_repr != NULL) {
481 if (!strcmp(child_repr->name(), "check")) {
482 Glib::ustring command_text = solve_reldir(child_repr);
483 if (command_text.size() > 0) {
484 /* I've got the command */
485 bool existance = check_existance(command_text);
486 if (!existance)
487 return FALSE;
488 }
489 }
491 if (!strcmp(child_repr->name(), "helper_extension")) {
492 gchar const *helper = sp_repr_children(child_repr)->content();
493 if (Inkscape::Extension::db.get(helper) == NULL) {
494 return FALSE;
495 }
496 }
498 child_repr = sp_repr_next(child_repr);
499 }
501 break;
502 }
503 child_repr = sp_repr_next(child_repr);
504 }
506 return true;
507 }
511 /**
512 \return A dialog for preferences
513 \brief A stub funtion right now
514 \param module Module who's preferences need getting
515 \param filename Hey, the file you're getting might be important
517 This function should really do something, right now it doesn't.
518 */
519 Gtk::Widget *
520 Script::prefs_input(Inkscape::Extension::Input *module,
521 const gchar *filename)
522 {
523 return module->autogui(NULL, NULL);
524 }
528 /**
529 \return A dialog for preferences
530 \brief A stub funtion right now
531 \param module Module whose preferences need getting
533 This function should really do something, right now it doesn't.
534 */
535 Gtk::Widget *
536 Script::prefs_output(Inkscape::Extension::Output *module)
537 {
538 return module->autogui(NULL, NULL);
539 }
543 /**
544 \return A dialog for preferences
545 \brief A stub funtion right now
546 \param module Module who's preferences need getting
548 This function should really do something, right now it doesn't.
549 */
550 Gtk::Widget *
551 Script::prefs_effect(Inkscape::Extension::Effect *module,
552 Inkscape::UI::View::View *view,
553 sigc::signal<void> * changeSignal)
554 {
555 SPDocument * current_document = view->doc();
557 using Inkscape::Util::GSListConstIterator;
558 GSListConstIterator<SPItem *> selected =
559 sp_desktop_selection((SPDesktop *)view)->itemList();
560 Inkscape::XML::Node * first_select = NULL;
561 if (selected != NULL) {
562 const SPItem * item = *selected;
563 first_select = SP_OBJECT_REPR(item);
564 }
566 return module->autogui(current_document, first_select, changeSignal);
567 }
572 /**
573 \return A new document that has been opened
574 \brief This function uses a filename that is put in, and calls
575 the extension's command to create an SVG file which is
576 returned.
577 \param module Extension to use.
578 \param filename File to open.
580 First things first, this function needs a temporary file name. To
581 create on of those the function g_file_open_tmp is used with
582 the header of ink_ext_.
584 The extension is then executed using the 'execute' function
585 with the filname coming in, and the temporary filename. After
586 That executing, the SVG should be in the temporary file.
588 Finally, the temporary file is opened using the SVG input module and
589 a document is returned. That document has its filename set to
590 the incoming filename (so that it's not the temporary filename).
591 That document is then returned from this function.
592 */
593 SPDocument *
594 Script::open(Inkscape::Extension::Input *module,
595 const gchar *filenameArg)
596 {
597 std::list<std::string> params;
598 module->paramListString(params);
600 std::string tempfilename_out;
601 int tempfd_out = 0;
602 try {
603 tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
604 } catch (...) {
605 /// \todo Popup dialog here
606 return NULL;
607 }
609 std::string lfilename = Glib::filename_from_utf8(filenameArg);
611 file_listener fileout;
612 int data_read = execute(command, params, lfilename, fileout);
613 fileout.toFile(tempfilename_out);
615 SPDocument * mydoc = NULL;
616 if (data_read > 10) {
617 if (helper_extension.size()==0) {
618 mydoc = Inkscape::Extension::open(
619 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
620 tempfilename_out.c_str());
621 } else {
622 mydoc = Inkscape::Extension::open(
623 Inkscape::Extension::db.get(helper_extension.c_str()),
624 tempfilename_out.c_str());
625 }
626 } // data_read
628 if (mydoc != NULL) {
629 sp_document_set_uri(mydoc, filenameArg);
630 }
632 // make sure we don't leak file descriptors from g_file_open_tmp
633 close(tempfd_out);
635 unlink(tempfilename_out.c_str());
637 return mydoc;
638 } // open
642 /**
643 \return none
644 \brief This function uses an extention to save a document. It first
645 creates an SVG file of the document, and then runs it through
646 the script.
647 \param module Extention to be used
648 \param doc Document to be saved
649 \param filename The name to save the final file as
651 Well, at some point people need to save - it is really what makes
652 the entire application useful. And, it is possible that someone
653 would want to use an extetion for this, so we need a function to
654 do that eh?
656 First things first, the document is saved to a temporary file that
657 is an SVG file. To get the temporary filename g_file_open_tmp is used with
658 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
659 end of the function.
661 After we have the SVG file, then extention_execute is called with
662 the temporary file name and the final output filename. This should
663 put the output of the script into the final output file. We then
664 delete the temporary file.
665 */
666 void
667 Script::save(Inkscape::Extension::Output *module,
668 SPDocument *doc,
669 const gchar *filenameArg)
670 {
671 std::list<std::string> params;
672 module->paramListString(params);
674 std::string tempfilename_in;
675 int tempfd_in = 0;
676 try {
677 tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
678 } catch (...) {
679 /// \todo Popup dialog here
680 return;
681 }
683 if (helper_extension.size() == 0) {
684 Inkscape::Extension::save(
685 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
686 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
687 } else {
688 Inkscape::Extension::save(
689 Inkscape::Extension::db.get(helper_extension.c_str()),
690 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
691 }
694 file_listener fileout;
695 execute(command, params, tempfilename_in, fileout);
697 std::string lfilename = Glib::filename_from_utf8(filenameArg);
698 fileout.toFile(lfilename);
700 // make sure we don't leak file descriptors from g_file_open_tmp
701 close(tempfd_in);
702 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
703 unlink(tempfilename_in.c_str());
705 return;
706 }
710 /**
711 \return none
712 \brief This function uses an extention as a effect on a document.
713 \param module Extention to effect with.
714 \param doc Document to run through the effect.
716 This function is a little bit trickier than the previous two. It
717 needs two temporary files to get it's work done. Both of these
718 files have random names created for them using the g_file_open_temp function
719 with the ink_ext_ prefix in the temporary directory. Like the other
720 functions, the temporary files are deleted at the end.
722 To save/load the two temporary documents (both are SVG) the internal
723 modules for SVG load and save are used. They are both used through
724 the module system function by passing their keys into the functions.
726 The command itself is built a little bit differently than in other
727 functions because the effect support selections. So on the command
728 line a list of all the ids that are selected is included. Currently,
729 this only works for a single selected object, but there will be more.
730 The command string is filled with the data, and then after the execution
731 it is freed.
733 The execute function is used at the core of this function
734 to execute the Script on the two SVG documents (actually only one
735 exists at the time, the other is created by that script). At that
736 point both should be full, and the second one is loaded.
737 */
738 void
739 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
740 {
741 std::list<std::string> params;
742 module->paramListString(params);
744 if (module->no_doc) {
745 // this is a no-doc extension, e.g. a Help menu command;
746 // just run the command without any files, ignoring errors
748 Glib::ustring empty;
749 file_listener outfile;
750 execute(command, params, empty, outfile);
752 return;
753 }
755 gchar *tmpname;
756 // FIXME: process the GError instead of passing NULL
757 gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
758 if (tempfd_in == -1) {
759 /* Error, couldn't create temporary filename */
760 if (errno == EINVAL) {
761 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
762 perror("Extension::Script: template for filenames is misconfigured.\n");
763 exit(-1);
764 } else if (errno == EEXIST) {
765 /* Now the contents of template are undefined. */
766 perror("Extension::Script: Could not create a unique temporary filename\n");
767 return;
768 } else {
769 perror("Extension::Script: Unknown error creating temporary filename\n");
770 exit(-1);
771 }
772 }
774 Glib::ustring tempfilename_in = tmpname;
775 g_free(tmpname);
778 // FIXME: process the GError instead of passing NULL
779 gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
780 if (tempfd_out == -1) {
781 /* Error, couldn't create temporary filename */
782 if (errno == EINVAL) {
783 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
784 perror("Extension::Script: template for filenames is misconfigured.\n");
785 exit(-1);
786 } else if (errno == EEXIST) {
787 /* Now the contents of template are undefined. */
788 perror("Extension::Script: Could not create a unique temporary filename\n");
789 return;
790 } else {
791 perror("Extension::Script: Unknown error creating temporary filename\n");
792 exit(-1);
793 }
794 }
796 Glib::ustring tempfilename_out= tmpname;
797 g_free(tmpname);
799 SPDesktop *desktop = (SPDesktop *) doc;
800 sp_namedview_document_from_window(desktop);
802 Inkscape::Extension::save(
803 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
804 doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
806 if (desktop != NULL) {
807 Inkscape::Util::GSListConstIterator<SPItem *> selected =
808 sp_desktop_selection(desktop)->itemList();
809 while ( selected != NULL ) {
810 Glib::ustring selected_id;
811 selected_id += "--id=";
812 selected_id += SP_OBJECT_ID(*selected);
813 params.insert(params.begin(), selected_id);
814 ++selected;
815 }
816 }
818 file_listener fileout;
819 int data_read = execute(command, params, tempfilename_in, fileout);
820 fileout.toFile(tempfilename_out);
822 SPDocument * mydoc = NULL;
823 if (data_read > 10) {
824 mydoc = Inkscape::Extension::open(
825 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
826 tempfilename_out.c_str());
827 } // data_read
829 // make sure we don't leak file descriptors from g_file_open_tmp
830 close(tempfd_in);
831 close(tempfd_out);
833 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
834 unlink(tempfilename_in.c_str());
835 unlink(tempfilename_out.c_str());
837 /* Do something with mydoc.... */
838 if (mydoc) {
839 doc->doc()->emitReconstructionStart();
840 copy_doc(doc->doc()->rroot, mydoc->rroot);
841 doc->doc()->emitReconstructionFinish();
842 mydoc->release();
843 sp_namedview_update_layers_from_document(desktop);
844 }
846 return;
847 }
851 /**
852 \brief A function to take all the svg elements from one document
853 and put them in another.
854 \param oldroot The root node of the document to be replaced
855 \param newroot The root node of the document to replace it with
857 This function first deletes all of the data in the old document. It
858 does this by creating a list of what needs to be deleted, and then
859 goes through the list. This two pass approach removes issues with
860 the list being change while parsing through it. Lots of nasty bugs.
862 Then, it goes through the new document, duplicating all of the
863 elements and putting them into the old document. The copy
864 is then complete.
865 */
866 void
867 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
868 {
869 std::vector<Inkscape::XML::Node *> delete_list;
870 for (Inkscape::XML::Node * child = oldroot->firstChild();
871 child != NULL;
872 child = child->next()) {
873 if (!strcmp("sodipodi:namedview", child->name()))
874 continue;
875 delete_list.push_back(child);
876 }
877 for (unsigned int i = 0; i < delete_list.size(); i++)
878 sp_repr_unparent(delete_list[i]);
880 for (Inkscape::XML::Node * child = newroot->firstChild();
881 child != NULL;
882 child = child->next()) {
883 if (!strcmp("sodipodi:namedview", child->name()))
884 continue;
885 oldroot->appendChild(child->duplicate(newroot->document()));
886 }
888 /** \todo Restore correct layer */
889 /** \todo Restore correct selection */
890 }
892 /** \brief This function checks the stderr file, and if it has data,
893 shows it in a warning dialog to the user
894 \param filename Filename of the stderr file
895 */
896 void
897 checkStderr (const Glib::ustring &data,
898 Gtk::MessageType type,
899 const Glib::ustring &message)
900 {
901 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
902 warning.set_resizable(true);
903 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
904 sp_transientize(dlg);
906 Gtk::VBox * vbox = warning.get_vbox();
908 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
909 Gtk::TextView * textview = new Gtk::TextView();
910 textview->set_editable(false);
911 textview->set_wrap_mode(Gtk::WRAP_WORD);
912 textview->show();
914 textview->get_buffer()->set_text(data.c_str());
916 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
917 scrollwindow->add(*textview);
918 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
919 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
920 scrollwindow->show();
922 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
924 warning.run();
926 return;
927 }
929 /** \brief This is the core of the extension file as it actually does
930 the execution of the extension.
931 \param in_command The command to be executed
932 \param filein Filename coming in
933 \param fileout Filename of the out file
934 \return Number of bytes that were read into the output file.
936 The first thing that this function does is build the command to be
937 executed. This consists of the first string (in_command) and then
938 the filename for input (filein). This file is put on the command
939 line.
941 The next thing is that this function does is open a pipe to the
942 command and get the file handle in the ppipe variable. It then
943 opens the output file with the output file handle. Both of these
944 operations are checked extensively for errors.
946 After both are opened, then the data is copied from the output
947 of the pipe into the file out using fread and fwrite. These two
948 functions are used because of their primitive nature they make
949 no assumptions about the data. A buffer is used in the transfer,
950 but the output of fread is stored so the exact number of bytes
951 is handled gracefully.
953 At the very end (after the data has been copied) both of the files
954 are closed, and we return to what we were doing.
955 */
956 int
957 execute (const std::list<std::string> &in_command,
958 const std::list<std::string> &in_params,
959 const Glib::ustring &filein,
960 file_listener &fileout)
961 {
962 g_return_val_if_fail(in_command.size() > 0, 0);
963 // printf("Executing\n");
965 std::vector <std::string> argv;
967 for (std::list<std::string>::const_iterator i = in_command.begin();
968 i != in_command.end(); i++) {
969 argv.push_back(*i);
970 }
972 if (!(filein.empty())) {
973 argv.push_back(filein);
974 }
976 for (std::list<std::string>::const_iterator i = in_params.begin();
977 i != in_params.end(); i++) {
978 argv.push_back(*i);
979 }
981 /*
982 for (std::vector<std::string>::const_iterator i = argv.begin();
983 i != argv.end(); i++) {
984 std::cout << *i << std::endl;
985 }
986 */
988 Glib::Pid pid;
989 int stdout_pipe, stderr_pipe;
991 try {
992 Glib::spawn_async_with_pipes(Glib::get_tmp_dir(), // working directory
993 argv, // arg v
994 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
995 sigc::slot<void>(),
996 &pid, // Pid
997 NULL, // STDIN
998 &stdout_pipe, // STDOUT
999 &stderr_pipe); // STDERR
1000 } catch (Glib::SpawnError e) {
1001 printf("Can't Spawn!!! %d\n", e.code());
1002 return 0;
1003 }
1005 Glib::RefPtr<Glib::MainLoop> main_loop = Glib::MainLoop::create(false);
1007 file_listener fileerr;
1008 fileout.init(stdout_pipe, main_loop);
1009 fileerr.init(stderr_pipe, main_loop);
1011 main_loop->run();
1013 Glib::ustring stderr_data = fileerr.string();
1014 if (stderr_data.length() != 0) {
1015 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1016 _("Inkscape has received additional data from the script executed. "
1017 "The script did not return an error, but this may indicate the results will not be as expected."));
1018 }
1020 Glib::ustring stdout_data = fileout.string();
1021 if (stdout_data.length() == 0) {
1022 return 0;
1023 }
1025 // std::cout << "Finishing Execution." << std::endl;
1026 return stdout_data.length();
1027 }
1032 } // namespace Implementation
1033 } // namespace Extension
1034 } // namespace Inkscape
1036 /*
1037 Local Variables:
1038 mode:c++
1039 c-file-style:"stroustrup"
1040 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1041 indent-tabs-mode:nil
1042 fill-column:99
1043 End:
1044 */
1045 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :