Code

Fix remaining glitches in the behaviour of the Save dialogs (w.r.t. remembering the...
[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 "system.h"
24 #include "preferences.h"
25 #include "extension.h"
26 #include "db.h"
27 #include "input.h"
28 #include "output.h"
29 #include "effect.h"
30 #include "patheffect.h"
31 #include "print.h"
32 #include "implementation/script.h"
33 #include "implementation/xslt.h"
34 #include "xml/rebase-hrefs.h"
35 /* #include "implementation/plugin.h" */
37 namespace Inkscape {
38 namespace Extension {
40 static void open_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data);
41 static void save_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data);
42 static Extension *build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp);
44 /**
45  * \return   A new document created from the filename passed in
46  * \brief    This is a generic function to use the open function of
47  *           a module (including Autodetect)
48  * \param    key       Identifier of which module to use
49  * \param    filename  The file that should be opened
50  *
51  * First things first, are we looking at an autodetection?  Well if that's the case then the module
52  * needs to be found, and that is done with a database lookup through the module DB.  The foreach
53  * function is called, with the parameter being a gpointer array.  It contains both the filename
54  * (to find its extension) and where to write the module when it is found.
55  *
56  * If there is no autodetection, then the module database is queried with the key given.
57  *
58  * If everything is cool at this point, the module is loaded, and there is possibility for
59  * preferences.  If there is a function, then it is executed to get the dialog to be displayed.
60  * After it is finished the function continues.
61  *
62  * Lastly, the open function is called in the module itself.
63  */
64 SPDocument *
65 open(Extension *key, gchar const *filename)
66 {
67     Input *imod = NULL;
68     if (key == NULL) {
69         gpointer parray[2];
70         parray[0] = (gpointer)filename;
71         parray[1] = (gpointer)&imod;
72         db.foreach(open_internal, (gpointer)&parray);
73     } else {
74         imod = dynamic_cast<Input *>(key);
75     }
77     bool last_chance_svg = false;
78     if (key == NULL && imod == NULL) {
79         last_chance_svg = true;
80         imod = dynamic_cast<Input *>(db.get(SP_MODULE_KEY_INPUT_SVG));
81     }
83     if (imod == NULL) {
84         throw Input::no_extension_found();
85     }
87     imod->set_state(Extension::STATE_LOADED);
89     if (!imod->loaded()) {
90         throw Input::open_failed();
91     }
93     if (!imod->prefs(filename))
94         return NULL;
96     SPDocument *doc = imod->open(filename);
97     if (!doc) {
98         throw Input::open_failed();
99     }
101     if (last_chance_svg) {
102         /* We can't call sp_ui_error_dialog because we may be
103            running from the console, in which case calling sp_ui
104            routines will cause a segfault.  See bug 1000350 - bryce */
105         // sp_ui_error_dialog(_("Format autodetect failed. The file is being opened as SVG."));
106         g_warning(_("Format autodetect failed. The file is being opened as SVG."));
107     }
109     /* This kinda overkill as most of these are already set, but I want
110        to make sure for this release -- TJG */
111     doc->setModifiedSinceSave(false);
113     sp_document_set_uri(doc, filename);
115     return doc;
118 /**
119  * \return   none
120  * \brief    This is the function that searches each module to see
121  *           if it matches the filename for autodetection.
122  * \param    in_plug  The module to be tested
123  * \param    in_data  An array of pointers containing the filename, and
124  *                    the place to put a successfully found module.
125  *
126  * Basically this function only looks at input modules as it is part of the open function.  If the
127  * module is an input module, it then starts to take it apart, and the data that is passed in.
128  * Because the data being passed in is in such a weird format, there are a few casts to make it
129  * easier to use.  While it looks like a lot of local variables, they'll all get removed by the
130  * compiler.
131  *
132  * First thing that is checked is if the filename is shorter than the extension itself.  There is
133  * no way for a match in that case.  If it's long enough then there is a string compare of the end
134  * of the filename (for the length of the extension), and the extension itself.  If this passes
135  * then the pointer passed in is set to the current module.
136  */
137 static void
138 open_internal(Extension *in_plug, gpointer in_data)
140     if (!in_plug->deactivated() && dynamic_cast<Input *>(in_plug)) {
141         gpointer *parray = (gpointer *)in_data;
142         gchar const *filename = (gchar const *)parray[0];
143         Input **pimod = (Input **)parray[1];
145         // skip all the rest if we already found a function to open it
146         // since they're ordered by preference now.
147         if (!*pimod) {
148             gchar const *ext = dynamic_cast<Input *>(in_plug)->get_extension();
150             gchar *filenamelower = g_utf8_strdown(filename, -1);
151             gchar *extensionlower = g_utf8_strdown(ext, -1);
153             if (g_str_has_suffix(filenamelower, extensionlower)) {
154                 *pimod = dynamic_cast<Input *>(in_plug);
155             }
157             g_free(filenamelower);
158             g_free(extensionlower);
159         }
160     }
162     return;
165 /**
166  * \return   None
167  * \brief    This is a generic function to use the save function of
168  *           a module (including Autodetect)
169  * \param    key       Identifier of which module to use
170  * \param    doc       The document to be saved
171  * \param    filename  The file that the document should be saved to
172  * \param    official  (optional) whether to set :output_module and :modified in the
173  *                     document; is true for normal save, false for temporary saves
174  *
175  * First things first, are we looking at an autodetection?  Well if that's the case then the module
176  * needs to be found, and that is done with a database lookup through the module DB.  The foreach
177  * function is called, with the parameter being a gpointer array.  It contains both the filename
178  * (to find its extension) and where to write the module when it is found.
179  *
180  * If there is no autodetection the module database is queried with the key given.
181  *
182  * If everything is cool at this point, the module is loaded, and there is possibility for
183  * preferences.  If there is a function, then it is executed to get the dialog to be displayed.
184  * After it is finished the function continues.
185  *
186  * Lastly, the save function is called in the module itself.
187  */
188 void
189 save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, bool check_overwrite, bool official,
190     Inkscape::Extension::FileSaveMethod save_method)
192     Output *omod;
193     if (key == NULL) {
194         gpointer parray[2];
195         parray[0] = (gpointer)filename;
196         parray[1] = (gpointer)&omod;
197         omod = NULL;
198         db.foreach(save_internal, (gpointer)&parray);
200         /* This is a nasty hack, but it is required to ensure that
201            autodetect will always save with the Inkscape extensions
202            if they are available. */
203         if (omod != NULL && !strcmp(omod->get_id(), SP_MODULE_KEY_OUTPUT_SVG)) {
204             omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE));
205         }
206         /* If autodetect fails, save as Inkscape SVG */
207         if (omod == NULL) {
208             omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE));
209         }
210     } else {
211         omod = dynamic_cast<Output *>(key);
212     }
214     if (!dynamic_cast<Output *>(omod)) {
215         g_warning("Unable to find output module to handle file: %s\n", filename);
216         throw Output::no_extension_found();
217         return;
218     }
220     omod->set_state(Extension::STATE_LOADED);
221     if (!omod->loaded()) {
222         throw Output::save_failed();
223     }
225     if (!omod->prefs()) {
226         throw Output::save_cancelled();
227     }
229     gchar *fileName = NULL;
230     if (setextension) {
231         gchar *lowerfile = g_utf8_strdown(filename, -1);
232         gchar *lowerext = g_utf8_strdown(omod->get_extension(), -1);
234         if (!g_str_has_suffix(lowerfile, lowerext)) {
235             fileName = g_strdup_printf("%s%s", filename, omod->get_extension());
236         }
238         g_free(lowerfile);
239         g_free(lowerext);
240     }
242     if (fileName == NULL) {
243         fileName = g_strdup(filename);
244     }
246     if (check_overwrite && !sp_ui_overwrite_file(fileName)) {
247         g_free(fileName);
248         throw Output::no_overwrite();
249     }
251     Inkscape::XML::Node *repr = sp_document_repr_root(doc);
254     // remember attributes in case this is an unofficial save and/or overwrite fails
255     gchar *saved_uri = g_strdup(doc->uri);
256     bool saved_modified = false;
257     gchar *saved_output_extension = NULL;
258     gchar *saved_dataloss = NULL;
259     saved_modified = doc->isModifiedSinceSave();
260     saved_output_extension = g_strdup(get_file_save_extension(save_method).c_str());
261     saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss"));
262     if (official) {
263         /* The document is changing name/uri. */
264         sp_document_change_uri_and_hrefs(doc, fileName);
265     }
267     // Update attributes:
268     {
269         bool const saved = sp_document_get_undo_sensitive(doc);
270         sp_document_set_undo_sensitive(doc, false);
271         {
272             // also save the extension for next use
273             store_file_extension_in_prefs (omod->get_id(), save_method);
274             // set the "dataloss" attribute if the chosen extension is lossy
275             repr->setAttribute("inkscape:dataloss", NULL);
276             if (omod->causes_dataloss()) {
277                 repr->setAttribute("inkscape:dataloss", "true");
278             }
279         }
280         sp_document_set_undo_sensitive(doc, saved);
281         doc->setModifiedSinceSave(false);
282     }
284     try {
285         omod->save(doc, fileName);
286     }
287     catch(...) {
288         // revert attributes in case of official and overwrite
289         if(check_overwrite && official) {
290             bool const saved = sp_document_get_undo_sensitive(doc);
291             sp_document_set_undo_sensitive(doc, false);
292             {
293                 store_file_extension_in_prefs (saved_output_extension, save_method);
294                 repr->setAttribute("inkscape:dataloss", saved_dataloss);
295             }
296             sp_document_set_undo_sensitive(doc, saved);
297             sp_document_change_uri_and_hrefs(doc, saved_uri);
298         }
299         doc->setModifiedSinceSave(saved_modified);
300         // free used ressources
301         g_free(saved_output_extension);
302         g_free(saved_dataloss);
303         g_free(saved_uri);
305         g_free(fileName);
307         throw Inkscape::Extension::Output::save_failed();
308     }
310     // If it is an unofficial save, set the modified attributes back to what they were.
311     if ( !official) {
312         bool const saved = sp_document_get_undo_sensitive(doc);
313         sp_document_set_undo_sensitive(doc, false);
314         {
315             store_file_extension_in_prefs (saved_output_extension, save_method);
316             repr->setAttribute("inkscape:dataloss", saved_dataloss);
317         }
318         sp_document_set_undo_sensitive(doc, saved);
319         doc->setModifiedSinceSave(saved_modified);
321         g_free(saved_output_extension);
322         g_free(saved_dataloss);
323     }
325     g_free(fileName);
326     return;
329 /**
330  * \return   none
331  * \brief    This is the function that searches each module to see
332  *           if it matches the filename for autodetection.
333  * \param    in_plug  The module to be tested
334  * \param    in_data  An array of pointers containing the filename, and
335  *                    the place to put a successfully found module.
336  *
337  * Basically this function only looks at output modules as it is part of the open function.  If the
338  * module is an output module, it then starts to take it apart, and the data that is passed in.
339  * Because the data being passed in is in such a weird format, there are a few casts to make it
340  * easier to use.  While it looks like a lot of local variables, they'll all get removed by the
341  * compiler.
342  *
343  * First thing that is checked is if the filename is shorter than the extension itself.  There is
344  * no way for a match in that case.  If it's long enough then there is a string compare of the end
345  * of the filename (for the length of the extension), and the extension itself.  If this passes
346  * then the pointer passed in is set to the current module.
347  */
348 static void
349 save_internal(Extension *in_plug, gpointer in_data)
351     if (!in_plug->deactivated() && dynamic_cast<Output *>(in_plug)) {
352         gpointer *parray = (gpointer *)in_data;
353         gchar const *filename = (gchar const *)parray[0];
354         Output **pomod = (Output **)parray[1];
356         // skip all the rest if we already found someone to save it
357         // since they're ordered by preference now.
358         if (!*pomod) {
359             gchar const *ext = dynamic_cast<Output *>(in_plug)->get_extension();
361             gchar *filenamelower = g_utf8_strdown(filename, -1);
362             gchar *extensionlower = g_utf8_strdown(ext, -1);
364             if (g_str_has_suffix(filenamelower, extensionlower)) {
365                 *pomod = dynamic_cast<Output *>(in_plug);
366             }
368             g_free(filenamelower);
369             g_free(extensionlower);
370         }
371     }
373     return;
376 Print *
377 get_print(gchar const *key)
379     return dynamic_cast<Print *>(db.get(key));
382 /**
383  * \return   The built module
384  * \brief    Creates a module from a Inkscape::XML::Document describing the module
385  * \param    doc  The XML description of the module
386  *
387  * This function basically has two segments.  The first is that it goes through the Repr tree
388  * provided, and determines what kind of of module this is, and what kind of implementation to use.
389  * All of these are then stored in two enums that are defined in this function.  This makes it
390  * easier to add additional types (which will happen in the future, I'm sure).
391  *
392  * Second, there is case statements for these enums.  The first one is the type of module.  This is
393  * the one where the module is actually created.  After that, then the implementation is applied to
394  * get the load and unload functions.  If there is no implementation then these are not set.  This
395  * case could apply to modules that are built in (like the SVG load/save functions).
396  */
397 static Extension *
398 build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp)
400     enum {
401         MODULE_EXTENSION,
402         MODULE_XSLT,
403         /* MODULE_PLUGIN, */
404         MODULE_UNKNOWN_IMP
405     } module_implementation_type = MODULE_UNKNOWN_IMP;
406     enum {
407         MODULE_INPUT,
408         MODULE_OUTPUT,
409         MODULE_FILTER,
410         MODULE_PRINT,
411         MODULE_PATH_EFFECT,
412         MODULE_UNKNOWN_FUNC
413     } module_functional_type = MODULE_UNKNOWN_FUNC;
415     g_return_val_if_fail(doc != NULL, NULL);
417     Inkscape::XML::Node *repr = doc->root();
419     if (strcmp(repr->name(), INKSCAPE_EXTENSION_NS "inkscape-extension")) {
420         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());
421         return NULL;
422     }
424     Inkscape::XML::Node *child_repr = sp_repr_children(repr);
425     while (child_repr != NULL) {
426         char const *element_name = child_repr->name();
427         /* printf("Child: %s\n", child_repr->name()); */
428         if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "input")) {
429             module_functional_type = MODULE_INPUT;
430         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "output")) {
431             module_functional_type = MODULE_OUTPUT;
432         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "effect")) {
433             module_functional_type = MODULE_FILTER;
434         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "print")) {
435             module_functional_type = MODULE_PRINT;
436         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "path-effect")) {
437             module_functional_type = MODULE_PATH_EFFECT;
438         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "script")) {
439             module_implementation_type = MODULE_EXTENSION;
440         } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "xslt")) {
441             module_implementation_type = MODULE_XSLT;
442 #if 0
443         } else if (!strcmp(element_name, "plugin")) {
444             module_implementation_type = MODULE_PLUGIN;
445 #endif
446         }
448         //Inkscape::XML::Node *old_repr = child_repr;
449         child_repr = sp_repr_next(child_repr);
450         //Inkscape::GC::release(old_repr);
451     }
453     Implementation::Implementation *imp;
454     if (in_imp == NULL) {
455         switch (module_implementation_type) {
456             case MODULE_EXTENSION: {
457                 Implementation::Script *script = new Implementation::Script();
458                 imp = static_cast<Implementation::Implementation *>(script);
459                 break;
460             }
461             case MODULE_XSLT: {
462                 Implementation::XSLT *xslt = new Implementation::XSLT();
463                 imp = static_cast<Implementation::Implementation *>(xslt);
464                 break;
465             }
466 #if 0
467             case MODULE_PLUGIN: {
468                 Implementation::Plugin *plugin = new Implementation::Plugin();
469                 imp = static_cast<Implementation::Implementation *>(plugin);
470                 break;
471             }
472 #endif
473             default: {
474                 imp = NULL;
475                 break;
476             }
477         }
478     } else {
479         imp = in_imp;
480     }
482     Extension *module = NULL;
483     switch (module_functional_type) {
484         case MODULE_INPUT: {
485             module = new Input(repr, imp);
486             break;
487         }
488         case MODULE_OUTPUT: {
489             module = new Output(repr, imp);
490             break;
491         }
492         case MODULE_FILTER: {
493             module = new Effect(repr, imp);
494             break;
495         }
496         case MODULE_PRINT: {
497             module = new Print(repr, imp);
498             break;
499         }
500         case MODULE_PATH_EFFECT: {
501             module = new PathEffect(repr, imp);
502             break;
503         }
504         default: {
505             break;
506         }
507     }
509     return module;
512 /**
513  * \return   The module created
514  * \brief    This function creates a module from a filename of an
515  *           XML description.
516  * \param    filename  The file holding the XML description of the module.
517  *
518  * This function calls build_from_reprdoc with using sp_repr_read_file to create the reprdoc.
519  */
520 Extension *
521 build_from_file(gchar const *filename)
523     Inkscape::XML::Document *doc = sp_repr_read_file(filename, INKSCAPE_EXTENSION_URI);
524     Extension *ext = build_from_reprdoc(doc, NULL);
525     if (ext != NULL)
526         Inkscape::GC::release(doc);
527     else
528         g_warning("Unable to create extension from definition file %s.\n", filename);
529     return ext;
532 /**
533  * \return   The module created
534  * \brief    This function creates a module from a buffer holding an
535  *           XML description.
536  * \param    buffer  The buffer holding the XML description of the module.
537  *
538  * This function calls build_from_reprdoc with using sp_repr_read_mem to create the reprdoc.  It
539  * finds the length of the buffer using strlen.
540  */
541 Extension *
542 build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp)
544     Inkscape::XML::Document *doc = sp_repr_read_mem(buffer, strlen(buffer), INKSCAPE_EXTENSION_URI);
545     Extension *ext = build_from_reprdoc(doc, in_imp);
546     Inkscape::GC::release(doc);
547     return ext;
550 /*
551  * TODO: Is it guaranteed that the returned extension is valid? If so, we can remove the check for
552  * filename_extension in sp_file_save_dialog().
553  */
554 Glib::ustring
555 get_file_save_extension (Inkscape::Extension::FileSaveMethod method) {
556     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
557     Glib::ustring extension;
558     switch (method) {
559         case FILE_SAVE_METHOD_SAVE_AS:
560         case FILE_SAVE_METHOD_TEMPORARY:
561             extension = prefs->getString("/dialogs/save_as/default");
562             break;
563         case FILE_SAVE_METHOD_SAVE_COPY:
564             extension = prefs->getString("/dialogs/save_copy/default");
565             break;
566         case FILE_SAVE_METHOD_INKSCAPE_SVG:
567             extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE;
568             break;
569     }
571     // this is probably unnecessary, but just to be on the safe side ...
572     if(extension.empty())
573         extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE;
575     return extension;
578 Glib::ustring
579 get_file_save_path (SPDocument *doc, FileSaveMethod method) {
580     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
581     Glib::ustring path;
582     switch (method) {
583         case FILE_SAVE_METHOD_SAVE_AS:
584         case FILE_SAVE_METHOD_TEMPORARY:
585             path = prefs->getString("/dialogs/save_as/path");
586             break;
587         case FILE_SAVE_METHOD_SAVE_COPY:
588             path = prefs->getString("/dialogs/save_copy/path");
589             break;
590         case FILE_SAVE_METHOD_INKSCAPE_SVG:
591             if (doc->uri) {
592                 path = Glib::path_get_dirname(doc->uri);
593             } else {
594                 // FIXME: should we use the save_as path here or the current directory or even something else?
595                 path = prefs->getString("/dialogs/save_as/path");
596             }
597     }
599     // this is probably unnecessary, but just to be on the safe side ...
600     if(path.empty())
601         path = g_get_current_dir(); // is this the most sensible solution?
603     return path;
606 void
607 store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method) {
608     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
609     switch (method) {
610         case FILE_SAVE_METHOD_SAVE_AS:
611         case FILE_SAVE_METHOD_TEMPORARY:
612             prefs->setString("/dialogs/save_as/default", extension);
613             break;
614         case FILE_SAVE_METHOD_SAVE_COPY:
615             prefs->setString("/dialogs/save_copy/default", extension);
616             break;
617         case FILE_SAVE_METHOD_INKSCAPE_SVG:
618             // do nothing
619             break;
620     }
623 void
624 store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method) {
625     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
626     switch (method) {
627         case FILE_SAVE_METHOD_SAVE_AS:
628         case FILE_SAVE_METHOD_TEMPORARY:
629             prefs->setString("/dialogs/save_as/path", path);
630             break;
631         case FILE_SAVE_METHOD_SAVE_COPY:
632             prefs->setString("/dialogs/save_copy/path", path);
633             break;
634         case FILE_SAVE_METHOD_INKSCAPE_SVG:
635             // do nothing
636             break;
637     }
640 } } /* namespace Inkscape::Extension */
642 /*
643   Local Variables:
644   mode:c++
645   c-file-style:"stroustrup"
646   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
647   indent-tabs-mode:nil
648   fill-column:99
649   End:
650 */
651 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :