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;
116 }
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)
139 {
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;
163 }
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)
191 {
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;
327 }
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)
350 {
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;
374 }
376 Print *
377 get_print(gchar const *key)
378 {
379 return dynamic_cast<Print *>(db.get(key));
380 }
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)
399 {
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;
510 }
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)
522 {
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;
530 }
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)
543 {
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;
548 }
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 if(extension.empty())
572 extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE;
574 return extension;
575 }
577 Glib::ustring
578 get_file_save_path (SPDocument *doc, FileSaveMethod method) {
579 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
580 Glib::ustring path;
581 switch (method) {
582 case FILE_SAVE_METHOD_SAVE_AS:
583 case FILE_SAVE_METHOD_TEMPORARY:
584 path = prefs->getString("/dialogs/save_as/path");
585 break;
586 case FILE_SAVE_METHOD_SAVE_COPY:
587 path = prefs->getString("/dialogs/save_copy/path");
588 break;
589 case FILE_SAVE_METHOD_INKSCAPE_SVG:
590 if (doc->uri) {
591 path = Glib::path_get_dirname(doc->uri);
592 } else {
593 // FIXME: should we use the save_as path here or something else? Maybe we should
594 // leave this as a choice to the user.
595 path = prefs->getString("/dialogs/save_as/path");
596 }
597 }
599 if(path.empty())
600 path = g_get_home_dir(); // Is this the most sensible solution? Note that we should avoid
601 // g_get_current_dir because this leads to problems on OS X where
602 // Inkscape opens the dialog inside application bundle when it is
603 // invoked for the first teim.
605 return path;
606 }
608 void
609 store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method) {
610 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
611 switch (method) {
612 case FILE_SAVE_METHOD_SAVE_AS:
613 case FILE_SAVE_METHOD_TEMPORARY:
614 prefs->setString("/dialogs/save_as/default", extension);
615 break;
616 case FILE_SAVE_METHOD_SAVE_COPY:
617 prefs->setString("/dialogs/save_copy/default", extension);
618 break;
619 case FILE_SAVE_METHOD_INKSCAPE_SVG:
620 // do nothing
621 break;
622 }
623 }
625 void
626 store_save_path_in_prefs (Glib::ustring path, 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/path", path);
632 break;
633 case FILE_SAVE_METHOD_SAVE_COPY:
634 prefs->setString("/dialogs/save_copy/path", path);
635 break;
636 case FILE_SAVE_METHOD_INKSCAPE_SVG:
637 // do nothing
638 break;
639 }
640 }
642 } } /* namespace Inkscape::Extension */
644 /*
645 Local Variables:
646 mode:c++
647 c-file-style:"stroustrup"
648 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
649 indent-tabs-mode:nil
650 fill-column:99
651 End:
652 */
653 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :