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