Code

apply patch 1498946 by zbsz (fixes #1492545 "PNG resolution value export")
[inkscape.git] / src / file.cpp
1 #define __SP_FILE_C__
3 /*
4  * File/Print operations
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Chema Celorio <chema@celorio.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *
11  * Copyright (C) 1999-2005 Authors
12  * Copyright (C) 2001-2002 Ximian, Inc.
13  * Copyright (C) 2004 David Turner
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 /**
19  * Note: This file needs to be cleaned up extensively.
20  * What it probably needs is to have one .h file for
21  * the API, and two or more .cpp files for the implementations.
22  */
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
28 #include <glib/gmem.h>
29 #include <libnr/nr-pixops.h>
31 #include "document-private.h"
32 #include "selection-chemistry.h"
33 #include "ui/view/view-widget.h"
34 #include "dir-util.h"
35 #include "helper/png-write.h"
36 #include "dialogs/export.h"
37 #include <glibmm/i18n.h>
38 #include "inkscape.h"
39 #include "desktop.h"
40 #include "selection.h"
41 #include "interface.h"
42 #include "style.h"
43 #include "print.h"
44 #include "file.h"
45 #include "message-stack.h"
46 #include "dialogs/filedialog.h"
47 #include "prefs-utils.h"
48 #include "path-prefix.h"
50 #include "sp-namedview.h"
51 #include "desktop-handles.h"
53 #include "extension/db.h"
54 #include "extension/input.h"
55 #include "extension/output.h"
56 /* #include "extension/menu.h"  */
57 #include "extension/system.h"
59 #include "io/sys.h"
60 #include "application/application.h"
61 #include "application/editor.h"
62 #include "inkscape.h"
63 #include "uri.h"
65 #ifdef WITH_INKBOARD
66 #include "jabber_whiteboard/session-manager.h"
67 #endif
69 /**
70  * 'Current' paths.  Used to remember which directory
71  * had the last file accessed.
72  * Static globals are evil.  This will be gone soon
73  * as C++ification continues
74  */
75 static gchar *import_path = NULL;
77 //#define INK_DUMP_FILENAME_CONV 1
78 #undef INK_DUMP_FILENAME_CONV
80 //#define INK_DUMP_FOPEN 1
81 #undef INK_DUMP_FOPEN
83 void dump_str(gchar const *str, gchar const *prefix);
84 void dump_ustr(Glib::ustring const &ustr);
87 /*######################
88 ## N E W
89 ######################*/
91 /**
92  * Create a blank document and add it to the desktop
93  */
94 SPDesktop*
95 sp_file_new(gchar const *templ)
96 {
97     SPDocument *doc = sp_document_new(templ, TRUE, true);
98     g_return_val_if_fail(doc != NULL, NULL);
100     SPDesktop *dt;
101     if (Inkscape::NSApplication::Application::getNewGui())
102     {
103         dt = Inkscape::NSApplication::Editor::createDesktop (doc);
104     } else {
105         SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
106         g_return_val_if_fail(dtw != NULL, NULL);
107         sp_document_unref(doc);
109         sp_create_window(dtw, TRUE);
110         dt = static_cast<SPDesktop*>(dtw->view);
111         sp_namedview_window_from_document(dt);
112     }
113     return dt;
116 SPDesktop*
117 sp_file_new_default()
119     std::list<gchar *> sources;
120     sources.push_back( profile_path("templates") ); // first try user's local dir
121     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
123     while (!sources.empty()) {
124         gchar *dirname = sources.front();
125         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
127             // TRANSLATORS: default.svg is localizable - this is the name of the default document
128             //  template. This way you can localize the default pagesize, translate the name of
129             //  the default layer, etc. If you wish to localize this file, please create a
130             //  localized share/templates/default.xx.svg file, where xx is your language code.
131             char *default_template = g_build_filename(dirname, _("default.svg"), NULL);
132             if (Inkscape::IO::file_test(default_template, G_FILE_TEST_IS_REGULAR)) {
133                 return sp_file_new(default_template);
134             }
135         }
136         g_free(dirname);
137         sources.pop_front();
138     }
140     return sp_file_new(NULL);
144 /*######################
145 ## D E L E T E
146 ######################*/
148 /**
149  *  Perform document closures preceding an exit()
150  */
151 void
152 sp_file_exit()
154     sp_ui_close_all();
155     // no need to call inkscape_exit here; last document being closed will take care of that
159 /*######################
160 ## O P E N
161 ######################*/
163 /**
164  *  Open a file, add the document to the desktop
165  *
166  *  \param replace_empty if true, and the current desktop is empty, this document
167  *  will replace the empty one.
168  */
169 bool
170 sp_file_open(gchar const *uri, Inkscape::Extension::Extension *key, bool add_to_recent, bool replace_empty)
172     SPDocument *doc;
173     try {
174         doc = Inkscape::Extension::open(key, uri);
175     } catch (Inkscape::Extension::Input::no_extension_found &e) {
176         doc = NULL;
177     } catch (Inkscape::Extension::Input::open_failed &e) {
178         doc = NULL;
179     }
181     if (doc) {
182         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
183         SPDocument *existing = desktop ? sp_desktop_document(desktop) : NULL;
185         if (existing && existing->virgin && replace_empty) {
186             // If the current desktop is empty, open the document there
187             sp_document_ensure_up_to_date (doc);
188             desktop->change_document(doc);
189             sp_document_resized_signal_emit (doc, sp_document_width(doc), sp_document_height(doc));
190         } else {
191             if (!Inkscape::NSApplication::Application::getNewGui()) {
192                 // create a whole new desktop and window
193                 SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
194                 sp_create_window(dtw, TRUE);
195                 desktop = static_cast<SPDesktop*>(dtw->view);
196             } else {
197                 desktop = Inkscape::NSApplication::Editor::createDesktop (doc);
198             }
199         }
201         doc->virgin = FALSE;
202         // everyone who cares now has a reference, get rid of ours
203         sp_document_unref(doc);
204         // resize the window to match the document properties
205         // (this may be redundant for new windows... if so, move to the "virgin"
206         //  section above)
207 #ifdef WITH_INKBOARD
208                 desktop->whiteboard_session_manager()->setDesktop(desktop);
209 #endif
210         sp_namedview_window_from_document(desktop);
212         if (add_to_recent) {
213             prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc));
214         }
216         return TRUE;
217     } else {
218         gchar *safeUri = Inkscape::IO::sanitizeString(uri);
219         gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), safeUri);
220         sp_ui_error_dialog(text);
221         g_free(text);
222         g_free(safeUri);
223         return FALSE;
224     }
227 /**
228  *  Handle prompting user for "do you want to revert"?  Revert on "OK"
229  */
230 void
231 sp_file_revert_dialog()
233     SPDesktop  *desktop = SP_ACTIVE_DESKTOP;
234     g_assert(desktop != NULL);
236     SPDocument *doc = sp_desktop_document(desktop);
237     g_assert(doc != NULL);
239     Inkscape::XML::Node     *repr = sp_document_repr_root(doc);
240     g_assert(repr != NULL);
242     gchar const *uri = doc->uri;
243     if (!uri) {
244         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved yet.  Cannot revert."));
245         return;
246     }
248     bool do_revert = true;
249     if (repr->attribute("sodipodi:modified") != NULL) {
250         gchar *text = g_strdup_printf(_("Changes will be lost!  Are you sure you want to reload document %s?"), uri);
252         bool response = desktop->warnDialog (text);
253         g_free(text);
255         if (!response) {
256             do_revert = false;
257         }
258     }
260     bool reverted;
261     if (do_revert) {
262         // Allow overwriting of current document.
263         doc->virgin = TRUE;
264         reverted = sp_file_open(uri,NULL);
265     } else {
266         reverted = false;
267     }
269     if (reverted) {
270         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document reverted."));
271     } else {
272         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not reverted."));
273     }
276 void dump_str(gchar const *str, gchar const *prefix)
278     Glib::ustring tmp;
279     tmp = prefix;
280     tmp += " [";
281     size_t const total = strlen(str);
282     for (unsigned i = 0; i < total; i++) {
283         gchar *const tmp2 = g_strdup_printf(" %02x", (0x0ff & str[i]));
284         tmp += tmp2;
285         g_free(tmp2);
286     }
288     tmp += "]";
289     g_message(tmp.c_str());
292 void dump_ustr(Glib::ustring const &ustr)
294     char const *cstr = ustr.c_str();
295     char const *data = ustr.data();
296     Glib::ustring::size_type const byteLen = ustr.bytes();
297     Glib::ustring::size_type const dataLen = ustr.length();
298     Glib::ustring::size_type const cstrLen = strlen(cstr);
300     g_message("   size: %lu\n   length: %lu\n   bytes: %lu\n    clen: %lu",
301               gulong(ustr.size()), gulong(dataLen), gulong(byteLen), gulong(cstrLen) );
302     g_message( "  ASCII? %s", (ustr.is_ascii() ? "yes":"no") );
303     g_message( "  UTF-8? %s", (ustr.validate() ? "yes":"no") );
305     try {
306         Glib::ustring tmp;
307         for (Glib::ustring::size_type i = 0; i < ustr.bytes(); i++) {
308             tmp = "    ";
309             if (i < dataLen) {
310                 Glib::ustring::value_type val = ustr.at(i);
311                 gchar* tmp2 = g_strdup_printf( (((val & 0xff00) == 0) ? "  %02x" : "%04x"), val );
312                 tmp += tmp2;
313                 g_free( tmp2 );
314             } else {
315                 tmp += "    ";
316             }
318             if (i < byteLen) {
319                 int val = (0x0ff & data[i]);
320                 gchar *tmp2 = g_strdup_printf("    %02x", val);
321                 tmp += tmp2;
322                 g_free( tmp2 );
323                 if ( val > 32 && val < 127 ) {
324                     tmp2 = g_strdup_printf( "   '%c'", (gchar)val );
325                     tmp += tmp2;
326                     g_free( tmp2 );
327                 } else {
328                     tmp += "    . ";
329                 }
330             } else {
331                 tmp += "       ";
332             }
334             if ( i < cstrLen ) {
335                 int val = (0x0ff & cstr[i]);
336                 gchar* tmp2 = g_strdup_printf("    %02x", val);
337                 tmp += tmp2;
338                 g_free(tmp2);
339                 if ( val > 32 && val < 127 ) {
340                     tmp2 = g_strdup_printf("   '%c'", (gchar) val);
341                     tmp += tmp2;
342                     g_free( tmp2 );
343                 } else {
344                     tmp += "    . ";
345                 }
346             } else {
347                 tmp += "            ";
348             }
350             g_message( tmp.c_str() );
351         }
352     } catch (...) {
353         g_message("XXXXXXXXXXXXXXXXXX Exception" );
354     }
355     g_message("---------------");
358 static Inkscape::UI::Dialogs::FileOpenDialog *openDialogInstance = NULL;
360 /**
361  *  Display an file Open selector.  Open a document if OK is pressed.
362  *  Can select single or multiple files for opening.
363  */
364 void
365 sp_file_open_dialog(gpointer object, gpointer data)
367     gchar *open_path2 = NULL;
369     gchar *open_path = g_strdup(prefs_get_string_attribute("dialogs.open", "path"));
370     if (open_path != NULL && open_path[0] == '\0') {
371         g_free(open_path);
372         open_path = NULL;
373     }
374     if (open_path && !Inkscape::IO::file_test(open_path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
375         g_free(open_path);
376         open_path = NULL;
377     }
378     if (open_path == NULL)
379         open_path = g_strconcat(g_get_home_dir(), G_DIR_SEPARATOR_S, NULL);
381     if (!openDialogInstance) {
382         openDialogInstance =
383               Inkscape::UI::Dialogs::FileOpenDialog::create(
384                  (char const *)open_path,
385                  Inkscape::UI::Dialogs::SVG_TYPES,
386                  (char const *)_("Select file to open"));
387     }
388     bool const success = openDialogInstance->show();
389     gchar *fileName = ( success
390                         ? g_strdup(openDialogInstance->getFilename())
391                         : NULL );
392     Inkscape::Extension::Extension *selection =
393             openDialogInstance->getSelectionType();
394     g_free(open_path);
396     if (!success) return;
398     // Code to check & open iff multiple files.
399     Glib::SListHandle<Glib::ustring> flist=openDialogInstance->getFilenames();
400     GSList *list=flist.data();
402     if(g_slist_length(list)>1)
403     {
404         gchar *fileName=NULL;
406         while(list!=NULL)
407         {
409 #ifdef INK_DUMP_FILENAME_CONV
410             g_message(" FileName: %s",(const char *)list->data);
411 #endif
413             fileName=(gchar *)g_strdup((gchar *)list->data);
415             if (fileName && !g_file_test(fileName,G_FILE_TEST_IS_DIR)) {
416                 gsize bytesRead = 0;
417                 gsize bytesWritten = 0;
418                 GError *error = NULL;
419 #ifdef INK_DUMP_FILENAME_CONV
420                 dump_str( fileName, "A file pre  is " );
421 #endif
422                 gchar *newFileName = g_filename_to_utf8(fileName,
423                                                 -1,
424                                                         &bytesRead,
425                                                         &bytesWritten,
426                                                         &error);
427                 if ( newFileName != NULL ) {
428                     g_free(fileName);
429                     fileName = newFileName;
430 #ifdef INK_DUMP_FILENAME_CONV
431                     dump_str( fileName, "A file post is " );
432 #endif
433                 } else {
434                     // TODO: bulia, please look over
435                     g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
436                 }
438 #ifdef INK_DUMP_FILENAME_CONV
439                 g_message("Opening File %s\n",fileName);
440 #endif
442                 sp_file_open(fileName, selection);
443                 g_free(fileName);
444             }
445             else
446             {
447                 g_message("Cannot Open Directory %s\n",fileName);
448             }
450             list=list->next;
451         }
453         return;
454     }
457     if (fileName) {
458         gsize bytesRead = 0;
459         gsize bytesWritten = 0;
460         GError *error = NULL;
461 #ifdef INK_DUMP_FILENAME_CONV
462         dump_str( fileName, "A file pre  is " );
463 #endif
464         gchar *newFileName = g_filename_to_utf8(fileName,
465                                                 -1,
466                                                 &bytesRead,
467                                                 &bytesWritten,
468                                                 &error);
469         if ( newFileName != NULL ) {
470             g_free(fileName);
471             fileName = newFileName;
472 #ifdef INK_DUMP_FILENAME_CONV
473             dump_str( fileName, "A file post is " );
474 #endif
475         } else {
476             // TODO: bulia, please look over
477             g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
478         }
481         if ( !g_utf8_validate(fileName, -1, NULL) ) {
482             // TODO: bulia, please look over
483             g_warning( "INPUT FILENAME IS NOT UTF-8" );
484         }
487         open_path = g_dirname(fileName);
488         open_path2 = g_strconcat(open_path, G_DIR_SEPARATOR_S, NULL);
489         prefs_set_string_attribute("dialogs.open", "path", open_path2);
490         g_free(open_path);
491         g_free(open_path2);
493         sp_file_open(fileName, selection);
494         g_free(fileName);
495     }
497     return;
501 /*######################
502 ## V A C U U M
503 ######################*/
505 /**
506  * Remove unreferenced defs from the defs section of the document.
507  */
510 void
511 sp_file_vacuum()
513     SPDocument *doc = SP_ACTIVE_DOCUMENT;
515     unsigned int diff = vacuum_document (doc);
517     sp_document_done(doc);
519     SPDesktop *dt = SP_ACTIVE_DESKTOP;
520     if (diff > 0) {
521         dt->messageStack()->flashF(Inkscape::NORMAL_MESSAGE,
522                 ngettext("Removed <b>%i</b> unused definition in &lt;defs&gt;.",
523                          "Removed <b>%i</b> unused definitions in &lt;defs&gt;.",
524                          diff),
525                 diff);
526     } else {
527         dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE,  _("No unused definitions in &lt;defs&gt;."));
528     }
533 /*######################
534 ## S A V E
535 ######################*/
537 /**
538  * This 'save' function called by the others below
539  */
540 static bool
541 file_save(SPDocument *doc, gchar const *uri, Inkscape::Extension::Extension *key, bool saveas)
543     if (!doc || !uri) //Safety check
544         return FALSE;
546     try {
547         Inkscape::Extension::save(key, doc, uri,
548                                   saveas && prefs_get_int_attribute("dialogs.save_as", "append_extension", 1),
549                                   saveas, TRUE); // save officially, with inkscape: attributes set
550     } catch (Inkscape::Extension::Output::no_extension_found &e) {
551         gchar *safeUri = Inkscape::IO::sanitizeString(uri);
552         gchar *text = g_strdup_printf(_("No Inkscape extension found to save document (%s).  This may have been caused by an unknown filename extension."), safeUri);
553         SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved."));
554         sp_ui_error_dialog(text);
555         g_free(text);
556         g_free(safeUri);
557         return FALSE;
558     } catch (Inkscape::Extension::Output::save_failed &e) {
559         gchar *safeUri = Inkscape::IO::sanitizeString(uri);
560         gchar *text = g_strdup_printf(_("File %s could not be saved."), safeUri);
561         SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved."));
562         sp_ui_error_dialog(text);
563         g_free(text);
564         g_free(safeUri);
565         return FALSE;
566     } catch (Inkscape::Extension::Output::no_overwrite &e) {
567         return sp_file_save_dialog(doc);
568     }
570     SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document saved."));
571     return TRUE;
574 static Inkscape::UI::Dialogs::FileSaveDialog *saveDialogInstance = NULL;
576 /**
577  *  Display a SaveAs dialog.  Save the document if OK pressed.
578  */
579 gboolean
580 sp_file_save_dialog(SPDocument *doc)
582     Inkscape::XML::Node *repr = sp_document_repr_root(doc);
583     gchar const *default_extension = NULL;
584     gchar *save_loc;
585     Inkscape::Extension::Output *extension;
586     gchar *save_path = NULL;
588     default_extension = repr->attribute("inkscape:output_extension");
589     if (default_extension == NULL) {
590         default_extension = prefs_get_string_attribute("dialogs.save_as", "default");
591     }
592     //g_warning("%s: extension name: '%s'", __FUNCTION__, default_extension);
594     if (doc->uri == NULL) {
595         int i = 1;
596         char const *filename_extension;
597         char *temp_filename;
599         extension = dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(default_extension));
600         //g_warning("%s: extension ptr: 0x%x", __FUNCTION__, (unsigned int)extension);
601         if (extension == NULL) {
602             filename_extension = ".svg";
603         } else {
604             filename_extension = extension->get_extension();
605         }
607         save_path = g_strdup(prefs_get_string_attribute("dialogs.save_as", "path"));
608         if (save_path != NULL && save_path[0] == '\0') {
609             g_free(save_path);
610             save_path = NULL;
611         }
612         if (save_path && !Inkscape::IO::file_test(save_path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
613             g_free(save_path);
614             save_path = NULL;
615         }
616         if (save_path == NULL)
617             save_path = g_strdup(g_get_home_dir());
618         temp_filename = g_strdup_printf(_("drawing%s"), filename_extension);
619         save_loc = g_build_filename(save_path, temp_filename, NULL);
620         g_free(temp_filename);
622         while (Inkscape::IO::file_test(save_loc, G_FILE_TEST_EXISTS)) {
623             g_free(save_loc);
624             temp_filename = g_strdup_printf(_("drawing-%d%s"), i++, filename_extension);
625             save_loc = g_build_filename(save_path, temp_filename, NULL);
626             g_free(temp_filename);
627         }
628     } else {
629         save_loc = g_path_get_dirname(doc->uri); /* \todo should use a getter */
630     }
632     { // convert save_loc from utf-8 to locale
633       // is this needed any more, now that everything is handled in
634       // Inkscape::IO?
635         gsize bytesRead = 0;
636         gsize bytesWritten = 0;
637         GError* error = NULL;
638 #ifdef INK_DUMP_FILENAME_CONV
639         dump_str( save_loc, "B file pre  is " );
640 #endif
641         gchar* save_loc_local = g_filename_from_utf8( save_loc, -1, &bytesRead, &bytesWritten, &error);
643         if ( save_loc_local != NULL ) {
644             g_free(save_loc);
645             save_loc = save_loc_local;
646 #ifdef INK_DUMP_FILENAME_CONV
647             dump_str( save_loc, "B file post is " );
648 #endif
649         } else {
650             //g_warning( "Error converting save filename stored in the file to locale encoding.");
651         }
652     }
654     if (!saveDialogInstance) {
655         saveDialogInstance =
656              Inkscape::UI::Dialogs::FileSaveDialog::create(
657                  (char const *) save_loc,
658                  Inkscape::UI::Dialogs::SVG_TYPES,
659                  (char const *) _("Select file to save to"),
660                  default_extension
661             );
662     } // FIXME: else (i.e. if reshowing an already shown dialog) save_loc is not used, it thus always displays the previously opened dir
663     bool success = saveDialogInstance->show();
664     char *fileName = ( success
665                        ? g_strdup(saveDialogInstance->getFilename())
666                        : NULL );
667     Inkscape::Extension::Extension *selectionType =
668         saveDialogInstance->getSelectionType();
669     g_free(save_loc);
670     g_free(save_path);
671     if (!success) {
672         return success;
673     }
675     if (fileName && *fileName) {
676         gsize bytesRead = 0;
677         gsize bytesWritten = 0;
678         GError *error = NULL;
679 #ifdef INK_DUMP_FILENAME_CONV
680         dump_str( fileName, "C file pre  is " );
681 #endif
682         gchar *newFileName = g_filename_to_utf8(fileName,
683                                                 -1,
684                                                 &bytesRead,
685                                                 &bytesWritten,
686                                                 &error);
687         if ( newFileName != NULL ) {
688             g_free(fileName);
689             fileName = newFileName;
690 #ifdef INK_DUMP_FILENAME_CONV
691             dump_str( fileName, "C file post is " );
692 #endif
693         } else {
694             g_warning( "Error converting save filename to UTF-8." );
695         }
697         if (!g_utf8_validate(fileName, -1, NULL)) {
698             // TODO: bulia, please look over
699             g_warning( "The filename is not UTF-8." );
700         }
702         success = file_save(doc, fileName, selectionType, TRUE);
704         if (success) {
705             prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc));
706         }
708         save_path = g_dirname(fileName);
709         prefs_set_string_attribute("dialogs.save_as", "path", save_path);
710         g_free(save_path);
712         g_free(fileName);
713         return success;
714     } else {
715         return FALSE;
716     }
720 /**
721  * Save a document, displaying a SaveAs dialog if necessary.
722  */
723 gboolean
724 sp_file_save_document(SPDocument *doc)
726     gboolean success = TRUE;
728     Inkscape::XML::Node *repr = sp_document_repr_root(doc);
730     gchar const *fn = repr->attribute("sodipodi:modified");
731     if (fn != NULL) {
732         if (doc->uri == NULL
733             || repr->attribute("inkscape:output_extension") == NULL)
734         {
735             return sp_file_save_dialog(doc);
736         } else {
737             fn = g_strdup(doc->uri);
738             gchar const *ext = repr->attribute("inkscape:output_extension");
739             success = file_save(doc, fn, Inkscape::Extension::db.get(ext), FALSE);
740             g_free((void *) fn);
741         }
742     } else {
743         SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No changes need to be saved."));
744         success = TRUE;
745     }
747     return success;
751 /**
752  * Save a document.
753  */
754 bool
755 sp_file_save(gpointer object, gpointer data)
757     if (!SP_ACTIVE_DOCUMENT)
758         return false;
759     sp_namedview_document_from_window(SP_ACTIVE_DESKTOP);
760     return sp_file_save_document(SP_ACTIVE_DOCUMENT);
764 /**
765  *  Save a document, always displaying the SaveAs dialog.
766  */
767 bool
768 sp_file_save_as(gpointer object, gpointer data)
770     if (!SP_ACTIVE_DOCUMENT)
771         return false;
772     sp_namedview_document_from_window(SP_ACTIVE_DESKTOP);
773     return sp_file_save_dialog(SP_ACTIVE_DOCUMENT);
779 /*######################
780 ## I M P O R T
781 ######################*/
783 /**
784  *  Import a resource.  Called by sp_file_import()
785  */
786 void
787 file_import(SPDocument *in_doc, gchar const *uri, Inkscape::Extension::Extension *key)
789     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
791     //DEBUG_MESSAGE( fileImport, "file_import( in_doc:%p uri:[%s], key:%p", in_doc, uri, key );
792     SPDocument *doc;
793     try {
794         doc = Inkscape::Extension::open(key, uri);
795     } catch (Inkscape::Extension::Input::no_extension_found &e) {
796         doc = NULL;
797     } catch (Inkscape::Extension::Input::open_failed &e) {
798         doc = NULL;
799     }
801     if (doc != NULL) {
802         // the import extension has passed us a document, now we need to embed it into our document
803         if ( 0 ) {
804 //            const gchar *docbase = (sp_repr_document_root( sp_repr_document( repr ))->attribute("sodipodi:docbase" );
805             g_message(" settings  uri  [%s]", doc->uri );
806             g_message("           base [%s]", doc->base );
807             g_message("           name [%s]", doc->name );
808             Inkscape::IO::fixupHrefs( doc, doc->base, TRUE );
809             g_message("        mid-fixup");
810             Inkscape::IO::fixupHrefs( doc, in_doc->base, TRUE );
811         }
813         // move imported defs to our document's defs
814         SPObject *in_defs = SP_DOCUMENT_DEFS(in_doc);
815         SPObject *defs = SP_DOCUMENT_DEFS(doc);
816         Inkscape::XML::Node *last_def = SP_OBJECT_REPR(in_defs)->lastChild();
817         for (SPObject *child = sp_object_first_child(defs);
818              child != NULL; child = SP_OBJECT_NEXT(child))
819         {
820             // FIXME: in case of id conflict, newly added thing will be re-ided and thus likely break a reference to it from imported stuff
821             SP_OBJECT_REPR(in_defs)->addChild(SP_OBJECT_REPR(child)->duplicate(), last_def);
822         }
824         guint items_count = 0;
825         for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc));
826              child != NULL; child = SP_OBJECT_NEXT(child)) {
827             if (SP_IS_ITEM(child))
828                 items_count ++;
829         }
830         SPCSSAttr *style = sp_css_attr_from_object (SP_DOCUMENT_ROOT (doc));
832         SPObject *new_obj = NULL;
834         if ((style && style->firstChild()) || items_count > 1) {
835             // create group
836             Inkscape::XML::Node *newgroup = sp_repr_new("svg:g");
837             sp_repr_css_set (newgroup, style, "style");
839             for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) {
840                 if (SP_IS_ITEM(child)) {
841                     Inkscape::XML::Node *newchild = SP_OBJECT_REPR(child)->duplicate();
843                     // convert layers to groups; FIXME: add "preserve layers" mode where each layer
844                     // from impot is copied to the same-named layer in host
845                     newchild->setAttribute("inkscape:groupmode", NULL);
847                     newgroup->appendChild(newchild);
848                 }
849             }
851             if (desktop) {
852                 // Add it to the current layer
853                 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
854             } else {
855                 // There's no desktop (command line run?)
856                 // FIXME: For such cases we need a document:: method to return the current layer
857                 new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newgroup);
858             }
860             Inkscape::GC::release(newgroup);
861         } else {
862             // just add one item
863             for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) {
864                 if (SP_IS_ITEM(child)) {
865                     Inkscape::XML::Node *newitem = SP_OBJECT_REPR(child)->duplicate();
866                     newitem->setAttribute("inkscape:groupmode", NULL);
868                     if (desktop) {
869                         // Add it to the current layer
870                         new_obj = desktop->currentLayer()->appendChildRepr(newitem);
871                     } else {
872                         // There's no desktop (command line run?)
873                         // FIXME: For such cases we need a document:: method to return the current layer
874                         new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newitem);
875                     }
877                 }
878             }
879         }
881         if (style) sp_repr_css_attr_unref (style);
883         // select and move the imported item
884         if (new_obj && SP_IS_ITEM(new_obj)) {
885             Inkscape::Selection *selection = sp_desktop_selection(desktop);
886             selection->set(SP_ITEM(new_obj));
888             // To move the imported object, we must temporarily set the "transform pattern with
889             // object" option.
890             {
891                 int const saved_pref = prefs_get_int_attribute("options.transform", "pattern", 1);
892                 prefs_set_int_attribute("options.transform", "pattern", 1);
893                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
894                 NR::Point m( desktop->point() - selection->bounds().midpoint() );
895                 sp_selection_move_relative(selection, m);
896                 prefs_set_int_attribute("options.transform", "pattern", saved_pref);
897             }
898         }
900         sp_document_unref(doc);
901         sp_document_done(in_doc);
903     } else {
904         gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), uri);
905         sp_ui_error_dialog(text);
906         g_free(text);
907     }
909     return;
913 static Inkscape::UI::Dialogs::FileOpenDialog *importDialogInstance = NULL;
915 /**
916  *  Display an Open dialog, import a resource if OK pressed.
917  */
918 void
919 sp_file_import(GtkWidget *widget)
921     SPDocument *doc = SP_ACTIVE_DOCUMENT;
922     if (!doc)
923         return;
925     if (!importDialogInstance) {
926         importDialogInstance =
927              Inkscape::UI::Dialogs::FileOpenDialog::create(
928                  (char const *)import_path,
929                  Inkscape::UI::Dialogs::IMPORT_TYPES,
930                  (char const *)_("Select file to import"));
931     }
932     bool success = importDialogInstance->show();
933     char *fileName = ( success
934                        ? g_strdup(importDialogInstance->getFilename())
935                        : NULL );
936     Inkscape::Extension::Extension *selection =
937         importDialogInstance->getSelectionType();
939     if (!success) return;
940     if (fileName) {
941         gsize bytesRead = 0;
942         gsize bytesWritten = 0;
943         GError *error = NULL;
944 #ifdef INK_DUMP_FILENAME_CONV
945         dump_str( fileName, "D file pre  is " );
946 #endif
947         gchar *newFileName = g_filename_to_utf8( fileName,
948                                                  -1,
949                                                  &bytesRead,
950                                                  &bytesWritten,
951                                                  &error);
952         if ( newFileName != NULL ) {
953             g_free(fileName);
954             fileName = newFileName;
955 #ifdef INK_DUMP_FILENAME_CONV
956             dump_str( fileName, "D file post is " );
957 #endif
958         } else {
959             // TODO: bulia, please look over
960             g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
961         }
964         if (!g_utf8_validate(fileName, -1, NULL)) {
965             // TODO: bulia, please look over
966             g_warning( "INPUT FILENAME IS NOT UTF-8" );
967         }
969         g_free(import_path);
970         import_path = g_dirname(fileName);
971         if (import_path) import_path = g_strconcat(import_path, G_DIR_SEPARATOR_S, NULL);
973         file_import(doc, fileName, selection);
974         g_free(fileName);
975     }
977     return;
982 /*######################
983 ## E X P O R T
984 ######################*/
986 /**
987  *
988  */
989 void
990 sp_file_export_dialog(void *widget)
992     sp_export_dialog();
995 #include <display/nr-arena-item.h>
996 #include <display/nr-arena.h>
998 struct SPEBP {
999     int width, height, sheight;
1000     guchar r, g, b, a;
1001     NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
1002     guchar *px;
1003     unsigned (*status)(float, void *);
1004     void *data;
1005 };
1008 /**
1009  *
1010  */
1011 static int
1012 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
1014     struct SPEBP *ebp = (struct SPEBP *) data;
1016     if (ebp->status) {
1017         if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
1018     }
1020     num_rows = MIN(num_rows, ebp->sheight);
1021     num_rows = MIN(num_rows, ebp->height - row);
1023     /* Set area of interest */
1024     NRRectL bbox;
1025     bbox.x0 = 0;
1026     bbox.y0 = row;
1027     bbox.x1 = ebp->width;
1028     bbox.y1 = row + num_rows;
1029     /* Update to renderable state */
1030     NRGC gc(NULL);
1031     nr_matrix_set_identity(&gc.transform);
1033     nr_arena_item_invoke_update(ebp->root, &bbox, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
1035     NRPixBlock pb;
1036     nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
1037                              bbox.x0, bbox.y0, bbox.x1, bbox.y1,
1038                              ebp->px, 4 * ebp->width, FALSE, FALSE);
1040     for (int r = 0; r < num_rows; r++) {
1041         guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
1042         for (int c = 0; c < ebp->width; c++) {
1043             *p++ = ebp->r;
1044             *p++ = ebp->g;
1045             *p++ = ebp->b;
1046             *p++ = ebp->a;
1047         }
1048     }
1050     /* Render */
1051     nr_arena_item_invoke_render(ebp->root, &bbox, &pb, 0);
1053     for (int r = 0; r < num_rows; r++) {
1054         rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
1055     }
1057     nr_pixblock_release(&pb);
1059     return num_rows;
1062 /**
1063 Hide all items which are not listed in list, recursively, skipping groups and defs
1064 */
1065 void
1066 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
1068     if (SP_IS_ITEM(o)
1069         && !SP_IS_DEFS(o)
1070         && !SP_IS_ROOT(o)
1071         && !SP_IS_GROUP(o)
1072         && !g_slist_find(list, o))
1073     {
1074         sp_item_invoke_hide(SP_ITEM(o), dkey);
1075     }
1077      // recurse
1078     if (!g_slist_find(list, o)) {
1079         for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
1080             hide_other_items_recursively(child, list, dkey);
1081         }
1082     }
1086 /**
1087  *  Render the SVG drawing onto a PNG raster image, then save to
1088  *  a file.  Returns TRUE if succeeded in writing the file,
1089  *  FALSE otherwise.
1090  */
1091 int
1092 sp_export_png_file(SPDocument *doc, gchar const *filename,
1093                    double x0, double y0, double x1, double y1,
1094                    unsigned width, unsigned height, double xdpi, double ydpi,
1095                    unsigned long bgcolor,
1096                    unsigned (*status)(float, void *),
1097                    void *data, bool force_overwrite,
1098                    GSList *items_only)
1100     int write_status = TRUE;
1101     g_return_val_if_fail(doc != NULL, FALSE);
1102     g_return_val_if_fail(filename != NULL, FALSE);
1103     g_return_val_if_fail(width >= 1, FALSE);
1104     g_return_val_if_fail(height >= 1, FALSE);
1106     if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
1107         return FALSE;
1108     }
1110     sp_document_ensure_up_to_date(doc);
1112     /* Go to document coordinates */
1113     gdouble t = y0;
1114     y0 = sp_document_height(doc) - y1;
1115     y1 = sp_document_height(doc) - t;
1117     /*
1118      * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
1119      * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
1120      * 3) a[0] * x1 + a[2] * y1 + a[4] = width
1121      * 4) a[1] * x0 + a[3] * y0 + a[5] = height
1122      * 5) a[1] = 0.0;
1123      * 6) a[2] = 0.0;
1124      *
1125      * (1,3) a[0] * x1 - a[0] * x0 = width
1126      * a[0] = width / (x1 - x0)
1127      * (2,4) a[3] * y0 - a[3] * y1 = height
1128      * a[3] = height / (y0 - y1)
1129      * (1) a[4] = -a[0] * x0
1130      * (2) a[5] = -a[3] * y1
1131      */
1133     NRMatrix affine;
1134     affine.c[0] = width / (x1 - x0);
1135     affine.c[1] = 0.0;
1136     affine.c[2] = 0.0;
1137     affine.c[3] = height / (y1 - y0);
1138     affine.c[4] = -affine.c[0] * x0;
1139     affine.c[5] = -affine.c[3] * y0;
1141     //SP_PRINT_MATRIX("SVG2PNG", &affine);
1143     struct SPEBP ebp;
1144     ebp.width  = width;
1145     ebp.height = height;
1146     ebp.r      = NR_RGBA32_R(bgcolor);
1147     ebp.g      = NR_RGBA32_G(bgcolor);
1148     ebp.b      = NR_RGBA32_B(bgcolor);
1149     ebp.a      = NR_RGBA32_A(bgcolor);
1151     /* Create new arena */
1152     NRArena *arena = NRArena::create();
1153     unsigned dkey = sp_item_display_key_new(1);
1155     /* Create ArenaItems and set transform */
1156     ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
1157     nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), NR::Matrix(&affine));
1159     // We show all and then hide all items we don't want, instead of showing only requested items,
1160     // because that would not work if the shown item references something in defs
1161     if (items_only) {
1162         hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
1163     }
1165     ebp.status = status;
1166     ebp.data   = data;
1168     if ((width < 256) || ((width * height) < 32768)) {
1169         ebp.px = nr_pixelstore_64K_new(FALSE, 0);
1170         ebp.sheight = 65536 / (4 * width);
1171         write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
1172         nr_pixelstore_64K_free(ebp.px);
1173     } else {
1174         ebp.px = g_new(guchar, 4 * 64 * width);
1175         ebp.sheight = 64;
1176         write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
1177         g_free(ebp.px);
1178     }
1180     // Hide items
1181     sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
1183     /* Free Arena and ArenaItem */
1184     nr_arena_item_unref(ebp.root);
1185     nr_object_unref((NRObject *) arena);
1186     return write_status;
1190 /*######################
1191 ## P R I N T
1192 ######################*/
1195 /**
1196  *  Print the current document, if any.
1197  */
1198 void
1199 sp_file_print()
1201     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1202     if (doc)
1203         sp_print_document(doc, FALSE);
1207 /**
1208  *  Print the current document, if any.  Do not use
1209  *  the machine's print drivers.
1210  */
1211 void
1212 sp_file_print_direct()
1214     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1215     if (doc)
1216         sp_print_document(doc, TRUE);
1220 /**
1221  * Display what the drawing would look like, if
1222  * printed.
1223  */
1224 void
1225 sp_file_print_preview(gpointer object, gpointer data)
1228     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1229     if (doc)
1230         sp_print_preview_document(doc);
1234 void Inkscape::IO::fixupHrefs( SPDocument *doc, const gchar *base, gboolean spns )
1236     //g_message("Inkscape::IO::fixupHrefs( , [%s], )", base );
1238     if ( 0 ) {
1239         gchar const* things[] = {
1240             "data:foo,bar",
1241             "http://www.google.com/image.png",
1242             "ftp://ssd.com/doo",
1243             "/foo/dee/bar.svg",
1244             "foo.svg",
1245             "file:/foo/dee/bar.svg",
1246             "file:///foo/dee/bar.svg",
1247             "file:foo.svg",
1248             "/foo/bar\xe1\x84\x92.svg",
1249             "file:///foo/bar\xe1\x84\x92.svg",
1250             "file:///foo/bar%e1%84%92.svg",
1251             "/foo/bar%e1%84%92.svg",
1252             "bar\xe1\x84\x92.svg",
1253             "bar%e1%84%92.svg",
1254             NULL
1255         };
1256         g_message("+------");
1257         for ( int i = 0; things[i]; i++ )
1258         {
1259             try
1260             {
1261                 URI uri(things[i]);
1262                 gboolean isAbs = g_path_is_absolute( things[i] );
1263                 gchar *str = uri.toString();
1264                 g_message( "abs:%d  isRel:%d  scheme:[%s]  path:[%s][%s]   uri[%s] / [%s]", (int)isAbs,
1265                            (int)uri.isRelative(),
1266                            uri.getScheme(),
1267                            uri.getPath(),
1268                            uri.getOpaque(),
1269                            things[i],
1270                            str );
1271                 g_free(str);
1272             }
1273             catch ( MalformedURIException err )
1274             {
1275                 dump_str( things[i], "MalformedURIException" );
1276                 xmlChar *redo = xmlURIEscape((xmlChar const *)things[i]);
1277                 g_message("    gone from [%s] to [%s]", things[i], redo );
1278                 if ( redo == NULL )
1279                 {
1280                     URI again = URI::fromUtf8( things[i] );
1281                     gboolean isAbs = g_path_is_absolute( things[i] );
1282                     gchar *str = again.toString();
1283                     g_message( "abs:%d  isRel:%d  scheme:[%s]  path:[%s][%s]   uri[%s] / [%s]", (int)isAbs,
1284                                (int)again.isRelative(),
1285                                again.getScheme(),
1286                                again.getPath(),
1287                                again.getOpaque(),
1288                                things[i],
1289                                str );
1290                     g_free(str);
1291                     g_message("    ----");
1292                 }
1293             }
1294         }
1295         g_message("+------");
1296     }
1298     GSList const *images = sp_document_get_resource_list(doc, "image");
1299     for (GSList const *l = images; l != NULL; l = l->next) {
1300         Inkscape::XML::Node *ir = SP_OBJECT_REPR(l->data);
1302         const gchar *href = ir->attribute("xlink:href");
1304         // First try to figure out an absolute path to the asset
1305         //g_message("image href [%s]", href );
1306         if (spns && !g_path_is_absolute(href)) {
1307             const gchar *absref = ir->attribute("sodipodi:absref");
1308             const gchar *base_href = g_build_filename(base, href, NULL);
1309             //g_message("      absr [%s]", absref );
1311             if ( absref && Inkscape::IO::file_test(absref, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_test(base_href, G_FILE_TEST_EXISTS))
1312             {
1313                 // only switch over if the absref is valid while href is not
1314                 href = absref;
1315                 //g_message("     copied absref to href");
1316             }
1317         }
1319         // Once we have an absolute path, convert it relative to the new location
1320         if (href && g_path_is_absolute(href)) {
1321             const gchar *relname = sp_relative_path_from_path(href, base);
1322             //g_message("     setting to [%s]", relname );
1323             ir->setAttribute("xlink:href", relname);
1324         }
1325 // TODO next refinement is to make the first choice keeping the relative path as-is if
1326 //      based on the new location it gives us a valid file.
1327     }
1331 /*
1332   Local Variables:
1333   mode:c++
1334   c-file-style:"stroustrup"
1335   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1336   indent-tabs-mode:nil
1337   fill-column:99
1338   End:
1339 */
1340 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :