]> www.fi.muni.cz Git - evince.git/blob - cut-n-paste/fileformatchooser/eggfileformatchooser.c
Allow exporting images in any format supported by GdkPixbuf. Fixes bug
[evince.git] / cut-n-paste / fileformatchooser / eggfileformatchooser.c
1 /* EggFileFormatChooser
2  * Copyright (C) 2007 Mathias Hasselmann
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 #include "eggfileformatchooser.h"
20 #include "egg-macros.h"
21
22 #include <glib/gi18n.h>
23 #include <gtk/gtk.h>
24 #include <string.h>
25 #include <ctype.h>
26
27 typedef struct _EggFileFormatFilterInfo EggFileFormatFilterInfo;
28 typedef struct _EggFileFormatSearch EggFileFormatSearch;
29
30 enum 
31 {
32   MODEL_COLUMN_ID,
33   MODEL_COLUMN_NAME,
34   MODEL_COLUMN_ICON,
35   MODEL_COLUMN_EXTENSIONS,
36   MODEL_COLUMN_FILTER,
37   MODEL_COLUMN_DATA,
38   MODEL_COLUMN_DESTROY
39 };
40
41 enum 
42 {
43   SIGNAL_SELECTION_CHANGED,
44   SIGNAL_LAST
45 };
46
47 struct _EggFileFormatChooserPrivate
48 {
49   GtkTreeStore *model;
50   GtkTreeSelection *selection;
51   guint idle_hack;
52   guint last_id;
53
54   GtkFileChooser *chooser;
55   GtkFileFilter *all_files;
56   GtkFileFilter *supported_files;
57 };
58
59 struct _EggFileFormatFilterInfo
60 {
61   GHashTable *extension_set;
62   GSList *extension_list;
63   gboolean show_extensions;
64   gchar *name;
65 };
66
67 struct _EggFileFormatSearch
68 {
69   gboolean success;
70   GtkTreeIter iter;
71
72   guint format;
73   const gchar *extension;
74 };
75
76 static guint signals[SIGNAL_LAST];
77
78 G_DEFINE_TYPE (EggFileFormatChooser, 
79                egg_file_format_chooser,
80                GTK_TYPE_EXPANDER);
81 EGG_DEFINE_QUARK (EggFileFormatFilterInfo,
82                   egg_file_format_filter_info);
83
84 static EggFileFormatFilterInfo*
85 egg_file_format_filter_info_new (const gchar *name,
86                                  gboolean     show_extensions)
87 {
88   EggFileFormatFilterInfo *self;
89
90   self = g_new0 (EggFileFormatFilterInfo, 1);
91   self->extension_set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
92   self->show_extensions = show_extensions;
93   self->name = g_strdup (name);
94
95   return self;
96 }
97
98 static void
99 egg_file_format_filter_info_free (gpointer boxed)
100 {
101   EggFileFormatFilterInfo *self;
102
103   if (boxed)
104     {
105       self = boxed;
106
107       g_hash_table_unref (self->extension_set);
108       g_slist_foreach (self->extension_list, (GFunc) g_free, NULL);
109       g_slist_free (self->extension_list);
110       g_free (self->name);
111       g_free (self);
112     }
113 }
114
115 static gboolean
116 egg_file_format_filter_find (gpointer key,
117                              gpointer value G_GNUC_UNUSED,
118                              gpointer data)
119 {
120   const GtkFileFilterInfo *info = data;
121   const gchar *pattern = key;
122
123   return g_str_has_suffix (info->filename, pattern + 1);
124 }
125
126 static gboolean
127 egg_file_format_filter_filter (const GtkFileFilterInfo *info,
128                                gpointer                 data)
129 {
130   EggFileFormatFilterInfo *self = data;
131
132   return NULL != g_hash_table_find (self->extension_set,
133                                     egg_file_format_filter_find,
134                                     (gpointer) info);
135 }
136
137 static GtkFileFilter*
138 egg_file_format_filter_new (const gchar *name,
139                             gboolean     show_extensions)
140 {
141   GtkFileFilter *filter;
142   EggFileFormatFilterInfo *info;
143
144   filter = gtk_file_filter_new ();
145   gtk_file_filter_set_name (filter, name);
146
147   info = egg_file_format_filter_info_new (name, show_extensions);
148
149   gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_FILENAME,
150                               egg_file_format_filter_filter,
151                               info, NULL);
152   g_object_set_qdata_full (G_OBJECT (filter),
153                            egg_file_format_filter_info_quark (),
154                            info, egg_file_format_filter_info_free);
155
156   return filter;
157 }
158
159 static void
160 egg_file_format_filter_add_extensions (GtkFileFilter *filter,
161                                        const gchar   *extensions)
162 {
163   EggFileFormatFilterInfo *info;
164   GString *filter_name;
165   const gchar *extptr;
166   gchar *pattern;
167   gsize length;
168
169   g_assert (NULL != extensions);
170
171   info = g_object_get_qdata (G_OBJECT (filter),
172                              egg_file_format_filter_info_quark ());
173
174   info->extension_list = g_slist_prepend (info->extension_list,
175                                           g_strdup (extensions));
176
177   if (info->show_extensions)
178     {
179       filter_name = g_string_new (info->name);
180       g_string_append (filter_name, " (");
181     }
182   else
183     filter_name = NULL;
184
185   extptr = extensions;
186   while (*extptr)
187     {
188       length = strcspn (extptr, ",");
189       pattern = g_new (gchar, length + 3);
190
191       memcpy (pattern, "*.", 2);
192       memcpy (pattern + 2, extptr, length);
193       pattern[length + 2] = '\0';
194
195       if (filter_name)
196         {
197           if (extptr != extensions)
198             g_string_append (filter_name, ", ");
199
200           g_string_append (filter_name, pattern);
201         }
202
203       extptr += length;
204
205       if (*extptr)
206         extptr += 2;
207
208       g_hash_table_replace (info->extension_set, pattern, pattern);
209     }
210
211   if (filter_name)
212     {
213       g_string_append (filter_name, ")");
214       gtk_file_filter_set_name (filter, filter_name->str);
215       g_string_free (filter_name, TRUE);
216     }
217 }
218
219 static void
220 selection_changed_cb (GtkTreeSelection     *selection,
221                       EggFileFormatChooser *self)
222 {
223   gchar *label;
224   gchar *name;
225
226   GtkFileFilter *filter;
227   GtkTreeModel *model;
228   GtkTreeIter parent;
229   GtkTreeIter iter;
230
231   if (gtk_tree_selection_get_selected (selection, &model, &iter)) 
232     {
233       gtk_tree_model_get (model, &iter, MODEL_COLUMN_NAME, &name, -1);
234
235       label = g_strdup_printf (_("File _Format: %s"), name);
236       gtk_expander_set_use_underline (GTK_EXPANDER (self), TRUE);
237       gtk_expander_set_label (GTK_EXPANDER (self), label);
238
239       g_free (name);
240       g_free (label);
241
242       if (self->priv->chooser)
243         {
244           while (gtk_tree_model_iter_parent (model, &parent, &iter))
245             iter = parent;
246
247           gtk_tree_model_get (model, &iter, MODEL_COLUMN_FILTER, &filter, -1);
248           gtk_file_chooser_set_filter (self->priv->chooser, filter);
249           g_object_unref (filter);
250         }
251
252       g_signal_emit (self, signals[SIGNAL_SELECTION_CHANGED], 0);
253     }
254 }
255
256 /* XXX This hack is needed, as gtk_expander_set_label seems 
257  * not to work from egg_file_format_chooser_init */
258 static gboolean
259 select_default_file_format (gpointer data)
260 {
261   EggFileFormatChooser *self = EGG_FILE_FORMAT_CHOOSER (data);
262   egg_file_format_chooser_set_format (self, 0);
263   self->priv->idle_hack = 0;
264   return FALSE;
265 }
266
267 static gboolean
268 find_by_format (GtkTreeModel *model,
269                 GtkTreePath  *path G_GNUC_UNUSED,
270                 GtkTreeIter  *iter,
271                 gpointer      data)
272 {
273   EggFileFormatSearch *search = data;
274   guint id;
275
276   gtk_tree_model_get (model, iter, MODEL_COLUMN_ID, &id, -1);
277
278   if (id == search->format)
279     {
280       search->success = TRUE;
281       search->iter = *iter;
282     }
283
284   return search->success;
285 }
286
287 static gboolean
288 find_in_list (gchar       *list,
289               const gchar *needle)
290 {
291   gchar *saveptr;
292   gchar *token;
293
294   for (token = strtok_r (list, ",", &saveptr); NULL != token;
295        token = strtok_r (NULL, ",", &saveptr))
296     {
297       token = g_strstrip (token);
298
299       if (g_str_equal (needle, token))
300         return TRUE;
301     }
302
303   return FALSE;
304 }
305
306 static gboolean
307 accept_filename (gchar       *extensions,
308                  const gchar *filename)
309 {
310   const gchar *extptr;
311   gchar *saveptr;
312   gchar *token;
313   gsize length;
314
315   length = strlen (filename);
316
317   for (token = strtok_r (extensions, ",", &saveptr); NULL != token;
318        token = strtok_r (NULL, ",", &saveptr))
319     {
320       token = g_strstrip (token);
321       extptr = filename + length - strlen (token) - 1;
322
323       if (extptr > filename && '.' == *extptr &&
324           !strcmp (extptr + 1, token))
325           return TRUE;
326     }
327
328   return FALSE;
329 }
330
331 static gboolean
332 find_by_extension (GtkTreeModel *model,
333                    GtkTreePath  *path G_GNUC_UNUSED,
334                    GtkTreeIter  *iter,
335                    gpointer      data)
336 {
337   EggFileFormatSearch *search = data;
338
339   gchar *extensions = NULL;
340   guint format = 0;
341
342   gtk_tree_model_get (model, iter,
343                       MODEL_COLUMN_EXTENSIONS, &extensions,
344                       MODEL_COLUMN_ID, &format,
345                       -1);
346
347   if (extensions && find_in_list (extensions, search->extension))
348     {
349       search->format = format;
350       search->success = TRUE;
351       search->iter = *iter;
352     }
353
354   g_free (extensions);
355   return search->success;
356 }
357
358 static void
359 egg_file_format_chooser_init (EggFileFormatChooser *self)
360 {
361   GtkWidget *scroller;
362   GtkWidget *view;
363
364   GtkTreeViewColumn *column;
365   GtkCellRenderer *cell;
366   GtkTreeIter iter;
367
368   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EGG_TYPE_FILE_FORMAT_CHOOSER, 
369                                             EggFileFormatChooserPrivate);
370
371 /* file filters */
372
373   self->priv->all_files = g_object_ref_sink (gtk_file_filter_new ());
374   gtk_file_filter_set_name (self->priv->all_files, _("All Files"));
375   self->priv->supported_files = egg_file_format_filter_new (_("All Supported Files"), FALSE);
376
377 /* tree model */
378
379   self->priv->model = gtk_tree_store_new (7, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
380                                              GTK_TYPE_FILE_FILTER, G_TYPE_POINTER, G_TYPE_POINTER);
381
382   gtk_tree_store_append (self->priv->model, &iter, NULL);
383   gtk_tree_store_set (self->priv->model, &iter,
384                       MODEL_COLUMN_NAME, _("By Extension"),
385                       MODEL_COLUMN_FILTER, self->priv->supported_files,
386                       MODEL_COLUMN_ID, 0,
387                       -1);
388
389 /* tree view */
390
391   view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (self->priv->model));
392   self->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
393   gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
394
395 /* file format column */
396
397   column = gtk_tree_view_column_new ();
398   gtk_tree_view_column_set_expand (column, TRUE);
399   gtk_tree_view_column_set_title (column, _("File Format"));
400   gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
401
402   cell = gtk_cell_renderer_pixbuf_new ();
403   gtk_tree_view_column_pack_start (column, cell, FALSE);
404   gtk_tree_view_column_set_attributes (column, cell,
405                                        "icon-name", MODEL_COLUMN_ICON,
406                                        NULL);
407
408   cell = gtk_cell_renderer_text_new ();
409   gtk_tree_view_column_pack_start (column, cell, TRUE);
410   gtk_tree_view_column_set_attributes (column, cell,
411                                        "text", MODEL_COLUMN_NAME,
412                                        NULL);
413
414 /* extensions column */
415
416   column = gtk_tree_view_column_new_with_attributes (
417     _("Extension(s)"), gtk_cell_renderer_text_new (),
418     "text", MODEL_COLUMN_EXTENSIONS, NULL);
419   gtk_tree_view_column_set_expand (column, FALSE);
420   gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
421
422 /* selection */
423
424   gtk_tree_selection_set_mode (self->priv->selection, GTK_SELECTION_BROWSE);
425   g_signal_connect (self->priv->selection, "changed",
426                     G_CALLBACK (selection_changed_cb), self);
427   self->priv->idle_hack = g_idle_add (select_default_file_format, self);
428
429 /* scroller */
430
431   scroller = gtk_scrolled_window_new (NULL, NULL);
432   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
433                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
434   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroller),
435                                        GTK_SHADOW_IN);
436   gtk_widget_set_size_request (scroller, -1, 150);
437   gtk_container_add (GTK_CONTAINER (scroller), view);
438   gtk_widget_show_all (scroller);
439
440   gtk_container_add (GTK_CONTAINER (self), scroller);
441 }
442
443 static void
444 reset_model (EggFileFormatChooser *self)
445 {
446   GtkTreeModel *model = GTK_TREE_MODEL (self->priv->model);
447   GtkTreeIter iter;
448
449   if (gtk_tree_model_get_iter_first (model, &iter))
450     {
451       do
452         {
453           GDestroyNotify destroy = NULL;
454           gpointer data = NULL;
455
456           gtk_tree_model_get (model, &iter,
457                               MODEL_COLUMN_DESTROY, &destroy,
458                               MODEL_COLUMN_DATA, &data,
459                               -1);
460
461           if (destroy)
462             destroy (data);
463         }
464       while (gtk_tree_model_iter_next (model, &iter));
465     }
466
467   gtk_tree_store_clear (self->priv->model);
468 }
469
470 static void
471 egg_file_format_chooser_dispose (GObject *obj)
472 {
473   EggFileFormatChooser *self = EGG_FILE_FORMAT_CHOOSER (obj);
474
475   if (NULL != self)
476     {
477       if (self->priv->idle_hack)
478         {
479           g_source_remove (self->priv->idle_hack);
480           self->priv->idle_hack = 0;
481         }
482     }
483
484   G_OBJECT_CLASS (egg_file_format_chooser_parent_class)->dispose (obj);
485 }
486
487 static void
488 egg_file_format_chooser_finalize (GObject *obj)
489 {
490   EggFileFormatChooser *self = EGG_FILE_FORMAT_CHOOSER (obj);
491
492   if (NULL != self)
493     {
494       if (self->priv->model)
495         {
496           reset_model (self);
497
498           g_object_unref (self->priv->model);
499           self->priv->model = NULL;
500
501           g_object_unref (self->priv->all_files);
502           self->priv->all_files = NULL;
503         }
504     }
505
506   G_OBJECT_CLASS (egg_file_format_chooser_parent_class)->finalize (obj);
507 }
508
509 static void
510 filter_changed_cb (GObject    *object,
511                    GParamSpec *spec,
512                    gpointer    data)
513 {
514   EggFileFormatChooser *self;
515
516   GtkFileFilter *current_filter;
517   GtkFileFilter *format_filter;
518
519   GtkTreeModel *model;
520   GtkTreeIter iter;
521   GtkTreeIter parent;
522
523   self = EGG_FILE_FORMAT_CHOOSER (data);
524
525   format_filter = NULL;
526   current_filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (object));
527   model = GTK_TREE_MODEL (self->priv->model);
528
529   if (gtk_tree_selection_get_selected (self->priv->selection, &model, &iter)) 
530     {
531       while (gtk_tree_model_iter_parent (model, &parent, &iter))
532         iter = parent;
533
534       gtk_tree_model_get (model, &iter,
535                           MODEL_COLUMN_FILTER,
536                           &format_filter, -1);
537       g_object_unref (format_filter);
538     }
539
540   if (current_filter && current_filter != format_filter &&
541       gtk_tree_model_get_iter_first (model, &iter))
542     {
543       if (current_filter == self->priv->all_files)
544         format_filter = current_filter;
545       else
546         {
547           format_filter = NULL;
548
549           do
550             {
551               gtk_tree_model_get (model, &iter,
552                                   MODEL_COLUMN_FILTER,
553                                   &format_filter, -1);
554               g_object_unref (format_filter);
555
556               if (format_filter == current_filter)
557                 break;
558             }
559           while (gtk_tree_model_iter_next (model, &iter));
560         }
561
562       if (format_filter)
563         gtk_tree_selection_select_iter (self->priv->selection, &iter);
564     }
565 }
566
567 /* Shows an error dialog set as transient for the specified window */
568 static void
569 error_message_with_parent (GtkWindow  *parent,
570                            const char *msg,
571                            const char *detail)
572 {
573   gboolean first_call = TRUE;
574   GtkWidget *dialog;
575
576   if (first_call)
577     {
578       g_warning ("%s: Merge with the code in Gtk{File,Recent}ChooserDefault.", G_STRLOC);
579       first_call = FALSE;
580     }
581
582   dialog = gtk_message_dialog_new (parent,
583                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
584                                    GTK_MESSAGE_ERROR,
585                                    GTK_BUTTONS_OK,
586                                    "%s",
587                                    msg);
588   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
589                                             "%s", detail);
590
591   if (parent->group)
592     gtk_window_group_add_window (parent->group, GTK_WINDOW (dialog));
593
594   gtk_dialog_run (GTK_DIALOG (dialog));
595   gtk_widget_destroy (dialog);
596 }
597
598 /* Returns a toplevel GtkWindow, or NULL if none */
599 static GtkWindow *
600 get_toplevel (GtkWidget *widget)
601 {
602   GtkWidget *toplevel;
603
604   toplevel = gtk_widget_get_toplevel (widget);
605   if (!GTK_WIDGET_TOPLEVEL (toplevel))
606     return NULL;
607   else
608     return GTK_WINDOW (toplevel);
609 }
610
611 /* Shows an error dialog for the file chooser */
612 static void
613 error_message (EggFileFormatChooser *impl,
614                const char           *msg,
615                const char           *detail)
616 {
617   error_message_with_parent (get_toplevel (GTK_WIDGET (impl)), msg, detail);
618 }
619
620 static void
621 chooser_response_cb (GtkDialog *dialog,
622                      gint       response_id,
623                      gpointer   data)
624 {
625   EggFileFormatChooser *self;
626   gchar *filename, *basename;
627   gchar *message;
628   guint format;
629
630   self = EGG_FILE_FORMAT_CHOOSER (data);
631
632   if (EGG_IS_POSITIVE_RESPONSE (response_id))
633     {
634       filename = gtk_file_chooser_get_filename (self->priv->chooser);
635       basename = g_filename_display_basename (filename);
636       g_free (filename);
637
638       format = egg_file_format_chooser_get_format (self, basename);
639       g_print ("%s: %s - %d\n", G_STRFUNC, basename, format);
640
641       if (0 == format)
642         {
643
644           message = g_strdup_printf (
645             _("The program was not able to find out the file format "
646               "you want to use for `%s'. Please make sure to use a "
647               "known extension for that file or manually choose a "
648               "file format from the list below."),
649               basename);
650
651           error_message (self,
652                          _("File format not recognized"),
653                         message);
654
655           g_free (message);
656
657           g_signal_stop_emission_by_name (dialog, "response");
658         }
659       else
660         {
661           filename = egg_file_format_chooser_append_extension (self, basename, format);
662
663           if (strcmp (filename, basename))
664             {
665               gtk_file_chooser_set_current_name (self->priv->chooser, filename);
666               g_signal_stop_emission_by_name (dialog, "response");
667             }
668
669           g_free (filename);
670         }
671
672       g_free (basename); 
673     }
674
675 }
676
677 static void
678 egg_file_format_chooser_realize (GtkWidget *widget)
679 {
680   EggFileFormatChooser *self;
681   GtkWidget *parent;
682
683   GtkFileFilter *filter;
684   GtkTreeModel *model;
685   GtkTreeIter iter;
686
687   GTK_WIDGET_CLASS (egg_file_format_chooser_parent_class)->realize (widget);
688
689   self = EGG_FILE_FORMAT_CHOOSER (widget);
690
691   g_return_if_fail (NULL == self->priv->chooser);
692
693   parent = gtk_widget_get_toplevel (widget);
694
695   if (!GTK_IS_FILE_CHOOSER (parent))
696     parent = gtk_widget_get_parent (widget);
697
698   while (parent && !GTK_IS_FILE_CHOOSER (parent))
699     parent = gtk_widget_get_parent (parent);
700
701   self->priv->chooser = GTK_FILE_CHOOSER (parent);
702
703   g_return_if_fail (GTK_IS_FILE_CHOOSER (self->priv->chooser));
704   g_return_if_fail (gtk_file_chooser_get_action (self->priv->chooser) ==
705                     GTK_FILE_CHOOSER_ACTION_SAVE);
706
707   g_object_ref (self->priv->chooser);
708
709   g_signal_connect (self->priv->chooser, "notify::filter", 
710                     G_CALLBACK (filter_changed_cb), self);
711   gtk_file_chooser_add_filter (self->priv->chooser, self->priv->all_files);
712
713   model = GTK_TREE_MODEL (self->priv->model);
714
715   if (gtk_tree_model_get_iter_first (model, &iter))
716     {
717       do
718         {
719           gtk_tree_model_get (model, &iter, MODEL_COLUMN_FILTER, &filter, -1);
720           gtk_file_chooser_add_filter (self->priv->chooser, filter);
721           g_object_unref (filter);
722         }
723       while (gtk_tree_model_iter_next (model, &iter));
724     }
725
726   gtk_file_chooser_set_filter (self->priv->chooser,
727                                self->priv->supported_files);
728
729   if (GTK_IS_DIALOG (self->priv->chooser))
730     g_signal_connect (self->priv->chooser, "response",
731                       G_CALLBACK (chooser_response_cb), self);
732 }
733
734 static void
735 egg_file_format_chooser_unrealize (GtkWidget *widget)
736 {
737   EggFileFormatChooser *self;
738
739   GtkFileFilter *filter;
740   GtkTreeModel *model;
741   GtkTreeIter iter;
742
743   GTK_WIDGET_CLASS (egg_file_format_chooser_parent_class)->unrealize (widget);
744
745   self = EGG_FILE_FORMAT_CHOOSER (widget);
746   model = GTK_TREE_MODEL (self->priv->model);
747
748   g_signal_handlers_disconnect_by_func (self->priv->chooser,
749                                         filter_changed_cb, self);
750   g_signal_handlers_disconnect_by_func (self->priv->chooser,
751                                         chooser_response_cb, self);
752
753   if (gtk_tree_model_get_iter_first (model, &iter))
754     {
755       do
756         {
757           gtk_tree_model_get (model, &iter, MODEL_COLUMN_FILTER, &filter, -1);
758           gtk_file_chooser_remove_filter (self->priv->chooser, filter);
759           g_object_unref (filter);
760         }
761       while (gtk_tree_model_iter_next (model, &iter));
762     }
763
764   gtk_file_chooser_remove_filter (self->priv->chooser, self->priv->all_files);
765   g_object_unref (self->priv->chooser);
766 }
767
768 static void
769 egg_file_format_chooser_class_init (EggFileFormatChooserClass *cls)
770 {
771   GObjectClass *object_class = G_OBJECT_CLASS (cls);
772   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (cls);
773
774   g_type_class_add_private (cls, sizeof (EggFileFormatChooserPrivate));
775
776   object_class->dispose = egg_file_format_chooser_dispose;
777   object_class->finalize = egg_file_format_chooser_finalize;
778
779   widget_class->realize = egg_file_format_chooser_realize;
780   widget_class->unrealize = egg_file_format_chooser_unrealize;
781
782   signals[SIGNAL_SELECTION_CHANGED] = g_signal_new (
783     "selection-changed", EGG_TYPE_FILE_FORMAT_CHOOSER, G_SIGNAL_RUN_FIRST,
784     G_STRUCT_OFFSET (EggFileFormatChooserClass, selection_changed),
785     NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
786 }
787
788 GtkWidget*
789 egg_file_format_chooser_new (void)
790 {
791   return g_object_new (EGG_TYPE_FILE_FORMAT_CHOOSER, NULL);
792 }
793
794 static guint
795 egg_file_format_chooser_add_format_impl (EggFileFormatChooser *self,
796                                          guint                 parent,
797                                          const gchar          *name,
798                                          const gchar          *icon,
799                                          const gchar          *extensions)
800 {
801   EggFileFormatSearch search;
802   GtkFileFilter *filter;
803   GtkTreeIter iter;
804
805   search.success = FALSE;
806   search.format = parent;
807   filter = NULL;
808
809   if (parent > 0)
810     {
811       gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->model),
812                               find_by_format, &search);
813       g_return_val_if_fail (search.success, -1);
814     }
815   else
816     filter = egg_file_format_filter_new (name, TRUE);
817
818   gtk_tree_store_append (self->priv->model, &iter, 
819                          parent > 0 ? &search.iter : NULL);
820
821   gtk_tree_store_set (self->priv->model, &iter,
822                       MODEL_COLUMN_ID, ++self->priv->last_id,
823                       MODEL_COLUMN_EXTENSIONS, extensions,
824                       MODEL_COLUMN_FILTER, filter,
825                       MODEL_COLUMN_NAME, name,
826                       MODEL_COLUMN_ICON, icon,
827                       -1);
828
829   if (extensions)
830     {
831       if (parent > 0)
832         gtk_tree_model_get (GTK_TREE_MODEL (self->priv->model), &search.iter,
833                             MODEL_COLUMN_FILTER, &filter, -1);
834
835       egg_file_format_filter_add_extensions (self->priv->supported_files, extensions);
836       egg_file_format_filter_add_extensions (filter, extensions);
837
838       if (parent > 0)
839         g_object_unref (filter);
840     }
841
842   return self->priv->last_id;
843 }
844
845 guint
846 egg_file_format_chooser_add_format (EggFileFormatChooser *self,
847                                     guint                 parent,
848                                     const gchar          *name,
849                                     const gchar          *icon,
850                                     ...)
851 {
852   GString *buffer = NULL;
853   const gchar* extptr;
854   va_list extensions;
855   guint id;
856
857   g_return_val_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self), 0);
858   g_return_val_if_fail (NULL != name, 0);
859
860   va_start (extensions, icon);
861
862   while (NULL != (extptr = va_arg (extensions, const gchar*)))
863     {
864       if (NULL == buffer)
865         buffer = g_string_new (NULL);
866       else
867         g_string_append (buffer, ", ");
868
869       g_string_append (buffer, extptr);
870     }
871
872   va_end (extensions);
873
874   id = egg_file_format_chooser_add_format_impl (self, parent, name, icon,
875                                                 buffer ? buffer->str : NULL);
876
877   if (buffer)
878     g_string_free (buffer, TRUE);
879
880   return id;
881 }
882
883 static gchar*
884 get_icon_name (const gchar *mime_type)
885 {
886   static gboolean first_call = TRUE;
887   gchar *name = NULL;
888   gchar *s;
889
890   if (first_call)
891     {
892       g_warning ("%s: Replace by g_content_type_get_icon "
893                  "when GVFS is merged into GLib.", G_STRLOC);
894       first_call = FALSE;
895     }
896
897   if (mime_type)
898     {
899       name = g_strconcat ("gnome-mime-", mime_type, NULL);
900
901       for(s = name; *s; ++s)
902         {
903           if (!isalpha (*s) || !isascii (*s))
904             *s = '-';
905         }
906     }
907
908   if (!name ||
909       !gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), name))
910     {
911       g_free (name);
912       name = g_strdup ("gnome-mime-image");
913     }
914
915   return name;
916 }
917
918 void           
919 egg_file_format_chooser_add_pixbuf_formats (EggFileFormatChooser *self,
920                                             guint                 parent G_GNUC_UNUSED,
921                                             guint               **formats)
922 {
923   GSList *pixbuf_formats = NULL;
924   GSList *iter;
925   gint i;
926
927   g_return_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self));
928
929   pixbuf_formats = gdk_pixbuf_get_formats ();
930
931   if (formats)
932     *formats = g_new0 (guint, g_slist_length (pixbuf_formats) + 1);
933
934   for(iter = pixbuf_formats, i = 0; iter; iter = iter->next, ++i)
935     {
936       GdkPixbufFormat *format = iter->data;
937
938       gchar *description, *name, *extensions, *icon;
939       gchar **mime_types, **extension_list;
940       guint id;
941
942       if (gdk_pixbuf_format_is_disabled (format) ||
943          !gdk_pixbuf_format_is_writable (format))
944         continue;
945
946       mime_types = gdk_pixbuf_format_get_mime_types (format);
947       icon = get_icon_name (mime_types[0]);
948       g_strfreev (mime_types);
949
950       extension_list = gdk_pixbuf_format_get_extensions (format);
951       extensions = g_strjoinv (", ", extension_list);
952       g_strfreev (extension_list);
953
954       description = gdk_pixbuf_format_get_description (format);
955       name = gdk_pixbuf_format_get_name (format);
956
957       id = egg_file_format_chooser_add_format_impl (self, parent, description, 
958                                                     icon, extensions);
959
960       g_free (description);
961       g_free (extensions);
962       g_free (icon);
963
964       egg_file_format_chooser_set_format_data (self, id, name, g_free);
965
966       if (formats)
967         *formats[i] = id;
968     }
969
970   g_slist_free (pixbuf_formats);
971 }
972
973 void
974 egg_file_format_chooser_remove_format (EggFileFormatChooser *self,
975                                        guint                 format)
976 {
977   GDestroyNotify destroy = NULL;
978   gpointer data = NULL;
979
980   EggFileFormatSearch search;
981   GtkFileFilter *filter;
982   GtkTreeModel *model;
983
984   g_return_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self));
985
986   search.success = FALSE;
987   search.format = format;
988
989   model = GTK_TREE_MODEL (self->priv->model);
990   gtk_tree_model_foreach (model, find_by_format, &search);
991
992   g_return_if_fail (search.success);
993
994   gtk_tree_model_get (model, &search.iter,
995                       MODEL_COLUMN_FILTER, &filter,
996                       MODEL_COLUMN_DESTROY, &destroy,
997                       MODEL_COLUMN_DATA, &data,
998                       -1);
999
1000   if (destroy)
1001     destroy (data);
1002
1003   if (filter)
1004     {
1005       if (self->priv->chooser)
1006         gtk_file_chooser_remove_filter (self->priv->chooser, filter);
1007
1008       g_object_unref (filter);
1009     }
1010   else
1011     g_warning ("TODO: Remove extensions from parent filter");
1012
1013   gtk_tree_store_remove (self->priv->model, &search.iter);
1014 }
1015
1016 void            
1017 egg_file_format_chooser_set_format (EggFileFormatChooser *self,
1018                                     guint                 format)
1019 {
1020   EggFileFormatSearch search;
1021
1022   GtkTreeModel *model;
1023   GtkTreePath *path;
1024   GtkTreeView *view;
1025
1026   g_return_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self));
1027
1028   search.success = FALSE;
1029   search.format = format;
1030
1031   model = GTK_TREE_MODEL (self->priv->model);
1032   gtk_tree_model_foreach (model, find_by_format, &search);
1033
1034   g_return_if_fail (search.success);
1035
1036   path = gtk_tree_model_get_path (model, &search.iter);
1037   view = gtk_tree_selection_get_tree_view (self->priv->selection);
1038
1039   gtk_tree_view_expand_to_path (view, path);
1040   gtk_tree_selection_unselect_all (self->priv->selection);
1041   gtk_tree_selection_select_path (self->priv->selection, path);
1042
1043   gtk_tree_path_free (path);
1044
1045   if (self->priv->idle_hack > 0)
1046     {
1047       g_source_remove (self->priv->idle_hack);
1048       self->priv->idle_hack = 0;
1049     }
1050 }
1051
1052 guint
1053 egg_file_format_chooser_get_format (EggFileFormatChooser *self,
1054                                     const gchar          *filename)
1055 {
1056   GtkTreeModel *model;
1057   GtkTreeIter iter;
1058   guint format = 0;
1059
1060   g_return_val_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self), -1);
1061
1062   if (gtk_tree_selection_get_selected (self->priv->selection, &model, &iter))
1063     gtk_tree_model_get (model, &iter, MODEL_COLUMN_ID, &format, -1);
1064
1065   if (0 == format && NULL != filename)
1066     {
1067       EggFileFormatSearch search;
1068
1069       search.extension = strrchr(filename, '.');
1070       search.success = FALSE;
1071
1072       if (search.extension++)
1073         gtk_tree_model_foreach (model, find_by_extension, &search);
1074       if (search.success)
1075         format = search.format;
1076     }
1077
1078   return format;
1079 }
1080
1081 void            
1082 egg_file_format_chooser_set_format_data (EggFileFormatChooser *self,
1083                                          guint                 format,
1084                                          gpointer              data,
1085                                          GDestroyNotify        destroy)
1086 {
1087   EggFileFormatSearch search;
1088
1089   g_return_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self));
1090
1091   search.success = FALSE;
1092   search.format = format;
1093
1094   gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->model),
1095                           find_by_format, &search);
1096
1097   g_return_if_fail (search.success);
1098
1099   gtk_tree_store_set (self->priv->model, &search.iter,
1100                       MODEL_COLUMN_DESTROY, destroy,
1101                       MODEL_COLUMN_DATA, data,
1102                       -1);
1103 }
1104
1105 gpointer
1106 egg_file_format_chooser_get_format_data (EggFileFormatChooser *self,
1107                                          guint                 format)
1108 {
1109   EggFileFormatSearch search;
1110   gpointer data = NULL;
1111   GtkTreeModel *model;
1112
1113   g_return_val_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self), NULL);
1114
1115   search.success = FALSE;
1116   search.format = format;
1117
1118   model = GTK_TREE_MODEL (self->priv->model);
1119   gtk_tree_model_foreach (model, find_by_format, &search);
1120
1121   g_return_val_if_fail (search.success, NULL);
1122
1123   gtk_tree_model_get (model, &search.iter,
1124                       MODEL_COLUMN_DATA, &data,
1125                       -1);
1126   return data;
1127 }
1128
1129 gchar*
1130 egg_file_format_chooser_append_extension (EggFileFormatChooser *self,
1131                                           const gchar          *filename,
1132                                           guint                 format)
1133 {
1134   EggFileFormatSearch search;
1135   GtkTreeModel *model;
1136   GtkTreeIter child;
1137
1138   gchar *extensions;
1139   gchar *result;
1140
1141   g_return_val_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self), NULL);
1142   g_return_val_if_fail (NULL != filename, NULL);
1143
1144   if (0 == format)
1145     format = egg_file_format_chooser_get_format (self, NULL);
1146
1147   if (0 == format)
1148     {
1149       g_warning ("%s: No file format selected. Cannot append extension.", __FUNCTION__);
1150       return NULL;
1151     }
1152
1153   search.success = FALSE;
1154   search.format = format;
1155
1156   model = GTK_TREE_MODEL (self->priv->model);
1157   gtk_tree_model_foreach (model, find_by_format, &search);
1158
1159   g_return_val_if_fail (search.success, NULL);
1160
1161   gtk_tree_model_get (model, &search.iter, 
1162                       MODEL_COLUMN_EXTENSIONS, &extensions,
1163                       -1);
1164
1165   if (NULL == extensions && 
1166       gtk_tree_model_iter_nth_child (model, &child, &search.iter, 0))
1167     {
1168       gtk_tree_model_get (model, &child, 
1169                           MODEL_COLUMN_EXTENSIONS, &extensions,
1170                           -1);
1171     }
1172
1173   if (NULL == extensions)
1174     {
1175       g_warning ("%s: File format %d doesn't provide file extensions. "
1176                  "Cannot append extension.", __FUNCTION__, format);
1177       return NULL;
1178     }
1179
1180   if (accept_filename (extensions, filename))
1181     result = g_strdup (filename);
1182   else
1183     result = g_strconcat (filename, ".", extensions, NULL);
1184
1185   g_assert (NULL == strchr(extensions, ','));
1186   g_free (extensions);
1187   return result;
1188 }
1189
1190 /* vim: set sw=2 sta et: */