1 /* Dia -- a diagram creation/manipulation program
2  * Copyright (C) 1998 Alexander Larsson
3  *
4  * filedlg.c: some dialogs for saving/loading/exporting files.
5  * Copyright (C) 1999 James Henstridge
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21 
22 #include <config.h>
23 
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <string.h>
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30 #include <stdio.h>
31 #include <glib/gstdio.h>
32 
33 #undef GTK_DISABLE_DEPRECATED /* gtk_file_chooser_dialog_new_with_backend */
34 #include <gtk/gtk.h>
35 #include "intl.h"
36 #include "filter.h"
37 #include "dia_dirs.h"
38 #include "persistence.h"
39 #include "display.h"
40 #include "message.h"
41 #include "layer_dialog.h"
42 #include "load_save.h"
43 #include "preferences.h"
44 #include "interface.h"
45 #include "recent_files.h"
46 #include "confirm.h"
47 
48 #include "filedlg.h"
49 
50 static GtkWidget *opendlg = NULL;
51 static GtkWidget *savedlg = NULL;
52 static GtkWidget *exportdlg = NULL;
53 
54 static void
toggle_compress_callback(GtkWidget * widget)55 toggle_compress_callback(GtkWidget *widget)
56 {
57   /* Changes prefs exactly when the user toggles the setting, i.e.
58    * the setting really remembers what the user chose last time, but
59    * lets diagrams of the opposite kind stay that way unless the user
60    * intervenes.
61    */
62   prefs.new_diagram.compress_save =
63     gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
64 }
65 
66 /**
67  * Given an import filter index and optionally a filename for fallback
68  * return the import filter to use
69  */
70 static DiaImportFilter *
ifilter_by_index(int index,const char * filename)71 ifilter_by_index (int index, const char* filename)
72 {
73   DiaImportFilter *ifilter = NULL;
74 
75   if (index >= 0)
76     ifilter = g_list_nth_data (filter_get_import_filters(), index);
77   else if (filename) /* fallback, should not happen */
78     ifilter = filter_guess_import_filter(filename);
79 
80   return ifilter;
81 }
82 
83 typedef void* (* FilterGuessFunc) (const gchar* filename);
84 
85 /**
86  * Respond to the file chooser filter facility, that is match
87  * the extension of a given filename to the selected filter
88  */
89 static gboolean
matching_extensions_filter(const GtkFileFilterInfo * fi,gpointer data)90 matching_extensions_filter (const GtkFileFilterInfo* fi,
91                             gpointer               data)
92 {
93   FilterGuessFunc guess_func = (FilterGuessFunc)data;
94 
95   g_assert (guess_func);
96 
97   if (!fi->filename)
98     return 0; /* filter it, IMO should not happen --hb */
99 
100   if (guess_func (fi->filename))
101      return 1;
102 
103   return 0;
104 }
105 
106 /**
107  * React on the diagram::removed signal by destroying the dialog
108  *
109  * This function isn't used cause it conflicts with the pattern introduced:
110  * instead of destroying the dialog with the diagram, the dialog is keeping
111  * a refernce to it. As a result we could access the diagram even when the
112  * display of it is gone ...
113  */
114 #if 0
115 static void
116 diagram_removed (Diagram* dia, GtkWidget* dialog)
117 {
118   g_return_if_fail (DIA_IS_DIAGRAM (dia));
119   g_return_if_fail (GTK_IS_WIDGET (dialog));
120 
121   gtk_widget_destroy (dialog);
122 }
123 #endif
124 
125 static GtkFileFilter *
build_gtk_file_filter_from_index(int index)126 build_gtk_file_filter_from_index (int index)
127 {
128   DiaImportFilter *ifilter = NULL;
129   GtkFileFilter *filter = NULL;
130 
131   ifilter = g_list_nth_data (filter_get_import_filters(), index-1);
132   if (ifilter) {
133     GString *pattern = g_string_new ("*.");
134     int i = 0;
135 
136     filter = gtk_file_filter_new ();
137 
138     while (ifilter->extensions[i] != NULL) {
139       if (i != 0)
140         g_string_append (pattern, "|*.");
141       g_string_append (pattern, ifilter->extensions[i]);
142       ++i;
143     }
144     gtk_file_filter_set_name (filter, _("Supported Formats"));
145     gtk_file_filter_add_pattern (filter, pattern->str);
146 
147     g_string_free (pattern, TRUE);
148 
149   } else {
150     /* match the other selections extension */
151     filter = gtk_file_filter_new ();
152     gtk_file_filter_set_name (filter, _("Supported Formats"));
153     gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_FILENAME,
154                                 matching_extensions_filter, filter_guess_import_filter, NULL);
155   }
156   return filter;
157 }
158 
159 static void
import_adapt_extension_callback(GtkWidget * widget)160 import_adapt_extension_callback(GtkWidget *widget)
161 {
162   int index = gtk_combo_box_get_active (GTK_COMBO_BOX(widget));
163   GtkFileFilter *former = NULL;
164   GSList *list, *elem;
165 
166   list = gtk_file_chooser_list_filters (GTK_FILE_CHOOSER (opendlg));
167   for (elem = list; elem != NULL; elem = g_slist_next (elem))
168     if (strcmp (_("Supported Formats"), gtk_file_filter_get_name (GTK_FILE_FILTER(elem->data))) == 0)
169       former = GTK_FILE_FILTER(elem->data);
170   g_slist_free (list);
171 
172   if (former) {
173     /* replace the previous filter */
174     GtkFileFilter *filter = build_gtk_file_filter_from_index (index);
175     gtk_file_chooser_remove_filter (GTK_FILE_CHOOSER (opendlg), former);
176     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (opendlg), filter);
177     gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (opendlg), filter);
178   }
179 }
180 
181 /**
182  * Create the combobox menu to select Import Filter options
183  */
184 static GtkWidget *
create_open_menu(void)185 create_open_menu(void)
186 {
187   GtkWidget *menu;
188   GList *tmp;
189 
190 
191   menu = gtk_combo_box_new_text ();
192   gtk_combo_box_append_text(GTK_COMBO_BOX(menu), _("By extension"));
193 
194   for (tmp = filter_get_import_filters(); tmp != NULL; tmp = tmp->next) {
195     DiaImportFilter *ifilter = tmp->data;
196     gchar *filter_label;
197 
198     if (!ifilter)
199       continue;
200     filter_label = filter_get_import_filter_label(ifilter);
201     gtk_combo_box_append_text (GTK_COMBO_BOX(menu), filter_label);
202     g_free(filter_label);
203   }
204   g_signal_connect(GTK_OBJECT(menu), "changed",
205 	           G_CALLBACK(import_adapt_extension_callback), NULL);
206   return menu;
207 }
208 
209 /**
210  * Respond to the user finishing the Open Dialog either accept or cancel/destroy
211  */
212 static void
file_open_response_callback(GtkWidget * fs,gint response,gpointer user_data)213 file_open_response_callback(GtkWidget *fs,
214                             gint       response,
215                             gpointer   user_data)
216 {
217   char *filename;
218   Diagram *diagram = NULL;
219 
220   if (response == GTK_RESPONSE_ACCEPT) {
221     gint index = gtk_combo_box_get_active (GTK_COMBO_BOX(user_data));
222 
223     if (index >= 0) /* remember it */
224       persistence_set_integer ("import-filter", index);
225     filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs));
226 
227     diagram = diagram_load(filename, ifilter_by_index (index - 1, filename));
228 
229     g_free (filename);
230 
231     if (diagram != NULL) {
232       diagram_update_extents(diagram);
233       layer_dialog_set_diagram(diagram);
234 
235       if (diagram->displays == NULL) {
236 /*	GSList *displays = diagram->displays;
237 	GSList *displays_head = displays;
238 	diagram->displays = NULL;
239 	for (; displays != NULL; displays = g_slist_next(displays)) {
240 	  DDisplay *loaded_display = (DDisplay *)displays->data;
241 	  copy_display(loaded_display);
242 	  g_free(loaded_display);
243 	}
244 	g_slist_free(displays_head);
245       } else {
246 */
247 	new_display(diagram);
248       }
249     }
250   }
251   gtk_widget_destroy(opendlg);
252 }
253 
254 /**
255  * Handle menu click File/Open
256  *
257  * This is either with or without diagram
258  */
259 void
file_open_callback(gpointer data,guint action,GtkWidget * widget)260 file_open_callback(gpointer data, guint action, GtkWidget *widget)
261 {
262   if (!opendlg) {
263     DDisplay *ddisp;
264     Diagram *dia = NULL;
265     GtkWindow *parent_window;
266     gchar *filename = NULL;
267 
268     /* FIXME: we should not use ddisp_active but instead get the current diagram
269      * from caller. Thus we could offer the option to "load into" if invoked by
270      * <Display/File/Open. It wouldn't make any sense if invoked by
271      * <Toolbox>/File/Open ...
272      */
273     ddisp = ddisplay_active();
274     if (ddisp) {
275       dia = ddisp->diagram;
276       parent_window = GTK_WINDOW(ddisp->shell);
277     } else {
278       parent_window = GTK_WINDOW(interface_get_toolbox_shell());
279     }
280     persistence_register_integer ("import-filter", 0);
281     opendlg = gtk_file_chooser_dialog_new_with_backend(_("Open Diagram"), parent_window,
282 					  GTK_FILE_CHOOSER_ACTION_OPEN,
283 					  "default", /* default, not gnome-vfs */
284 					  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
285 					  GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
286 					  NULL);
287     gtk_dialog_set_default_response(GTK_DIALOG(opendlg), GTK_RESPONSE_ACCEPT);
288     gtk_window_set_role(GTK_WINDOW(opendlg), "open_diagram");
289     if (dia && dia->filename)
290       filename = g_filename_from_utf8(dia->filename, -1, NULL, NULL, NULL);
291     if (filename != NULL) {
292       char* fnabs = dia_get_absolute_filename (filename);
293       if (fnabs)
294         gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(opendlg), fnabs);
295       g_free(fnabs);
296       g_free(filename);
297     }
298     g_signal_connect(GTK_OBJECT(opendlg), "destroy",
299 		     G_CALLBACK(gtk_widget_destroyed), &opendlg);
300   } else {
301     gtk_widget_set_sensitive(opendlg, TRUE);
302     if (GTK_WIDGET_VISIBLE(opendlg))
303       return;
304   }
305   if (!gtk_file_chooser_get_extra_widget(GTK_FILE_CHOOSER(opendlg))) {
306     GtkWidget *hbox, *label, *omenu, *options;
307     GtkFileFilter* filter;
308 
309     options = gtk_frame_new(_("Open Options"));
310     gtk_frame_set_shadow_type(GTK_FRAME(options), GTK_SHADOW_ETCHED_IN);
311 
312     hbox = gtk_hbox_new(FALSE, 1);
313     gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
314     gtk_container_add(GTK_CONTAINER(options), hbox);
315     gtk_widget_show(hbox);
316 
317     label = gtk_label_new (_("Determine file type:"));
318     gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
319     gtk_widget_show (label);
320 
321     omenu = create_open_menu();
322     gtk_box_pack_start(GTK_BOX(hbox), omenu, TRUE, TRUE, 0);
323     gtk_widget_show(omenu);
324 
325     gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(opendlg),
326 				      options);
327 
328     gtk_widget_show(options);
329     g_signal_connect(GTK_OBJECT(opendlg), "response",
330 		     G_CALLBACK(file_open_response_callback), omenu);
331 
332     /* set up the gtk file (name) filters */
333     /* 0 = by extension */
334     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (opendlg),
335 	                         build_gtk_file_filter_from_index (0));
336     filter = gtk_file_filter_new ();
337     gtk_file_filter_set_name (filter, _("All Files"));
338     gtk_file_filter_add_pattern (filter, "*");
339     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (opendlg), filter);
340 
341     gtk_combo_box_set_active (GTK_COMBO_BOX (omenu), persistence_get_integer ("import-filter"));
342   }
343 
344   gtk_widget_show(opendlg);
345 }
346 
347 /**
348  * Respond to a button press (also destroy) in the save as dialog.
349  */
350 static void
file_save_as_response_callback(GtkWidget * fs,gint response,gpointer user_data)351 file_save_as_response_callback(GtkWidget *fs,
352                                gint       response,
353                                gpointer   user_data)
354 {
355   char *filename;
356   Diagram *dia;
357   struct stat stat_struct;
358 
359   if (response == GTK_RESPONSE_ACCEPT) {
360     dia = g_object_get_data (G_OBJECT(fs), "user_data");
361 
362     filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs));
363     if (!filename) {
364       /* Not getting a filename looks like a contract violation in Gtk+ to me.
365        * Still Dia would be crashing (bug #651949) - instead simply go back to the dialog. */
366       gtk_window_present (GTK_WINDOW (fs));
367       return;
368     }
369 
370     if (g_stat(filename, &stat_struct) == 0) {
371       GtkWidget *dialog = NULL;
372       char *utf8filename = NULL;
373       if (!g_utf8_validate(filename, -1, NULL)) {
374 	utf8filename = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
375 	if (utf8filename == NULL) {
376 	  message_warning(_("Some characters in the filename are neither UTF-8\n"
377 			    "nor your local encoding.\nSome things will break."));
378 	}
379       }
380       if (utf8filename == NULL) utf8filename = g_strdup(filename);
381 
382 
383       dialog = gtk_message_dialog_new (GTK_WINDOW(fs),
384 				       GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
385 				       GTK_BUTTONS_YES_NO,
386 				       _("File already exists"));
387       gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
388         _("The file '%s' already exists.\n"
389           "Do you want to overwrite it?"), utf8filename);
390       g_free(utf8filename);
391       gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
392 
393       if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_YES) {
394 	/* don't hide/destroy the dialog, but simply go back to it */
395 	gtk_window_present (GTK_WINDOW (fs));
396 	gtk_widget_destroy(dialog);
397         g_free (filename);
398 	return;
399       }
400       gtk_widget_destroy(dialog);
401     }
402 
403     dia->data->is_compressed = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(user_data));
404 
405     diagram_update_extents(dia);
406 
407     diagram_set_filename(dia, filename);
408     if (diagram_save(dia, filename))
409       recent_file_history_add(filename);
410 
411     g_free (filename);
412   }
413   /* if we have our own reference, drop it before destroy */
414   if ((dia = g_object_get_data (G_OBJECT(fs), "user_data")) != NULL) {
415     g_object_set_data (G_OBJECT(fs), "user_data", NULL);
416     g_object_unref (dia);
417   }
418   gtk_widget_destroy(GTK_WIDGET(fs));
419 }
420 
421 /**
422  * Respond to the File/Save As.. menu
423  *
424  * We have only one file save dialog at a time. So if the dialog alread exists
425  * and the user tries to Save as once more only the diagram refernced will
426  * change. Maybe we should also indicate the refernced diagram in the dialog.
427  */
428 void
file_save_as_callback(gpointer data,guint action,GtkWidget * widget)429 file_save_as_callback(gpointer data, guint action, GtkWidget *widget)
430 {
431   DDisplay *ddisp;
432   Diagram *dia;
433   gchar *filename = NULL;
434 
435   ddisp = ddisplay_active();
436   if (!ddisp) return;
437   dia = ddisp->diagram;
438 
439   if (!savedlg) {
440     GtkWidget *compressbutton;
441 
442     savedlg = gtk_file_chooser_dialog_new_with_backend(_("Save Diagram"),
443 					  GTK_WINDOW(ddisp->shell),
444 					  GTK_FILE_CHOOSER_ACTION_SAVE,
445 					  "gtk+", /* default, not gnome-vfs */
446 					  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
447 					  GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
448 					  NULL);
449     gtk_dialog_set_default_response(GTK_DIALOG(savedlg), GTK_RESPONSE_ACCEPT);
450     gtk_window_set_role(GTK_WINDOW(savedlg), "save_diagram");
451     /* Need better way to make it a reasonable size.  Isn't there some*/
452     /* standard look for them (or is that just Gnome?)*/
453     compressbutton = gtk_check_button_new_with_label(_("Compress diagram files"));
454     gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(savedlg),
455 				      compressbutton);
456     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compressbutton),
457 				 dia->data->is_compressed);
458     g_signal_connect(G_OBJECT(compressbutton), "toggled",
459 		     G_CALLBACK(toggle_compress_callback), NULL);
460     gtk_widget_show(compressbutton);
461 #if GTK_CHECK_VERSION (2,12,0)
462     gtk_widget_set_tooltip_text (compressbutton,
463 			 _("Compression reduces file size to less than 1/10th "
464 			   "size and speeds up loading and saving.  Some text "
465 			   "programs cannot manipulate compressed files."));
466 #else
467     gtk_tooltips_set_tip(tool_tips, compressbutton,
468 			 _("Compression reduces file size to less than 1/10th "
469 			   "size and speeds up loading and saving.  Some text "
470 			   "programs cannot manipulate compressed files."), NULL);
471 #endif
472     g_signal_connect (GTK_FILE_CHOOSER(savedlg),
473 		      "response", G_CALLBACK(file_save_as_response_callback), compressbutton);
474     g_signal_connect(GTK_OBJECT(savedlg), "destroy",
475 		     G_CALLBACK(gtk_widget_destroyed), &savedlg);
476   } else {
477     GtkWidget *compressbutton = gtk_file_chooser_get_extra_widget(GTK_FILE_CHOOSER(savedlg));
478     gtk_widget_set_sensitive(savedlg, TRUE);
479     g_signal_handlers_block_by_func(G_OBJECT(compressbutton), toggle_compress_callback, NULL);
480     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compressbutton),
481 				 dia->data->is_compressed);
482     g_signal_handlers_unblock_by_func(G_OBJECT(compressbutton), toggle_compress_callback, NULL);
483     if (g_object_get_data (G_OBJECT (savedlg), "user_data") != NULL)
484       g_object_unref (g_object_get_data (G_OBJECT (savedlg), "user_data"));
485     if (GTK_WIDGET_VISIBLE(savedlg)) {
486       /* keep a refernce to the diagram */
487       g_object_ref(dia);
488       g_object_set_data (G_OBJECT (savedlg), "user_data", dia);
489       gtk_window_present (GTK_WINDOW(savedlg));
490       return;
491     }
492   }
493   if (dia && dia->filename)
494     filename = g_filename_from_utf8(dia->filename, -1, NULL, NULL, NULL);
495   if (filename != NULL) {
496     char* fnabs = dia_get_absolute_filename (filename);
497     if (fnabs) {
498       gchar *base = g_path_get_basename(dia->filename);
499       gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(savedlg), fnabs);
500       /* FileChooser api insist on exiting files for set_filename  */
501       /* ... and does not use filename encoding on this one. */
502       gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(savedlg), base);
503       g_free(base);
504     }
505     g_free(fnabs);
506     g_free(filename);
507   }
508   g_object_ref(dia);
509   g_object_set_data (G_OBJECT (savedlg), "user_data", dia);
510 
511   gtk_widget_show(savedlg);
512 }
513 
514 /**
515  * Respond to the File/Save menu entry.
516  *
517  * Delegates to Save As if there is no filename set yet.
518  */
519 void
file_save_callback(gpointer data,guint action,GtkWidget * widget)520 file_save_callback(gpointer data, guint action, GtkWidget *widget)
521 {
522   Diagram *diagram;
523 
524   diagram = ddisplay_active_diagram();
525   if (!diagram) return;
526 
527   if (diagram->unsaved) {
528     file_save_as_callback(data, action, widget);
529   } else {
530     gchar *filename = g_filename_from_utf8(diagram->filename, -1, NULL, NULL, NULL);
531     diagram_update_extents(diagram);
532     if (diagram_save(diagram, filename))
533       recent_file_history_add(filename);
534     g_free (filename);
535   }
536 }
537 
538 /**
539  * Given an export filter index return the export filter to use
540  */
541 static DiaExportFilter *
efilter_by_index(int index,const gchar ** ext)542 efilter_by_index (int index, const gchar** ext)
543 {
544   DiaExportFilter *efilter = NULL;
545 
546   /* the index in the selection list *is* the index of the filter,
547    * filters supporing multiple formats are multiple times in the list */
548   if (index >= 0) {
549     efilter = g_list_nth_data (filter_get_export_filters(), index);
550     if (efilter) {
551       if (ext)
552         *ext = efilter->extensions[0];
553       return efilter;
554     }
555     else /* getting here means invalid index */
556       g_warning ("efilter_by_index() index=%d out of range", index);
557   }
558 
559   return efilter;
560 }
561 
562 /**
563  * Adapt the filename to the export filter index
564  */
565 static void
export_adapt_extension(const gchar * name,int index)566 export_adapt_extension (const gchar* name, int index)
567 {
568   const gchar* ext = NULL;
569   DiaExportFilter *efilter = efilter_by_index (index, &ext);
570   gchar *basename = g_path_get_basename (name);
571   gchar *utf8_name = NULL;
572 
573   if (!efilter || !ext)
574     utf8_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
575   else {
576     const gchar *last_dot = strrchr(basename, '.');
577     GString *s = g_string_new(basename);
578     if (last_dot)
579       g_string_truncate(s, last_dot-basename);
580     g_string_append(s, ".");
581     g_string_append(s, ext);
582     utf8_name = g_filename_to_utf8 (s->str, -1, NULL, NULL, NULL);
583     g_string_free (s, TRUE);
584   }
585   gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(exportdlg), utf8_name);
586   g_free (utf8_name);
587   g_free (basename);
588 }
589 static void
export_adapt_extension_callback(GtkWidget * widget)590 export_adapt_extension_callback(GtkWidget *widget)
591 {
592   int index = gtk_combo_box_get_active (GTK_COMBO_BOX(widget));
593   gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(exportdlg));
594 
595   if (name && index > 0) /* Ignore "By Extension" */
596     export_adapt_extension (name, index - 1);
597   g_free (name);
598 }
599 
600 /**
601  * Create a new "option menu" for the export options
602  */
603 static GtkWidget *
create_export_menu(void)604 create_export_menu(void)
605 {
606   GtkWidget *menu;
607   GList *tmp;
608 
609   menu = gtk_combo_box_new_text ();
610   gtk_combo_box_append_text(GTK_COMBO_BOX(menu), _("By extension"));
611 
612   for (tmp = filter_get_export_filters(); tmp != NULL; tmp = tmp->next) {
613     DiaExportFilter *ef = tmp->data;
614     gchar *filter_label;
615 
616     if (!ef)
617       continue;
618     filter_label = filter_get_export_filter_label(ef);
619     gtk_combo_box_append_text (GTK_COMBO_BOX(menu), filter_label);
620     g_free(filter_label);
621   }
622   g_signal_connect(GTK_OBJECT(menu), "changed",
623 	           G_CALLBACK(export_adapt_extension_callback), NULL);
624   return menu;
625 }
626 
627 /**
628  * A button hit in the Export Dialog
629  */
630 static void
file_export_response_callback(GtkWidget * fs,gint response,gpointer user_data)631 file_export_response_callback(GtkWidget *fs,
632                               gint       response,
633                               gpointer   user_data)
634 {
635   char *filename;
636   Diagram *dia;
637   DiaExportFilter *ef;
638   struct stat statbuf;
639 
640   dia = g_object_get_data (G_OBJECT (fs), "user_data");
641   g_assert (dia);
642 
643   if (response == GTK_RESPONSE_ACCEPT) {
644     gint index;
645 
646     diagram_update_extents(dia);
647 
648     filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs));
649 
650     if (g_stat(filename, &statbuf) == 0) {
651       GtkWidget *dialog = NULL;
652 
653       dialog = gtk_message_dialog_new (GTK_WINDOW(fs),
654 				       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
655 				       GTK_MESSAGE_QUESTION,
656 				       GTK_BUTTONS_YES_NO,
657 				       _("File already exists"));
658       gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
659         _("The file '%s' already exists.\n"
660         "Do you want to overwrite it?"), dia_message_filename(filename));
661       gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
662 
663       if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_YES) {
664 	/* if not overwrite allow to select another filename */
665 	gtk_widget_destroy(dialog);
666 	g_free (filename);
667 	return;
668       }
669       gtk_widget_destroy(dialog);
670     }
671 
672     index = gtk_combo_box_get_active (GTK_COMBO_BOX(user_data));
673     if (index >= 0)
674       persistence_set_integer ("export-filter", index);
675     ef = efilter_by_index (index - 1, NULL);
676     if (!ef)
677       ef = filter_guess_export_filter(filename);
678     if (ef) {
679       g_object_ref(dia->data);
680       ef->export_func(dia->data, filename, dia->filename, ef->user_data);
681       g_object_unref(dia->data);
682     } else
683       message_error(_("Could not determine which export filter\n"
684 		      "to use to save '%s'"), dia_message_filename(filename));
685     g_free (filename);
686   }
687   g_object_unref (dia); /* drop our diagram reference */
688   gtk_widget_destroy(exportdlg);
689 }
690 
691 /**
692  * React to <Display>/File/Export
693  */
694 void
file_export_callback(gpointer data,guint action,GtkWidget * widget)695 file_export_callback(gpointer data, guint action, GtkWidget *widget)
696 {
697   DDisplay *ddisp;
698   Diagram *dia;
699   gchar *filename = NULL;
700 
701   ddisp = ddisplay_active();
702   if (!ddisp) return;
703   dia = ddisp->diagram;
704 
705   if (!confirm_export_size (dia, GTK_WINDOW(ddisp->shell), CONFIRM_MEMORY|CONFIRM_PAGES))
706     return;
707 
708   if (!exportdlg) {
709     persistence_register_integer ("export-filter", 0);
710     exportdlg = gtk_file_chooser_dialog_new_with_backend(_("Export Diagram"),
711 					    GTK_WINDOW(ddisp->shell),
712 					    GTK_FILE_CHOOSER_ACTION_SAVE,
713 					    "gtk+", /* default, not gnome-vfs */
714 					    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
715 					    GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
716 					    NULL);
717     gtk_dialog_set_default_response(GTK_DIALOG(exportdlg), GTK_RESPONSE_ACCEPT);
718     gtk_window_set_role(GTK_WINDOW(exportdlg), "export_diagram");
719     g_signal_connect(GTK_OBJECT(exportdlg), "destroy",
720 		     G_CALLBACK(gtk_widget_destroyed), &exportdlg);
721   }
722   if (!gtk_file_chooser_get_extra_widget(GTK_FILE_CHOOSER(exportdlg))) {
723     GtkWidget *hbox, *label, *omenu, *options;
724     GtkFileFilter* filter;
725 
726     options = gtk_frame_new(_("Export Options"));
727     gtk_frame_set_shadow_type(GTK_FRAME(options), GTK_SHADOW_ETCHED_IN);
728 
729     hbox = gtk_hbox_new(FALSE, 1);
730     gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
731     gtk_container_add(GTK_CONTAINER(options), hbox);
732     gtk_widget_show(hbox);
733 
734     label = gtk_label_new (_("Determine file type:"));
735     gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
736     gtk_widget_show (label);
737 
738     omenu = create_export_menu();
739     gtk_box_pack_start(GTK_BOX(hbox), omenu, TRUE, TRUE, 0);
740     gtk_widget_show(omenu);
741     g_object_set_data(G_OBJECT(exportdlg), "export-menu", omenu);
742 
743     gtk_widget_show(options);
744     gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(exportdlg), options);
745     /* set up file filters */
746     filter = gtk_file_filter_new ();
747     gtk_file_filter_set_name (filter, _("All Files"));
748     gtk_file_filter_add_pattern (filter, "*");
749     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (exportdlg), filter);
750     /* match the other selections extension */
751     filter = gtk_file_filter_new ();
752     gtk_file_filter_set_name (filter, _("Supported Formats"));
753     gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_FILENAME,
754                                 matching_extensions_filter, filter_guess_export_filter, NULL);
755     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (exportdlg), filter);
756 
757     gtk_combo_box_set_active (GTK_COMBO_BOX (omenu), persistence_get_integer ("export-filter"));
758 
759     g_signal_connect(GTK_FILE_CHOOSER(exportdlg),
760 		     "response", G_CALLBACK(file_export_response_callback), omenu);
761   }
762   if (g_object_get_data (G_OBJECT(exportdlg), "user_data"))
763     g_object_unref (g_object_get_data (G_OBJECT(exportdlg), "user_data"));
764   g_object_ref(dia);
765   g_object_set_data (G_OBJECT (exportdlg), "user_data", dia);
766   gtk_widget_set_sensitive(exportdlg, TRUE);
767 
768   if (dia && dia->filename)
769     filename = g_filename_from_utf8(dia->filename, -1, NULL, NULL, NULL);
770   if (filename != NULL) {
771     char* fnabs = dia_get_absolute_filename (filename);
772     if (fnabs) {
773       char *folder = g_path_get_dirname (fnabs);
774       char *basename = g_path_get_basename (fnabs);
775       /* can't use gtk_file_chooser_set_filename for various reasons, see e.g. bug #305850 */
776       gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(exportdlg), folder);
777       export_adapt_extension (basename, persistence_get_integer ("export-filter") - 1);
778       g_free (folder);
779       g_free (basename);
780     }
781     g_free(fnabs);
782     g_free(filename);
783   }
784 
785   gtk_widget_show(exportdlg);
786 }
787