cf58f2733f27285224af9f974934699ca93a16c2
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;
119 }
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)
142 {
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;
166 }
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)
194 {
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;
337 }
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)
360 {
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;
384 }
386 Print *
387 get_print(gchar const *key)
388 {
389 return dynamic_cast<Print *>(db.get(key));
390 }
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)
409 {
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;
520 }
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)
532 {
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;
540 }
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)
553 {
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;
558 }
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;
585 }
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;
625 }
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 }
642 }
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 }
659 }
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 :