Code

r19076@shi: ted | 2008-04-21 15:42:45 -0700
[inkscape.git] / src / extension / system.cpp
1 /*
2  * This is file is kind of the junk file.  Basically everything that
3  * didn't fit in one of the other well defined areas, well, it's now
4  * here.  Which is good in someways, but this file really needs some
5  * definition.  Hopefully that will come ASAP.
6  *
7  * Authors:
8  *   Ted Gould <ted@gould.cx>
9  *   Johan Engelen <johan@shouraizou.nl>
10  *
11  * Copyright (C) 2006-2007 Johan Engelen 
12  * Copyright (C) 2002-2004 Ted Gould
13  *
14  * Released under GNU GPL, read the file 'COPYING' for more information
15  */
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
21 #include <interface.h>
23 #include "extension.h"
24 #include "db.h"
25 #include "input.h"
26 #include "output.h"
27 #include "effect.h"
28 #include "patheffect.h"
29 #include "print.h"
30 #include "implementation/script.h"
31 #include "implementation/xslt.h"
32 /* #include "implementation/plugin.h" */
34 namespace Inkscape {
35 namespace Extension {
37 static void open_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data);
38 static void save_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data);
39 static Extension *build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp);
41 /**
42  * \return   A new document created from the filename passed in
43  * \brief    This is a generic function to use the open function of
44  *           a module (including Autodetect)
45  * \param    key       Identifier of which module to use
46  * \param    filename  The file that should be opened
47  *
48  * First things first, are we looking at an autodetection?  Well if that's the case then the module
49  * needs to be found, and that is done with a database lookup through the module DB.  The foreach
50  * function is called, with the parameter being a gpointer array.  It contains both the filename
51  * (to find its extension) and where to write the module when it is found.
52  *
53  * If there is no autodetection, then the module database is queried with the key given.
54  *
55  * If everything is cool at this point, the module is loaded, and there is possibility for
56  * preferences.  If there is a function, then it is executed to get the dialog to be displayed.
57  * After it is finished the function continues.
58  *
59  * Lastly, the open function is called in the module itself.
60  */
61 SPDocument *
62 open(Extension *key, gchar const *filename)
63 {
64     Input *imod = NULL;
65     if (key == NULL) {
66         gpointer parray[2];
67         parray[0] = (gpointer)filename;
68         parray[1] = (gpointer)&imod;
69         db.foreach(open_internal, (gpointer)&parray);
70     } else {
71         imod = dynamic_cast<Input *>(key);
72     }
74     bool last_chance_svg = false;
75     if (key == NULL && imod == NULL) {
76         last_chance_svg = true;
77         imod = dynamic_cast<Input *>(db.get(SP_MODULE_KEY_INPUT_SVG));
78     }
80     if (imod == NULL) {
81         throw Input::no_extension_found();
82     }
84     imod->set_state(Extension::STATE_LOADED);
86     if (!imod->loaded()) {
87         throw Input::open_failed();
88     }
90     if (!imod->prefs(filename))
91         return NULL;
93     SPDocument *doc = imod->open(filename);
94     if (!doc) {
95         return NULL;
96     }
98     if (last_chance_svg) {
99         /* We can't call sp_ui_error_dialog because we may be 
100            running from the console, in which case calling sp_ui
101            routines will cause a segfault.  See bug 1000350 - bryce */
102         // sp_ui_error_dialog(_("Format autodetect failed. The file is being opened as SVG."));
103         g_warning(_("Format autodetect failed. The file is being opened as SVG."));
104     }
106     /* This kinda overkill as most of these are already set, but I want
107        to make sure for this release -- TJG */
108     doc->setModifiedSinceSave(false);
110     sp_document_set_uri(doc, filename);
112     return doc;
115 /**
116  * \return   none
117  * \brief    This is the function that searches each module to see
118  *           if it matches the filename for autodetection.
119  * \param    in_plug  The module to be tested
120  * \param    in_data  An array of pointers containing the filename, and
121  *                    the place to put a successfully found module.
122  *
123  * Basically this function only looks at input modules as it is part of the open function.  If the
124  * module is an input module, it then starts to take it apart, and the data that is passed in.
125  * Because the data being passed in is in such a weird format, there are a few casts to make it
126  * easier to use.  While it looks like a lot of local variables, they'll all get removed by the
127  * compiler.
128  *
129  * First thing that is checked is if the filename is shorter than the extension itself.  There is
130  * no way for a match in that case.  If it's long enough then there is a string compare of the end
131  * of the filename (for the length of the extension), and the extension itself.  If this passes
132  * then the pointer passed in is set to the current module.
133  */
134 static void
135 open_internal(Extension *in_plug, gpointer in_data)
137     if (!in_plug->deactivated() && dynamic_cast<Input *>(in_plug)) {
138         gpointer *parray = (gpointer *)in_data;
139         gchar const *filename = (gchar const *)parray[0];
140         Input **pimod = (Input **)parray[1];
142         // skip all the rest if we already found a function to open it
143         // since they're ordered by preference now.
144         if (!*pimod) {
145             gchar const *ext = dynamic_cast<Input *>(in_plug)->get_extension();
147             gchar *filenamelower = g_utf8_strdown(filename, -1);
148             gchar *extensionlower = g_utf8_strdown(ext, -1);
150             if (g_str_has_suffix(filenamelower, extensionlower)) {
151                 *pimod = dynamic_cast<Input *>(in_plug);
152             }
154             g_free(filenamelower);
155             g_free(extensionlower);
156         }
157     }
159     return;
162 /**
163  * \return   None
164  * \brief    This is a generic function to use the save function of
165  *           a module (including Autodetect)
166  * \param    key       Identifier of which module to use
167  * \param    doc       The document to be saved
168  * \param    filename  The file that the document should be saved to
169  * \param    official  (optional) whether to set :output_module and :modified in the
170  *                     document; is true for normal save, false for temporary saves
171  *
172  * First things first, are we looking at an autodetection?  Well if that's the case then the module
173  * needs to be found, and that is done with a database lookup through the module DB.  The foreach
174  * function is called, with the parameter being a gpointer array.  It contains both the filename
175  * (to find its extension) and where to write the module when it is found.
176  *
177  * If there is no autodetection the module database is queried with the key given.
178  *
179  * If everything is cool at this point, the module is loaded, and there is possibility for
180  * preferences.  If there is a function, then it is executed to get the dialog to be displayed.
181  * After it is finished the function continues.
182  *
183  * Lastly, the save function is called in the module itself.
184  */
185 void
186 save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, bool check_overwrite, bool official)
188     Output *omod;
189     if (key == NULL) {
190         gpointer parray[2];
191         parray[0] = (gpointer)filename;
192         parray[1] = (gpointer)&omod;
193         omod = NULL;
194         db.foreach(save_internal, (gpointer)&parray);
196         /* This is a nasty hack, but it is required to ensure that
197            autodetect will always save with the Inkscape extensions
198            if they are available. */
199         if (omod != NULL && !strcmp(omod->get_id(), SP_MODULE_KEY_OUTPUT_SVG)) {
200             omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE));
201         }
202         /* If autodetect fails, save as Inkscape SVG */
203         if (omod == NULL) {
204             omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE));
205         }
206     } else {
207         omod = dynamic_cast<Output *>(key);
208     }
210     if (!dynamic_cast<Output *>(omod)) {
211         g_warning("Unable to find output module to handle file: %s\n", filename);
212         throw Output::no_extension_found();
213         return;
214     }
216     omod->set_state(Extension::STATE_LOADED);
217     if (!omod->loaded()) {
218         throw Output::save_failed();
219     }
221     if (!omod->prefs())
222         return;
224     gchar *fileName = NULL;
225     if (setextension) {
226         gchar *lowerfile = g_utf8_strdown(filename, -1);
227         gchar *lowerext = g_utf8_strdown(omod->get_extension(), -1);
229         if (!g_str_has_suffix(lowerfile, lowerext)) {
230             fileName = g_strdup_printf("%s%s", filename, omod->get_extension());
231         }
233         g_free(lowerfile);
234         g_free(lowerext);
235     }
237     if (fileName == NULL) {
238         fileName = g_strdup(filename);
239     }
241     if (check_overwrite && !sp_ui_overwrite_file(fileName)) {
242         g_free(fileName);
243         throw Output::no_overwrite();
244     }
246     Inkscape::XML::Node *repr = sp_document_repr_root(doc);
248     // remember attributes in case this is an unofficial save
249     bool saved_modified = false;
250     gchar *saved_output_extension = NULL;
251     gchar *saved_dataloss = NULL;
252     gchar *saved_uri = NULL;
253     if (!official) {
254         saved_modified = doc->isModifiedSinceSave();
255         if (repr->attribute("inkscape:output_extension")) {
256             saved_output_extension = g_strdup(repr->attribute("inkscape:output_extension"));
257         }
258         if (repr->attribute("inkscape:dataloss")) {
259             saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss"));
260         }
261         if (doc->uri) {
262             saved_uri = g_strdup(doc->uri);    
263         }
264     }    
266     // update attributes:
267     bool saved = sp_document_get_undo_sensitive(doc);
268     sp_document_set_undo_sensitive (doc, false); 
269         // save the filename for next use
270         sp_document_set_uri(doc, fileName);
271         // also save the extension for next use
272         repr->setAttribute("inkscape:output_extension", omod->get_id());
273         // set the "dataloss" attribute if the chosen extension is lossy
274         repr->setAttribute("inkscape:dataloss", NULL);
275         if ( omod->causes_dataloss() ) {
276             repr->setAttribute("inkscape:dataloss", "true");
277         }
278     sp_document_set_undo_sensitive (doc, saved);
279     doc->setModifiedSinceSave(false);
281     omod->save(doc, fileName);
282     
283     // if it is an unofficial save, set the modified attributes back to what they were    
284     if ( !official) {
285         saved = sp_document_get_undo_sensitive(doc);
286         sp_document_set_undo_sensitive (doc, false);
287             repr->setAttribute("inkscape:output_extension", saved_output_extension);
288             repr->setAttribute("inkscape:dataloss", saved_dataloss);
289             sp_document_set_uri(doc, saved_uri);
290         sp_document_set_undo_sensitive (doc, saved);
291         doc->setModifiedSinceSave(saved_modified);
292     }
293     
294     if (saved_output_extension)  g_free(saved_output_extension);
295     if (saved_dataloss)          g_free(saved_dataloss);
296     if (saved_uri)               g_free(saved_uri);    
297     
298     g_free(fileName);
299     return;
302 /**
303  * \return   none
304  * \brief    This is the function that searches each module to see
305  *           if it matches the filename for autodetection.
306  * \param    in_plug  The module to be tested
307  * \param    in_data  An array of pointers containing the filename, and
308  *                    the place to put a successfully found module.
309  *
310  * Basically this function only looks at output modules as it is part of the open function.  If the
311  * module is an output module, it then starts to take it apart, and the data that is passed in.
312  * Because the data being passed in is in such a weird format, there are a few casts to make it
313  * easier to use.  While it looks like a lot of local variables, they'll all get removed by the
314  * compiler.
315  *
316  * First thing that is checked is if the filename is shorter than the extension itself.  There is
317  * no way for a match in that case.  If it's long enough then there is a string compare of the end
318  * of the filename (for the length of the extension), and the extension itself.  If this passes
319  * then the pointer passed in is set to the current module.
320  */
321 static void
322 save_internal(Extension *in_plug, gpointer in_data)
324     if (!in_plug->deactivated() && dynamic_cast<Output *>(in_plug)) {
325         gpointer *parray = (gpointer *)in_data;
326         gchar const *filename = (gchar const *)parray[0];
327         Output **pomod = (Output **)parray[1];
329         // skip all the rest if we already found someone to save it
330         // since they're ordered by preference now.
331         if (!*pomod) {
332             gchar const *ext = dynamic_cast<Output *>(in_plug)->get_extension();
334             gchar *filenamelower = g_utf8_strdown(filename, -1);
335             gchar *extensionlower = g_utf8_strdown(ext, -1);
337             if (g_str_has_suffix(filenamelower, extensionlower)) {
338                 *pomod = dynamic_cast<Output *>(in_plug);
339             }
341             g_free(filenamelower);
342             g_free(extensionlower);
343         }
344     }
346     return;
349 Print *
350 get_print(gchar const *key)
352     return dynamic_cast<Print *>(db.get(key));
355 /**
356  * \return   The built module
357  * \brief    Creates a module from a Inkscape::XML::Document describing the module
358  * \param    doc  The XML description of the module
359  *
360  * This function basically has two segments.  The first is that it goes through the Repr tree
361  * provided, and determines what kind of of module this is, and what kind of implementation to use.
362  * All of these are then stored in two enums that are defined in this function.  This makes it
363  * easier to add additional types (which will happen in the future, I'm sure).
364  *
365  * Second, there is case statements for these enums.  The first one is the type of module.  This is
366  * the one where the module is actually created.  After that, then the implementation is applied to
367  * get the load and unload functions.  If there is no implementation then these are not set.  This
368  * case could apply to modules that are built in (like the SVG load/save functions).
369  */
370 static Extension *
371 build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp)
373     enum {
374         MODULE_EXTENSION,
375         MODULE_XSLT,
376         /* MODULE_PLUGIN, */
377         MODULE_UNKNOWN_IMP
378     } module_implementation_type = MODULE_UNKNOWN_IMP;
379     enum {
380         MODULE_INPUT,
381         MODULE_OUTPUT,
382         MODULE_FILTER,
383         MODULE_PRINT,
384         MODULE_PATH_EFFECT,
385         MODULE_UNKNOWN_FUNC
386     } module_functional_type = MODULE_UNKNOWN_FUNC;
388     g_return_val_if_fail(doc != NULL, NULL);
390     Inkscape::XML::Node *repr = doc->root();
392     /* sp_repr_print(repr); */
394     if (strcmp(repr->name(), INKSCAPE_EXTENSION_NS "inkscape-extension")) {
395         g_warning("Extension definition started with <%s> instead of <" INKSCAPE_EXTENSION_NS "inkscape-extension>.  Extension will not be created.\n", repr->name());
396         return NULL;
397     }
399     Inkscape::XML::Node *child_repr = sp_repr_children(repr);
400     while (child_repr != NULL) {
401         char const *element_name = child_repr->name();
402         /* printf("Child: %s\n", child_repr->name()); */
403         if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "input")) {
404             module_functional_type = MODULE_INPUT;
405         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "output")) {
406             module_functional_type = MODULE_OUTPUT;
407         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "effect")) {
408             module_functional_type = MODULE_FILTER;
409         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "print")) {
410             module_functional_type = MODULE_PRINT;
411         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "path-effect")) {
412             module_functional_type = MODULE_PATH_EFFECT;
413         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "script")) {
414             module_implementation_type = MODULE_EXTENSION;
415         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "xslt")) {
416             module_implementation_type = MODULE_XSLT;
417 #if 0
418         } else if (!strcmp(element_name, "plugin")) {
419             module_implementation_type = MODULE_PLUGIN;
420 #endif
421         }
423         //Inkscape::XML::Node *old_repr = child_repr;
424         child_repr = sp_repr_next(child_repr);
425         //Inkscape::GC::release(old_repr);
426     }
428     Implementation::Implementation *imp;
429     if (in_imp == NULL) {
430         switch (module_implementation_type) {
431             case MODULE_EXTENSION: {
432                 Implementation::Script *script = new Implementation::Script();
433                 imp = static_cast<Implementation::Implementation *>(script);
434                 break;
435             }
436             case MODULE_XSLT: {
437                 Implementation::XSLT *xslt = new Implementation::XSLT();
438                 imp = static_cast<Implementation::Implementation *>(xslt);
439                 break;
440             }
441 #if 0
442             case MODULE_PLUGIN: {
443                 Implementation::Plugin *plugin = new Implementation::Plugin();
444                 imp = static_cast<Implementation::Implementation *>(plugin);
445                 break;
446             }
447 #endif
448             default: {
449                 imp = NULL;
450                 break;
451             }
452         }
453     } else {
454         imp = in_imp;
455     }
457     Extension *module = NULL;
458     switch (module_functional_type) {
459         case MODULE_INPUT: {
460             module = new Input(repr, imp);
461             break;
462         }
463         case MODULE_OUTPUT: {
464             module = new Output(repr, imp);
465             break;
466         }
467         case MODULE_FILTER: {
468             module = new Effect(repr, imp);
469             break;
470         }
471         case MODULE_PRINT: {
472             module = new Print(repr, imp);
473             break;
474         }
475         case MODULE_PATH_EFFECT: {
476             module = new PathEffect(repr, imp);
477             break;
478         }
479         default: {
480             break;
481         }
482     }
484     return module;
487 /**
488  * \return   The module created
489  * \brief    This function creates a module from a filename of an
490  *           XML description.
491  * \param    filename  The file holding the XML description of the module.
492  *
493  * This function calls build_from_reprdoc with using sp_repr_read_file to create the reprdoc.
494  */
495 Extension *
496 build_from_file(gchar const *filename)
498     /* TODO: Need to define namespace here, need to write the
499        DTD in general for this stuff */
500     Inkscape::XML::Document *doc = sp_repr_read_file(filename, INKSCAPE_EXTENSION_URI);
501     Extension *ext = build_from_reprdoc(doc, NULL);
502     if (ext != NULL)
503         Inkscape::GC::release(doc);
504     else
505         g_warning("Unable to create extension from definition file %s.\n", filename);
506     return ext;
509 /**
510  * \return   The module created
511  * \brief    This function creates a module from a buffer holding an
512  *           XML description.
513  * \param    buffer  The buffer holding the XML description of the module.
514  *
515  * This function calls build_from_reprdoc with using sp_repr_read_mem to create the reprdoc.  It
516  * finds the length of the buffer using strlen.
517  */
518 Extension *
519 build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp)
521     Inkscape::XML::Document *doc = sp_repr_read_mem(buffer, strlen(buffer), INKSCAPE_EXTENSION_URI);
522     Extension *ext = build_from_reprdoc(doc, in_imp);
523     Inkscape::GC::release(doc);
524     return ext;
528 } } /* namespace Inkscape::Extension */
530 /*
531   Local Variables:
532   mode:c++
533   c-file-style:"stroustrup"
534   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
535   indent-tabs-mode:nil
536   fill-column:99
537   End:
538 */
539 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :