1 /* GTK - The GIMP Toolkit
2  * gtkfilefilter.c: Filters for selecting a file subset
3  * Copyright (C) 2003, Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /**
20  * SECTION:gtkfilefilter
21  * @Short_description: A filter for selecting a file subset
22  * @Title: GtkFileFilter
23  * @see_also: #GtkFileChooser
24  *
25  * A GtkFileFilter can be used to restrict the files being shown in a
26  * #GtkFileChooser. Files can be filtered based on their name (with
27  * gtk_file_filter_add_pattern()), on their mime type (with
28  * gtk_file_filter_add_mime_type()), or by a custom filter function
29  * (with gtk_file_filter_add_custom()).
30  *
31  * Filtering by mime types handles aliasing and subclassing of mime
32  * types; e.g. a filter for text/plain also matches a file with mime
33  * type application/rtf, since application/rtf is a subclass of
34  * text/plain. Note that #GtkFileFilter allows wildcards for the
35  * subtype of a mime type, so you can e.g. filter for image/\*.
36  *
37  * Normally, filters are used by adding them to a #GtkFileChooser,
38  * see gtk_file_chooser_add_filter(), but it is also possible
39  * to manually use a filter on a file with gtk_file_filter_filter().
40  *
41  * # GtkFileFilter as GtkBuildable
42  *
43  * The GtkFileFilter implementation of the GtkBuildable interface
44  * supports adding rules using the `<mime-types>`, `<patterns>` and
45  * `<applications>` elements and listing the rules within. Specifying
46  * a `<mime-type>` or `<pattern>` has the same effect as as calling
47  * gtk_file_filter_add_mime_type() or gtk_file_filter_add_pattern().
48  *
49  * An example of a UI definition fragment specifying GtkFileFilter
50  * rules:
51  *
52  * |[<!-- language="xml" -->
53  * <object class="GtkFileFilter">
54  *   <mime-types>
55  *     <mime-type>text/plain</mime-type>
56  *     <mime-type>image/ *</mime-type>
57  *   </mime-types>
58  *   <patterns>
59  *     <pattern>*.txt</pattern>
60  *     <pattern>*.png</pattern>
61  *   </patterns>
62  * </object>
63  * ]|
64  */
65 
66 #include "config.h"
67 #include <string.h>
68 
69 #include <gdk-pixbuf/gdk-pixbuf.h>
70 
71 #include "gtkfilefilterprivate.h"
72 #include "gtkbuildable.h"
73 #include "gtkbuilderprivate.h"
74 #include "gtkintl.h"
75 #include "gtkprivate.h"
76 
77 typedef struct _GtkFileFilterClass GtkFileFilterClass;
78 typedef struct _FilterRule FilterRule;
79 
80 #define GTK_FILE_FILTER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_FILTER, GtkFileFilterClass))
81 #define GTK_IS_FILE_FILTER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_FILTER))
82 #define GTK_FILE_FILTER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_FILTER, GtkFileFilterClass))
83 
84 typedef enum {
85   FILTER_RULE_PATTERN,
86   FILTER_RULE_MIME_TYPE,
87   FILTER_RULE_PIXBUF_FORMATS,
88   FILTER_RULE_CUSTOM
89 } FilterRuleType;
90 
91 struct _GtkFileFilterClass
92 {
93   GInitiallyUnownedClass parent_class;
94 };
95 
96 struct _GtkFileFilter
97 {
98   GInitiallyUnowned parent_instance;
99 
100   gchar *name;
101   GSList *rules;
102 
103   GtkFileFilterFlags needed;
104 };
105 
106 struct _FilterRule
107 {
108   FilterRuleType type;
109   GtkFileFilterFlags needed;
110 
111   union {
112     gchar *pattern;
113     gchar *mime_type;
114     GSList *pixbuf_formats;
115     struct {
116       GtkFileFilterFunc func;
117       gpointer data;
118       GDestroyNotify notify;
119     } custom;
120   } u;
121 };
122 
123 static void gtk_file_filter_finalize   (GObject            *object);
124 
125 
126 static void     gtk_file_filter_buildable_init                 (GtkBuildableIface *iface);
127 static void     gtk_file_filter_buildable_set_name             (GtkBuildable *buildable,
128                                                                 const gchar  *name);
129 static const gchar* gtk_file_filter_buildable_get_name         (GtkBuildable *buildable);
130 
131 
132 static gboolean gtk_file_filter_buildable_custom_tag_start     (GtkBuildable  *buildable,
133 								GtkBuilder    *builder,
134 								GObject       *child,
135 								const gchar   *tagname,
136 								GMarkupParser *parser,
137 								gpointer      *data);
138 static void     gtk_file_filter_buildable_custom_tag_end       (GtkBuildable  *buildable,
139 								GtkBuilder    *builder,
140 								GObject       *child,
141 								const gchar   *tagname,
142 								gpointer      *data);
143 
G_DEFINE_TYPE_WITH_CODE(GtkFileFilter,gtk_file_filter,G_TYPE_INITIALLY_UNOWNED,G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,gtk_file_filter_buildable_init))144 G_DEFINE_TYPE_WITH_CODE (GtkFileFilter, gtk_file_filter, G_TYPE_INITIALLY_UNOWNED,
145                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
146                                                 gtk_file_filter_buildable_init))
147 
148 static void
149 gtk_file_filter_init (GtkFileFilter *object)
150 {
151 }
152 
153 static void
gtk_file_filter_class_init(GtkFileFilterClass * class)154 gtk_file_filter_class_init (GtkFileFilterClass *class)
155 {
156   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
157 
158   gobject_class->finalize = gtk_file_filter_finalize;
159 }
160 
161 static void
filter_rule_free(FilterRule * rule)162 filter_rule_free (FilterRule *rule)
163 {
164   switch (rule->type)
165     {
166     case FILTER_RULE_MIME_TYPE:
167       g_free (rule->u.mime_type);
168       break;
169     case FILTER_RULE_PATTERN:
170       g_free (rule->u.pattern);
171       break;
172     case FILTER_RULE_CUSTOM:
173       if (rule->u.custom.notify)
174 	rule->u.custom.notify (rule->u.custom.data);
175       break;
176     case FILTER_RULE_PIXBUF_FORMATS:
177       g_slist_free (rule->u.pixbuf_formats);
178       break;
179     default:
180       g_assert_not_reached ();
181     }
182 
183   g_slice_free (FilterRule, rule);
184 }
185 
186 static void
gtk_file_filter_finalize(GObject * object)187 gtk_file_filter_finalize (GObject  *object)
188 {
189   GtkFileFilter *filter = GTK_FILE_FILTER (object);
190 
191   g_slist_free_full (filter->rules, (GDestroyNotify)filter_rule_free);
192 
193   g_free (filter->name);
194 
195   G_OBJECT_CLASS (gtk_file_filter_parent_class)->finalize (object);
196 }
197 
198 /*
199  * GtkBuildable implementation
200  */
201 static void
gtk_file_filter_buildable_init(GtkBuildableIface * iface)202 gtk_file_filter_buildable_init (GtkBuildableIface *iface)
203 {
204   iface->custom_tag_start = gtk_file_filter_buildable_custom_tag_start;
205   iface->custom_tag_end = gtk_file_filter_buildable_custom_tag_end;
206   iface->set_name = gtk_file_filter_buildable_set_name;
207   iface->get_name = gtk_file_filter_buildable_get_name;
208 }
209 
210 static void
gtk_file_filter_buildable_set_name(GtkBuildable * buildable,const gchar * name)211 gtk_file_filter_buildable_set_name (GtkBuildable *buildable,
212                                     const gchar  *name)
213 {
214   gtk_file_filter_set_name (GTK_FILE_FILTER (buildable), name);
215 }
216 
217 static const gchar *
gtk_file_filter_buildable_get_name(GtkBuildable * buildable)218 gtk_file_filter_buildable_get_name (GtkBuildable *buildable)
219 {
220   return gtk_file_filter_get_name (GTK_FILE_FILTER (buildable));
221 }
222 
223 typedef enum {
224   PARSE_MIME_TYPES,
225   PARSE_PATTERNS
226 } ParserType;
227 
228 typedef struct {
229   GtkFileFilter *filter;
230   GtkBuilder    *builder;
231   ParserType     type;
232   GString       *string;
233   gboolean       parsing;
234 } SubParserData;
235 
236 static void
parser_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)237 parser_start_element (GMarkupParseContext  *context,
238                       const gchar          *element_name,
239                       const gchar         **names,
240                       const gchar         **values,
241                       gpointer              user_data,
242                       GError              **error)
243 {
244   SubParserData *data = (SubParserData*)user_data;
245 
246   if (!g_markup_collect_attributes (element_name, names, values, error,
247                                     G_MARKUP_COLLECT_INVALID, NULL, NULL,
248                                     G_MARKUP_COLLECT_INVALID))
249     {
250       _gtk_builder_prefix_error (data->builder, context, error);
251       return;
252     }
253 
254   if (strcmp (element_name, "mime-types") == 0 ||
255       strcmp (element_name, "patterns") == 0)
256     {
257       if (!_gtk_builder_check_parent (data->builder, context, "object", error))
258         return;
259     }
260   else if (strcmp (element_name, "mime-type") == 0)
261     {
262       if (!_gtk_builder_check_parent (data->builder, context, "mime-types", error))
263         return;
264 
265       data->parsing = TRUE;
266     }
267   else if (strcmp (element_name, "pattern") == 0)
268     {
269       if (!_gtk_builder_check_parent (data->builder, context, "patterns", error))
270         return;
271 
272       data->parsing = TRUE;
273     }
274   else
275     {
276       _gtk_builder_error_unhandled_tag (data->builder, context,
277                                         "GtkFileFilter", element_name,
278                                         error);
279     }
280 }
281 
282 static void
parser_text_element(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)283 parser_text_element (GMarkupParseContext *context,
284                      const gchar         *text,
285                      gsize                text_len,
286                      gpointer             user_data,
287                      GError             **error)
288 {
289   SubParserData *data = (SubParserData*)user_data;
290 
291   if (data->parsing)
292     g_string_append_len (data->string, text, text_len);
293 }
294 
295 static void
parser_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)296 parser_end_element (GMarkupParseContext *context,
297                     const gchar         *element_name,
298                     gpointer             user_data,
299                     GError             **error)
300 {
301   SubParserData *data = (SubParserData*)user_data;
302 
303   if (data->string != NULL && data->string->len != 0)
304     {
305       switch (data->type)
306         {
307         case PARSE_MIME_TYPES:
308           gtk_file_filter_add_mime_type (data->filter, data->string->str);
309           break;
310         case PARSE_PATTERNS:
311           gtk_file_filter_add_pattern (data->filter, data->string->str);
312           break;
313         default:
314           break;
315         }
316     }
317 
318   g_string_set_size (data->string, 0);
319   data->parsing = FALSE;
320 }
321 
322 static const GMarkupParser sub_parser =
323   {
324     parser_start_element,
325     parser_end_element,
326     parser_text_element,
327   };
328 
329 static gboolean
gtk_file_filter_buildable_custom_tag_start(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,GMarkupParser * parser,gpointer * parser_data)330 gtk_file_filter_buildable_custom_tag_start (GtkBuildable  *buildable,
331                                             GtkBuilder    *builder,
332                                             GObject       *child,
333                                             const gchar   *tagname,
334                                             GMarkupParser *parser,
335                                             gpointer      *parser_data)
336 {
337   SubParserData *data = NULL;
338 
339   if (strcmp (tagname, "mime-types") == 0)
340     {
341       data = g_slice_new0 (SubParserData);
342       data->string = g_string_new ("");
343       data->type = PARSE_MIME_TYPES;
344       data->filter = GTK_FILE_FILTER (buildable);
345       data->builder = builder;
346 
347       *parser = sub_parser;
348       *parser_data = data;
349     }
350   else if (strcmp (tagname, "patterns") == 0)
351     {
352       data = g_slice_new0 (SubParserData);
353       data->string = g_string_new ("");
354       data->type = PARSE_PATTERNS;
355       data->filter = GTK_FILE_FILTER (buildable);
356       data->builder = builder;
357 
358       *parser = sub_parser;
359       *parser_data = data;
360     }
361 
362   return data != NULL;
363 }
364 
365 static void
gtk_file_filter_buildable_custom_tag_end(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,gpointer * user_data)366 gtk_file_filter_buildable_custom_tag_end (GtkBuildable *buildable,
367                                           GtkBuilder   *builder,
368                                           GObject      *child,
369                                           const gchar  *tagname,
370                                           gpointer     *user_data)
371 {
372   if (strcmp (tagname, "mime-types") == 0 ||
373       strcmp (tagname, "patterns") == 0)
374     {
375       SubParserData *data = (SubParserData*)user_data;
376 
377       g_string_free (data->string, TRUE);
378       g_slice_free (SubParserData, data);
379     }
380 }
381 
382 
383 /**
384  * gtk_file_filter_new:
385  *
386  * Creates a new #GtkFileFilter with no rules added to it.
387  * Such a filter doesn’t accept any files, so is not
388  * particularly useful until you add rules with
389  * gtk_file_filter_add_mime_type(), gtk_file_filter_add_pattern(),
390  * or gtk_file_filter_add_custom(). To create a filter
391  * that accepts any file, use:
392  * |[<!-- language="C" -->
393  * GtkFileFilter *filter = gtk_file_filter_new ();
394  * gtk_file_filter_add_pattern (filter, "*");
395  * ]|
396  *
397  * Returns: a new #GtkFileFilter
398  *
399  * Since: 2.4
400  **/
401 GtkFileFilter *
gtk_file_filter_new(void)402 gtk_file_filter_new (void)
403 {
404   return g_object_new (GTK_TYPE_FILE_FILTER, NULL);
405 }
406 
407 /**
408  * gtk_file_filter_set_name:
409  * @filter: a #GtkFileFilter
410  * @name: (allow-none): the human-readable-name for the filter, or %NULL
411  *   to remove any existing name.
412  *
413  * Sets the human-readable name of the filter; this is the string
414  * that will be displayed in the file selector user interface if
415  * there is a selectable list of filters.
416  *
417  * Since: 2.4
418  **/
419 void
gtk_file_filter_set_name(GtkFileFilter * filter,const gchar * name)420 gtk_file_filter_set_name (GtkFileFilter *filter,
421 			  const gchar   *name)
422 {
423   g_return_if_fail (GTK_IS_FILE_FILTER (filter));
424 
425   g_free (filter->name);
426 
427   filter->name = g_strdup (name);
428 }
429 
430 /**
431  * gtk_file_filter_get_name:
432  * @filter: a #GtkFileFilter
433  *
434  * Gets the human-readable name for the filter. See gtk_file_filter_set_name().
435  *
436  * Returns: (nullable): The human-readable name of the filter,
437  *   or %NULL. This value is owned by GTK+ and must not
438  *   be modified or freed.
439  *
440  * Since: 2.4
441  **/
442 const gchar *
gtk_file_filter_get_name(GtkFileFilter * filter)443 gtk_file_filter_get_name (GtkFileFilter *filter)
444 {
445   g_return_val_if_fail (GTK_IS_FILE_FILTER (filter), NULL);
446 
447   return filter->name;
448 }
449 
450 static void
file_filter_add_rule(GtkFileFilter * filter,FilterRule * rule)451 file_filter_add_rule (GtkFileFilter *filter,
452 		      FilterRule    *rule)
453 {
454   filter->needed |= rule->needed;
455   filter->rules = g_slist_append (filter->rules, rule);
456 }
457 
458 /**
459  * gtk_file_filter_add_mime_type:
460  * @filter: A #GtkFileFilter
461  * @mime_type: name of a MIME type
462  *
463  * Adds a rule allowing a given mime type to @filter.
464  *
465  * Since: 2.4
466  **/
467 void
gtk_file_filter_add_mime_type(GtkFileFilter * filter,const gchar * mime_type)468 gtk_file_filter_add_mime_type (GtkFileFilter *filter,
469 			       const gchar   *mime_type)
470 {
471   FilterRule *rule;
472 
473   g_return_if_fail (GTK_IS_FILE_FILTER (filter));
474   g_return_if_fail (mime_type != NULL);
475 
476   rule = g_slice_new (FilterRule);
477   rule->type = FILTER_RULE_MIME_TYPE;
478   rule->needed = GTK_FILE_FILTER_MIME_TYPE;
479   rule->u.mime_type = g_strdup (mime_type);
480 
481   file_filter_add_rule (filter, rule);
482 }
483 
484 /**
485  * gtk_file_filter_add_pattern:
486  * @filter: a #GtkFileFilter
487  * @pattern: a shell style glob
488  *
489  * Adds a rule allowing a shell style glob to a filter.
490  *
491  * Since: 2.4
492  **/
493 void
gtk_file_filter_add_pattern(GtkFileFilter * filter,const gchar * pattern)494 gtk_file_filter_add_pattern (GtkFileFilter *filter,
495 			     const gchar   *pattern)
496 {
497   FilterRule *rule;
498 
499   g_return_if_fail (GTK_IS_FILE_FILTER (filter));
500   g_return_if_fail (pattern != NULL);
501 
502   rule = g_slice_new (FilterRule);
503   rule->type = FILTER_RULE_PATTERN;
504   rule->needed = GTK_FILE_FILTER_DISPLAY_NAME;
505   rule->u.pattern = g_strdup (pattern);
506 
507   file_filter_add_rule (filter, rule);
508 }
509 
510 /**
511  * gtk_file_filter_add_pixbuf_formats:
512  * @filter: a #GtkFileFilter
513  *
514  * Adds a rule allowing image files in the formats supported
515  * by GdkPixbuf.
516  *
517  * Since: 2.6
518  **/
519 void
gtk_file_filter_add_pixbuf_formats(GtkFileFilter * filter)520 gtk_file_filter_add_pixbuf_formats (GtkFileFilter *filter)
521 {
522   FilterRule *rule;
523 
524   g_return_if_fail (GTK_IS_FILE_FILTER (filter));
525 
526   rule = g_slice_new (FilterRule);
527   rule->type = FILTER_RULE_PIXBUF_FORMATS;
528   rule->needed = GTK_FILE_FILTER_MIME_TYPE;
529   rule->u.pixbuf_formats = gdk_pixbuf_get_formats ();
530   file_filter_add_rule (filter, rule);
531 }
532 
533 
534 /**
535  * gtk_file_filter_add_custom:
536  * @filter: a #GtkFileFilter
537  * @needed: bitfield of flags indicating the information that the custom
538  *          filter function needs.
539  * @func: callback function; if the function returns %TRUE, then
540  *   the file will be displayed.
541  * @data: data to pass to @func
542  * @notify: function to call to free @data when it is no longer needed.
543  *
544  * Adds rule to a filter that allows files based on a custom callback
545  * function. The bitfield @needed which is passed in provides information
546  * about what sorts of information that the filter function needs;
547  * this allows GTK+ to avoid retrieving expensive information when
548  * it isn’t needed by the filter.
549  *
550  * Since: 2.4
551  **/
552 void
gtk_file_filter_add_custom(GtkFileFilter * filter,GtkFileFilterFlags needed,GtkFileFilterFunc func,gpointer data,GDestroyNotify notify)553 gtk_file_filter_add_custom (GtkFileFilter         *filter,
554 			    GtkFileFilterFlags     needed,
555 			    GtkFileFilterFunc      func,
556 			    gpointer               data,
557 			    GDestroyNotify         notify)
558 {
559   FilterRule *rule;
560 
561   g_return_if_fail (GTK_IS_FILE_FILTER (filter));
562   g_return_if_fail (func != NULL);
563 
564   rule = g_slice_new (FilterRule);
565   rule->type = FILTER_RULE_CUSTOM;
566   rule->needed = needed;
567   rule->u.custom.func = func;
568   rule->u.custom.data = data;
569   rule->u.custom.notify = notify;
570 
571   file_filter_add_rule (filter, rule);
572 }
573 
574 /**
575  * gtk_file_filter_get_needed:
576  * @filter: a #GtkFileFilter
577  *
578  * Gets the fields that need to be filled in for the #GtkFileFilterInfo
579  * passed to gtk_file_filter_filter()
580  *
581  * This function will not typically be used by applications; it
582  * is intended principally for use in the implementation of
583  * #GtkFileChooser.
584  *
585  * Returns: bitfield of flags indicating needed fields when
586  *   calling gtk_file_filter_filter()
587  *
588  * Since: 2.4
589  **/
590 GtkFileFilterFlags
gtk_file_filter_get_needed(GtkFileFilter * filter)591 gtk_file_filter_get_needed (GtkFileFilter *filter)
592 {
593   return filter->needed;
594 }
595 
596 #ifdef GDK_WINDOWING_QUARTZ
597 
598 #import <Foundation/Foundation.h>
599 
_gtk_file_filter_get_as_pattern_nsstrings(GtkFileFilter * filter)600 NSArray * _gtk_file_filter_get_as_pattern_nsstrings (GtkFileFilter *filter)
601 {
602   NSMutableArray *array = [[NSMutableArray alloc] init];
603   GSList *tmp_list;
604 
605   for (tmp_list = filter->rules; tmp_list; tmp_list = tmp_list->next)
606     {
607       FilterRule *rule = tmp_list->data;
608 
609       switch (rule->type)
610 	{
611 	case FILTER_RULE_CUSTOM:
612 	  [array release];
613           return NULL;
614 	  break;
615 	case FILTER_RULE_MIME_TYPE:
616 	  {
617 	    // convert mime-types to UTI
618 	    NSString *mime_type_nsstring = [NSString stringWithUTF8String: rule->u.mime_type];
619 	    NSString *uti_nsstring = (NSString *) UTTypeCreatePreferredIdentifierForTag (kUTTagClassMIMEType, (CFStringRef) mime_type_nsstring, NULL);
620 	    if (uti_nsstring == NULL)
621 	      {
622 	        [array release];
623 		return NULL;
624 	      }
625 	    [array addObject:uti_nsstring];
626 	  }
627 	  break;
628 	case FILTER_RULE_PATTERN:
629 	  {
630 	    // patterns will need to be stripped of their leading *.
631 	    GString *pattern = g_string_new (rule->u.pattern);
632 	    if (strncmp (pattern->str, "*.", 2) == 0)
633 	      {
634 	        pattern = g_string_erase (pattern, 0, 2);
635 	      }
636 	    else if (strncmp (pattern->str, "*", 1) == 0)
637 	      {
638 	        pattern = g_string_erase (pattern, 0, 1);
639 	      }
640 	    gchar *pattern_c = g_string_free (pattern, FALSE);
641 	    NSString *pattern_nsstring = [NSString stringWithUTF8String:pattern_c];
642 	    g_free (pattern_c);
643 	    [pattern_nsstring retain];
644 	    [array addObject:pattern_nsstring];
645 	  }
646 	  break;
647 	case FILTER_RULE_PIXBUF_FORMATS:
648 	  {
649 	    GSList *list;
650 
651 	    for (list = rule->u.pixbuf_formats; list; list = list->next)
652 	      {
653 		int i;
654 		gchar **extensions;
655 
656 		extensions = gdk_pixbuf_format_get_extensions (list->data);
657 
658 		for (i = 0; extensions[i] != NULL; i++)
659 		  {
660 		    NSString *extension = [NSString stringWithUTF8String: extensions[i]];
661 		    [extension retain];
662 		    [array addObject:extension];
663 		  }
664 		g_strfreev (extensions);
665 	      }
666 	    break;
667 	  }
668 	}
669     }
670   return array;
671 }
672 #endif
673 
674 char **
_gtk_file_filter_get_as_patterns(GtkFileFilter * filter)675 _gtk_file_filter_get_as_patterns (GtkFileFilter      *filter)
676 {
677   GPtrArray *array;
678   GSList *tmp_list;
679 
680   array = g_ptr_array_new_with_free_func (g_free);
681 
682   for (tmp_list = filter->rules; tmp_list; tmp_list = tmp_list->next)
683     {
684       FilterRule *rule = tmp_list->data;
685 
686       switch (rule->type)
687 	{
688 	case FILTER_RULE_CUSTOM:
689 	case FILTER_RULE_MIME_TYPE:
690           g_ptr_array_free (array, TRUE);
691           return NULL;
692 	  break;
693 	case FILTER_RULE_PATTERN:
694           g_ptr_array_add (array, g_strdup (rule->u.pattern));
695 	  break;
696 	case FILTER_RULE_PIXBUF_FORMATS:
697 	  {
698 	    GSList *list;
699 
700 	    for (list = rule->u.pixbuf_formats; list; list = list->next)
701 	      {
702 		int i;
703 		gchar **extensions;
704 
705 		extensions = gdk_pixbuf_format_get_extensions (list->data);
706 
707 		for (i = 0; extensions[i] != NULL; i++)
708                   g_ptr_array_add (array, g_strdup_printf ("*.%s", extensions[i]));
709 
710 		g_strfreev (extensions);
711 	      }
712 	    break;
713 	  }
714 	}
715     }
716 
717   g_ptr_array_add (array, NULL); /* Null terminate */
718   return (char **)g_ptr_array_free (array, FALSE);
719 }
720 
721 /**
722  * gtk_file_filter_filter:
723  * @filter: a #GtkFileFilter
724  * @filter_info: a #GtkFileFilterInfo containing information
725  *  about a file.
726  *
727  * Tests whether a file should be displayed according to @filter.
728  * The #GtkFileFilterInfo @filter_info should include
729  * the fields returned from gtk_file_filter_get_needed().
730  *
731  * This function will not typically be used by applications; it
732  * is intended principally for use in the implementation of
733  * #GtkFileChooser.
734  *
735  * Returns: %TRUE if the file should be displayed
736  *
737  * Since: 2.4
738  **/
739 gboolean
gtk_file_filter_filter(GtkFileFilter * filter,const GtkFileFilterInfo * filter_info)740 gtk_file_filter_filter (GtkFileFilter           *filter,
741 			const GtkFileFilterInfo *filter_info)
742 {
743   GSList *tmp_list;
744 
745   for (tmp_list = filter->rules; tmp_list; tmp_list = tmp_list->next)
746     {
747       FilterRule *rule = tmp_list->data;
748 
749       if ((filter_info->contains & rule->needed) != rule->needed)
750 	continue;
751 
752       switch (rule->type)
753 	{
754 	case FILTER_RULE_MIME_TYPE:
755           if (filter_info->mime_type != NULL)
756             {
757               gchar *filter_content_type, *rule_content_type;
758               gboolean match;
759 
760               filter_content_type = g_content_type_from_mime_type (filter_info->mime_type);
761               rule_content_type = g_content_type_from_mime_type (rule->u.mime_type);
762               match = filter_content_type != NULL &&
763                       rule_content_type != NULL &&
764                       g_content_type_is_a (filter_content_type, rule_content_type);
765               g_free (filter_content_type);
766               g_free (rule_content_type);
767 
768               if (match)
769                 return TRUE;
770         }
771 	  break;
772 	case FILTER_RULE_PATTERN:
773 	  if (filter_info->display_name != NULL &&
774 	      _gtk_fnmatch (rule->u.pattern, filter_info->display_name, FALSE))
775 	    return TRUE;
776 	  break;
777 	case FILTER_RULE_PIXBUF_FORMATS:
778 	  {
779 	    GSList *list;
780 
781 	    if (!filter_info->mime_type)
782 	      break;
783 
784 	    for (list = rule->u.pixbuf_formats; list; list = list->next)
785 	      {
786 		int i;
787 		gchar **mime_types;
788 
789 		mime_types = gdk_pixbuf_format_get_mime_types (list->data);
790 
791 		for (i = 0; mime_types[i] != NULL; i++)
792 		  {
793 		    if (strcmp (mime_types[i], filter_info->mime_type) == 0)
794 		      {
795 			g_strfreev (mime_types);
796 			return TRUE;
797 		      }
798 		  }
799 
800 		g_strfreev (mime_types);
801 	      }
802 	    break;
803 	  }
804 	case FILTER_RULE_CUSTOM:
805 	  if (rule->u.custom.func (filter_info, rule->u.custom.data))
806 	    return TRUE;
807 	  break;
808 	}
809     }
810 
811   return FALSE;
812 }
813 
814 /**
815  * gtk_file_filter_to_gvariant:
816  * @filter: a #GtkFileFilter
817  *
818  * Serialize a file filter to an a{sv} variant.
819  *
820  * Returns: (transfer none): a new, floating, #GVariant
821  *
822  * Since: 3.22
823  */
824 GVariant *
gtk_file_filter_to_gvariant(GtkFileFilter * filter)825 gtk_file_filter_to_gvariant (GtkFileFilter *filter)
826 {
827   GVariantBuilder builder;
828   GSList *l;
829 
830   g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(us)"));
831   for (l = filter->rules; l; l = l->next)
832     {
833       FilterRule *rule = l->data;
834 
835       switch (rule->type)
836         {
837         case FILTER_RULE_PATTERN:
838           g_variant_builder_add (&builder, "(us)", 0, rule->u.pattern);
839           break;
840         case FILTER_RULE_MIME_TYPE:
841           g_variant_builder_add (&builder, "(us)", 1, rule->u.mime_type);
842           break;
843         case FILTER_RULE_PIXBUF_FORMATS:
844           {
845 	    GSList *f;
846 
847 	    for (f = rule->u.pixbuf_formats; f; f = f->next)
848 	      {
849                 GdkPixbufFormat *fmt = f->data;
850                 gchar **mime_types;
851                 int i;
852 
853                 mime_types = gdk_pixbuf_format_get_mime_types (fmt);
854                 for (i = 0; mime_types[i]; i++)
855                   g_variant_builder_add (&builder, "(us)", 1, mime_types[i]);
856                 g_strfreev (mime_types);
857               }
858           }
859           break;
860         case FILTER_RULE_CUSTOM:
861         default:
862           break;
863         }
864     }
865 
866   return g_variant_new ("(s@a(us))", filter->name, g_variant_builder_end (&builder));
867 }
868 
869 /**
870  * gtk_file_filter_new_from_gvariant:
871  * @variant: an a{sv} #GVariant
872  *
873  * Deserialize a file filter from an a{sv} variant in
874  * the format produced by gtk_file_filter_to_gvariant().
875  *
876  * Returns: (transfer full): a new #GtkFileFilter object
877  *
878  * Since: 3.22
879  */
880 GtkFileFilter *
gtk_file_filter_new_from_gvariant(GVariant * variant)881 gtk_file_filter_new_from_gvariant (GVariant *variant)
882 {
883   GtkFileFilter *filter;
884   GVariantIter *iter;
885   const char *name;
886   int type;
887   char *tmp;
888 
889   filter = gtk_file_filter_new ();
890 
891   g_variant_get (variant, "(&sa(us))", &name, &iter);
892 
893   gtk_file_filter_set_name (filter, name);
894 
895   while (g_variant_iter_next (iter, "(u&s)", &type, &tmp))
896     {
897       switch (type)
898         {
899         case 0:
900           gtk_file_filter_add_pattern (filter, tmp);
901           break;
902         case 1:
903           gtk_file_filter_add_mime_type (filter, tmp);
904           break;
905         default:
906           break;
907        }
908     }
909   g_variant_iter_free (iter);
910 
911   return filter;
912 }
913