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;
113 }
115 SPDesktop*
116 sp_file_new_default()
117 {
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);
140 }
143 /*######################
144 ## D E L E T E
145 ######################*/
147 /**
148 * Perform document closures preceding an exit()
149 */
150 void
151 sp_file_exit()
152 {
153 sp_ui_close_all();
154 // no need to call inkscape_exit here; last document being closed will take care of that
155 }
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)
170 {
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_DT_DOCUMENT(desktop) : NULL;
184 if (existing && existing->virgin && replace_empty) {
185 // If the current desktop is empty, open the document there
186 desktop->change_document(doc);
187 } else {
188 if (!Inkscape::NSApplication::Application::getNewGui()) {
189 // create a whole new desktop and window
190 SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
191 sp_create_window(dtw, TRUE);
192 desktop = static_cast<SPDesktop*>(dtw->view);
193 } else {
194 desktop = Inkscape::NSApplication::Editor::createDesktop (doc);
195 }
196 }
198 doc->virgin = FALSE;
199 // everyone who cares now has a reference, get rid of ours
200 sp_document_unref(doc);
201 // resize the window to match the document properties
202 // (this may be redundant for new windows... if so, move to the "virgin"
203 // section above)
204 #ifdef WITH_INKBOARD
205 desktop->whiteboard_session_manager()->setDesktop(desktop);
206 #endif
207 sp_namedview_window_from_document(desktop);
209 if (add_to_recent) {
210 prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc));
211 }
213 return TRUE;
214 } else {
215 gchar *safeUri = Inkscape::IO::sanitizeString(uri);
216 gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), safeUri);
217 sp_ui_error_dialog(text);
218 g_free(text);
219 g_free(safeUri);
220 return FALSE;
221 }
222 }
224 /**
225 * Handle prompting user for "do you want to revert"? Revert on "OK"
226 */
227 void
228 sp_file_revert_dialog()
229 {
230 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
231 g_assert(desktop != NULL);
233 SPDocument *doc = SP_DT_DOCUMENT(desktop);
234 g_assert(doc != NULL);
236 Inkscape::XML::Node *repr = sp_document_repr_root(doc);
237 g_assert(repr != NULL);
239 gchar const *uri = doc->uri;
240 if (!uri) {
241 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved yet. Cannot revert."));
242 return;
243 }
245 bool do_revert = true;
246 if (repr->attribute("sodipodi:modified") != NULL) {
247 gchar *text = g_strdup_printf(_("Changes will be lost! Are you sure you want to reload document %s?"), uri);
249 bool response = desktop->warnDialog (text);
250 g_free(text);
252 if (!response) {
253 do_revert = false;
254 }
255 }
257 bool reverted;
258 if (do_revert) {
259 // Allow overwriting of current document.
260 doc->virgin = TRUE;
261 reverted = sp_file_open(uri,NULL);
262 } else {
263 reverted = false;
264 }
266 if (reverted) {
267 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document reverted."));
268 } else {
269 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not reverted."));
270 }
271 }
273 void dump_str(gchar const *str, gchar const *prefix)
274 {
275 Glib::ustring tmp;
276 tmp = prefix;
277 tmp += " [";
278 size_t const total = strlen(str);
279 for (unsigned i = 0; i < total; i++) {
280 gchar *const tmp2 = g_strdup_printf(" %02x", (0x0ff & str[i]));
281 tmp += tmp2;
282 g_free(tmp2);
283 }
285 tmp += "]";
286 g_message(tmp.c_str());
287 }
289 void dump_ustr(Glib::ustring const &ustr)
290 {
291 char const *cstr = ustr.c_str();
292 char const *data = ustr.data();
293 Glib::ustring::size_type const byteLen = ustr.bytes();
294 Glib::ustring::size_type const dataLen = ustr.length();
295 Glib::ustring::size_type const cstrLen = strlen(cstr);
297 g_message(" size: %lu\n length: %lu\n bytes: %lu\n clen: %lu",
298 gulong(ustr.size()), gulong(dataLen), gulong(byteLen), gulong(cstrLen) );
299 g_message( " ASCII? %s", (ustr.is_ascii() ? "yes":"no") );
300 g_message( " UTF-8? %s", (ustr.validate() ? "yes":"no") );
302 try {
303 Glib::ustring tmp;
304 for (Glib::ustring::size_type i = 0; i < ustr.bytes(); i++) {
305 tmp = " ";
306 if (i < dataLen) {
307 Glib::ustring::value_type val = ustr.at(i);
308 gchar* tmp2 = g_strdup_printf( (((val & 0xff00) == 0) ? " %02x" : "%04x"), val );
309 tmp += tmp2;
310 g_free( tmp2 );
311 } else {
312 tmp += " ";
313 }
315 if (i < byteLen) {
316 int val = (0x0ff & data[i]);
317 gchar *tmp2 = g_strdup_printf(" %02x", val);
318 tmp += tmp2;
319 g_free( tmp2 );
320 if ( val > 32 && val < 127 ) {
321 tmp2 = g_strdup_printf( " '%c'", (gchar)val );
322 tmp += tmp2;
323 g_free( tmp2 );
324 } else {
325 tmp += " . ";
326 }
327 } else {
328 tmp += " ";
329 }
331 if ( i < cstrLen ) {
332 int val = (0x0ff & cstr[i]);
333 gchar* tmp2 = g_strdup_printf(" %02x", val);
334 tmp += tmp2;
335 g_free(tmp2);
336 if ( val > 32 && val < 127 ) {
337 tmp2 = g_strdup_printf(" '%c'", (gchar) val);
338 tmp += tmp2;
339 g_free( tmp2 );
340 } else {
341 tmp += " . ";
342 }
343 } else {
344 tmp += " ";
345 }
347 g_message( tmp.c_str() );
348 }
349 } catch (...) {
350 g_message("XXXXXXXXXXXXXXXXXX Exception" );
351 }
352 g_message("---------------");
353 }
355 static Inkscape::UI::Dialogs::FileOpenDialog *openDialogInstance = NULL;
357 /**
358 * Display an file Open selector. Open a document if OK is pressed.
359 * Can select single or multiple files for opening.
360 */
361 void
362 sp_file_open_dialog(gpointer object, gpointer data)
363 {
364 gchar *open_path2 = NULL;
366 gchar *open_path = g_strdup(prefs_get_string_attribute("dialogs.open", "path"));
367 if (open_path != NULL && open_path[0] == '\0') {
368 g_free(open_path);
369 open_path = NULL;
370 }
371 if (open_path && !Inkscape::IO::file_test(open_path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
372 g_free(open_path);
373 open_path = NULL;
374 }
375 if (open_path == NULL)
376 open_path = g_strconcat(g_get_home_dir(), G_DIR_SEPARATOR_S, NULL);
378 if (!openDialogInstance) {
379 openDialogInstance =
380 Inkscape::UI::Dialogs::FileOpenDialog::create(
381 (char const *)open_path,
382 Inkscape::UI::Dialogs::SVG_TYPES,
383 (char const *)_("Select file to open"));
384 }
385 bool const success = openDialogInstance->show();
386 gchar *fileName = ( success
387 ? g_strdup(openDialogInstance->getFilename())
388 : NULL );
389 Inkscape::Extension::Extension *selection =
390 openDialogInstance->getSelectionType();
391 g_free(open_path);
393 if (!success) return;
395 // Code to check & open iff multiple files.
396 Glib::SListHandle<Glib::ustring> flist=openDialogInstance->getFilenames();
397 GSList *list=flist.data();
399 if(g_slist_length(list)>1)
400 {
401 gchar *fileName=NULL;
403 while(list!=NULL)
404 {
406 #ifdef INK_DUMP_FILENAME_CONV
407 g_message(" FileName: %s",(const char *)list->data);
408 #endif
410 fileName=(gchar *)g_strdup((gchar *)list->data);
412 if (fileName && !g_file_test(fileName,G_FILE_TEST_IS_DIR)) {
413 gsize bytesRead = 0;
414 gsize bytesWritten = 0;
415 GError *error = NULL;
416 #ifdef INK_DUMP_FILENAME_CONV
417 dump_str( fileName, "A file pre is " );
418 #endif
419 gchar *newFileName = g_filename_to_utf8(fileName,
420 -1,
421 &bytesRead,
422 &bytesWritten,
423 &error);
424 if ( newFileName != NULL ) {
425 g_free(fileName);
426 fileName = newFileName;
427 #ifdef INK_DUMP_FILENAME_CONV
428 dump_str( fileName, "A file post is " );
429 #endif
430 } else {
431 // TODO: bulia, please look over
432 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
433 }
435 #ifdef INK_DUMP_FILENAME_CONV
436 g_message("Opening File %s\n",fileName);
437 #endif
439 sp_file_open(fileName, selection);
440 g_free(fileName);
441 }
442 else
443 {
444 g_message("Cannot Open Directory %s\n",fileName);
445 }
447 list=list->next;
448 }
450 return;
451 }
454 if (fileName) {
455 gsize bytesRead = 0;
456 gsize bytesWritten = 0;
457 GError *error = NULL;
458 #ifdef INK_DUMP_FILENAME_CONV
459 dump_str( fileName, "A file pre is " );
460 #endif
461 gchar *newFileName = g_filename_to_utf8(fileName,
462 -1,
463 &bytesRead,
464 &bytesWritten,
465 &error);
466 if ( newFileName != NULL ) {
467 g_free(fileName);
468 fileName = newFileName;
469 #ifdef INK_DUMP_FILENAME_CONV
470 dump_str( fileName, "A file post is " );
471 #endif
472 } else {
473 // TODO: bulia, please look over
474 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
475 }
478 if ( !g_utf8_validate(fileName, -1, NULL) ) {
479 // TODO: bulia, please look over
480 g_warning( "INPUT FILENAME IS NOT UTF-8" );
481 }
484 open_path = g_dirname(fileName);
485 open_path2 = g_strconcat(open_path, G_DIR_SEPARATOR_S, NULL);
486 prefs_set_string_attribute("dialogs.open", "path", open_path2);
487 g_free(open_path);
488 g_free(open_path2);
490 sp_file_open(fileName, selection);
491 g_free(fileName);
492 }
494 return;
495 }
498 /*######################
499 ## V A C U U M
500 ######################*/
502 /**
503 * Remove unreferenced defs from the defs section of the document.
504 */
507 void
508 sp_file_vacuum()
509 {
510 SPDocument *doc = SP_ACTIVE_DOCUMENT;
512 unsigned int diff = vacuum_document (doc);
514 sp_document_done(doc);
516 SPDesktop *dt = SP_ACTIVE_DESKTOP;
517 if (diff > 0) {
518 dt->messageStack()->flashF(Inkscape::NORMAL_MESSAGE,
519 ngettext("Removed <b>%i</b> unused definition in <defs>.",
520 "Removed <b>%i</b> unused definitions in <defs>.",
521 diff),
522 diff);
523 } else {
524 dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No unused definitions in <defs>."));
525 }
526 }
530 /*######################
531 ## S A V E
532 ######################*/
534 /**
535 * This 'save' function called by the others below
536 */
537 static bool
538 file_save(SPDocument *doc, gchar const *uri, Inkscape::Extension::Extension *key, bool saveas)
539 {
540 if (!doc || !uri) //Safety check
541 return FALSE;
543 try {
544 Inkscape::Extension::save(key, doc, uri,
545 saveas && prefs_get_int_attribute("dialogs.save_as", "append_extension", 1),
546 saveas, TRUE); // save officially, with inkscape: attributes set
547 } catch (Inkscape::Extension::Output::no_extension_found &e) {
548 gchar *safeUri = Inkscape::IO::sanitizeString(uri);
549 gchar *text = g_strdup_printf(_("No Inkscape extension found to save document (%s). This may have been caused by an unknown filename extension."), safeUri);
550 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved."));
551 sp_ui_error_dialog(text);
552 g_free(text);
553 g_free(safeUri);
554 return FALSE;
555 } catch (Inkscape::Extension::Output::save_failed &e) {
556 gchar *safeUri = Inkscape::IO::sanitizeString(uri);
557 gchar *text = g_strdup_printf(_("File %s could not be saved."), safeUri);
558 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved."));
559 sp_ui_error_dialog(text);
560 g_free(text);
561 g_free(safeUri);
562 return FALSE;
563 } catch (Inkscape::Extension::Output::no_overwrite &e) {
564 return sp_file_save_dialog(doc);
565 }
567 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document saved."));
568 return TRUE;
569 }
571 static Inkscape::UI::Dialogs::FileSaveDialog *saveDialogInstance = NULL;
573 /**
574 * Display a SaveAs dialog. Save the document if OK pressed.
575 */
576 gboolean
577 sp_file_save_dialog(SPDocument *doc)
578 {
579 Inkscape::XML::Node *repr = sp_document_repr_root(doc);
580 gchar const *default_extension = NULL;
581 gchar *save_loc;
582 Inkscape::Extension::Output *extension;
583 gchar *save_path = NULL;
585 default_extension = repr->attribute("inkscape:output_extension");
586 if (default_extension == NULL) {
587 default_extension = prefs_get_string_attribute("dialogs.save_as", "default");
588 }
589 //g_warning("%s: extension name: '%s'", __FUNCTION__, default_extension);
591 if (doc->uri == NULL) {
592 int i = 1;
593 char const *filename_extension;
594 char *temp_filename;
596 extension = dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(default_extension));
597 //g_warning("%s: extension ptr: 0x%x", __FUNCTION__, (unsigned int)extension);
598 if (extension == NULL) {
599 filename_extension = ".svg";
600 } else {
601 filename_extension = extension->get_extension();
602 }
604 save_path = g_strdup(prefs_get_string_attribute("dialogs.save_as", "path"));
605 if (save_path != NULL && save_path[0] == '\0') {
606 g_free(save_path);
607 save_path = NULL;
608 }
609 if (save_path && !Inkscape::IO::file_test(save_path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
610 g_free(save_path);
611 save_path = NULL;
612 }
613 if (save_path == NULL)
614 save_path = g_strdup(g_get_home_dir());
615 temp_filename = g_strdup_printf(_("drawing%s"), filename_extension);
616 save_loc = g_build_filename(save_path, temp_filename, NULL);
617 g_free(temp_filename);
619 while (Inkscape::IO::file_test(save_loc, G_FILE_TEST_EXISTS)) {
620 g_free(save_loc);
621 temp_filename = g_strdup_printf(_("drawing-%d%s"), i++, filename_extension);
622 save_loc = g_build_filename(save_path, temp_filename, NULL);
623 g_free(temp_filename);
624 }
625 } else {
626 save_loc = g_path_get_dirname(doc->uri); /* \todo should use a getter */
627 }
629 { // convert save_loc from utf-8 to locale
630 // is this needed any more, now that everything is handled in
631 // Inkscape::IO?
632 gsize bytesRead = 0;
633 gsize bytesWritten = 0;
634 GError* error = NULL;
635 #ifdef INK_DUMP_FILENAME_CONV
636 dump_str( save_loc, "B file pre is " );
637 #endif
638 gchar* save_loc_local = g_filename_from_utf8( save_loc, -1, &bytesRead, &bytesWritten, &error);
640 if ( save_loc_local != NULL ) {
641 g_free(save_loc);
642 save_loc = save_loc_local;
643 #ifdef INK_DUMP_FILENAME_CONV
644 dump_str( save_loc, "B file post is " );
645 #endif
646 } else {
647 //g_warning( "Error converting save filename stored in the file to locale encoding.");
648 }
649 }
651 if (!saveDialogInstance) {
652 saveDialogInstance =
653 Inkscape::UI::Dialogs::FileSaveDialog::create(
654 (char const *) save_loc,
655 Inkscape::UI::Dialogs::SVG_TYPES,
656 (char const *) _("Select file to save to"),
657 default_extension
658 );
659 } // FIXME: else (i.e. if reshowing an already shown dialog) save_loc is not used, it thus always displays the previously opened dir
660 bool success = saveDialogInstance->show();
661 char *fileName = ( success
662 ? g_strdup(saveDialogInstance->getFilename())
663 : NULL );
664 Inkscape::Extension::Extension *selectionType =
665 saveDialogInstance->getSelectionType();
666 g_free(save_loc);
667 g_free(save_path);
668 if (!success) {
669 return success;
670 }
672 if (fileName && *fileName) {
673 gsize bytesRead = 0;
674 gsize bytesWritten = 0;
675 GError *error = NULL;
676 #ifdef INK_DUMP_FILENAME_CONV
677 dump_str( fileName, "C file pre is " );
678 #endif
679 gchar *newFileName = g_filename_to_utf8(fileName,
680 -1,
681 &bytesRead,
682 &bytesWritten,
683 &error);
684 if ( newFileName != NULL ) {
685 g_free(fileName);
686 fileName = newFileName;
687 #ifdef INK_DUMP_FILENAME_CONV
688 dump_str( fileName, "C file post is " );
689 #endif
690 } else {
691 g_warning( "Error converting save filename to UTF-8." );
692 }
694 if (!g_utf8_validate(fileName, -1, NULL)) {
695 // TODO: bulia, please look over
696 g_warning( "The filename is not UTF-8." );
697 }
699 success = file_save(doc, fileName, selectionType, TRUE);
701 if (success) {
702 prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc));
703 }
705 save_path = g_dirname(fileName);
706 prefs_set_string_attribute("dialogs.save_as", "path", save_path);
707 g_free(save_path);
709 g_free(fileName);
710 return success;
711 } else {
712 return FALSE;
713 }
714 }
717 /**
718 * Save a document, displaying a SaveAs dialog if necessary.
719 */
720 gboolean
721 sp_file_save_document(SPDocument *doc)
722 {
723 gboolean success = TRUE;
725 Inkscape::XML::Node *repr = sp_document_repr_root(doc);
727 gchar const *fn = repr->attribute("sodipodi:modified");
728 if (fn != NULL) {
729 if (doc->uri == NULL
730 || repr->attribute("inkscape:output_extension") == NULL)
731 {
732 return sp_file_save_dialog(doc);
733 } else {
734 fn = g_strdup(doc->uri);
735 gchar const *ext = repr->attribute("inkscape:output_extension");
736 success = file_save(doc, fn, Inkscape::Extension::db.get(ext), FALSE);
737 g_free((void *) fn);
738 }
739 } else {
740 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No changes need to be saved."));
741 success = TRUE;
742 }
744 return success;
745 }
748 /**
749 * Save a document.
750 */
751 bool
752 sp_file_save(gpointer object, gpointer data)
753 {
754 if (!SP_ACTIVE_DOCUMENT)
755 return false;
756 sp_namedview_document_from_window(SP_ACTIVE_DESKTOP);
757 return sp_file_save_document(SP_ACTIVE_DOCUMENT);
758 }
761 /**
762 * Save a document, always displaying the SaveAs dialog.
763 */
764 bool
765 sp_file_save_as(gpointer object, gpointer data)
766 {
767 if (!SP_ACTIVE_DOCUMENT)
768 return false;
769 sp_namedview_document_from_window(SP_ACTIVE_DESKTOP);
770 return sp_file_save_dialog(SP_ACTIVE_DOCUMENT);
771 }
776 /*######################
777 ## I M P O R T
778 ######################*/
780 /**
781 * Import a resource. Called by sp_file_import()
782 */
783 void
784 file_import(SPDocument *in_doc, gchar const *uri, Inkscape::Extension::Extension *key)
785 {
786 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
788 //DEBUG_MESSAGE( fileImport, "file_import( in_doc:%p uri:[%s], key:%p", in_doc, uri, key );
789 SPDocument *doc;
790 try {
791 doc = Inkscape::Extension::open(key, uri);
792 } catch (Inkscape::Extension::Input::no_extension_found &e) {
793 doc = NULL;
794 } catch (Inkscape::Extension::Input::open_failed &e) {
795 doc = NULL;
796 }
798 if (doc != NULL) {
799 // the import extension has passed us a document, now we need to embed it into our document
800 if ( 0 ) {
801 // const gchar *docbase = (sp_repr_document_root( sp_repr_document( repr ))->attribute("sodipodi:docbase" );
802 g_message(" settings uri [%s]", doc->uri );
803 g_message(" base [%s]", doc->base );
804 g_message(" name [%s]", doc->name );
805 Inkscape::IO::fixupHrefs( doc, doc->base, TRUE );
806 g_message(" mid-fixup");
807 Inkscape::IO::fixupHrefs( doc, in_doc->base, TRUE );
808 }
810 // move imported defs to our document's defs
811 SPObject *in_defs = SP_DOCUMENT_DEFS(in_doc);
812 SPObject *defs = SP_DOCUMENT_DEFS(doc);
813 Inkscape::XML::Node *last_def = SP_OBJECT_REPR(in_defs)->lastChild();
814 for (SPObject *child = sp_object_first_child(defs);
815 child != NULL; child = SP_OBJECT_NEXT(child))
816 {
817 // FIXME: in case of id conflict, newly added thing will be re-ided and thus likely break a reference to it from imported stuff
818 SP_OBJECT_REPR(in_defs)->addChild(SP_OBJECT_REPR(child)->duplicate(), last_def);
819 }
821 guint items_count = 0;
822 for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc));
823 child != NULL; child = SP_OBJECT_NEXT(child)) {
824 if (SP_IS_ITEM(child))
825 items_count ++;
826 }
827 SPCSSAttr *style = sp_css_attr_from_object (SP_DOCUMENT_ROOT (doc));
829 SPObject *new_obj = NULL;
831 if ((style && style->firstChild()) || items_count > 1) {
832 // create group
833 Inkscape::XML::Node *newgroup = sp_repr_new("svg:g");
834 sp_repr_css_set (newgroup, style, "style");
836 for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) {
837 if (SP_IS_ITEM(child)) {
838 Inkscape::XML::Node *newchild = SP_OBJECT_REPR(child)->duplicate();
840 // convert layers to groups; FIXME: add "preserve layers" mode where each layer
841 // from impot is copied to the same-named layer in host
842 newchild->setAttribute("inkscape:groupmode", NULL);
844 newgroup->appendChild(newchild);
845 }
846 }
848 if (desktop) {
849 // Add it to the current layer
850 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
851 } else {
852 // There's no desktop (command line run?)
853 // FIXME: For such cases we need a document:: method to return the current layer
854 new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newgroup);
855 }
857 Inkscape::GC::release(newgroup);
858 } else {
859 // just add one item
860 for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) {
861 if (SP_IS_ITEM(child)) {
862 Inkscape::XML::Node *newitem = SP_OBJECT_REPR(child)->duplicate();
863 newitem->setAttribute("inkscape:groupmode", NULL);
865 if (desktop) {
866 // Add it to the current layer
867 new_obj = desktop->currentLayer()->appendChildRepr(newitem);
868 } else {
869 // There's no desktop (command line run?)
870 // FIXME: For such cases we need a document:: method to return the current layer
871 new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newitem);
872 }
874 }
875 }
876 }
878 if (style) sp_repr_css_attr_unref (style);
880 // select and move the imported item
881 if (new_obj && SP_IS_ITEM(new_obj)) {
882 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
883 selection->set(SP_ITEM(new_obj));
885 // To move the imported object, we must temporarily set the "transform pattern with
886 // object" option.
887 {
888 int const saved_pref = prefs_get_int_attribute("options.transform", "pattern", 1);
889 prefs_set_int_attribute("options.transform", "pattern", 1);
890 sp_document_ensure_up_to_date(SP_DT_DOCUMENT(desktop));
891 NR::Point m( desktop->point() - selection->bounds().midpoint() );
892 sp_selection_move_relative(selection, m);
893 prefs_set_int_attribute("options.transform", "pattern", saved_pref);
894 }
895 }
897 sp_document_unref(doc);
898 sp_document_done(in_doc);
900 } else {
901 gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), uri);
902 sp_ui_error_dialog(text);
903 g_free(text);
904 }
906 return;
907 }
910 static Inkscape::UI::Dialogs::FileOpenDialog *importDialogInstance = NULL;
912 /**
913 * Display an Open dialog, import a resource if OK pressed.
914 */
915 void
916 sp_file_import(GtkWidget *widget)
917 {
918 SPDocument *doc = SP_ACTIVE_DOCUMENT;
919 if (!doc)
920 return;
922 if (!importDialogInstance) {
923 importDialogInstance =
924 Inkscape::UI::Dialogs::FileOpenDialog::create(
925 (char const *)import_path,
926 Inkscape::UI::Dialogs::IMPORT_TYPES,
927 (char const *)_("Select file to import"));
928 }
929 bool success = importDialogInstance->show();
930 char *fileName = ( success
931 ? g_strdup(importDialogInstance->getFilename())
932 : NULL );
933 Inkscape::Extension::Extension *selection =
934 importDialogInstance->getSelectionType();
936 if (!success) return;
937 if (fileName) {
938 gsize bytesRead = 0;
939 gsize bytesWritten = 0;
940 GError *error = NULL;
941 #ifdef INK_DUMP_FILENAME_CONV
942 dump_str( fileName, "D file pre is " );
943 #endif
944 gchar *newFileName = g_filename_to_utf8( fileName,
945 -1,
946 &bytesRead,
947 &bytesWritten,
948 &error);
949 if ( newFileName != NULL ) {
950 g_free(fileName);
951 fileName = newFileName;
952 #ifdef INK_DUMP_FILENAME_CONV
953 dump_str( fileName, "D file post is " );
954 #endif
955 } else {
956 // TODO: bulia, please look over
957 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
958 }
961 if (!g_utf8_validate(fileName, -1, NULL)) {
962 // TODO: bulia, please look over
963 g_warning( "INPUT FILENAME IS NOT UTF-8" );
964 }
966 g_free(import_path);
967 import_path = g_dirname(fileName);
968 if (import_path) import_path = g_strconcat(import_path, G_DIR_SEPARATOR_S, NULL);
970 file_import(doc, fileName, selection);
971 g_free(fileName);
972 }
974 return;
975 }
979 /*######################
980 ## E X P O R T
981 ######################*/
983 /**
984 *
985 */
986 void
987 sp_file_export_dialog(void *widget)
988 {
989 sp_export_dialog();
990 }
992 #include <display/nr-arena-item.h>
993 #include <display/nr-arena.h>
995 struct SPEBP {
996 int width, height, sheight;
997 guchar r, g, b, a;
998 NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
999 guchar *px;
1000 unsigned (*status)(float, void *);
1001 void *data;
1002 };
1005 /**
1006 *
1007 */
1008 static int
1009 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
1010 {
1011 struct SPEBP *ebp = (struct SPEBP *) data;
1013 if (ebp->status) {
1014 if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
1015 }
1017 num_rows = MIN(num_rows, ebp->sheight);
1018 num_rows = MIN(num_rows, ebp->height - row);
1020 /* Set area of interest */
1021 NRRectL bbox;
1022 bbox.x0 = 0;
1023 bbox.y0 = row;
1024 bbox.x1 = ebp->width;
1025 bbox.y1 = row + num_rows;
1026 /* Update to renderable state */
1027 NRGC gc(NULL);
1028 nr_matrix_set_identity(&gc.transform);
1030 nr_arena_item_invoke_update(ebp->root, &bbox, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
1032 NRPixBlock pb;
1033 nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
1034 bbox.x0, bbox.y0, bbox.x1, bbox.y1,
1035 ebp->px, 4 * ebp->width, FALSE, FALSE);
1037 for (int r = 0; r < num_rows; r++) {
1038 guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
1039 for (int c = 0; c < ebp->width; c++) {
1040 *p++ = ebp->r;
1041 *p++ = ebp->g;
1042 *p++ = ebp->b;
1043 *p++ = ebp->a;
1044 }
1045 }
1047 /* Render */
1048 nr_arena_item_invoke_render(ebp->root, &bbox, &pb, 0);
1050 for (int r = 0; r < num_rows; r++) {
1051 rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
1052 }
1054 nr_pixblock_release(&pb);
1056 return num_rows;
1057 }
1059 /**
1060 Hide all items which are not listed in list, recursively, skipping groups and defs
1061 */
1062 void
1063 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
1064 {
1065 if (SP_IS_ITEM(o)
1066 && !SP_IS_DEFS(o)
1067 && !SP_IS_ROOT(o)
1068 && !SP_IS_GROUP(o)
1069 && !g_slist_find(list, o))
1070 {
1071 sp_item_invoke_hide(SP_ITEM(o), dkey);
1072 }
1074 // recurse
1075 if (!g_slist_find(list, o)) {
1076 for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
1077 hide_other_items_recursively(child, list, dkey);
1078 }
1079 }
1080 }
1083 /**
1084 * Render the SVG drawing onto a PNG raster image, then save to
1085 * a file. Returns TRUE if succeeded in writing the file,
1086 * FALSE otherwise.
1087 */
1088 int
1089 sp_export_png_file(SPDocument *doc, gchar const *filename,
1090 double x0, double y0, double x1, double y1,
1091 unsigned width, unsigned height,
1092 unsigned long bgcolor,
1093 unsigned (*status)(float, void *),
1094 void *data, bool force_overwrite,
1095 GSList *items_only)
1096 {
1097 int write_status = TRUE;
1098 g_return_val_if_fail(doc != NULL, FALSE);
1099 g_return_val_if_fail(filename != NULL, FALSE);
1100 g_return_val_if_fail(width >= 1, FALSE);
1101 g_return_val_if_fail(height >= 1, FALSE);
1103 if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
1104 return FALSE;
1105 }
1107 sp_document_ensure_up_to_date(doc);
1109 /* Go to document coordinates */
1110 gdouble t = y0;
1111 y0 = sp_document_height(doc) - y1;
1112 y1 = sp_document_height(doc) - t;
1114 /*
1115 * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
1116 * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
1117 * 3) a[0] * x1 + a[2] * y1 + a[4] = width
1118 * 4) a[1] * x0 + a[3] * y0 + a[5] = height
1119 * 5) a[1] = 0.0;
1120 * 6) a[2] = 0.0;
1121 *
1122 * (1,3) a[0] * x1 - a[0] * x0 = width
1123 * a[0] = width / (x1 - x0)
1124 * (2,4) a[3] * y0 - a[3] * y1 = height
1125 * a[3] = height / (y0 - y1)
1126 * (1) a[4] = -a[0] * x0
1127 * (2) a[5] = -a[3] * y1
1128 */
1130 NRMatrix affine;
1131 affine.c[0] = width / (x1 - x0);
1132 affine.c[1] = 0.0;
1133 affine.c[2] = 0.0;
1134 affine.c[3] = height / (y1 - y0);
1135 affine.c[4] = -affine.c[0] * x0;
1136 affine.c[5] = -affine.c[3] * y0;
1138 //SP_PRINT_MATRIX("SVG2PNG", &affine);
1140 struct SPEBP ebp;
1141 ebp.width = width;
1142 ebp.height = height;
1143 ebp.r = NR_RGBA32_R(bgcolor);
1144 ebp.g = NR_RGBA32_G(bgcolor);
1145 ebp.b = NR_RGBA32_B(bgcolor);
1146 ebp.a = NR_RGBA32_A(bgcolor);
1148 /* Create new arena */
1149 NRArena *arena = NRArena::create();
1150 unsigned dkey = sp_item_display_key_new(1);
1152 /* Create ArenaItems and set transform */
1153 ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
1154 nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), NR::Matrix(&affine));
1156 // We show all and then hide all items we don't want, instead of showing only requested items,
1157 // because that would not work if the shown item references something in defs
1158 if (items_only) {
1159 hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
1160 }
1162 ebp.status = status;
1163 ebp.data = data;
1165 if ((width < 256) || ((width * height) < 32768)) {
1166 ebp.px = nr_pixelstore_64K_new(FALSE, 0);
1167 ebp.sheight = 65536 / (4 * width);
1168 write_status = sp_png_write_rgba_striped(filename, width, height, sp_export_get_rows, &ebp);
1169 nr_pixelstore_64K_free(ebp.px);
1170 } else {
1171 ebp.px = nr_new(guchar, 4 * 64 * width);
1172 ebp.sheight = 64;
1173 write_status = sp_png_write_rgba_striped(filename, width, height, sp_export_get_rows, &ebp);
1174 nr_free(ebp.px);
1175 }
1177 // Hide items
1178 sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
1180 /* Free Arena and ArenaItem */
1181 nr_arena_item_unref(ebp.root);
1182 nr_object_unref((NRObject *) arena);
1183 return write_status;
1184 }
1187 /*######################
1188 ## P R I N T
1189 ######################*/
1192 /**
1193 * Print the current document, if any.
1194 */
1195 void
1196 sp_file_print()
1197 {
1198 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1199 if (doc)
1200 sp_print_document(doc, FALSE);
1201 }
1204 /**
1205 * Print the current document, if any. Do not use
1206 * the machine's print drivers.
1207 */
1208 void
1209 sp_file_print_direct()
1210 {
1211 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1212 if (doc)
1213 sp_print_document(doc, TRUE);
1214 }
1217 /**
1218 * Display what the drawing would look like, if
1219 * printed.
1220 */
1221 void
1222 sp_file_print_preview(gpointer object, gpointer data)
1223 {
1225 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1226 if (doc)
1227 sp_print_preview_document(doc);
1229 }
1231 void Inkscape::IO::fixupHrefs( SPDocument *doc, const gchar *base, gboolean spns )
1232 {
1233 //g_message("Inkscape::IO::fixupHrefs( , [%s], )", base );
1235 if ( 0 ) {
1236 gchar const* things[] = {
1237 "data:foo,bar",
1238 "http://www.google.com/image.png",
1239 "ftp://ssd.com/doo",
1240 "/foo/dee/bar.svg",
1241 "foo.svg",
1242 "file:/foo/dee/bar.svg",
1243 "file:///foo/dee/bar.svg",
1244 "file:foo.svg",
1245 "/foo/bar\xe1\x84\x92.svg",
1246 "file:///foo/bar\xe1\x84\x92.svg",
1247 "file:///foo/bar%e1%84%92.svg",
1248 "/foo/bar%e1%84%92.svg",
1249 "bar\xe1\x84\x92.svg",
1250 "bar%e1%84%92.svg",
1251 NULL
1252 };
1253 g_message("+------");
1254 for ( int i = 0; things[i]; i++ )
1255 {
1256 try
1257 {
1258 URI uri(things[i]);
1259 gboolean isAbs = g_path_is_absolute( things[i] );
1260 gchar *str = uri.toString();
1261 g_message( "abs:%d isRel:%d scheme:[%s] path:[%s][%s] uri[%s] / [%s]", (int)isAbs,
1262 (int)uri.isRelative(),
1263 uri.getScheme(),
1264 uri.getPath(),
1265 uri.getOpaque(),
1266 things[i],
1267 str );
1268 g_free(str);
1269 }
1270 catch ( MalformedURIException err )
1271 {
1272 dump_str( things[i], "MalformedURIException" );
1273 xmlChar *redo = xmlURIEscape((xmlChar const *)things[i]);
1274 g_message(" gone from [%s] to [%s]", things[i], redo );
1275 if ( redo == NULL )
1276 {
1277 URI again = URI::fromUtf8( things[i] );
1278 gboolean isAbs = g_path_is_absolute( things[i] );
1279 gchar *str = again.toString();
1280 g_message( "abs:%d isRel:%d scheme:[%s] path:[%s][%s] uri[%s] / [%s]", (int)isAbs,
1281 (int)again.isRelative(),
1282 again.getScheme(),
1283 again.getPath(),
1284 again.getOpaque(),
1285 things[i],
1286 str );
1287 g_free(str);
1288 g_message(" ----");
1289 }
1290 }
1291 }
1292 g_message("+------");
1293 }
1295 GSList const *images = sp_document_get_resource_list(doc, "image");
1296 for (GSList const *l = images; l != NULL; l = l->next) {
1297 Inkscape::XML::Node *ir = SP_OBJECT_REPR(l->data);
1299 const gchar *href = ir->attribute("xlink:href");
1301 // First try to figure out an absolute path to the asset
1302 //g_message("image href [%s]", href );
1303 if (spns && !g_path_is_absolute(href)) {
1304 const gchar *absref = ir->attribute("sodipodi:absref");
1305 const gchar *base_href = g_build_filename(base, href, NULL);
1306 //g_message(" absr [%s]", absref );
1308 if ( absref && Inkscape::IO::file_test(absref, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_test(base_href, G_FILE_TEST_EXISTS))
1309 {
1310 // only switch over if the absref is valid while href is not
1311 href = absref;
1312 //g_message(" copied absref to href");
1313 }
1314 }
1316 // Once we have an absolute path, convert it relative to the new location
1317 if (href && g_path_is_absolute(href)) {
1318 const gchar *relname = sp_relative_path_from_path(href, base);
1319 //g_message(" setting to [%s]", relname );
1320 ir->setAttribute("xlink:href", relname);
1321 }
1322 // TODO next refinement is to make the first choice keeping the relative path as-is if
1323 // based on the new location it gives us a valid file.
1324 }
1325 }
1328 /*
1329 Local Variables:
1330 mode:c++
1331 c-file-style:"stroustrup"
1332 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1333 indent-tabs-mode:nil
1334 fill-column:99
1335 End:
1336 */
1337 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :