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 * GtkFileFilter:
21 *
22 * `GtkFileFilter` filters files by name or mime type.
23 *
24 * `GtkFileFilter` can be used to restrict the files being shown in a
25 * `GtkFileChooser`. Files can be filtered based on their name (with
26 * [method@Gtk.FileFilter.add_pattern] or [method@Gtk.FileFilter.add_suffix])
27 * or on their mime type (with [method@Gtk.FileFilter.add_mime_type]).
28 *
29 * Filtering by mime types handles aliasing and subclassing of mime
30 * types; e.g. a filter for text/plain also matches a file with mime
31 * type application/rtf, since application/rtf is a subclass of
32 * text/plain. Note that `GtkFileFilter` allows wildcards for the
33 * subtype of a mime type, so you can e.g. filter for image/\*.
34 *
35 * Normally, file filters are used by adding them to a `GtkFileChooser`
36 * (see [method@Gtk.FileChooser.add_filter]), but it is also possible to
37 * manually use a file filter on any [class@Gtk.FilterListModel] containing
38 * `GFileInfo` objects.
39 *
40 * # GtkFileFilter as GtkBuildable
41 *
42 * The `GtkFileFilter` implementation of the `GtkBuildable` interface
43 * supports adding rules using the `<mime-types>` and `<patterns>` and
44 * `<suffixes>` elements and listing the rules within. Specifying a
45 * `<mime-type>` or `<pattern>` or `<suffix>` has the same effect as
46 * as calling
47 * [method@Gtk.FileFilter.add_mime_type] or
48 * [method@Gtk.FileFilter.add_pattern] or
49 * [method@Gtk.FileFilter.add_suffix].
50 *
51 * An example of a UI definition fragment specifying `GtkFileFilter`
52 * rules:
53 * ```xml
54 * <object class="GtkFileFilter">
55 * <property name="name" translatable="yes">Text and Images</property>
56 * <mime-types>
57 * <mime-type>text/plain</mime-type>
58 * <mime-type>image/ *</mime-type>
59 * </mime-types>
60 * <patterns>
61 * <pattern>*.txt</pattern>
62 * </patterns>
63 * <suffixes>
64 * <suffix>png</suffix>
65 * </suffixes>
66 * </object>
67 * ```
68 */
69
70#include "config.h"
71#include <string.h>
72
73#include <gdk-pixbuf/gdk-pixbuf.h>
74
75#include "gtkfilefilterprivate.h"
76#include "gtkbuildable.h"
77#include "gtkbuilderprivate.h"
78#include "gtkintl.h"
79#include "gtkprivate.h"
80#include "gtkfilter.h"
81
82typedef struct _GtkFileFilterClass GtkFileFilterClass;
83typedef struct _FilterRule FilterRule;
84
85#define GTK_FILE_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_FILTER, GtkFileFilterClass))
86#define GTK_IS_FILE_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_FILTER))
87#define GTK_FILE_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_FILTER, GtkFileFilterClass))
88
89typedef enum {
90 FILTER_RULE_PATTERN,
91 FILTER_RULE_MIME_TYPE,
92 FILTER_RULE_SUFFIX,
93 FILTER_RULE_PIXBUF_FORMATS
94} FilterRuleType;
95
96struct _GtkFileFilterClass
97{
98 GtkFilterClass parent_class;
99};
100
101struct _GtkFileFilter
102{
103 GtkFilter parent_instance;
104
105 char *name;
106 GSList *rules;
107
108 char **attributes;
109};
110
111struct _FilterRule
112{
113 FilterRuleType type;
114
115 union {
116 char *pattern;
117 char **content_types;
118 } u;
119};
120
121enum {
122 PROP_0,
123 PROP_NAME,
124 NUM_PROPERTIES
125};
126
127static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
128
129
130static gboolean gtk_file_filter_match (GtkFilter *filter,
131 gpointer item);
132static GtkFilterMatch gtk_file_filter_get_strictness (GtkFilter *filter);
133
134static void gtk_file_filter_buildable_init (GtkBuildableIface *iface);
135
136
137G_DEFINE_TYPE_WITH_CODE (GtkFileFilter, gtk_file_filter, GTK_TYPE_FILTER,
138 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
139 gtk_file_filter_buildable_init))
140
141static void
142gtk_file_filter_init (GtkFileFilter *object)
143{
144}
145
146static void
147gtk_file_filter_set_property (GObject *object,
148 guint prop_id,
149 const GValue *value,
150 GParamSpec *pspec)
151{
152 GtkFileFilter *filter = GTK_FILE_FILTER (object);
153
154 switch (prop_id)
155 {
156 case PROP_NAME:
157 gtk_file_filter_set_name (filter, name: g_value_get_string (value));
158 break;
159 default:
160 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
161 break;
162 }
163}
164
165static void
166gtk_file_filter_get_property (GObject *object,
167 guint prop_id,
168 GValue *value,
169 GParamSpec *pspec)
170{
171 GtkFileFilter *filter = GTK_FILE_FILTER (object);
172
173 switch (prop_id)
174 {
175 case PROP_NAME:
176 g_value_set_string (value, v_string: filter->name);
177 break;
178 default:
179 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
180 break;
181 }
182}
183
184static void
185filter_rule_free (FilterRule *rule)
186{
187 switch (rule->type)
188 {
189 case FILTER_RULE_PATTERN:
190 case FILTER_RULE_SUFFIX:
191 g_free (mem: rule->u.pattern);
192 break;
193 case FILTER_RULE_MIME_TYPE:
194 case FILTER_RULE_PIXBUF_FORMATS:
195 g_strfreev (str_array: rule->u.content_types);
196 break;
197 default:
198 break;
199 }
200 g_slice_free (FilterRule, rule);
201}
202
203static void
204gtk_file_filter_finalize (GObject *object)
205{
206 GtkFileFilter *filter = GTK_FILE_FILTER (object);
207
208 g_slist_free_full (list: filter->rules, free_func: (GDestroyNotify)filter_rule_free);
209 g_strfreev (str_array: filter->attributes);
210
211 g_free (mem: filter->name);
212
213 G_OBJECT_CLASS (gtk_file_filter_parent_class)->finalize (object);
214}
215
216static void
217gtk_file_filter_class_init (GtkFileFilterClass *class)
218{
219 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
220 GtkFilterClass *filter_class = GTK_FILTER_CLASS (ptr: class);
221
222 gobject_class->set_property = gtk_file_filter_set_property;
223 gobject_class->get_property = gtk_file_filter_get_property;
224 gobject_class->finalize = gtk_file_filter_finalize;
225
226 filter_class->get_strictness = gtk_file_filter_get_strictness;
227 filter_class->match = gtk_file_filter_match;
228
229 /**
230 * GtkFileFilter:name: (attributes org.gtk.Property.get=gtk_file_filter_get_name org.gtk.Property.set=gtk_file_filter_set_name)
231 *
232 * The human-readable name of the filter.
233 *
234 * This is the string that will be displayed in the file chooser
235 * user interface if there is a selectable list of filters.
236 */
237 props[PROP_NAME] =
238 g_param_spec_string (name: "name",
239 P_("Name"),
240 P_("The human-readable name for this filter"),
241 NULL,
242 flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
243
244 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: props);
245}
246
247/*
248 * GtkBuildable implementation
249 */
250
251typedef enum
252{
253 PARSE_MIME_TYPES,
254 PARSE_PATTERNS,
255 PARSE_SUFFIXES
256} ParserType;
257
258typedef struct
259{
260 GtkFileFilter *filter;
261 GtkBuilder *builder;
262 ParserType type;
263 GString *string;
264 gboolean parsing;
265} SubParserData;
266
267static void
268parser_start_element (GtkBuildableParseContext *context,
269 const char *element_name,
270 const char **names,
271 const char **values,
272 gpointer user_data,
273 GError **error)
274{
275 SubParserData *data = (SubParserData*)user_data;
276
277 if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error,
278 first_type: G_MARKUP_COLLECT_INVALID, NULL, NULL,
279 G_MARKUP_COLLECT_INVALID))
280 {
281 _gtk_builder_prefix_error (builder: data->builder, context, error);
282 return;
283 }
284
285 if (strcmp (s1: element_name, s2: "mime-types") == 0 ||
286 strcmp (s1: element_name, s2: "patterns") == 0 ||
287 strcmp (s1: element_name, s2: "suffixes") == 0)
288 {
289 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "object", error))
290 return;
291 }
292 else if (strcmp (s1: element_name, s2: "mime-type") == 0)
293 {
294 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "mime-types", error))
295 return;
296
297 data->parsing = TRUE;
298 }
299 else if (strcmp (s1: element_name, s2: "pattern") == 0)
300 {
301 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "patterns", error))
302 return;
303
304 data->parsing = TRUE;
305 }
306 else if (strcmp (s1: element_name, s2: "suffix") == 0)
307 {
308 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "suffixes", error))
309 return;
310
311 data->parsing = TRUE;
312 }
313 else
314 {
315 _gtk_builder_error_unhandled_tag (builder: data->builder, context,
316 object: "GtkFileFilter", element_name,
317 error);
318 }
319}
320
321static void
322parser_text_element (GtkBuildableParseContext *context,
323 const char *text,
324 gsize text_len,
325 gpointer user_data,
326 GError **error)
327{
328 SubParserData *data = (SubParserData*)user_data;
329
330 if (data->parsing)
331 g_string_append_len (string: data->string, val: text, len: text_len);
332}
333
334static void
335parser_end_element (GtkBuildableParseContext *context,
336 const char *element_name,
337 gpointer user_data,
338 GError **error)
339{
340 SubParserData *data = (SubParserData*)user_data;
341
342 if (data->string && data->parsing)
343 {
344 switch (data->type)
345 {
346 case PARSE_MIME_TYPES:
347 gtk_file_filter_add_mime_type (filter: data->filter, mime_type: data->string->str);
348 break;
349 case PARSE_PATTERNS:
350 gtk_file_filter_add_pattern (filter: data->filter, pattern: data->string->str);
351 break;
352 case PARSE_SUFFIXES:
353 gtk_file_filter_add_suffix (filter: data->filter, suffix: data->string->str);
354 break;
355 default:
356 break;
357 }
358 }
359
360 g_string_set_size (string: data->string, len: 0);
361 data->parsing = FALSE;
362}
363
364static const GtkBuildableParser sub_parser =
365 {
366 parser_start_element,
367 parser_end_element,
368 parser_text_element,
369 };
370
371static gboolean
372gtk_file_filter_buildable_custom_tag_start (GtkBuildable *buildable,
373 GtkBuilder *builder,
374 GObject *child,
375 const char *tagname,
376 GtkBuildableParser *parser,
377 gpointer *parser_data)
378{
379 SubParserData *data = NULL;
380
381 if (strcmp (s1: tagname, s2: "mime-types") == 0)
382 {
383 data = g_slice_new0 (SubParserData);
384 data->string = g_string_new (init: "");
385 data->type = PARSE_MIME_TYPES;
386 data->filter = GTK_FILE_FILTER (buildable);
387 data->builder = builder;
388
389 *parser = sub_parser;
390 *parser_data = data;
391 }
392 else if (strcmp (s1: tagname, s2: "patterns") == 0)
393 {
394 data = g_slice_new0 (SubParserData);
395 data->string = g_string_new (init: "");
396 data->type = PARSE_PATTERNS;
397 data->filter = GTK_FILE_FILTER (buildable);
398 data->builder = builder;
399
400 *parser = sub_parser;
401 *parser_data = data;
402 }
403 else if (strcmp (s1: tagname, s2: "suffixes") == 0)
404 {
405 data = g_slice_new0 (SubParserData);
406 data->string = g_string_new (init: "");
407 data->type = PARSE_SUFFIXES;
408 data->filter = GTK_FILE_FILTER (buildable);
409 data->builder = builder;
410
411 *parser = sub_parser;
412 *parser_data = data;
413 }
414
415 return data != NULL;
416}
417
418static void
419gtk_file_filter_buildable_custom_tag_end (GtkBuildable *buildable,
420 GtkBuilder *builder,
421 GObject *child,
422 const char *tagname,
423 gpointer user_data)
424{
425 if (strcmp (s1: tagname, s2: "mime-types") == 0 ||
426 strcmp (s1: tagname, s2: "patterns") == 0 ||
427 strcmp (s1: tagname, s2: "suffixes") == 0)
428 {
429 SubParserData *data = (SubParserData*)user_data;
430
431 g_string_free (string: data->string, TRUE);
432 g_slice_free (SubParserData, data);
433 }
434}
435
436static void
437gtk_file_filter_buildable_init (GtkBuildableIface *iface)
438{
439 iface->custom_tag_start = gtk_file_filter_buildable_custom_tag_start;
440 iface->custom_tag_end = gtk_file_filter_buildable_custom_tag_end;
441}
442
443/*
444 * Public api
445 */
446
447/**
448 * gtk_file_filter_new:
449 *
450 * Creates a new `GtkFileFilter` with no rules added to it.
451 *
452 * Such a filter doesn’t accept any files, so is not
453 * particularly useful until you add rules with
454 * [method@Gtk.FileFilter.add_mime_type],
455 * [method@Gtk.FileFilter.add_pattern],
456 * [method@Gtk.FileFilter.add_suffix] or
457 * [method@Gtk.FileFilter.add_pixbuf_formats].
458 *
459 * To create a filter that accepts any file, use:
460 * ```c
461 * GtkFileFilter *filter = gtk_file_filter_new ();
462 * gtk_file_filter_add_pattern (filter, "*");
463 * ```
464 *
465 * Returns: a new `GtkFileFilter`
466 */
467GtkFileFilter *
468gtk_file_filter_new (void)
469{
470 return g_object_new (GTK_TYPE_FILE_FILTER, NULL);
471}
472
473/**
474 * gtk_file_filter_set_name: (attributes org.gtk.Method.set_property=name)
475 * @filter: a `GtkFileFilter`
476 * @name: (nullable): the human-readable-name for the filter, or %NULL
477 * to remove any existing name.
478 *
479 * Sets a human-readable name of the filter.
480 *
481 * This is the string that will be displayed in the file chooser
482 * if there is a selectable list of filters.
483 */
484void
485gtk_file_filter_set_name (GtkFileFilter *filter,
486 const char *name)
487{
488 g_return_if_fail (GTK_IS_FILE_FILTER (filter));
489
490 if (g_strcmp0 (str1: filter->name, str2: name) == 0)
491 return;
492
493 g_free (mem: filter->name);
494 filter->name = g_strdup (str: name);
495
496 g_object_notify_by_pspec (G_OBJECT (filter), pspec: props[PROP_NAME]);
497}
498
499/**
500 * gtk_file_filter_get_name: (attributes org.gtk.Method.get_property=name)
501 * @filter: a `GtkFileFilter`
502 *
503 * Gets the human-readable name for the filter.
504 *
505 * See [method@Gtk.FileFilter.set_name].
506 *
507 * Returns: (nullable): The human-readable name of the filter
508 */
509const char *
510gtk_file_filter_get_name (GtkFileFilter *filter)
511{
512 g_return_val_if_fail (GTK_IS_FILE_FILTER (filter), NULL);
513
514 return filter->name;
515}
516
517static void
518file_filter_add_rule (GtkFileFilter *filter,
519 FilterRule *rule)
520{
521 filter->rules = g_slist_append (list: filter->rules, data: rule);
522
523 gtk_filter_changed (self: GTK_FILTER (ptr: filter), change: GTK_FILTER_CHANGE_LESS_STRICT);
524}
525
526static void
527file_filter_add_attribute (GtkFileFilter *filter,
528 const char *attribute)
529{
530 int i;
531
532 if (filter->attributes)
533 for (i = 0; filter->attributes[i]; i++)
534 {
535 if (strcmp (s1: filter->attributes[i], s2: attribute) == 0)
536 return;
537 }
538 else
539 i = 0;
540
541 filter->attributes = (char **)g_renew (char **, filter->attributes, i + 2);
542 filter->attributes[i] = g_strdup (str: attribute);
543 filter->attributes[i + 1] = NULL;
544}
545
546/**
547 * gtk_file_filter_add_mime_type:
548 * @filter: A `GtkFileFilter`
549 * @mime_type: name of a MIME type
550 *
551 * Adds a rule allowing a given mime type to @filter.
552 */
553void
554gtk_file_filter_add_mime_type (GtkFileFilter *filter,
555 const char *mime_type)
556{
557 FilterRule *rule;
558
559 g_return_if_fail (GTK_IS_FILE_FILTER (filter));
560 g_return_if_fail (mime_type != NULL);
561
562 rule = g_slice_new (FilterRule);
563 rule->type = FILTER_RULE_MIME_TYPE;
564 rule->u.content_types = g_new0 (char *, 2);
565 rule->u.content_types[0] = g_content_type_from_mime_type (mime_type);
566
567 file_filter_add_attribute (filter, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
568 file_filter_add_rule (filter, rule);
569}
570
571/**
572 * gtk_file_filter_add_pattern:
573 * @filter: a `GtkFileFilter`
574 * @pattern: a shell style glob
575 *
576 * Adds a rule allowing a shell style glob to a filter.
577 *
578 * Note that it depends on the platform whether pattern
579 * matching ignores case or not. On Windows, it does, on
580 * other platforms, it doesn't.
581 */
582void
583gtk_file_filter_add_pattern (GtkFileFilter *filter,
584 const char *pattern)
585{
586 FilterRule *rule;
587
588 g_return_if_fail (GTK_IS_FILE_FILTER (filter));
589 g_return_if_fail (pattern != NULL);
590
591 rule = g_slice_new (FilterRule);
592 rule->type = FILTER_RULE_PATTERN;
593 rule->u.pattern = g_strdup (str: pattern);
594
595 file_filter_add_attribute (filter, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
596 file_filter_add_rule (filter, rule);
597}
598
599/**
600 * gtk_file_filter_add_suffix:
601 * @filter: a `GtkFileFilter`
602 * @suffix: filename suffix to match
603 *
604 * Adds a suffix match rule to a filter.
605 *
606 * This is similar to adding a match for the pattern
607 * "*.@suffix".
608 *
609 * In contrast to pattern matches, suffix matches
610 * are *always* case-insensitive.
611 *
612 * Since: 4.4
613 */
614void
615gtk_file_filter_add_suffix (GtkFileFilter *filter,
616 const char *suffix)
617{
618 FilterRule *rule;
619
620 g_return_if_fail (GTK_IS_FILE_FILTER (filter));
621 g_return_if_fail (suffix != NULL);
622
623 rule = g_slice_new (FilterRule);
624 rule->type = FILTER_RULE_SUFFIX;
625 rule->u.pattern = g_strconcat (string1: "*.", suffix, NULL);
626
627 file_filter_add_attribute (filter, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
628 file_filter_add_rule (filter, rule);
629}
630
631/**
632 * gtk_file_filter_add_pixbuf_formats:
633 * @filter: a `GtkFileFilter`
634 *
635 * Adds a rule allowing image files in the formats supported
636 * by GdkPixbuf.
637 *
638 * This is equivalent to calling [method@Gtk.FileFilter.add_mime_type]
639 * for all the supported mime types.
640 */
641void
642gtk_file_filter_add_pixbuf_formats (GtkFileFilter *filter)
643{
644 FilterRule *rule;
645 GPtrArray *array;
646 GSList *formats, *l;
647
648 g_return_if_fail (GTK_IS_FILE_FILTER (filter));
649
650 rule = g_slice_new (FilterRule);
651 rule->type = FILTER_RULE_PIXBUF_FORMATS;
652
653 array = g_ptr_array_new ();
654
655 formats = gdk_pixbuf_get_formats ();
656 for (l = formats; l; l = l->next)
657 {
658 int i;
659 char **mime_types;
660
661 mime_types = gdk_pixbuf_format_get_mime_types (format: l->data);
662
663 for (i = 0; mime_types[i] != NULL; i++)
664 {
665 g_ptr_array_add (array, data: g_content_type_from_mime_type (mime_type: mime_types[i]));
666 }
667 }
668 g_slist_free (list: formats);
669
670 g_ptr_array_add (array, NULL);
671
672 rule->u.content_types = (char **)g_ptr_array_free (array, FALSE);
673
674 file_filter_add_attribute (filter, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
675 file_filter_add_rule (filter, rule);
676}
677
678/**
679 * gtk_file_filter_get_attributes:
680 * @filter: a `GtkFileFilter`
681 *
682 * Gets the attributes that need to be filled in for the `GFileInfo`
683 * passed to this filter.
684 *
685 * This function will not typically be used by applications;
686 * it is intended principally for use in the implementation
687 * of `GtkFileChooser`.
688 *
689 * Returns: (transfer none): the attributes
690 */
691const char **
692gtk_file_filter_get_attributes (GtkFileFilter *filter)
693{
694 return (const char **)filter->attributes;
695}
696
697#ifdef GDK_WINDOWING_MACOS
698
699#import <Foundation/Foundation.h>
700
701NSArray * _gtk_file_filter_get_as_pattern_nsstrings (GtkFileFilter *filter)
702{
703 NSMutableArray *array = [[NSMutableArray alloc] init];
704 GSList *tmp_list;
705
706 for (tmp_list = filter->rules; tmp_list; tmp_list = tmp_list->next)
707 {
708 FilterRule *rule = tmp_list->data;
709
710 switch (rule->type)
711 {
712 case FILTER_RULE_MIME_TYPE:
713 {
714 // convert mime-types to UTI
715 NSString *mime_type_nsstring = [NSString stringWithUTF8String: rule->u.content_types[0]];
716 NSString *uti_nsstring = (NSString *) UTTypeCreatePreferredIdentifierForTag (kUTTagClassMIMEType, (CFStringRef) mime_type_nsstring, NULL);
717 if (uti_nsstring == NULL)
718 {
719 [array release];
720 return NULL;
721 }
722 [array addObject:uti_nsstring];
723 }
724 break;
725
726 case FILTER_RULE_PATTERN:
727 case FILTER_RULE_SUFFIX:
728 {
729 // patterns will need to be stripped of their leading *.
730 GString *pattern = g_string_new (rule->u.pattern);
731 if (strncmp (pattern->str, "*.", 2) == 0)
732 {
733 pattern = g_string_erase (pattern, 0, 2);
734 }
735 else if (strncmp (pattern->str, "*", 1) == 0)
736 {
737 pattern = g_string_erase (pattern, 0, 1);
738 }
739 char *pattern_c = g_string_free (pattern, FALSE);
740 NSString *pattern_nsstring = [NSString stringWithUTF8String:pattern_c];
741 g_free (pattern_c);
742 [pattern_nsstring retain];
743 [array addObject:pattern_nsstring];
744 }
745 break;
746
747 case FILTER_RULE_PIXBUF_FORMATS:
748 {
749 GSList *formats, *l;
750
751 formats = gdk_pixbuf_get_formats ();
752 for (l = formats; l; l = l->next)
753 {
754 int i;
755 char **extensions;
756
757 extensions = gdk_pixbuf_format_get_extensions (l->data);
758
759 for (i = 0; extensions[i] != NULL; i++)
760 {
761 NSString *extension = [NSString stringWithUTF8String: extensions[i]];
762 [extension retain];
763 [array addObject:extension];
764 }
765 g_strfreev (extensions);
766 }
767 g_slist_free (formats);
768 break;
769 }
770 }
771 }
772 return array;
773}
774#endif
775
776char **
777_gtk_file_filter_get_as_patterns (GtkFileFilter *filter)
778{
779 GPtrArray *array;
780 GSList *tmp_list;
781
782 array = g_ptr_array_new_with_free_func (element_free_func: g_free);
783
784 for (tmp_list = filter->rules; tmp_list; tmp_list = tmp_list->next)
785 {
786 FilterRule *rule = tmp_list->data;
787
788 switch (rule->type)
789 {
790 case FILTER_RULE_MIME_TYPE:
791 g_ptr_array_free (array, TRUE);
792 return NULL;
793 break;
794
795 case FILTER_RULE_PATTERN:
796 case FILTER_RULE_SUFFIX:
797 /* Note: we don't make the suffix pattern explicitly
798 * case-insensitive, since this is only used on Windows
799 */
800 g_ptr_array_add (array, data: g_strdup (str: rule->u.pattern));
801 break;
802
803 case FILTER_RULE_PIXBUF_FORMATS:
804 {
805 GSList *formats, *l;
806
807 formats = gdk_pixbuf_get_formats ();
808 for (l = formats; l; l = l->next)
809 {
810 int i;
811 char **extensions;
812
813 extensions = gdk_pixbuf_format_get_extensions (format: l->data);
814
815 for (i = 0; extensions[i] != NULL; i++)
816 g_ptr_array_add (array, data: g_strdup_printf (format: "*.%s", extensions[i]));
817
818 g_strfreev (str_array: extensions);
819 }
820 g_slist_free (list: formats);
821 break;
822 }
823 default:
824 break;
825 }
826 }
827
828 g_ptr_array_add (array, NULL); /* Null terminate */
829 return (char **)g_ptr_array_free (array, FALSE);
830}
831
832static GtkFilterMatch
833gtk_file_filter_get_strictness (GtkFilter *filter)
834{
835 GtkFileFilter *file_filter = GTK_FILE_FILTER (filter);
836
837 /* Handle only the documented cases for 'match all'
838 * and match none. There are of course other ways to
839 * make filters that do this.
840 */
841 if (file_filter->rules == NULL)
842 return GTK_FILTER_MATCH_NONE;
843
844 if (file_filter->rules->next == NULL)
845 {
846 FilterRule *rule = file_filter->rules->data;
847 if (rule->type == FILTER_RULE_PATTERN &&
848 strcmp (s1: rule->u.pattern, s2: "*") == 0)
849 return GTK_FILTER_MATCH_ALL;
850 }
851
852 return GTK_FILTER_MATCH_SOME;
853}
854
855static gboolean
856gtk_file_filter_match (GtkFilter *filter,
857 gpointer item)
858{
859 GtkFileFilter *file_filter = GTK_FILE_FILTER (filter);
860 GFileInfo *info = item;
861 GSList *tmp_list;
862
863 if (!G_IS_FILE_INFO (item))
864 return TRUE;
865
866 for (tmp_list = file_filter->rules; tmp_list; tmp_list = tmp_list->next)
867 {
868 FilterRule *rule = tmp_list->data;
869 gboolean ignore_case = FALSE;
870
871 switch (rule->type)
872 {
873 case FILTER_RULE_SUFFIX:
874 ignore_case = TRUE;
875 G_GNUC_FALLTHROUGH;
876
877 case FILTER_RULE_PATTERN:
878 {
879 const char *display_name;
880
881 display_name = g_file_info_get_display_name (info);
882 if (display_name)
883 {
884 if (_gtk_fnmatch (pattern: rule->u.pattern, string: display_name, FALSE, casefold: ignore_case))
885 return TRUE;
886 }
887 }
888 break;
889
890 case FILTER_RULE_MIME_TYPE:
891 case FILTER_RULE_PIXBUF_FORMATS:
892 {
893 const char *filter_content_type;
894
895 filter_content_type = g_file_info_get_content_type (info);
896 if (filter_content_type)
897 {
898 int i;
899
900 for (i = 0; rule->u.content_types[i]; i++)
901 {
902 if (g_content_type_is_a (type: filter_content_type, supertype: rule->u.content_types[i]))
903 return TRUE;
904 }
905 }
906 }
907 break;
908
909 default:
910 break;
911 }
912 }
913
914 return FALSE;
915}
916
917/**
918 * gtk_file_filter_to_gvariant:
919 * @filter: a `GtkFileFilter`
920 *
921 * Serialize a file filter to an `a{sv}` variant.
922 *
923 * Returns: (transfer none): a new, floating, `GVariant`
924 */
925GVariant *
926gtk_file_filter_to_gvariant (GtkFileFilter *filter)
927{
928 GVariantBuilder builder;
929 GSList *l;
930
931 g_variant_builder_init (builder: &builder, G_VARIANT_TYPE ("a(us)"));
932 for (l = filter->rules; l; l = l->next)
933 {
934 FilterRule *rule = l->data;
935 int i;
936
937 switch (rule->type)
938 {
939 case FILTER_RULE_PATTERN:
940 g_variant_builder_add (builder: &builder, format_string: "(us)", 0, rule->u.pattern);
941 break;
942
943 case FILTER_RULE_SUFFIX:
944 {
945 /* Tweak the glob, since the filechooser portal has no api
946 * for case-insensitive globs
947 */
948 char *pattern = _gtk_make_ci_glob_pattern (pattern: rule->u.pattern);
949 g_variant_builder_add (builder: &builder, format_string: "(us)", 0, pattern);
950 g_free (mem: pattern);
951 }
952 break;
953
954 case FILTER_RULE_MIME_TYPE:
955 case FILTER_RULE_PIXBUF_FORMATS:
956 for (i = 0; rule->u.content_types[i]; i++)
957 g_variant_builder_add (builder: &builder, format_string: "(us)", 1, rule->u.content_types[i]);
958 break;
959
960 default:
961 break;
962 }
963 }
964
965 return g_variant_new (format_string: "(s@a(us))", filter->name, g_variant_builder_end (builder: &builder));
966}
967
968/**
969 * gtk_file_filter_new_from_gvariant:
970 * @variant: an `a{sv}` `GVariant`
971 *
972 * Deserialize a file filter from a `GVariant`.
973 *
974 * The variant must be in the format produced by
975 * [method@Gtk.FileFilter.to_gvariant].
976 *
977 * Returns: (transfer full): a new `GtkFileFilter` object
978 */
979GtkFileFilter *
980gtk_file_filter_new_from_gvariant (GVariant *variant)
981{
982 GtkFileFilter *filter;
983 GVariantIter *iter;
984 const char *name;
985 int type;
986 char *tmp;
987
988 filter = gtk_file_filter_new ();
989
990 g_variant_get (value: variant, format_string: "(&sa(us))", &name, &iter);
991
992 gtk_file_filter_set_name (filter, name);
993
994 while (g_variant_iter_next (iter, format_string: "(u&s)", &type, &tmp))
995 {
996 switch (type)
997 {
998 case 0:
999 gtk_file_filter_add_pattern (filter, pattern: tmp);
1000 break;
1001 case 1:
1002 gtk_file_filter_add_mime_type (filter, mime_type: tmp);
1003 break;
1004 default:
1005 break;
1006 }
1007 }
1008 g_variant_iter_free (iter);
1009
1010 return filter;
1011}
1012

source code of gtk/gtk/gtkfilefilter.c