1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpfileprocview.c
5  * Copyright (C) 2004  Sven Neumann <sven@gimp.org>
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 3 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, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <string.h>
24 
25 #include <gtk/gtk.h>
26 #include <gegl.h>
27 
28 #include "widgets-types.h"
29 
30 #include "core/gimp.h"
31 #include "core/gimpmarshal.h"
32 
33 #include "plug-in/gimppluginprocedure.h"
34 
35 #include "gimpfileprocview.h"
36 
37 #include "gimp-intl.h"
38 
39 /*  an arbitrary limit to keep the file dialog from becoming too wide  */
40 #define MAX_EXTENSIONS  4
41 
42 enum
43 {
44   COLUMN_PROC,
45   COLUMN_LABEL,
46   COLUMN_EXTENSIONS,
47   COLUMN_HELP_ID,
48   COLUMN_FILTER,
49   N_COLUMNS
50 };
51 
52 enum
53 {
54   CHANGED,
55   LAST_SIGNAL
56 };
57 
58 
59 static void            gimp_file_proc_view_finalize               (GObject             *object);
60 
61 static void            gimp_file_proc_view_selection_changed      (GtkTreeSelection    *selection,
62                                                                    GimpFileProcView    *view);
63 
64 static GtkFileFilter * gimp_file_proc_view_process_procedure      (GimpPlugInProcedure *file_proc,
65                                                                    GtkFileFilter       *all);
66 static gchar         * gimp_file_proc_view_pattern_from_extension (const gchar         *extension);
67 
68 
69 G_DEFINE_TYPE (GimpFileProcView, gimp_file_proc_view, GTK_TYPE_TREE_VIEW)
70 
71 #define parent_class gimp_file_proc_view_parent_class
72 
73 static guint view_signals[LAST_SIGNAL] = { 0 };
74 
75 
76 static void
gimp_file_proc_view_class_init(GimpFileProcViewClass * klass)77 gimp_file_proc_view_class_init (GimpFileProcViewClass *klass)
78 {
79   GObjectClass *object_class = G_OBJECT_CLASS (klass);
80 
81   object_class->finalize = gimp_file_proc_view_finalize;
82 
83   klass->changed         = NULL;
84 
85   view_signals[CHANGED] = g_signal_new ("changed",
86                                         G_TYPE_FROM_CLASS (klass),
87                                         G_SIGNAL_RUN_LAST,
88                                         G_STRUCT_OFFSET (GimpFileProcViewClass,
89                                                          changed),
90                                         NULL, NULL,
91                                         gimp_marshal_VOID__VOID,
92                                         G_TYPE_NONE, 0);
93 }
94 
95 static void
gimp_file_proc_view_init(GimpFileProcView * view)96 gimp_file_proc_view_init (GimpFileProcView *view)
97 {
98 }
99 
100 static void
gimp_file_proc_view_finalize(GObject * object)101 gimp_file_proc_view_finalize (GObject *object)
102 {
103   GimpFileProcView *view = GIMP_FILE_PROC_VIEW (object);
104 
105   if (view->meta_extensions)
106     {
107       g_list_free_full (view->meta_extensions, (GDestroyNotify) g_free);
108       view->meta_extensions = NULL;
109     }
110 
111   G_OBJECT_CLASS (parent_class)->finalize (object);
112 }
113 
114 GtkWidget *
gimp_file_proc_view_new(Gimp * gimp,GSList * procedures,const gchar * automatic,const gchar * automatic_help_id)115 gimp_file_proc_view_new (Gimp        *gimp,
116                          GSList      *procedures,
117                          const gchar *automatic,
118                          const gchar *automatic_help_id)
119 {
120   GtkFileFilter     *all_filter;
121   GtkTreeView       *view;
122   GtkTreeViewColumn *column;
123   GtkCellRenderer   *cell;
124   GtkListStore      *store;
125   GSList            *list;
126   GtkTreeIter        iter;
127 
128   g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
129 
130   store = gtk_list_store_new (N_COLUMNS,
131                               GIMP_TYPE_PLUG_IN_PROCEDURE, /*  COLUMN_PROC       */
132                               G_TYPE_STRING,               /*  COLUMN_LABEL      */
133                               G_TYPE_STRING,               /*  COLUMN_EXTENSIONS */
134                               G_TYPE_STRING,               /*  COLUMN_HELP_ID    */
135                               GTK_TYPE_FILE_FILTER);       /*  COLUMN_FILTER     */
136 
137   view = g_object_new (GIMP_TYPE_FILE_PROC_VIEW,
138                        "model",      store,
139                        "rules-hint", TRUE,
140                        NULL);
141 
142   g_object_unref (store);
143 
144   all_filter = gtk_file_filter_new ();
145 
146   for (list = procedures; list; list = g_slist_next (list))
147     {
148       GimpPlugInProcedure *proc = list->data;
149 
150       if (! proc->prefixes_list) /*  skip URL loaders  */
151         {
152           const gchar *label   = gimp_procedure_get_label (GIMP_PROCEDURE (proc));
153           const gchar *help_id = gimp_procedure_get_help_id (GIMP_PROCEDURE (proc));
154           GSList      *list2;
155 
156           if (label)
157             {
158               GtkFileFilter *filter;
159 
160               filter = gimp_file_proc_view_process_procedure (proc, all_filter);
161               gtk_list_store_append (store, &iter);
162               gtk_list_store_set (store, &iter,
163                                   COLUMN_PROC,       proc,
164                                   COLUMN_LABEL,      label,
165                                   COLUMN_EXTENSIONS, proc->extensions,
166                                   COLUMN_HELP_ID,    help_id,
167                                   COLUMN_FILTER,     filter,
168                                   -1);
169               g_object_unref (filter);
170             }
171 
172           for (list2 = proc->extensions_list;
173                list2;
174                list2 = g_slist_next (list2))
175             {
176               GimpFileProcView *proc_view = GIMP_FILE_PROC_VIEW (view);
177               const gchar      *ext       = list2->data;
178               const gchar      *dot       = strchr (ext, '.');
179 
180               if (dot && dot != ext)
181                 proc_view->meta_extensions =
182                   g_list_append (proc_view->meta_extensions,
183                                  g_strdup (dot + 1));
184             }
185         }
186     }
187 
188   if (automatic)
189     {
190       gtk_list_store_prepend (store, &iter);
191       gtk_list_store_set (store, &iter,
192                           COLUMN_PROC,    NULL,
193                           COLUMN_LABEL,   automatic,
194                           COLUMN_HELP_ID, automatic_help_id,
195                           COLUMN_FILTER,  all_filter,
196                           -1);
197     }
198 
199   column = gtk_tree_view_column_new ();
200   gtk_tree_view_column_set_title (column, _("File Type"));
201   gtk_tree_view_column_set_expand (column, TRUE);
202 
203   cell = gtk_cell_renderer_text_new ();
204   gtk_tree_view_column_pack_start (column, cell, TRUE);
205   gtk_tree_view_column_set_attributes (column, cell,
206                                        "text", COLUMN_LABEL,
207                                        NULL);
208 
209   gtk_tree_view_append_column (view, column);
210 
211   column = gtk_tree_view_column_new ();
212   gtk_tree_view_column_set_title (column, _("Extensions"));
213 
214   cell = gtk_cell_renderer_text_new ();
215   gtk_tree_view_column_pack_start (column, cell, TRUE);
216   gtk_tree_view_column_set_attributes (column, cell,
217                                        "text", COLUMN_EXTENSIONS,
218                                        NULL);
219 
220   gtk_tree_view_append_column (view, column);
221 
222   g_signal_connect (gtk_tree_view_get_selection (view), "changed",
223                     G_CALLBACK (gimp_file_proc_view_selection_changed),
224                     view);
225 
226   return GTK_WIDGET (view);
227 }
228 
229 GimpPlugInProcedure *
gimp_file_proc_view_get_proc(GimpFileProcView * view,gchar ** label,GtkFileFilter ** filter)230 gimp_file_proc_view_get_proc (GimpFileProcView  *view,
231                               gchar            **label,
232                               GtkFileFilter    **filter)
233 {
234   GtkTreeModel        *model;
235   GtkTreeSelection    *selection;
236   GimpPlugInProcedure *proc;
237   GtkTreeIter          iter;
238   gboolean             has_selection;
239 
240   g_return_val_if_fail (GIMP_IS_FILE_PROC_VIEW (view), NULL);
241 
242   if (label)  *label  = NULL;
243   if (filter) *filter = NULL;
244 
245   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
246 
247   has_selection = gtk_tree_selection_get_selected (selection, &model, &iter);
248 
249   /* if there's no selected item, we return the "automatic" procedure, which,
250    * if exists, is the first item.
251    */
252   if (! has_selection)
253     {
254       model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
255 
256       if (! gtk_tree_model_get_iter_first (model, &iter))
257         return NULL;
258     }
259 
260   gtk_tree_model_get (model, &iter,
261                       COLUMN_PROC, &proc,
262                       -1);
263 
264   if (proc)
265     {
266       g_object_unref (proc);
267 
268       /* there's no selected item, and no "automatic" procedure.  return NULL.
269        */
270       if (! has_selection)
271         return NULL;
272     }
273 
274   if (label)
275     {
276       gtk_tree_model_get (model, &iter,
277                           COLUMN_LABEL, label,
278                           -1);
279     }
280 
281   if (filter)
282     {
283       gtk_tree_model_get (model, &iter,
284                           COLUMN_FILTER, filter,
285                           -1);
286     }
287 
288   return proc;
289 }
290 
291 gboolean
gimp_file_proc_view_set_proc(GimpFileProcView * view,GimpPlugInProcedure * proc)292 gimp_file_proc_view_set_proc (GimpFileProcView    *view,
293                               GimpPlugInProcedure *proc)
294 {
295   GtkTreeModel *model;
296   GtkTreeIter   iter;
297   gboolean      iter_valid;
298 
299   g_return_val_if_fail (GIMP_IS_FILE_PROC_VIEW (view), FALSE);
300 
301   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
302 
303   for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
304        iter_valid;
305        iter_valid = gtk_tree_model_iter_next (model, &iter))
306     {
307       GimpPlugInProcedure *this;
308 
309       gtk_tree_model_get (model, &iter,
310                           COLUMN_PROC, &this,
311                           -1);
312 
313       if (this)
314         g_object_unref (this);
315 
316       if (this == proc)
317         break;
318     }
319 
320   if (iter_valid)
321     {
322       GtkTreeSelection *selection;
323 
324       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
325 
326       gtk_tree_selection_select_iter (selection, &iter);
327     }
328 
329   return iter_valid;
330 }
331 
332 gchar *
gimp_file_proc_view_get_help_id(GimpFileProcView * view)333 gimp_file_proc_view_get_help_id (GimpFileProcView *view)
334 {
335   GtkTreeModel     *model;
336   GtkTreeSelection *selection;
337   GtkTreeIter       iter;
338 
339   g_return_val_if_fail (GIMP_IS_FILE_PROC_VIEW (view), NULL);
340 
341   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
342 
343   if (gtk_tree_selection_get_selected (selection, &model, &iter))
344     {
345       gchar *help_id;
346 
347       gtk_tree_model_get (model, &iter,
348                           COLUMN_HELP_ID, &help_id,
349                           -1);
350 
351       return help_id;
352     }
353 
354   return NULL;
355 }
356 
357 static void
gimp_file_proc_view_selection_changed(GtkTreeSelection * selection,GimpFileProcView * view)358 gimp_file_proc_view_selection_changed (GtkTreeSelection *selection,
359                                        GimpFileProcView *view)
360 {
361   g_signal_emit (view, view_signals[CHANGED], 0);
362 }
363 
364 /**
365  * gimp_file_proc_view_process_procedure:
366  * @file_proc:
367  * @all:
368  *
369  * Creates a #GtkFileFilter of @file_proc and adds the extensions to
370  * the @all filter.
371  * The returned #GtkFileFilter has a normal ref and must be unreffed
372  * when used.
373  **/
374 static GtkFileFilter *
gimp_file_proc_view_process_procedure(GimpPlugInProcedure * file_proc,GtkFileFilter * all)375 gimp_file_proc_view_process_procedure (GimpPlugInProcedure *file_proc,
376                                        GtkFileFilter       *all)
377 {
378   GtkFileFilter *filter;
379   GString       *str;
380   GSList        *list;
381   gint           i;
382 
383   if (! file_proc->extensions_list)
384     return NULL;
385 
386   filter = gtk_file_filter_new ();
387   str    = g_string_new (gimp_procedure_get_label (GIMP_PROCEDURE (file_proc)));
388 
389   /* Take ownership directly so we don't have to mess with a floating
390    * ref
391    */
392   g_object_ref_sink (filter);
393 
394   for (list = file_proc->mime_types_list; list; list = g_slist_next (list))
395     {
396       const gchar *mime_type = list->data;
397 
398       gtk_file_filter_add_mime_type (filter, mime_type);
399       gtk_file_filter_add_mime_type (all, mime_type);
400     }
401 
402   for (list = file_proc->extensions_list, i = 0;
403        list;
404        list = g_slist_next (list), i++)
405     {
406       const gchar *extension = list->data;
407       gchar       *pattern;
408 
409       pattern = gimp_file_proc_view_pattern_from_extension (extension);
410       gtk_file_filter_add_pattern (filter, pattern);
411       gtk_file_filter_add_pattern (all, pattern);
412       g_free (pattern);
413 
414       if (i == 0)
415         {
416           g_string_append (str, " (");
417         }
418       else if (i <= MAX_EXTENSIONS)
419         {
420           g_string_append (str, ", ");
421         }
422 
423       if (i < MAX_EXTENSIONS)
424         {
425           g_string_append (str, "*.");
426           g_string_append (str, extension);
427         }
428       else if (i == MAX_EXTENSIONS)
429         {
430           g_string_append (str, "...");
431         }
432 
433       if (! list->next)
434         {
435           g_string_append (str, ")");
436         }
437     }
438 
439   gtk_file_filter_set_name (filter, str->str);
440   g_string_free (str, TRUE);
441 
442   return filter;
443 }
444 
445 static gchar *
gimp_file_proc_view_pattern_from_extension(const gchar * extension)446 gimp_file_proc_view_pattern_from_extension (const gchar *extension)
447 {
448   gchar *pattern;
449   gchar *p;
450   gint   len, i;
451 
452   g_return_val_if_fail (extension != NULL, NULL);
453 
454   /* This function assumes that file extensions are 7bit ASCII.  It
455    * could certainly be rewritten to handle UTF-8 if this assumption
456    * turns out to be incorrect.
457    */
458 
459   len = strlen (extension);
460 
461   pattern = g_new (gchar, 4 + 4 * len);
462 
463   strcpy (pattern, "*.");
464 
465   for (i = 0, p = pattern + 2; i < len; i++, p+= 4)
466     {
467       p[0] = '[';
468       p[1] = g_ascii_tolower (extension[i]);
469       p[2] = g_ascii_toupper (extension[i]);
470       p[3] = ']';
471     }
472 
473   *p = '\0';
474 
475   return pattern;
476 }
477