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