Code

added refcount logging to GC::Anchored and shared string printf in util
[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 <libnr/nr-pixops.h>
30 #include "document-private.h"
31 #include "selection-chemistry.h"
32 #include "ui/view/view-widget.h"
33 #include "dir-util.h"
34 #include "helper/png-write.h"
35 #include "dialogs/export.h"
36 #include <glibmm/i18n.h>
37 #include "inkscape.h"
38 #include "desktop.h"
39 #include "selection.h"
40 #include "interface.h"
41 #include "style.h"
42 #include "print.h"
43 #include "file.h"
44 #include "message-stack.h"
45 #include "dialogs/filedialog.h"
46 #include "prefs-utils.h"
47 #include "path-prefix.h"
49 #include "sp-namedview.h"
50 #include "desktop-handles.h"
52 #include "extension/db.h"
53 #include "extension/input.h"
54 #include "extension/output.h"
55 /* #include "extension/menu.h"  */
56 #include "extension/system.h"
58 #include "io/sys.h"
59 #include "application/application.h"
60 #include "application/editor.h"
61 #include "inkscape.h"
62 #include "uri.h"
64 #ifdef WITH_INKBOARD
65 #include "jabber_whiteboard/session-manager.h"
66 #endif
68 /**
69  * 'Current' paths.  Used to remember which directory
70  * had the last file accessed.
71  * Static globals are evil.  This will be gone soon
72  * as C++ification continues
73  */
74 static gchar *import_path = NULL;
76 //#define INK_DUMP_FILENAME_CONV 1
77 #undef INK_DUMP_FILENAME_CONV
79 //#define INK_DUMP_FOPEN 1
80 #undef INK_DUMP_FOPEN
82 void dump_str(gchar const *str, gchar const *prefix);
83 void dump_ustr(Glib::ustring const &ustr);
86 /*######################
87 ## N E W
88 ######################*/
90 /**
91  * Create a blank document and add it to the desktop
92  */
93 SPDesktop*
94 sp_file_new(gchar const *templ)
95 {
96     SPDocument *doc = sp_document_new(templ, TRUE, true);
97     g_return_val_if_fail(doc != NULL, NULL);
99     SPDesktop *dt;
100     if (Inkscape::NSApplication::Application::getNewGui())
101     {
102         dt = Inkscape::NSApplication::Editor::createDesktop (doc);
103     } else {
104         SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
105         g_return_val_if_fail(dtw != NULL, NULL);
106         sp_document_unref(doc);
108         sp_create_window(dtw, TRUE);
109         dt = static_cast<SPDesktop*>(dtw->view);
110         sp_namedview_window_from_document(dt);
111     }
112     return dt;
115 SPDesktop*
116 sp_file_new_default()
118     std::list<gchar *> sources;
119     sources.push_back( profile_path("templates") ); // first try user's local dir
120     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
122     while (!sources.empty()) {
123         gchar *dirname = sources.front();
124         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
126             // TRANSLATORS: default.svg is localizable - this is the name of the default document
127             //  template. This way you can localize the default pagesize, translate the name of
128             //  the default layer, etc. If you wish to localize this file, please create a
129             //  localized share/templates/default.xx.svg file, where xx is your language code.
130             char *default_template = g_build_filename(dirname, _("default.svg"), NULL);
131             if (Inkscape::IO::file_test(default_template, G_FILE_TEST_IS_REGULAR)) {
132                 return sp_file_new(default_template);
133             }
134         }
135         g_free(dirname);
136         sources.pop_front();
137     }
139     return sp_file_new(NULL);
143 /*######################
144 ## D E L E T E
145 ######################*/
147 /**
148  *  Perform document closures preceding an exit()
149  */
150 void
151 sp_file_exit()
153     sp_ui_close_all();
154     // no need to call inkscape_exit here; last document being closed will take care of that
158 /*######################
159 ## O P E N
160 ######################*/
162 /**
163  *  Open a file, add the document to the desktop
164  *
165  *  \param replace_empty if true, and the current desktop is empty, this document
166  *  will replace the empty one.
167  */
168 bool
169 sp_file_open(gchar const *uri, Inkscape::Extension::Extension *key, bool add_to_recent, bool replace_empty)
171     SPDocument *doc;
172     try {
173         doc = Inkscape::Extension::open(key, uri);
174     } catch (Inkscape::Extension::Input::no_extension_found &e) {
175         doc = NULL;
176     } catch (Inkscape::Extension::Input::open_failed &e) {
177         doc = NULL;
178     }
180     if (doc) {
181         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
182         SPDocument *existing = desktop ? sp_desktop_document(desktop) : NULL;
184         if (existing && existing->virgin && replace_empty) {
185             // If the current desktop is empty, open the document there
186             sp_document_ensure_up_to_date (doc);
187             desktop->change_document(doc);
188             sp_document_resized_signal_emit (doc, sp_document_width(doc), sp_document_height(doc));
189         } else {
190             if (!Inkscape::NSApplication::Application::getNewGui()) {
191                 // create a whole new desktop and window
192                 SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
193                 sp_create_window(dtw, TRUE);
194                 desktop = static_cast<SPDesktop*>(dtw->view);
195             } else {
196                 desktop = Inkscape::NSApplication::Editor::createDesktop (doc);
197             }
198         }
200         doc->virgin = FALSE;
201         // everyone who cares now has a reference, get rid of ours
202         sp_document_unref(doc);
203         // resize the window to match the document properties
204         // (this may be redundant for new windows... if so, move to the "virgin"
205         //  section above)
206 #ifdef WITH_INKBOARD
207                 desktop->whiteboard_session_manager()->setDesktop(desktop);
208 #endif
209         sp_namedview_window_from_document(desktop);
211         if (add_to_recent) {
212             prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc));
213         }
215         return TRUE;
216     } else {
217         gchar *safeUri = Inkscape::IO::sanitizeString(uri);
218         gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), safeUri);
219         sp_ui_error_dialog(text);
220         g_free(text);
221         g_free(safeUri);
222         return FALSE;
223     }
226 /**
227  *  Handle prompting user for "do you want to revert"?  Revert on "OK"
228  */
229 void
230 sp_file_revert_dialog()
232     SPDesktop  *desktop = SP_ACTIVE_DESKTOP;
233     g_assert(desktop != NULL);
235     SPDocument *doc = sp_desktop_document(desktop);
236     g_assert(doc != NULL);
238     Inkscape::XML::Node     *repr = sp_document_repr_root(doc);
239     g_assert(repr != NULL);
241     gchar const *uri = doc->uri;
242     if (!uri) {
243         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved yet.  Cannot revert."));
244         return;
245     }
247     bool do_revert = true;
248     if (repr->attribute("sodipodi:modified") != NULL) {
249         gchar *text = g_strdup_printf(_("Changes will be lost!  Are you sure you want to reload document %s?"), uri);
251         bool response = desktop->warnDialog (text);
252         g_free(text);
254         if (!response) {
255             do_revert = false;
256         }
257     }
259     bool reverted;
260     if (do_revert) {
261         // Allow overwriting of current document.
262         doc->virgin = TRUE;
263         reverted = sp_file_open(uri,NULL);
264     } else {
265         reverted = false;
266     }
268     if (reverted) {
269         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document reverted."));
270     } else {
271         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not reverted."));
272     }
275 void dump_str(gchar const *str, gchar const *prefix)
277     Glib::ustring tmp;
278     tmp = prefix;
279     tmp += " [";
280     size_t const total = strlen(str);
281     for (unsigned i = 0; i < total; i++) {
282         gchar *const tmp2 = g_strdup_printf(" %02x", (0x0ff & str[i]));
283         tmp += tmp2;
284         g_free(tmp2);
285     }
287     tmp += "]";
288     g_message(tmp.c_str());
291 void dump_ustr(Glib::ustring const &ustr)
293     char const *cstr = ustr.c_str();
294     char const *data = ustr.data();
295     Glib::ustring::size_type const byteLen = ustr.bytes();
296     Glib::ustring::size_type const dataLen = ustr.length();
297     Glib::ustring::size_type const cstrLen = strlen(cstr);
299     g_message("   size: %lu\n   length: %lu\n   bytes: %lu\n    clen: %lu",
300               gulong(ustr.size()), gulong(dataLen), gulong(byteLen), gulong(cstrLen) );
301     g_message( "  ASCII? %s", (ustr.is_ascii() ? "yes":"no") );
302     g_message( "  UTF-8? %s", (ustr.validate() ? "yes":"no") );
304     try {
305         Glib::ustring tmp;
306         for (Glib::ustring::size_type i = 0; i < ustr.bytes(); i++) {
307             tmp = "    ";
308             if (i < dataLen) {
309                 Glib::ustring::value_type val = ustr.at(i);
310                 gchar* tmp2 = g_strdup_printf( (((val & 0xff00) == 0) ? "  %02x" : "%04x"), val );
311                 tmp += tmp2;
312                 g_free( tmp2 );
313             } else {
314                 tmp += "    ";
315             }
317             if (i < byteLen) {
318                 int val = (0x0ff & data[i]);
319                 gchar *tmp2 = g_strdup_printf("    %02x", val);
320                 tmp += tmp2;
321                 g_free( tmp2 );
322                 if ( val > 32 && val < 127 ) {
323                     tmp2 = g_strdup_printf( "   '%c'", (gchar)val );
324                     tmp += tmp2;
325                     g_free( tmp2 );
326                 } else {
327                     tmp += "    . ";
328                 }
329             } else {
330                 tmp += "       ";
331             }
333             if ( i < cstrLen ) {
334                 int val = (0x0ff & cstr[i]);
335                 gchar* tmp2 = g_strdup_printf("    %02x", val);
336                 tmp += tmp2;
337                 g_free(tmp2);
338                 if ( val > 32 && val < 127 ) {
339                     tmp2 = g_strdup_printf("   '%c'", (gchar) val);
340                     tmp += tmp2;
341                     g_free( tmp2 );
342                 } else {
343                     tmp += "    . ";
344                 }
345             } else {
346                 tmp += "            ";
347             }
349             g_message( tmp.c_str() );
350         }
351     } catch (...) {
352         g_message("XXXXXXXXXXXXXXXXXX Exception" );
353     }
354     g_message("---------------");
357 static Inkscape::UI::Dialogs::FileOpenDialog *openDialogInstance = NULL;
359 /**
360  *  Display an file Open selector.  Open a document if OK is pressed.
361  *  Can select single or multiple files for opening.
362  */
363 void
364 sp_file_open_dialog(gpointer object, gpointer data)
366     gchar *open_path2 = NULL;
368     gchar *open_path = g_strdup(prefs_get_string_attribute("dialogs.open", "path"));
369     if (open_path != NULL && open_path[0] == '\0') {
370         g_free(open_path);
371         open_path = NULL;
372     }
373     if (open_path && !Inkscape::IO::file_test(open_path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
374         g_free(open_path);
375         open_path = NULL;
376     }
377     if (open_path == NULL)
378         open_path = g_strconcat(g_get_home_dir(), G_DIR_SEPARATOR_S, NULL);
380     if (!openDialogInstance) {
381         openDialogInstance =
382               Inkscape::UI::Dialogs::FileOpenDialog::create(
383                  (char const *)open_path,
384                  Inkscape::UI::Dialogs::SVG_TYPES,
385                  (char const *)_("Select file to open"));
386     }
387     bool const success = openDialogInstance->show();
388     gchar *fileName = ( success
389                         ? g_strdup(openDialogInstance->getFilename())
390                         : NULL );
391     Inkscape::Extension::Extension *selection =
392             openDialogInstance->getSelectionType();
393     g_free(open_path);
395     if (!success) return;
397     // Code to check & open iff multiple files.
398     Glib::SListHandle<Glib::ustring> flist=openDialogInstance->getFilenames();
399     GSList *list=flist.data();
401     if(g_slist_length(list)>1)
402     {
403         gchar *fileName=NULL;
405         while(list!=NULL)
406         {
408 #ifdef INK_DUMP_FILENAME_CONV
409             g_message(" FileName: %s",(const char *)list->data);
410 #endif
412             fileName=(gchar *)g_strdup((gchar *)list->data);
414             if (fileName && !g_file_test(fileName,G_FILE_TEST_IS_DIR)) {
415                 gsize bytesRead = 0;
416                 gsize bytesWritten = 0;
417                 GError *error = NULL;
418 #ifdef INK_DUMP_FILENAME_CONV
419                 dump_str( fileName, "A file pre  is " );
420 #endif
421                 gchar *newFileName = g_filename_to_utf8(fileName,
422                                                 -1,
423                                                         &bytesRead,
424                                                         &bytesWritten,
425                                                         &error);
426                 if ( newFileName != NULL ) {
427                     g_free(fileName);
428                     fileName = newFileName;
429 #ifdef INK_DUMP_FILENAME_CONV
430                     dump_str( fileName, "A file post is " );
431 #endif
432                 } else {
433                     // TODO: bulia, please look over
434                     g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
435                 }
437 #ifdef INK_DUMP_FILENAME_CONV
438                 g_message("Opening File %s\n",fileName);
439 #endif
441                 sp_file_open(fileName, selection);
442                 g_free(fileName);
443             }
444             else
445             {
446                 g_message("Cannot Open Directory %s\n",fileName);
447             }
449             list=list->next;
450         }
452         return;
453     }
456     if (fileName) {
457         gsize bytesRead = 0;
458         gsize bytesWritten = 0;
459         GError *error = NULL;
460 #ifdef INK_DUMP_FILENAME_CONV
461         dump_str( fileName, "A file pre  is " );
462 #endif
463         gchar *newFileName = g_filename_to_utf8(fileName,
464                                                 -1,
465                                                 &bytesRead,
466                                                 &bytesWritten,
467                                                 &error);
468         if ( newFileName != NULL ) {
469             g_free(fileName);
470             fileName = newFileName;
471 #ifdef INK_DUMP_FILENAME_CONV
472             dump_str( fileName, "A file post is " );
473 #endif
474         } else {
475             // TODO: bulia, please look over
476             g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
477         }
480         if ( !g_utf8_validate(fileName, -1, NULL) ) {
481             // TODO: bulia, please look over
482             g_warning( "INPUT FILENAME IS NOT UTF-8" );
483         }
486         open_path = g_dirname(fileName);
487         open_path2 = g_strconcat(open_path, G_DIR_SEPARATOR_S, NULL);
488         prefs_set_string_attribute("dialogs.open", "path", open_path2);
489         g_free(open_path);
490         g_free(open_path2);
492         sp_file_open(fileName, selection);
493         g_free(fileName);
494     }
496     return;
500 /*######################
501 ## V A C U U M
502 ######################*/
504 /**
505  * Remove unreferenced defs from the defs section of the document.
506  */
509 void
510 sp_file_vacuum()
512     SPDocument *doc = SP_ACTIVE_DOCUMENT;
514     unsigned int diff = vacuum_document (doc);
516     sp_document_done(doc);
518     SPDesktop *dt = SP_ACTIVE_DESKTOP;
519     if (diff > 0) {
520         dt->messageStack()->flashF(Inkscape::NORMAL_MESSAGE,
521                 ngettext("Removed <b>%i</b> unused definition in &lt;defs&gt;.",
522                          "Removed <b>%i</b> unused definitions in &lt;defs&gt;.",
523                          diff),
524                 diff);
525     } else {
526         dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE,  _("No unused definitions in &lt;defs&gt;."));
527     }
532 /*######################
533 ## S A V E
534 ######################*/
536 /**
537  * This 'save' function called by the others below
538  */
539 static bool
540 file_save(SPDocument *doc, gchar const *uri, Inkscape::Extension::Extension *key, bool saveas)
542     if (!doc || !uri) //Safety check
543         return FALSE;
545     try {
546         Inkscape::Extension::save(key, doc, uri,
547                                   saveas && prefs_get_int_attribute("dialogs.save_as", "append_extension", 1),
548                                   saveas, TRUE); // save officially, with inkscape: attributes set
549     } catch (Inkscape::Extension::Output::no_extension_found &e) {
550         gchar *safeUri = Inkscape::IO::sanitizeString(uri);
551         gchar *text = g_strdup_printf(_("No Inkscape extension found to save document (%s).  This may have been caused by an unknown filename extension."), safeUri);
552         SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved."));
553         sp_ui_error_dialog(text);
554         g_free(text);
555         g_free(safeUri);
556         return FALSE;
557     } catch (Inkscape::Extension::Output::save_failed &e) {
558         gchar *safeUri = Inkscape::IO::sanitizeString(uri);
559         gchar *text = g_strdup_printf(_("File %s could not be saved."), safeUri);
560         SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved."));
561         sp_ui_error_dialog(text);
562         g_free(text);
563         g_free(safeUri);
564         return FALSE;
565     } catch (Inkscape::Extension::Output::no_overwrite &e) {
566         return sp_file_save_dialog(doc);
567     }
569     SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document saved."));
570     return TRUE;
573 static Inkscape::UI::Dialogs::FileSaveDialog *saveDialogInstance = NULL;
575 /**
576  *  Display a SaveAs dialog.  Save the document if OK pressed.
577  */
578 gboolean
579 sp_file_save_dialog(SPDocument *doc)
581     Inkscape::XML::Node *repr = sp_document_repr_root(doc);
582     gchar const *default_extension = NULL;
583     gchar *save_loc;
584     Inkscape::Extension::Output *extension;
585     gchar *save_path = NULL;
587     default_extension = repr->attribute("inkscape:output_extension");
588     if (default_extension == NULL) {
589         default_extension = prefs_get_string_attribute("dialogs.save_as", "default");
590     }
591     //g_warning("%s: extension name: '%s'", __FUNCTION__, default_extension);
593     if (doc->uri == NULL) {
594         int i = 1;
595         char const *filename_extension;
596         char *temp_filename;
598         extension = dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(default_extension));
599         //g_warning("%s: extension ptr: 0x%x", __FUNCTION__, (unsigned int)extension);
600         if (extension == NULL) {
601             filename_extension = ".svg";
602         } else {
603             filename_extension = extension->get_extension();
604         }
606         save_path = g_strdup(prefs_get_string_attribute("dialogs.save_as", "path"));
607         if (save_path != NULL && save_path[0] == '\0') {
608             g_free(save_path);
609             save_path = NULL;
610         }
611         if (save_path && !Inkscape::IO::file_test(save_path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
612             g_free(save_path);
613             save_path = NULL;
614         }
615         if (save_path == NULL)
616             save_path = g_strdup(g_get_home_dir());
617         temp_filename = g_strdup_printf(_("drawing%s"), filename_extension);
618         save_loc = g_build_filename(save_path, temp_filename, NULL);
619         g_free(temp_filename);
621         while (Inkscape::IO::file_test(save_loc, G_FILE_TEST_EXISTS)) {
622             g_free(save_loc);
623             temp_filename = g_strdup_printf(_("drawing-%d%s"), i++, filename_extension);
624             save_loc = g_build_filename(save_path, temp_filename, NULL);
625             g_free(temp_filename);
626         }
627     } else {
628         save_loc = g_path_get_dirname(doc->uri); /* \todo should use a getter */
629     }
631     { // convert save_loc from utf-8 to locale
632       // is this needed any more, now that everything is handled in
633       // Inkscape::IO?
634         gsize bytesRead = 0;
635         gsize bytesWritten = 0;
636         GError* error = NULL;
637 #ifdef INK_DUMP_FILENAME_CONV
638         dump_str( save_loc, "B file pre  is " );
639 #endif
640         gchar* save_loc_local = g_filename_from_utf8( save_loc, -1, &bytesRead, &bytesWritten, &error);
642         if ( save_loc_local != NULL ) {
643             g_free(save_loc);
644             save_loc = save_loc_local;
645 #ifdef INK_DUMP_FILENAME_CONV
646             dump_str( save_loc, "B file post is " );
647 #endif
648         } else {
649             //g_warning( "Error converting save filename stored in the file to locale encoding.");
650         }
651     }
653     if (!saveDialogInstance) {
654         saveDialogInstance =
655              Inkscape::UI::Dialogs::FileSaveDialog::create(
656                  (char const *) save_loc,
657                  Inkscape::UI::Dialogs::SVG_TYPES,
658                  (char const *) _("Select file to save to"),
659                  default_extension
660             );
661     } // FIXME: else (i.e. if reshowing an already shown dialog) save_loc is not used, it thus always displays the previously opened dir
662     bool success = saveDialogInstance->show();
663     char *fileName = ( success
664                        ? g_strdup(saveDialogInstance->getFilename())
665                        : NULL );
666     Inkscape::Extension::Extension *selectionType =
667         saveDialogInstance->getSelectionType();
668     g_free(save_loc);
669     g_free(save_path);
670     if (!success) {
671         return success;
672     }
674     if (fileName && *fileName) {
675         gsize bytesRead = 0;
676         gsize bytesWritten = 0;
677         GError *error = NULL;
678 #ifdef INK_DUMP_FILENAME_CONV
679         dump_str( fileName, "C file pre  is " );
680 #endif
681         gchar *newFileName = g_filename_to_utf8(fileName,
682                                                 -1,
683                                                 &bytesRead,
684                                                 &bytesWritten,
685                                                 &error);
686         if ( newFileName != NULL ) {
687             g_free(fileName);
688             fileName = newFileName;
689 #ifdef INK_DUMP_FILENAME_CONV
690             dump_str( fileName, "C file post is " );
691 #endif
692         } else {
693             g_warning( "Error converting save filename to UTF-8." );
694         }
696         if (!g_utf8_validate(fileName, -1, NULL)) {
697             // TODO: bulia, please look over
698             g_warning( "The filename is not UTF-8." );
699         }
701         success = file_save(doc, fileName, selectionType, TRUE);
703         if (success) {
704             prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc));
705         }
707         save_path = g_dirname(fileName);
708         prefs_set_string_attribute("dialogs.save_as", "path", save_path);
709         g_free(save_path);
711         g_free(fileName);
712         return success;
713     } else {
714         return FALSE;
715     }
719 /**
720  * Save a document, displaying a SaveAs dialog if necessary.
721  */
722 gboolean
723 sp_file_save_document(SPDocument *doc)
725     gboolean success = TRUE;
727     Inkscape::XML::Node *repr = sp_document_repr_root(doc);
729     gchar const *fn = repr->attribute("sodipodi:modified");
730     if (fn != NULL) {
731         if (doc->uri == NULL
732             || repr->attribute("inkscape:output_extension") == NULL)
733         {
734             return sp_file_save_dialog(doc);
735         } else {
736             fn = g_strdup(doc->uri);
737             gchar const *ext = repr->attribute("inkscape:output_extension");
738             success = file_save(doc, fn, Inkscape::Extension::db.get(ext), FALSE);
739             g_free((void *) fn);
740         }
741     } else {
742         SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No changes need to be saved."));
743         success = TRUE;
744     }
746     return success;
750 /**
751  * Save a document.
752  */
753 bool
754 sp_file_save(gpointer object, gpointer data)
756     if (!SP_ACTIVE_DOCUMENT)
757         return false;
758     sp_namedview_document_from_window(SP_ACTIVE_DESKTOP);
759     return sp_file_save_document(SP_ACTIVE_DOCUMENT);
763 /**
764  *  Save a document, always displaying the SaveAs dialog.
765  */
766 bool
767 sp_file_save_as(gpointer object, gpointer data)
769     if (!SP_ACTIVE_DOCUMENT)
770         return false;
771     sp_namedview_document_from_window(SP_ACTIVE_DESKTOP);
772     return sp_file_save_dialog(SP_ACTIVE_DOCUMENT);
778 /*######################
779 ## I M P O R T
780 ######################*/
782 /**
783  *  Import a resource.  Called by sp_file_import()
784  */
785 void
786 file_import(SPDocument *in_doc, gchar const *uri, Inkscape::Extension::Extension *key)
788     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
790     //DEBUG_MESSAGE( fileImport, "file_import( in_doc:%p uri:[%s], key:%p", in_doc, uri, key );
791     SPDocument *doc;
792     try {
793         doc = Inkscape::Extension::open(key, uri);
794     } catch (Inkscape::Extension::Input::no_extension_found &e) {
795         doc = NULL;
796     } catch (Inkscape::Extension::Input::open_failed &e) {
797         doc = NULL;
798     }
800     if (doc != NULL) {
801         // the import extension has passed us a document, now we need to embed it into our document
802         if ( 0 ) {
803 //            const gchar *docbase = (sp_repr_document_root( sp_repr_document( repr ))->attribute("sodipodi:docbase" );
804             g_message(" settings  uri  [%s]", doc->uri );
805             g_message("           base [%s]", doc->base );
806             g_message("           name [%s]", doc->name );
807             Inkscape::IO::fixupHrefs( doc, doc->base, TRUE );
808             g_message("        mid-fixup");
809             Inkscape::IO::fixupHrefs( doc, in_doc->base, TRUE );
810         }
812         // move imported defs to our document's defs
813         SPObject *in_defs = SP_DOCUMENT_DEFS(in_doc);
814         SPObject *defs = SP_DOCUMENT_DEFS(doc);
815         Inkscape::XML::Node *last_def = SP_OBJECT_REPR(in_defs)->lastChild();
816         for (SPObject *child = sp_object_first_child(defs);
817              child != NULL; child = SP_OBJECT_NEXT(child))
818         {
819             // FIXME: in case of id conflict, newly added thing will be re-ided and thus likely break a reference to it from imported stuff
820             SP_OBJECT_REPR(in_defs)->addChild(SP_OBJECT_REPR(child)->duplicate(), last_def);
821         }
823         guint items_count = 0;
824         for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc));
825              child != NULL; child = SP_OBJECT_NEXT(child)) {
826             if (SP_IS_ITEM(child))
827                 items_count ++;
828         }
829         SPCSSAttr *style = sp_css_attr_from_object (SP_DOCUMENT_ROOT (doc));
831         SPObject *new_obj = NULL;
833         if ((style && style->firstChild()) || items_count > 1) {
834             // create group
835             Inkscape::XML::Node *newgroup = sp_repr_new("svg:g");
836             sp_repr_css_set (newgroup, style, "style");
838             for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) {
839                 if (SP_IS_ITEM(child)) {
840                     Inkscape::XML::Node *newchild = SP_OBJECT_REPR(child)->duplicate();
842                     // convert layers to groups; FIXME: add "preserve layers" mode where each layer
843                     // from impot is copied to the same-named layer in host
844                     newchild->setAttribute("inkscape:groupmode", NULL);
846                     newgroup->appendChild(newchild);
847                 }
848             }
850             if (desktop) {
851                 // Add it to the current layer
852                 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
853             } else {
854                 // There's no desktop (command line run?)
855                 // FIXME: For such cases we need a document:: method to return the current layer
856                 new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newgroup);
857             }
859             Inkscape::GC::release(newgroup);
860         } else {
861             // just add one item
862             for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) {
863                 if (SP_IS_ITEM(child)) {
864                     Inkscape::XML::Node *newitem = SP_OBJECT_REPR(child)->duplicate();
865                     newitem->setAttribute("inkscape:groupmode", NULL);
867                     if (desktop) {
868                         // Add it to the current layer
869                         new_obj = desktop->currentLayer()->appendChildRepr(newitem);
870                     } else {
871                         // There's no desktop (command line run?)
872                         // FIXME: For such cases we need a document:: method to return the current layer
873                         new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newitem);
874                     }
876                 }
877             }
878         }
880         if (style) sp_repr_css_attr_unref (style);
882         // select and move the imported item
883         if (new_obj && SP_IS_ITEM(new_obj)) {
884             Inkscape::Selection *selection = sp_desktop_selection(desktop);
885             selection->set(SP_ITEM(new_obj));
887             // To move the imported object, we must temporarily set the "transform pattern with
888             // object" option.
889             {
890                 int const saved_pref = prefs_get_int_attribute("options.transform", "pattern", 1);
891                 prefs_set_int_attribute("options.transform", "pattern", 1);
892                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
893                 NR::Point m( desktop->point() - selection->bounds().midpoint() );
894                 sp_selection_move_relative(selection, m);
895                 prefs_set_int_attribute("options.transform", "pattern", saved_pref);
896             }
897         }
899         sp_document_unref(doc);
900         sp_document_done(in_doc);
902     } else {
903         gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), uri);
904         sp_ui_error_dialog(text);
905         g_free(text);
906     }
908     return;
912 static Inkscape::UI::Dialogs::FileOpenDialog *importDialogInstance = NULL;
914 /**
915  *  Display an Open dialog, import a resource if OK pressed.
916  */
917 void
918 sp_file_import(GtkWidget *widget)
920     SPDocument *doc = SP_ACTIVE_DOCUMENT;
921     if (!doc)
922         return;
924     if (!importDialogInstance) {
925         importDialogInstance =
926              Inkscape::UI::Dialogs::FileOpenDialog::create(
927                  (char const *)import_path,
928                  Inkscape::UI::Dialogs::IMPORT_TYPES,
929                  (char const *)_("Select file to import"));
930     }
931     bool success = importDialogInstance->show();
932     char *fileName = ( success
933                        ? g_strdup(importDialogInstance->getFilename())
934                        : NULL );
935     Inkscape::Extension::Extension *selection =
936         importDialogInstance->getSelectionType();
938     if (!success) return;
939     if (fileName) {
940         gsize bytesRead = 0;
941         gsize bytesWritten = 0;
942         GError *error = NULL;
943 #ifdef INK_DUMP_FILENAME_CONV
944         dump_str( fileName, "D file pre  is " );
945 #endif
946         gchar *newFileName = g_filename_to_utf8( fileName,
947                                                  -1,
948                                                  &bytesRead,
949                                                  &bytesWritten,
950                                                  &error);
951         if ( newFileName != NULL ) {
952             g_free(fileName);
953             fileName = newFileName;
954 #ifdef INK_DUMP_FILENAME_CONV
955             dump_str( fileName, "D file post is " );
956 #endif
957         } else {
958             // TODO: bulia, please look over
959             g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
960         }
963         if (!g_utf8_validate(fileName, -1, NULL)) {
964             // TODO: bulia, please look over
965             g_warning( "INPUT FILENAME IS NOT UTF-8" );
966         }
968         g_free(import_path);
969         import_path = g_dirname(fileName);
970         if (import_path) import_path = g_strconcat(import_path, G_DIR_SEPARATOR_S, NULL);
972         file_import(doc, fileName, selection);
973         g_free(fileName);
974     }
976     return;
981 /*######################
982 ## E X P O R T
983 ######################*/
985 /**
986  *
987  */
988 void
989 sp_file_export_dialog(void *widget)
991     sp_export_dialog();
994 #include <display/nr-arena-item.h>
995 #include <display/nr-arena.h>
997 struct SPEBP {
998     int width, height, sheight;
999     guchar r, g, b, a;
1000     NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
1001     guchar *px;
1002     unsigned (*status)(float, void *);
1003     void *data;
1004 };
1007 /**
1008  *
1009  */
1010 static int
1011 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
1013     struct SPEBP *ebp = (struct SPEBP *) data;
1015     if (ebp->status) {
1016         if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
1017     }
1019     num_rows = MIN(num_rows, ebp->sheight);
1020     num_rows = MIN(num_rows, ebp->height - row);
1022     /* Set area of interest */
1023     NRRectL bbox;
1024     bbox.x0 = 0;
1025     bbox.y0 = row;
1026     bbox.x1 = ebp->width;
1027     bbox.y1 = row + num_rows;
1028     /* Update to renderable state */
1029     NRGC gc(NULL);
1030     nr_matrix_set_identity(&gc.transform);
1032     nr_arena_item_invoke_update(ebp->root, &bbox, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
1034     NRPixBlock pb;
1035     nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
1036                              bbox.x0, bbox.y0, bbox.x1, bbox.y1,
1037                              ebp->px, 4 * ebp->width, FALSE, FALSE);
1039     for (int r = 0; r < num_rows; r++) {
1040         guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
1041         for (int c = 0; c < ebp->width; c++) {
1042             *p++ = ebp->r;
1043             *p++ = ebp->g;
1044             *p++ = ebp->b;
1045             *p++ = ebp->a;
1046         }
1047     }
1049     /* Render */
1050     nr_arena_item_invoke_render(ebp->root, &bbox, &pb, 0);
1052     for (int r = 0; r < num_rows; r++) {
1053         rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
1054     }
1056     nr_pixblock_release(&pb);
1058     return num_rows;
1061 /**
1062 Hide all items which are not listed in list, recursively, skipping groups and defs
1063 */
1064 void
1065 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
1067     if (SP_IS_ITEM(o)
1068         && !SP_IS_DEFS(o)
1069         && !SP_IS_ROOT(o)
1070         && !SP_IS_GROUP(o)
1071         && !g_slist_find(list, o))
1072     {
1073         sp_item_invoke_hide(SP_ITEM(o), dkey);
1074     }
1076      // recurse
1077     if (!g_slist_find(list, o)) {
1078         for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
1079             hide_other_items_recursively(child, list, dkey);
1080         }
1081     }
1085 /**
1086  *  Render the SVG drawing onto a PNG raster image, then save to
1087  *  a file.  Returns TRUE if succeeded in writing the file,
1088  *  FALSE otherwise.
1089  */
1090 int
1091 sp_export_png_file(SPDocument *doc, gchar const *filename,
1092                    double x0, double y0, double x1, double y1,
1093                    unsigned width, unsigned height,
1094                    unsigned long bgcolor,
1095                    unsigned (*status)(float, void *),
1096                    void *data, bool force_overwrite,
1097                    GSList *items_only)
1099     int write_status = TRUE;
1100     g_return_val_if_fail(doc != NULL, FALSE);
1101     g_return_val_if_fail(filename != NULL, FALSE);
1102     g_return_val_if_fail(width >= 1, FALSE);
1103     g_return_val_if_fail(height >= 1, FALSE);
1105     if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
1106         return FALSE;
1107     }
1109     sp_document_ensure_up_to_date(doc);
1111     /* Go to document coordinates */
1112     gdouble t = y0;
1113     y0 = sp_document_height(doc) - y1;
1114     y1 = sp_document_height(doc) - t;
1116     /*
1117      * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
1118      * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
1119      * 3) a[0] * x1 + a[2] * y1 + a[4] = width
1120      * 4) a[1] * x0 + a[3] * y0 + a[5] = height
1121      * 5) a[1] = 0.0;
1122      * 6) a[2] = 0.0;
1123      *
1124      * (1,3) a[0] * x1 - a[0] * x0 = width
1125      * a[0] = width / (x1 - x0)
1126      * (2,4) a[3] * y0 - a[3] * y1 = height
1127      * a[3] = height / (y0 - y1)
1128      * (1) a[4] = -a[0] * x0
1129      * (2) a[5] = -a[3] * y1
1130      */
1132     NRMatrix affine;
1133     affine.c[0] = width / (x1 - x0);
1134     affine.c[1] = 0.0;
1135     affine.c[2] = 0.0;
1136     affine.c[3] = height / (y1 - y0);
1137     affine.c[4] = -affine.c[0] * x0;
1138     affine.c[5] = -affine.c[3] * y0;
1140     //SP_PRINT_MATRIX("SVG2PNG", &affine);
1142     struct SPEBP ebp;
1143     ebp.width  = width;
1144     ebp.height = height;
1145     ebp.r      = NR_RGBA32_R(bgcolor);
1146     ebp.g      = NR_RGBA32_G(bgcolor);
1147     ebp.b      = NR_RGBA32_B(bgcolor);
1148     ebp.a      = NR_RGBA32_A(bgcolor);
1150     /* Create new arena */
1151     NRArena *arena = NRArena::create();
1152     unsigned dkey = sp_item_display_key_new(1);
1154     /* Create ArenaItems and set transform */
1155     ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
1156     nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), NR::Matrix(&affine));
1158     // We show all and then hide all items we don't want, instead of showing only requested items,
1159     // because that would not work if the shown item references something in defs
1160     if (items_only) {
1161         hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
1162     }
1164     ebp.status = status;
1165     ebp.data   = data;
1167     if ((width < 256) || ((width * height) < 32768)) {
1168         ebp.px = nr_pixelstore_64K_new(FALSE, 0);
1169         ebp.sheight = 65536 / (4 * width);
1170         write_status = sp_png_write_rgba_striped(filename, width, height, sp_export_get_rows, &ebp);
1171         nr_pixelstore_64K_free(ebp.px);
1172     } else {
1173         ebp.px = nr_new(guchar, 4 * 64 * width);
1174         ebp.sheight = 64;
1175         write_status = sp_png_write_rgba_striped(filename, width, height, sp_export_get_rows, &ebp);
1176         nr_free(ebp.px);
1177     }
1179     // Hide items
1180     sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
1182     /* Free Arena and ArenaItem */
1183     nr_arena_item_unref(ebp.root);
1184     nr_object_unref((NRObject *) arena);
1185     return write_status;
1189 /*######################
1190 ## P R I N T
1191 ######################*/
1194 /**
1195  *  Print the current document, if any.
1196  */
1197 void
1198 sp_file_print()
1200     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1201     if (doc)
1202         sp_print_document(doc, FALSE);
1206 /**
1207  *  Print the current document, if any.  Do not use
1208  *  the machine's print drivers.
1209  */
1210 void
1211 sp_file_print_direct()
1213     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1214     if (doc)
1215         sp_print_document(doc, TRUE);
1219 /**
1220  * Display what the drawing would look like, if
1221  * printed.
1222  */
1223 void
1224 sp_file_print_preview(gpointer object, gpointer data)
1227     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1228     if (doc)
1229         sp_print_preview_document(doc);
1233 void Inkscape::IO::fixupHrefs( SPDocument *doc, const gchar *base, gboolean spns )
1235     //g_message("Inkscape::IO::fixupHrefs( , [%s], )", base );
1237     if ( 0 ) {
1238         gchar const* things[] = {
1239             "data:foo,bar",
1240             "http://www.google.com/image.png",
1241             "ftp://ssd.com/doo",
1242             "/foo/dee/bar.svg",
1243             "foo.svg",
1244             "file:/foo/dee/bar.svg",
1245             "file:///foo/dee/bar.svg",
1246             "file:foo.svg",
1247             "/foo/bar\xe1\x84\x92.svg",
1248             "file:///foo/bar\xe1\x84\x92.svg",
1249             "file:///foo/bar%e1%84%92.svg",
1250             "/foo/bar%e1%84%92.svg",
1251             "bar\xe1\x84\x92.svg",
1252             "bar%e1%84%92.svg",
1253             NULL
1254         };
1255         g_message("+------");
1256         for ( int i = 0; things[i]; i++ )
1257         {
1258             try
1259             {
1260                 URI uri(things[i]);
1261                 gboolean isAbs = g_path_is_absolute( things[i] );
1262                 gchar *str = uri.toString();
1263                 g_message( "abs:%d  isRel:%d  scheme:[%s]  path:[%s][%s]   uri[%s] / [%s]", (int)isAbs,
1264                            (int)uri.isRelative(),
1265                            uri.getScheme(),
1266                            uri.getPath(),
1267                            uri.getOpaque(),
1268                            things[i],
1269                            str );
1270                 g_free(str);
1271             }
1272             catch ( MalformedURIException err )
1273             {
1274                 dump_str( things[i], "MalformedURIException" );
1275                 xmlChar *redo = xmlURIEscape((xmlChar const *)things[i]);
1276                 g_message("    gone from [%s] to [%s]", things[i], redo );
1277                 if ( redo == NULL )
1278                 {
1279                     URI again = URI::fromUtf8( things[i] );
1280                     gboolean isAbs = g_path_is_absolute( things[i] );
1281                     gchar *str = again.toString();
1282                     g_message( "abs:%d  isRel:%d  scheme:[%s]  path:[%s][%s]   uri[%s] / [%s]", (int)isAbs,
1283                                (int)again.isRelative(),
1284                                again.getScheme(),
1285                                again.getPath(),
1286                                again.getOpaque(),
1287                                things[i],
1288                                str );
1289                     g_free(str);
1290                     g_message("    ----");
1291                 }
1292             }
1293         }
1294         g_message("+------");
1295     }
1297     GSList const *images = sp_document_get_resource_list(doc, "image");
1298     for (GSList const *l = images; l != NULL; l = l->next) {
1299         Inkscape::XML::Node *ir = SP_OBJECT_REPR(l->data);
1301         const gchar *href = ir->attribute("xlink:href");
1303         // First try to figure out an absolute path to the asset
1304         //g_message("image href [%s]", href );
1305         if (spns && !g_path_is_absolute(href)) {
1306             const gchar *absref = ir->attribute("sodipodi:absref");
1307             const gchar *base_href = g_build_filename(base, href, NULL);
1308             //g_message("      absr [%s]", absref );
1310             if ( absref && Inkscape::IO::file_test(absref, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_test(base_href, G_FILE_TEST_EXISTS))
1311             {
1312                 // only switch over if the absref is valid while href is not
1313                 href = absref;
1314                 //g_message("     copied absref to href");
1315             }
1316         }
1318         // Once we have an absolute path, convert it relative to the new location
1319         if (href && g_path_is_absolute(href)) {
1320             const gchar *relname = sp_relative_path_from_path(href, base);
1321             //g_message("     setting to [%s]", relname );
1322             ir->setAttribute("xlink:href", relname);
1323         }
1324 // TODO next refinement is to make the first choice keeping the relative path as-is if
1325 //      based on the new location it gives us a valid file.
1326     }
1330 /*
1331   Local Variables:
1332   mode:c++
1333   c-file-style:"stroustrup"
1334   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1335   indent-tabs-mode:nil
1336   fill-column:99
1337   End:
1338 */
1339 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :