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 "ui/dialog/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
70 //#define INK_DUMP_FILENAME_CONV 1
71 #undef INK_DUMP_FILENAME_CONV
73 //#define INK_DUMP_FOPEN 1
74 #undef INK_DUMP_FOPEN
76 void dump_str(gchar const *str, gchar const *prefix);
77 void dump_ustr(Glib::ustring const &ustr);
80 /*######################
81 ## N E W
82 ######################*/
84 /**
85 * Create a blank document and add it to the desktop
86 */
87 SPDesktop*
88 sp_file_new(const Glib::ustring &templ)
89 {
90 SPDocument *doc = sp_document_new(templ.c_str(), TRUE, true);
91 g_return_val_if_fail(doc != NULL, NULL);
93 SPDesktop *dt;
94 if (Inkscape::NSApplication::Application::getNewGui())
95 {
96 dt = Inkscape::NSApplication::Editor::createDesktop (doc);
97 } else {
98 SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
99 g_return_val_if_fail(dtw != NULL, NULL);
100 sp_document_unref(doc);
102 sp_create_window(dtw, TRUE);
103 dt = static_cast<SPDesktop*>(dtw->view);
104 sp_namedview_window_from_document(dt);
105 }
106 return dt;
107 }
109 SPDesktop*
110 sp_file_new_default()
111 {
112 std::list<gchar *> sources;
113 sources.push_back( profile_path("templates") ); // first try user's local dir
114 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
116 while (!sources.empty()) {
117 gchar *dirname = sources.front();
118 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
120 // TRANSLATORS: default.svg is localizable - this is the name of the default document
121 // template. This way you can localize the default pagesize, translate the name of
122 // the default layer, etc. If you wish to localize this file, please create a
123 // localized share/templates/default.xx.svg file, where xx is your language code.
124 char *default_template = g_build_filename(dirname, _("default.svg"), NULL);
125 if (Inkscape::IO::file_test(default_template, G_FILE_TEST_IS_REGULAR)) {
126 return sp_file_new(default_template);
127 }
128 }
129 g_free(dirname);
130 sources.pop_front();
131 }
133 return sp_file_new(NULL);
134 }
137 /*######################
138 ## D E L E T E
139 ######################*/
141 /**
142 * Perform document closures preceding an exit()
143 */
144 void
145 sp_file_exit()
146 {
147 sp_ui_close_all();
148 // no need to call inkscape_exit here; last document being closed will take care of that
149 }
152 /*######################
153 ## O P E N
154 ######################*/
156 /**
157 * Open a file, add the document to the desktop
158 *
159 * \param replace_empty if true, and the current desktop is empty, this document
160 * will replace the empty one.
161 */
162 bool
163 sp_file_open(const Glib::ustring &uri,
164 Inkscape::Extension::Extension *key,
165 bool add_to_recent, bool replace_empty)
166 {
167 SPDocument *doc = NULL;
168 try {
169 doc = Inkscape::Extension::open(key, uri.c_str());
170 } catch (Inkscape::Extension::Input::no_extension_found &e) {
171 doc = NULL;
172 } catch (Inkscape::Extension::Input::open_failed &e) {
173 doc = NULL;
174 }
176 if (doc) {
177 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
178 SPDocument *existing = desktop ? sp_desktop_document(desktop) : NULL;
180 if (existing && existing->virgin && replace_empty) {
181 // If the current desktop is empty, open the document there
182 sp_document_ensure_up_to_date (doc);
183 desktop->change_document(doc);
184 sp_document_resized_signal_emit (doc, sp_document_width(doc), sp_document_height(doc));
185 } else {
186 if (!Inkscape::NSApplication::Application::getNewGui()) {
187 // create a whole new desktop and window
188 SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
189 sp_create_window(dtw, TRUE);
190 desktop = static_cast<SPDesktop*>(dtw->view);
191 } else {
192 desktop = Inkscape::NSApplication::Editor::createDesktop (doc);
193 }
194 }
196 doc->virgin = FALSE;
197 // everyone who cares now has a reference, get rid of ours
198 sp_document_unref(doc);
199 // resize the window to match the document properties
200 // (this may be redundant for new windows... if so, move to the "virgin"
201 // section above)
202 sp_namedview_window_from_document(desktop);
204 if (add_to_recent) {
205 prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc));
206 }
208 return TRUE;
209 } else {
210 gchar *safeUri = Inkscape::IO::sanitizeString(uri.c_str());
211 gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), safeUri);
212 sp_ui_error_dialog(text);
213 g_free(text);
214 g_free(safeUri);
215 return FALSE;
216 }
217 }
219 /**
220 * Handle prompting user for "do you want to revert"? Revert on "OK"
221 */
222 void
223 sp_file_revert_dialog()
224 {
225 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
226 g_assert(desktop != NULL);
228 SPDocument *doc = sp_desktop_document(desktop);
229 g_assert(doc != NULL);
231 Inkscape::XML::Node *repr = sp_document_repr_root(doc);
232 g_assert(repr != NULL);
234 gchar const *uri = doc->uri;
235 if (!uri) {
236 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved yet. Cannot revert."));
237 return;
238 }
240 bool do_revert = true;
241 if (repr->attribute("sodipodi:modified") != NULL) {
242 gchar *text = g_strdup_printf(_("Changes will be lost! Are you sure you want to reload document %s?"), uri);
244 bool response = desktop->warnDialog (text);
245 g_free(text);
247 if (!response) {
248 do_revert = false;
249 }
250 }
252 bool reverted;
253 if (do_revert) {
254 // Allow overwriting of current document.
255 doc->virgin = TRUE;
256 reverted = sp_file_open(uri,NULL);
257 } else {
258 reverted = false;
259 }
261 if (reverted) {
262 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document reverted."));
263 } else {
264 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not reverted."));
265 }
266 }
268 void dump_str(gchar const *str, gchar const *prefix)
269 {
270 Glib::ustring tmp;
271 tmp = prefix;
272 tmp += " [";
273 size_t const total = strlen(str);
274 for (unsigned i = 0; i < total; i++) {
275 gchar *const tmp2 = g_strdup_printf(" %02x", (0x0ff & str[i]));
276 tmp += tmp2;
277 g_free(tmp2);
278 }
280 tmp += "]";
281 g_message(tmp.c_str());
282 }
284 void dump_ustr(Glib::ustring const &ustr)
285 {
286 char const *cstr = ustr.c_str();
287 char const *data = ustr.data();
288 Glib::ustring::size_type const byteLen = ustr.bytes();
289 Glib::ustring::size_type const dataLen = ustr.length();
290 Glib::ustring::size_type const cstrLen = strlen(cstr);
292 g_message(" size: %lu\n length: %lu\n bytes: %lu\n clen: %lu",
293 gulong(ustr.size()), gulong(dataLen), gulong(byteLen), gulong(cstrLen) );
294 g_message( " ASCII? %s", (ustr.is_ascii() ? "yes":"no") );
295 g_message( " UTF-8? %s", (ustr.validate() ? "yes":"no") );
297 try {
298 Glib::ustring tmp;
299 for (Glib::ustring::size_type i = 0; i < ustr.bytes(); i++) {
300 tmp = " ";
301 if (i < dataLen) {
302 Glib::ustring::value_type val = ustr.at(i);
303 gchar* tmp2 = g_strdup_printf( (((val & 0xff00) == 0) ? " %02x" : "%04x"), val );
304 tmp += tmp2;
305 g_free( tmp2 );
306 } else {
307 tmp += " ";
308 }
310 if (i < byteLen) {
311 int val = (0x0ff & data[i]);
312 gchar *tmp2 = g_strdup_printf(" %02x", val);
313 tmp += tmp2;
314 g_free( tmp2 );
315 if ( val > 32 && val < 127 ) {
316 tmp2 = g_strdup_printf( " '%c'", (gchar)val );
317 tmp += tmp2;
318 g_free( tmp2 );
319 } else {
320 tmp += " . ";
321 }
322 } else {
323 tmp += " ";
324 }
326 if ( i < cstrLen ) {
327 int val = (0x0ff & cstr[i]);
328 gchar* tmp2 = g_strdup_printf(" %02x", val);
329 tmp += tmp2;
330 g_free(tmp2);
331 if ( val > 32 && val < 127 ) {
332 tmp2 = g_strdup_printf(" '%c'", (gchar) val);
333 tmp += tmp2;
334 g_free( tmp2 );
335 } else {
336 tmp += " . ";
337 }
338 } else {
339 tmp += " ";
340 }
342 g_message( tmp.c_str() );
343 }
344 } catch (...) {
345 g_message("XXXXXXXXXXXXXXXXXX Exception" );
346 }
347 g_message("---------------");
348 }
350 static Inkscape::UI::Dialog::FileOpenDialog *openDialogInstance = NULL;
352 /**
353 * Display an file Open selector. Open a document if OK is pressed.
354 * Can select single or multiple files for opening.
355 */
356 void
357 sp_file_open_dialog(gpointer object, gpointer data)
358 {
360 //# Get the current directory for finding files
361 Glib::ustring open_path;
362 char *attr = (char *)prefs_get_string_attribute("dialogs.open", "path");
363 if (attr)
364 open_path = attr;
367 //# Test if the open_path directory exists
368 if (!Inkscape::IO::file_test(open_path.c_str(),
369 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
370 open_path = "";
372 //# If no open path, default to our home directory
373 if (open_path.size() < 1)
374 {
375 open_path = g_get_home_dir();
376 open_path.append(G_DIR_SEPARATOR_S);
377 }
379 //# Create a dialog if we don't already have one
380 if (!openDialogInstance) {
381 openDialogInstance =
382 Inkscape::UI::Dialog::FileOpenDialog::create(
383 open_path,
384 Inkscape::UI::Dialog::SVG_TYPES,
385 (char const *)_("Select file to open"));
386 }
388 //# Show the dialog
389 bool const success = openDialogInstance->show();
390 if (!success)
391 return;
393 //# User selected something. Get name and type
394 Glib::ustring fileName = openDialogInstance->getFilename();
395 Inkscape::Extension::Extension *selection =
396 openDialogInstance->getSelectionType();
398 //# Code to check & open iff multiple files.
399 std::vector<Glib::ustring> flist=openDialogInstance->getFilenames();
401 //# Iterate through filenames if more than 1
402 if (flist.size() > 1)
403 {
404 for (unsigned int i=1 ; i<flist.size() ; i++)
405 {
406 Glib::ustring fName = flist[i];
408 if (Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) {
409 Glib::ustring newFileName = Glib::filename_to_utf8(fName);
410 if ( newFileName.size() > 0 )
411 fName = newFileName;
412 else
413 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
415 #ifdef INK_DUMP_FILENAME_CONV
416 g_message("Opening File %s\n",fileName);
417 #endif
418 sp_file_open(fileName, selection);
419 }
420 }
421 return;
422 }
425 if (fileName.size() > 0) {
427 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
429 if ( newFileName.size() > 0)
430 fileName = newFileName;
431 else
432 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
434 open_path = fileName;
435 open_path.append(G_DIR_SEPARATOR_S);
436 prefs_set_string_attribute("dialogs.open", "path", open_path.c_str());
438 sp_file_open(fileName, selection);
439 }
441 return;
442 }
445 /*######################
446 ## V A C U U M
447 ######################*/
449 /**
450 * Remove unreferenced defs from the defs section of the document.
451 */
454 void
455 sp_file_vacuum()
456 {
457 SPDocument *doc = SP_ACTIVE_DOCUMENT;
459 unsigned int diff = vacuum_document (doc);
461 sp_document_done(doc, SP_VERB_FILE_VACUUM,
462 /* TODO: annotate */ "file.cpp:515");
464 SPDesktop *dt = SP_ACTIVE_DESKTOP;
465 if (diff > 0) {
466 dt->messageStack()->flashF(Inkscape::NORMAL_MESSAGE,
467 ngettext("Removed <b>%i</b> unused definition in <defs>.",
468 "Removed <b>%i</b> unused definitions in <defs>.",
469 diff),
470 diff);
471 } else {
472 dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No unused definitions in <defs>."));
473 }
474 }
478 /*######################
479 ## S A V E
480 ######################*/
482 /**
483 * This 'save' function called by the others below
484 */
485 static bool
486 file_save(SPDocument *doc, const Glib::ustring &uri,
487 Inkscape::Extension::Extension *key, bool saveas)
488 {
489 if (!doc || uri.size()<1) //Safety check
490 return false;
492 try {
493 Inkscape::Extension::save(key, doc, uri.c_str(),
494 saveas && prefs_get_int_attribute("dialogs.save_as", "append_extension", 1),
495 saveas, TRUE); // save officially, with inkscape: attributes set
496 } catch (Inkscape::Extension::Output::no_extension_found &e) {
497 gchar *safeUri = Inkscape::IO::sanitizeString(uri.c_str());
498 gchar *text = g_strdup_printf(_("No Inkscape extension found to save document (%s). This may have been caused by an unknown filename extension."), safeUri);
499 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved."));
500 sp_ui_error_dialog(text);
501 g_free(text);
502 g_free(safeUri);
503 return FALSE;
504 } catch (Inkscape::Extension::Output::save_failed &e) {
505 gchar *safeUri = Inkscape::IO::sanitizeString(uri.c_str());
506 gchar *text = g_strdup_printf(_("File %s could not be saved."), safeUri);
507 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved."));
508 sp_ui_error_dialog(text);
509 g_free(text);
510 g_free(safeUri);
511 return FALSE;
512 } catch (Inkscape::Extension::Output::no_overwrite &e) {
513 return sp_file_save_dialog(doc);
514 }
516 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Document saved."));
517 return true;
518 }
524 static Inkscape::UI::Dialog::FileSaveDialog *saveDialogInstance = NULL;
526 /**
527 * Display a SaveAs dialog. Save the document if OK pressed.
528 */
529 bool
530 sp_file_save_dialog(SPDocument *doc)
531 {
533 Inkscape::XML::Node *repr = sp_document_repr_root(doc);
535 Inkscape::Extension::Output *extension;
537 //# Get the default extension name
538 Glib::ustring default_extension;
539 char *attr = (char *)repr->attribute("inkscape:output_extension");
540 if (!attr)
541 attr = (char *)prefs_get_string_attribute("dialogs.save_as", "default");
542 if (attr)
543 default_extension = attr;
544 //g_message("%s: extension name: '%s'", __FUNCTION__, default_extension);
546 Glib::ustring save_path;
547 Glib::ustring save_loc;
549 if (doc->uri == NULL) {
550 char formatBuf[256];
551 int i = 1;
553 Glib::ustring filename_extension = ".svg";
554 extension = dynamic_cast<Inkscape::Extension::Output *>
555 (Inkscape::Extension::db.get(default_extension.c_str()));
556 //g_warning("%s: extension ptr: 0x%x", __FUNCTION__, (unsigned int)extension);
557 if (extension)
558 filename_extension = extension->get_extension();
560 attr = (char *)prefs_get_string_attribute("dialogs.save_as", "path");
561 if (attr)
562 save_path = attr;
564 if (!Inkscape::IO::file_test(save_path.c_str(),
565 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
566 save_path = "";
568 if (save_path.size()<1)
569 save_path = g_get_home_dir();
571 save_loc = save_path;
572 save_loc.append(G_DIR_SEPARATOR_S);
573 snprintf(formatBuf, 255, _("drawing%s"), filename_extension.c_str());
574 save_loc.append(formatBuf);
576 while (Inkscape::IO::file_test(save_loc.c_str(), G_FILE_TEST_EXISTS)) {
577 save_loc = save_path;
578 save_loc.append(G_DIR_SEPARATOR_S);
579 snprintf(formatBuf, 255, _("drawing-%d%s"), i++, filename_extension.c_str());
580 save_loc.append(formatBuf);
581 }
582 } else {
583 save_loc = Glib::path_get_dirname(doc->uri);
584 }
586 // convert save_loc from utf-8 to locale
587 // is this needed any more, now that everything is handled in
588 // Inkscape::IO?
589 Glib::ustring save_loc_local = Glib::filename_from_utf8(save_loc);
591 if ( save_loc_local.size() > 0)
592 save_loc = save_loc_local;
594 //# Show the SaveAs dialog
595 if (!saveDialogInstance)
596 saveDialogInstance =
597 Inkscape::UI::Dialog::FileSaveDialog::create(
598 save_loc,
599 Inkscape::UI::Dialog::SVG_TYPES,
600 (char const *) _("Select file to save to"),
601 default_extension
602 );
604 bool success = saveDialogInstance->show();
605 if (!success)
606 return success;
608 Glib::ustring fileName = saveDialogInstance->getFilename();
610 Inkscape::Extension::Extension *selectionType =
611 saveDialogInstance->getSelectionType();
614 if (fileName.size() > 0) {
615 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
617 if ( newFileName.size()>0 )
618 fileName = newFileName;
619 else
620 g_warning( "Error converting save filename to UTF-8." );
622 success = file_save(doc, fileName, selectionType, TRUE);
624 if (success)
625 prefs_set_recent_file(SP_DOCUMENT_URI(doc), SP_DOCUMENT_NAME(doc));
627 save_path = fileName;
628 prefs_set_string_attribute("dialogs.save_as", "path", save_path.c_str());
630 return success;
631 }
634 return false;
635 }
638 /**
639 * Save a document, displaying a SaveAs dialog if necessary.
640 */
641 bool
642 sp_file_save_document(SPDocument *doc)
643 {
644 bool success = true;
646 Inkscape::XML::Node *repr = sp_document_repr_root(doc);
648 gchar const *fn = repr->attribute("sodipodi:modified");
649 if (fn != NULL) {
650 if (doc->uri == NULL
651 || repr->attribute("inkscape:output_extension") == NULL)
652 {
653 return sp_file_save_dialog(doc);
654 } else {
655 fn = g_strdup(doc->uri);
656 gchar const *ext = repr->attribute("inkscape:output_extension");
657 success = file_save(doc, fn, Inkscape::Extension::db.get(ext), FALSE);
658 g_free((void *) fn);
659 }
660 } else {
661 SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No changes need to be saved."));
662 success = TRUE;
663 }
665 return success;
666 }
669 /**
670 * Save a document.
671 */
672 bool
673 sp_file_save(gpointer object, gpointer data)
674 {
675 if (!SP_ACTIVE_DOCUMENT)
676 return false;
677 sp_namedview_document_from_window(SP_ACTIVE_DESKTOP);
678 return sp_file_save_document(SP_ACTIVE_DOCUMENT);
679 }
682 /**
683 * Save a document, always displaying the SaveAs dialog.
684 */
685 bool
686 sp_file_save_as(gpointer object, gpointer data)
687 {
688 if (!SP_ACTIVE_DOCUMENT)
689 return false;
690 sp_namedview_document_from_window(SP_ACTIVE_DESKTOP);
691 return sp_file_save_dialog(SP_ACTIVE_DOCUMENT);
692 }
697 /*######################
698 ## I M P O R T
699 ######################*/
701 /**
702 * Import a resource. Called by sp_file_import()
703 */
704 void
705 file_import(SPDocument *in_doc, const Glib::ustring &uri,
706 Inkscape::Extension::Extension *key)
707 {
708 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
710 //DEBUG_MESSAGE( fileImport, "file_import( in_doc:%p uri:[%s], key:%p", in_doc, uri, key );
711 SPDocument *doc;
712 try {
713 doc = Inkscape::Extension::open(key, uri.c_str());
714 } catch (Inkscape::Extension::Input::no_extension_found &e) {
715 doc = NULL;
716 } catch (Inkscape::Extension::Input::open_failed &e) {
717 doc = NULL;
718 }
720 if (doc != NULL) {
721 // the import extension has passed us a document, now we need to embed it into our document
722 if ( 0 ) {
723 // const gchar *docbase = (sp_repr_document_root( sp_repr_document( repr ))->attribute("sodipodi:docbase" );
724 g_message(" settings uri [%s]", doc->uri );
725 g_message(" base [%s]", doc->base );
726 g_message(" name [%s]", doc->name );
727 Inkscape::IO::fixupHrefs( doc, doc->base, TRUE );
728 g_message(" mid-fixup");
729 Inkscape::IO::fixupHrefs( doc, in_doc->base, TRUE );
730 }
732 // move imported defs to our document's defs
733 SPObject *in_defs = SP_DOCUMENT_DEFS(in_doc);
734 SPObject *defs = SP_DOCUMENT_DEFS(doc);
735 Inkscape::XML::Node *last_def = SP_OBJECT_REPR(in_defs)->lastChild();
736 for (SPObject *child = sp_object_first_child(defs);
737 child != NULL; child = SP_OBJECT_NEXT(child))
738 {
739 // FIXME: in case of id conflict, newly added thing will be re-ided and thus likely break a reference to it from imported stuff
740 SP_OBJECT_REPR(in_defs)->addChild(SP_OBJECT_REPR(child)->duplicate(), last_def);
741 }
743 guint items_count = 0;
744 for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc));
745 child != NULL; child = SP_OBJECT_NEXT(child)) {
746 if (SP_IS_ITEM(child))
747 items_count ++;
748 }
749 SPCSSAttr *style = sp_css_attr_from_object (SP_DOCUMENT_ROOT (doc));
751 SPObject *new_obj = NULL;
753 if ((style && style->firstChild()) || items_count > 1) {
754 // create group
755 Inkscape::XML::Node *newgroup = sp_repr_new("svg:g");
756 sp_repr_css_set (newgroup, style, "style");
758 for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) {
759 if (SP_IS_ITEM(child)) {
760 Inkscape::XML::Node *newchild = SP_OBJECT_REPR(child)->duplicate();
762 // convert layers to groups; FIXME: add "preserve layers" mode where each layer
763 // from impot is copied to the same-named layer in host
764 newchild->setAttribute("inkscape:groupmode", NULL);
766 newgroup->appendChild(newchild);
767 }
768 }
770 if (desktop) {
771 // Add it to the current layer
772 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
773 } else {
774 // There's no desktop (command line run?)
775 // FIXME: For such cases we need a document:: method to return the current layer
776 new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newgroup);
777 }
779 Inkscape::GC::release(newgroup);
780 } else {
781 // just add one item
782 for (SPObject *child = sp_object_first_child(SP_DOCUMENT_ROOT(doc)); child != NULL; child = SP_OBJECT_NEXT(child) ) {
783 if (SP_IS_ITEM(child)) {
784 Inkscape::XML::Node *newitem = SP_OBJECT_REPR(child)->duplicate();
785 newitem->setAttribute("inkscape:groupmode", NULL);
787 if (desktop) {
788 // Add it to the current layer
789 new_obj = desktop->currentLayer()->appendChildRepr(newitem);
790 } else {
791 // There's no desktop (command line run?)
792 // FIXME: For such cases we need a document:: method to return the current layer
793 new_obj = SP_DOCUMENT_ROOT(in_doc)->appendChildRepr(newitem);
794 }
796 }
797 }
798 }
800 if (style) sp_repr_css_attr_unref (style);
802 // select and move the imported item
803 if (new_obj && SP_IS_ITEM(new_obj)) {
804 Inkscape::Selection *selection = sp_desktop_selection(desktop);
805 selection->set(SP_ITEM(new_obj));
807 // To move the imported object, we must temporarily set the "transform pattern with
808 // object" option.
809 {
810 int const saved_pref = prefs_get_int_attribute("options.transform", "pattern", 1);
811 prefs_set_int_attribute("options.transform", "pattern", 1);
812 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
813 NR::Point m( desktop->point() - selection->bounds().midpoint() );
814 sp_selection_move_relative(selection, m);
815 prefs_set_int_attribute("options.transform", "pattern", saved_pref);
816 }
817 }
819 sp_document_unref(doc);
820 sp_document_done(in_doc, SP_VERB_FILE_IMPORT,
821 /* TODO: annotate */ "file.cpp:900");
823 } else {
824 gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), uri.c_str());
825 sp_ui_error_dialog(text);
826 g_free(text);
827 }
829 return;
830 }
833 static Inkscape::UI::Dialog::FileOpenDialog *importDialogInstance = NULL;
835 /**
836 * Display an Open dialog, import a resource if OK pressed.
837 */
838 void
839 sp_file_import(GtkWidget *widget)
840 {
841 static Glib::ustring import_path;
843 SPDocument *doc = SP_ACTIVE_DOCUMENT;
844 if (!doc)
845 return;
847 if (!importDialogInstance) {
848 importDialogInstance =
849 Inkscape::UI::Dialog::FileOpenDialog::create(
850 import_path,
851 Inkscape::UI::Dialog::IMPORT_TYPES,
852 (char const *)_("Select file to import"));
853 }
855 bool success = importDialogInstance->show();
856 if (!success)
857 return;
859 //# Get file name and extension type
860 Glib::ustring fileName = importDialogInstance->getFilename();
861 Inkscape::Extension::Extension *selection =
862 importDialogInstance->getSelectionType();
865 if (fileName.size() > 0) {
867 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
869 if ( newFileName.size() > 0)
870 fileName = newFileName;
871 else
872 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
874 import_path = fileName;
875 if (import_path.size()>0)
876 import_path.append(G_DIR_SEPARATOR_S);
878 file_import(doc, fileName, selection);
879 }
881 return;
882 }
886 /*######################
887 ## E X P O R T
888 ######################*/
890 /**
891 *
892 */
893 void
894 sp_file_export_dialog(void *widget)
895 {
896 sp_export_dialog();
897 }
899 #include <display/nr-arena-item.h>
900 #include <display/nr-arena.h>
902 struct SPEBP {
903 int width, height, sheight;
904 guchar r, g, b, a;
905 NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
906 guchar *px;
907 unsigned (*status)(float, void *);
908 void *data;
909 };
912 /**
913 *
914 */
915 static int
916 sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
917 {
918 struct SPEBP *ebp = (struct SPEBP *) data;
920 if (ebp->status) {
921 if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
922 }
924 num_rows = MIN(num_rows, ebp->sheight);
925 num_rows = MIN(num_rows, ebp->height - row);
927 /* Set area of interest */
928 NRRectL bbox;
929 bbox.x0 = 0;
930 bbox.y0 = row;
931 bbox.x1 = ebp->width;
932 bbox.y1 = row + num_rows;
933 /* Update to renderable state */
934 NRGC gc(NULL);
935 nr_matrix_set_identity(&gc.transform);
937 nr_arena_item_invoke_update(ebp->root, &bbox, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
939 NRPixBlock pb;
940 nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
941 bbox.x0, bbox.y0, bbox.x1, bbox.y1,
942 ebp->px, 4 * ebp->width, FALSE, FALSE);
944 for (int r = 0; r < num_rows; r++) {
945 guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
946 for (int c = 0; c < ebp->width; c++) {
947 *p++ = ebp->r;
948 *p++ = ebp->g;
949 *p++ = ebp->b;
950 *p++ = ebp->a;
951 }
952 }
954 /* Render */
955 nr_arena_item_invoke_render(ebp->root, &bbox, &pb, 0);
957 for (int r = 0; r < num_rows; r++) {
958 rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
959 }
961 nr_pixblock_release(&pb);
963 return num_rows;
964 }
966 /**
967 Hide all items which are not listed in list, recursively, skipping groups and defs
968 */
969 void
970 hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
971 {
972 if (SP_IS_ITEM(o)
973 && !SP_IS_DEFS(o)
974 && !SP_IS_ROOT(o)
975 && !SP_IS_GROUP(o)
976 && !g_slist_find(list, o))
977 {
978 sp_item_invoke_hide(SP_ITEM(o), dkey);
979 }
981 // recurse
982 if (!g_slist_find(list, o)) {
983 for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
984 hide_other_items_recursively(child, list, dkey);
985 }
986 }
987 }
990 /**
991 * Render the SVG drawing onto a PNG raster image, then save to
992 * a file. Returns TRUE if succeeded in writing the file,
993 * FALSE otherwise.
994 */
995 int
996 sp_export_png_file(SPDocument *doc, gchar const *filename,
997 double x0, double y0, double x1, double y1,
998 unsigned width, unsigned height, double xdpi, double ydpi,
999 unsigned long bgcolor,
1000 unsigned (*status)(float, void *),
1001 void *data, bool force_overwrite,
1002 GSList *items_only)
1003 {
1004 int write_status = TRUE;
1005 g_return_val_if_fail(doc != NULL, FALSE);
1006 g_return_val_if_fail(filename != NULL, FALSE);
1007 g_return_val_if_fail(width >= 1, FALSE);
1008 g_return_val_if_fail(height >= 1, FALSE);
1010 if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
1011 return FALSE;
1012 }
1014 sp_document_ensure_up_to_date(doc);
1016 /* Go to document coordinates */
1017 gdouble t = y0;
1018 y0 = sp_document_height(doc) - y1;
1019 y1 = sp_document_height(doc) - t;
1021 /*
1022 * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
1023 * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
1024 * 3) a[0] * x1 + a[2] * y1 + a[4] = width
1025 * 4) a[1] * x0 + a[3] * y0 + a[5] = height
1026 * 5) a[1] = 0.0;
1027 * 6) a[2] = 0.0;
1028 *
1029 * (1,3) a[0] * x1 - a[0] * x0 = width
1030 * a[0] = width / (x1 - x0)
1031 * (2,4) a[3] * y0 - a[3] * y1 = height
1032 * a[3] = height / (y0 - y1)
1033 * (1) a[4] = -a[0] * x0
1034 * (2) a[5] = -a[3] * y1
1035 */
1037 NRMatrix affine;
1038 affine.c[0] = width / (x1 - x0);
1039 affine.c[1] = 0.0;
1040 affine.c[2] = 0.0;
1041 affine.c[3] = height / (y1 - y0);
1042 affine.c[4] = -affine.c[0] * x0;
1043 affine.c[5] = -affine.c[3] * y0;
1045 //SP_PRINT_MATRIX("SVG2PNG", &affine);
1047 struct SPEBP ebp;
1048 ebp.width = width;
1049 ebp.height = height;
1050 ebp.r = NR_RGBA32_R(bgcolor);
1051 ebp.g = NR_RGBA32_G(bgcolor);
1052 ebp.b = NR_RGBA32_B(bgcolor);
1053 ebp.a = NR_RGBA32_A(bgcolor);
1055 /* Create new arena */
1056 NRArena *arena = NRArena::create();
1057 unsigned dkey = sp_item_display_key_new(1);
1059 /* Create ArenaItems and set transform */
1060 ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
1061 nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), NR::Matrix(&affine));
1063 // We show all and then hide all items we don't want, instead of showing only requested items,
1064 // because that would not work if the shown item references something in defs
1065 if (items_only) {
1066 hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
1067 }
1069 ebp.status = status;
1070 ebp.data = data;
1072 if ((width < 256) || ((width * height) < 32768)) {
1073 ebp.px = nr_pixelstore_64K_new(FALSE, 0);
1074 ebp.sheight = 65536 / (4 * width);
1075 write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
1076 nr_pixelstore_64K_free(ebp.px);
1077 } else {
1078 ebp.px = g_new(guchar, 4 * 64 * width);
1079 ebp.sheight = 64;
1080 write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
1081 g_free(ebp.px);
1082 }
1084 // Hide items
1085 sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
1087 /* Free Arena and ArenaItem */
1088 nr_arena_item_unref(ebp.root);
1089 nr_object_unref((NRObject *) arena);
1090 return write_status;
1091 }
1094 /*######################
1095 ## P R I N T
1096 ######################*/
1099 /**
1100 * Print the current document, if any.
1101 */
1102 void
1103 sp_file_print()
1104 {
1105 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1106 if (doc)
1107 sp_print_document(doc, FALSE);
1108 }
1111 /**
1112 * Print the current document, if any. Do not use
1113 * the machine's print drivers.
1114 */
1115 void
1116 sp_file_print_direct()
1117 {
1118 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1119 if (doc)
1120 sp_print_document(doc, TRUE);
1121 }
1124 /**
1125 * Display what the drawing would look like, if
1126 * printed.
1127 */
1128 void
1129 sp_file_print_preview(gpointer object, gpointer data)
1130 {
1132 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1133 if (doc)
1134 sp_print_preview_document(doc);
1136 }
1138 void Inkscape::IO::fixupHrefs( SPDocument *doc, const gchar *base, gboolean spns )
1139 {
1140 //g_message("Inkscape::IO::fixupHrefs( , [%s], )", base );
1142 if ( 0 ) {
1143 gchar const* things[] = {
1144 "data:foo,bar",
1145 "http://www.google.com/image.png",
1146 "ftp://ssd.com/doo",
1147 "/foo/dee/bar.svg",
1148 "foo.svg",
1149 "file:/foo/dee/bar.svg",
1150 "file:///foo/dee/bar.svg",
1151 "file:foo.svg",
1152 "/foo/bar\xe1\x84\x92.svg",
1153 "file:///foo/bar\xe1\x84\x92.svg",
1154 "file:///foo/bar%e1%84%92.svg",
1155 "/foo/bar%e1%84%92.svg",
1156 "bar\xe1\x84\x92.svg",
1157 "bar%e1%84%92.svg",
1158 NULL
1159 };
1160 g_message("+------");
1161 for ( int i = 0; things[i]; i++ )
1162 {
1163 try
1164 {
1165 URI uri(things[i]);
1166 gboolean isAbs = g_path_is_absolute( things[i] );
1167 gchar *str = uri.toString();
1168 g_message( "abs:%d isRel:%d scheme:[%s] path:[%s][%s] uri[%s] / [%s]", (int)isAbs,
1169 (int)uri.isRelative(),
1170 uri.getScheme(),
1171 uri.getPath(),
1172 uri.getOpaque(),
1173 things[i],
1174 str );
1175 g_free(str);
1176 }
1177 catch ( MalformedURIException err )
1178 {
1179 dump_str( things[i], "MalformedURIException" );
1180 xmlChar *redo = xmlURIEscape((xmlChar const *)things[i]);
1181 g_message(" gone from [%s] to [%s]", things[i], redo );
1182 if ( redo == NULL )
1183 {
1184 URI again = URI::fromUtf8( things[i] );
1185 gboolean isAbs = g_path_is_absolute( things[i] );
1186 gchar *str = again.toString();
1187 g_message( "abs:%d isRel:%d scheme:[%s] path:[%s][%s] uri[%s] / [%s]", (int)isAbs,
1188 (int)again.isRelative(),
1189 again.getScheme(),
1190 again.getPath(),
1191 again.getOpaque(),
1192 things[i],
1193 str );
1194 g_free(str);
1195 g_message(" ----");
1196 }
1197 }
1198 }
1199 g_message("+------");
1200 }
1202 GSList const *images = sp_document_get_resource_list(doc, "image");
1203 for (GSList const *l = images; l != NULL; l = l->next) {
1204 Inkscape::XML::Node *ir = SP_OBJECT_REPR(l->data);
1206 const gchar *href = ir->attribute("xlink:href");
1208 // First try to figure out an absolute path to the asset
1209 //g_message("image href [%s]", href );
1210 if (spns && !g_path_is_absolute(href)) {
1211 const gchar *absref = ir->attribute("sodipodi:absref");
1212 const gchar *base_href = g_build_filename(base, href, NULL);
1213 //g_message(" absr [%s]", absref );
1215 if ( absref && Inkscape::IO::file_test(absref, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_test(base_href, G_FILE_TEST_EXISTS))
1216 {
1217 // only switch over if the absref is valid while href is not
1218 href = absref;
1219 //g_message(" copied absref to href");
1220 }
1221 }
1223 // Once we have an absolute path, convert it relative to the new location
1224 if (href && g_path_is_absolute(href)) {
1225 const gchar *relname = sp_relative_path_from_path(href, base);
1226 //g_message(" setting to [%s]", relname );
1227 ir->setAttribute("xlink:href", relname);
1228 }
1229 // TODO next refinement is to make the first choice keeping the relative path as-is if
1230 // based on the new location it gives us a valid file.
1231 }
1232 }
1235 /*
1236 Local Variables:
1237 mode:c++
1238 c-file-style:"stroustrup"
1239 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1240 indent-tabs-mode:nil
1241 fill-column:99
1242 End:
1243 */
1244 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :