Code

Merge and cleanup of GSoC C++-ification project.
[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  *   Jon A. Cruz <jon@joncruz.org>
11  *   Abhishek Sharma
12  *
13  * Copyright (C) 2006-2007 Johan Engelen
14  * Copyright (C) 2002-2004 Ted Gould
15  *
16  * Released under GNU GPL, read the file 'COPYING' for more information
17  */
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
23 #include <interface.h>
25 #include "system.h"
26 #include "preferences.h"
27 #include "extension.h"
28 #include "db.h"
29 #include "input.h"
30 #include "output.h"
31 #include "effect.h"
32 #include "patheffect.h"
33 #include "print.h"
34 #include "implementation/script.h"
35 #include "implementation/xslt.h"
36 #include "xml/rebase-hrefs.h"
37 #include "io/sys.h"
38 /* #include "implementation/plugin.h" */
40 namespace Inkscape {
41 namespace Extension {
43 static void open_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data);
44 static void save_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data);
45 static Extension *build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp);
47 /**
48  * \return   A new document created from the filename passed in
49  * \brief    This is a generic function to use the open function of
50  *           a module (including Autodetect)
51  * \param    key       Identifier of which module to use
52  * \param    filename  The file that should be opened
53  *
54  * First things first, are we looking at an autodetection?  Well if that's the case then the module
55  * needs to be found, and that is done with a database lookup through the module DB.  The foreach
56  * function is called, with the parameter being a gpointer array.  It contains both the filename
57  * (to find its extension) and where to write the module when it is found.
58  *
59  * If there is no autodetection, then the module database is queried with the key given.
60  *
61  * If everything is cool at this point, the module is loaded, and there is possibility for
62  * preferences.  If there is a function, then it is executed to get the dialog to be displayed.
63  * After it is finished the function continues.
64  *
65  * Lastly, the open function is called in the module itself.
66  */
67 SPDocument *
68 open(Extension *key, gchar const *filename)
69 {
70     Input *imod = NULL;
71     if (key == NULL) {
72         gpointer parray[2];
73         parray[0] = (gpointer)filename;
74         parray[1] = (gpointer)&imod;
75         db.foreach(open_internal, (gpointer)&parray);
76     } else {
77         imod = dynamic_cast<Input *>(key);
78     }
80     bool last_chance_svg = false;
81     if (key == NULL && imod == NULL) {
82         last_chance_svg = true;
83         imod = dynamic_cast<Input *>(db.get(SP_MODULE_KEY_INPUT_SVG));
84     }
86     if (imod == NULL) {
87         throw Input::no_extension_found();
88     }
90     imod->set_state(Extension::STATE_LOADED);
92     if (!imod->loaded()) {
93         throw Input::open_failed();
94     }
96     if (!imod->prefs(filename))
97         return NULL;
99     SPDocument *doc = imod->open(filename);
100     if (!doc) {
101         throw Input::open_failed();
102     }
104     if (last_chance_svg) {
105         /* We can't call sp_ui_error_dialog because we may be
106            running from the console, in which case calling sp_ui
107            routines will cause a segfault.  See bug 1000350 - bryce */
108         // sp_ui_error_dialog(_("Format autodetect failed. The file is being opened as SVG."));
109         g_warning(_("Format autodetect failed. The file is being opened as SVG."));
110     }
112     /* This kinda overkill as most of these are already set, but I want
113        to make sure for this release -- TJG */
114     doc->setModifiedSinceSave(false);
116     doc->setUri(filename);
118     return doc;
121 /**
122  * \return   none
123  * \brief    This is the function that searches each module to see
124  *           if it matches the filename for autodetection.
125  * \param    in_plug  The module to be tested
126  * \param    in_data  An array of pointers containing the filename, and
127  *                    the place to put a successfully found module.
128  *
129  * Basically this function only looks at input modules as it is part of the open function.  If the
130  * module is an input module, it then starts to take it apart, and the data that is passed in.
131  * Because the data being passed in is in such a weird format, there are a few casts to make it
132  * easier to use.  While it looks like a lot of local variables, they'll all get removed by the
133  * compiler.
134  *
135  * First thing that is checked is if the filename is shorter than the extension itself.  There is
136  * no way for a match in that case.  If it's long enough then there is a string compare of the end
137  * of the filename (for the length of the extension), and the extension itself.  If this passes
138  * then the pointer passed in is set to the current module.
139  */
140 static void
141 open_internal(Extension *in_plug, gpointer in_data)
143     if (!in_plug->deactivated() && dynamic_cast<Input *>(in_plug)) {
144         gpointer *parray = (gpointer *)in_data;
145         gchar const *filename = (gchar const *)parray[0];
146         Input **pimod = (Input **)parray[1];
148         // skip all the rest if we already found a function to open it
149         // since they're ordered by preference now.
150         if (!*pimod) {
151             gchar const *ext = dynamic_cast<Input *>(in_plug)->get_extension();
153             gchar *filenamelower = g_utf8_strdown(filename, -1);
154             gchar *extensionlower = g_utf8_strdown(ext, -1);
156             if (g_str_has_suffix(filenamelower, extensionlower)) {
157                 *pimod = dynamic_cast<Input *>(in_plug);
158             }
160             g_free(filenamelower);
161             g_free(extensionlower);
162         }
163     }
165     return;
168 /**
169  * \return   None
170  * \brief    This is a generic function to use the save function of
171  *           a module (including Autodetect)
172  * \param    key       Identifier of which module to use
173  * \param    doc       The document to be saved
174  * \param    filename  The file that the document should be saved to
175  * \param    official  (optional) whether to set :output_module and :modified in the
176  *                     document; is true for normal save, false for temporary saves
177  *
178  * First things first, are we looking at an autodetection?  Well if that's the case then the module
179  * needs to be found, and that is done with a database lookup through the module DB.  The foreach
180  * function is called, with the parameter being a gpointer array.  It contains both the filename
181  * (to find its extension) and where to write the module when it is found.
182  *
183  * If there is no autodetection the module database is queried with the key given.
184  *
185  * If everything is cool at this point, the module is loaded, and there is possibility for
186  * preferences.  If there is a function, then it is executed to get the dialog to be displayed.
187  * After it is finished the function continues.
188  *
189  * Lastly, the save function is called in the module itself.
190  */
191 void
192 save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, bool check_overwrite, bool official,
193     Inkscape::Extension::FileSaveMethod save_method)
195     Output *omod;
196     if (key == NULL) {
197         gpointer parray[2];
198         parray[0] = (gpointer)filename;
199         parray[1] = (gpointer)&omod;
200         omod = NULL;
201         db.foreach(save_internal, (gpointer)&parray);
203         /* This is a nasty hack, but it is required to ensure that
204            autodetect will always save with the Inkscape extensions
205            if they are available. */
206         if (omod != NULL && !strcmp(omod->get_id(), SP_MODULE_KEY_OUTPUT_SVG)) {
207             omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE));
208         }
209         /* If autodetect fails, save as Inkscape SVG */
210         if (omod == NULL) {
211             // omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); use exception and let user choose
212         }
213     } else {
214         omod = dynamic_cast<Output *>(key);
215     }
217     if (!dynamic_cast<Output *>(omod)) {
218         g_warning("Unable to find output module to handle file: %s\n", filename);
219         throw Output::no_extension_found();
220         return;
221     }
223     omod->set_state(Extension::STATE_LOADED);
224     if (!omod->loaded()) {
225         throw Output::save_failed();
226     }
228     if (!omod->prefs()) {
229         throw Output::save_cancelled();
230     }
232     gchar *fileName = NULL;
233     if (setextension) {
234         gchar *lowerfile = g_utf8_strdown(filename, -1);
235         gchar *lowerext = g_utf8_strdown(omod->get_extension(), -1);
237         if (!g_str_has_suffix(lowerfile, lowerext)) {
238             fileName = g_strdup_printf("%s%s", filename, omod->get_extension());
239         }
241         g_free(lowerfile);
242         g_free(lowerext);
243     }
245     if (fileName == NULL) {
246         fileName = g_strdup(filename);
247     }
249     if (check_overwrite && !sp_ui_overwrite_file(fileName)) {
250         g_free(fileName);
251         throw Output::no_overwrite();
252     }
254     // test if the file exists and is writable
255     // the test only checks the file attributes and might pass where ACL does not allow to write
256     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_is_writable(filename)) {
257         g_free(fileName);
258         throw Output::file_read_only();
259     }
261     Inkscape::XML::Node *repr = doc->getReprRoot();
264     // remember attributes in case this is an unofficial save and/or overwrite fails
265     gchar *saved_uri = g_strdup(doc->getURI());
266     bool saved_modified = false;
267     gchar *saved_output_extension = NULL;
268     gchar *saved_dataloss = NULL;
269     saved_modified = doc->isModifiedSinceSave();
270     saved_output_extension = g_strdup(get_file_save_extension(save_method).c_str());
271     saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss"));
272     if (official) {
273         // The document is changing name/uri.
274         doc->changeUriAndHrefs(fileName);
275     }
277     // Update attributes:
278     {
279         bool const saved = DocumentUndo::getUndoSensitive(doc);
280         DocumentUndo::setUndoSensitive(doc, false);
281         {
282             // also save the extension for next use
283             store_file_extension_in_prefs (omod->get_id(), save_method);
284             // set the "dataloss" attribute if the chosen extension is lossy
285             repr->setAttribute("inkscape:dataloss", NULL);
286             if (omod->causes_dataloss()) {
287                 repr->setAttribute("inkscape:dataloss", "true");
288             }
289         }
290         DocumentUndo::setUndoSensitive(doc, saved);
291         doc->setModifiedSinceSave(false);
292     }
294     try {
295         omod->save(doc, fileName);
296     }
297     catch(...) {
298         // revert attributes in case of official and overwrite
299         if(check_overwrite && official) {
300             bool const saved = DocumentUndo::getUndoSensitive(doc);
301             DocumentUndo::setUndoSensitive(doc, false);
302             {
303                 store_file_extension_in_prefs (saved_output_extension, save_method);
304                 repr->setAttribute("inkscape:dataloss", saved_dataloss);
305             }
306             DocumentUndo::setUndoSensitive(doc, saved);
307             doc->changeUriAndHrefs(saved_uri);
308         }
309         doc->setModifiedSinceSave(saved_modified);
310         // free used ressources
311         g_free(saved_output_extension);
312         g_free(saved_dataloss);
313         g_free(saved_uri);
315         g_free(fileName);
317         throw Inkscape::Extension::Output::save_failed();
318     }
320     // If it is an unofficial save, set the modified attributes back to what they were.
321     if ( !official) {
322         bool const saved = DocumentUndo::getUndoSensitive(doc);
323         DocumentUndo::setUndoSensitive(doc, false);
324         {
325             store_file_extension_in_prefs (saved_output_extension, save_method);
326             repr->setAttribute("inkscape:dataloss", saved_dataloss);
327         }
328         DocumentUndo::setUndoSensitive(doc, saved);
329         doc->setModifiedSinceSave(saved_modified);
331         g_free(saved_output_extension);
332         g_free(saved_dataloss);
333     }
335     g_free(fileName);
336     return;
339 /**
340  * \return   none
341  * \brief    This is the function that searches each module to see
342  *           if it matches the filename for autodetection.
343  * \param    in_plug  The module to be tested
344  * \param    in_data  An array of pointers containing the filename, and
345  *                    the place to put a successfully found module.
346  *
347  * Basically this function only looks at output modules as it is part of the open function.  If the
348  * module is an output module, it then starts to take it apart, and the data that is passed in.
349  * Because the data being passed in is in such a weird format, there are a few casts to make it
350  * easier to use.  While it looks like a lot of local variables, they'll all get removed by the
351  * compiler.
352  *
353  * First thing that is checked is if the filename is shorter than the extension itself.  There is
354  * no way for a match in that case.  If it's long enough then there is a string compare of the end
355  * of the filename (for the length of the extension), and the extension itself.  If this passes
356  * then the pointer passed in is set to the current module.
357  */
358 static void
359 save_internal(Extension *in_plug, gpointer in_data)
361     if (!in_plug->deactivated() && dynamic_cast<Output *>(in_plug)) {
362         gpointer *parray = (gpointer *)in_data;
363         gchar const *filename = (gchar const *)parray[0];
364         Output **pomod = (Output **)parray[1];
366         // skip all the rest if we already found someone to save it
367         // since they're ordered by preference now.
368         if (!*pomod) {
369             gchar const *ext = dynamic_cast<Output *>(in_plug)->get_extension();
371             gchar *filenamelower = g_utf8_strdown(filename, -1);
372             gchar *extensionlower = g_utf8_strdown(ext, -1);
374             if (g_str_has_suffix(filenamelower, extensionlower)) {
375                 *pomod = dynamic_cast<Output *>(in_plug);
376             }
378             g_free(filenamelower);
379             g_free(extensionlower);
380         }
381     }
383     return;
386 Print *
387 get_print(gchar const *key)
389     return dynamic_cast<Print *>(db.get(key));
392 /**
393  * \return   The built module
394  * \brief    Creates a module from a Inkscape::XML::Document describing the module
395  * \param    doc  The XML description of the module
396  *
397  * This function basically has two segments.  The first is that it goes through the Repr tree
398  * provided, and determines what kind of of module this is, and what kind of implementation to use.
399  * All of these are then stored in two enums that are defined in this function.  This makes it
400  * easier to add additional types (which will happen in the future, I'm sure).
401  *
402  * Second, there is case statements for these enums.  The first one is the type of module.  This is
403  * the one where the module is actually created.  After that, then the implementation is applied to
404  * get the load and unload functions.  If there is no implementation then these are not set.  This
405  * case could apply to modules that are built in (like the SVG load/save functions).
406  */
407 static Extension *
408 build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp)
410     enum {
411         MODULE_EXTENSION,
412         MODULE_XSLT,
413         /* MODULE_PLUGIN, */
414         MODULE_UNKNOWN_IMP
415     } module_implementation_type = MODULE_UNKNOWN_IMP;
416     enum {
417         MODULE_INPUT,
418         MODULE_OUTPUT,
419         MODULE_FILTER,
420         MODULE_PRINT,
421         MODULE_PATH_EFFECT,
422         MODULE_UNKNOWN_FUNC
423     } module_functional_type = MODULE_UNKNOWN_FUNC;
425     g_return_val_if_fail(doc != NULL, NULL);
427     Inkscape::XML::Node *repr = doc->root();
429     if (strcmp(repr->name(), INKSCAPE_EXTENSION_NS "inkscape-extension")) {
430         g_warning("Extension definition started with <%s> instead of <" INKSCAPE_EXTENSION_NS "inkscape-extension>.  Extension will not be created. See http://wiki.inkscape.org/wiki/index.php/Extensions for reference.\n", repr->name());
431         return NULL;
432     }
434     Inkscape::XML::Node *child_repr = sp_repr_children(repr);
435     while (child_repr != NULL) {
436         char const *element_name = child_repr->name();
437         /* printf("Child: %s\n", child_repr->name()); */
438         if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "input")) {
439             module_functional_type = MODULE_INPUT;
440         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "output")) {
441             module_functional_type = MODULE_OUTPUT;
442         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "effect")) {
443             module_functional_type = MODULE_FILTER;
444         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "print")) {
445             module_functional_type = MODULE_PRINT;
446         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "path-effect")) {
447             module_functional_type = MODULE_PATH_EFFECT;
448         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "script")) {
449             module_implementation_type = MODULE_EXTENSION;
450         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "xslt")) {
451             module_implementation_type = MODULE_XSLT;
452 #if 0
453         } else if (!strcmp(element_name, "plugin")) {
454             module_implementation_type = MODULE_PLUGIN;
455 #endif
456         }
458         //Inkscape::XML::Node *old_repr = child_repr;
459         child_repr = sp_repr_next(child_repr);
460         //Inkscape::GC::release(old_repr);
461     }
463     Implementation::Implementation *imp;
464     if (in_imp == NULL) {
465         switch (module_implementation_type) {
466             case MODULE_EXTENSION: {
467                 Implementation::Script *script = new Implementation::Script();
468                 imp = static_cast<Implementation::Implementation *>(script);
469                 break;
470             }
471             case MODULE_XSLT: {
472                 Implementation::XSLT *xslt = new Implementation::XSLT();
473                 imp = static_cast<Implementation::Implementation *>(xslt);
474                 break;
475             }
476 #if 0
477             case MODULE_PLUGIN: {
478                 Implementation::Plugin *plugin = new Implementation::Plugin();
479                 imp = static_cast<Implementation::Implementation *>(plugin);
480                 break;
481             }
482 #endif
483             default: {
484                 imp = NULL;
485                 break;
486             }
487         }
488     } else {
489         imp = in_imp;
490     }
492     Extension *module = NULL;
493     switch (module_functional_type) {
494         case MODULE_INPUT: {
495             module = new Input(repr, imp);
496             break;
497         }
498         case MODULE_OUTPUT: {
499             module = new Output(repr, imp);
500             break;
501         }
502         case MODULE_FILTER: {
503             module = new Effect(repr, imp);
504             break;
505         }
506         case MODULE_PRINT: {
507             module = new Print(repr, imp);
508             break;
509         }
510         case MODULE_PATH_EFFECT: {
511             module = new PathEffect(repr, imp);
512             break;
513         }
514         default: {
515             break;
516         }
517     }
519     return module;
522 /**
523  * \return   The module created
524  * \brief    This function creates a module from a filename of an
525  *           XML description.
526  * \param    filename  The file holding the XML description of the module.
527  *
528  * This function calls build_from_reprdoc with using sp_repr_read_file to create the reprdoc.
529  */
530 Extension *
531 build_from_file(gchar const *filename)
533     Inkscape::XML::Document *doc = sp_repr_read_file(filename, INKSCAPE_EXTENSION_URI);
534     Extension *ext = build_from_reprdoc(doc, NULL);
535     if (ext != NULL)
536         Inkscape::GC::release(doc);
537     else
538         g_warning("Unable to create extension from definition file %s.\n", filename);
539     return ext;
542 /**
543  * \return   The module created
544  * \brief    This function creates a module from a buffer holding an
545  *           XML description.
546  * \param    buffer  The buffer holding the XML description of the module.
547  *
548  * This function calls build_from_reprdoc with using sp_repr_read_mem to create the reprdoc.  It
549  * finds the length of the buffer using strlen.
550  */
551 Extension *
552 build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp)
554     Inkscape::XML::Document *doc = sp_repr_read_mem(buffer, strlen(buffer), INKSCAPE_EXTENSION_URI);
555     Extension *ext = build_from_reprdoc(doc, in_imp);
556     Inkscape::GC::release(doc);
557     return ext;
560 /*
561  * TODO: Is it guaranteed that the returned extension is valid? If so, we can remove the check for
562  * filename_extension in sp_file_save_dialog().
563  */
564 Glib::ustring
565 get_file_save_extension (Inkscape::Extension::FileSaveMethod method) {
566     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
567     Glib::ustring extension;
568     switch (method) {
569         case FILE_SAVE_METHOD_SAVE_AS:
570         case FILE_SAVE_METHOD_TEMPORARY:
571             extension = prefs->getString("/dialogs/save_as/default");
572             break;
573         case FILE_SAVE_METHOD_SAVE_COPY:
574             extension = prefs->getString("/dialogs/save_copy/default");
575             break;
576         case FILE_SAVE_METHOD_INKSCAPE_SVG:
577             extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE;
578             break;
579     }
581     if(extension.empty())
582         extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE;
584     return extension;
587 Glib::ustring
588 get_file_save_path (SPDocument *doc, FileSaveMethod method) {
589     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
590     Glib::ustring path;
591     switch (method) {
592         case FILE_SAVE_METHOD_SAVE_AS:
593         {
594             bool use_current_dir = prefs->getBool("/dialogs/save_as/use_current_dir", true);
595             if (doc->getURI() && use_current_dir) {
596                 path = Glib::path_get_dirname(doc->getURI());
597             } else {
598                 path = prefs->getString("/dialogs/save_as/path");
599             }
600             break;
601         }
602         case FILE_SAVE_METHOD_TEMPORARY:
603             path = prefs->getString("/dialogs/save_as/path");
604             break;
605         case FILE_SAVE_METHOD_SAVE_COPY:
606             path = prefs->getString("/dialogs/save_copy/path");
607             break;
608         case FILE_SAVE_METHOD_INKSCAPE_SVG:
609             if (doc->getURI()) {
610                 path = Glib::path_get_dirname(doc->getURI());
611             } else {
612                 // FIXME: should we use the save_as path here or something else? Maybe we should
613                 // leave this as a choice to the user.
614                 path = prefs->getString("/dialogs/save_as/path");
615             }
616     }
618     if(path.empty())
619         path = g_get_home_dir(); // Is this the most sensible solution? Note that we should avoid
620                                  // g_get_current_dir because this leads to problems on OS X where
621                                  // Inkscape opens the dialog inside application bundle when it is
622                                  // invoked for the first teim.
624     return path;
627 void
628 store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method) {
629     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
630     switch (method) {
631         case FILE_SAVE_METHOD_SAVE_AS:
632         case FILE_SAVE_METHOD_TEMPORARY:
633             prefs->setString("/dialogs/save_as/default", extension);
634             break;
635         case FILE_SAVE_METHOD_SAVE_COPY:
636             prefs->setString("/dialogs/save_copy/default", extension);
637             break;
638         case FILE_SAVE_METHOD_INKSCAPE_SVG:
639             // do nothing
640             break;
641     }
644 void
645 store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method) {
646     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
647     switch (method) {
648         case FILE_SAVE_METHOD_SAVE_AS:
649         case FILE_SAVE_METHOD_TEMPORARY:
650             prefs->setString("/dialogs/save_as/path", path);
651             break;
652         case FILE_SAVE_METHOD_SAVE_COPY:
653             prefs->setString("/dialogs/save_copy/path", path);
654             break;
655         case FILE_SAVE_METHOD_INKSCAPE_SVG:
656             // do nothing
657             break;
658     }
661 } } /* namespace Inkscape::Extension */
663 /*
664   Local Variables:
665   mode:c++
666   c-file-style:"stroustrup"
667   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
668   indent-tabs-mode:nil
669   fill-column:99
670   End:
671 */
672 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :