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 | |
82 | typedef struct _GtkFileFilterClass GtkFileFilterClass; |
83 | typedef 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 | |
89 | typedef enum { |
90 | FILTER_RULE_PATTERN, |
91 | FILTER_RULE_MIME_TYPE, |
92 | FILTER_RULE_SUFFIX, |
93 | FILTER_RULE_PIXBUF_FORMATS |
94 | } FilterRuleType; |
95 | |
96 | struct _GtkFileFilterClass |
97 | { |
98 | GtkFilterClass parent_class; |
99 | }; |
100 | |
101 | struct _GtkFileFilter |
102 | { |
103 | GtkFilter parent_instance; |
104 | |
105 | char *name; |
106 | GSList *rules; |
107 | |
108 | char **attributes; |
109 | }; |
110 | |
111 | struct _FilterRule |
112 | { |
113 | FilterRuleType type; |
114 | |
115 | union { |
116 | char *pattern; |
117 | char **content_types; |
118 | } u; |
119 | }; |
120 | |
121 | enum { |
122 | PROP_0, |
123 | PROP_NAME, |
124 | NUM_PROPERTIES |
125 | }; |
126 | |
127 | static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; |
128 | |
129 | |
130 | static gboolean gtk_file_filter_match (GtkFilter *filter, |
131 | gpointer item); |
132 | static GtkFilterMatch gtk_file_filter_get_strictness (GtkFilter *filter); |
133 | |
134 | static void gtk_file_filter_buildable_init (GtkBuildableIface *iface); |
135 | |
136 | |
137 | G_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 | |
141 | static void |
142 | gtk_file_filter_init (GtkFileFilter *object) |
143 | { |
144 | } |
145 | |
146 | static void |
147 | gtk_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 | |
165 | static void |
166 | gtk_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 | |
184 | static void |
185 | filter_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 | |
203 | static void |
204 | gtk_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 | |
216 | static void |
217 | gtk_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 | |
251 | typedef enum |
252 | { |
253 | PARSE_MIME_TYPES, |
254 | PARSE_PATTERNS, |
255 | PARSE_SUFFIXES |
256 | } ParserType; |
257 | |
258 | typedef struct |
259 | { |
260 | GtkFileFilter *filter; |
261 | GtkBuilder *builder; |
262 | ParserType type; |
263 | GString *string; |
264 | gboolean parsing; |
265 | } SubParserData; |
266 | |
267 | static void |
268 | parser_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 | |
321 | static void |
322 | parser_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 | |
334 | static void |
335 | parser_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 | |
364 | static const GtkBuildableParser sub_parser = |
365 | { |
366 | parser_start_element, |
367 | parser_end_element, |
368 | parser_text_element, |
369 | }; |
370 | |
371 | static gboolean |
372 | gtk_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 | |
418 | static void |
419 | gtk_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 | |
436 | static void |
437 | gtk_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 | */ |
467 | GtkFileFilter * |
468 | gtk_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 | */ |
484 | void |
485 | gtk_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 | */ |
509 | const char * |
510 | gtk_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 | |
517 | static void |
518 | file_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 | |
526 | static void |
527 | file_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 | */ |
553 | void |
554 | gtk_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 | */ |
582 | void |
583 | gtk_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 | */ |
614 | void |
615 | gtk_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 | */ |
641 | void |
642 | gtk_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 | */ |
691 | const char ** |
692 | gtk_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 | |
701 | NSArray * _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 | |
776 | char ** |
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 | |
832 | static GtkFilterMatch |
833 | gtk_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 | |
855 | static gboolean |
856 | gtk_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 | */ |
925 | GVariant * |
926 | gtk_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 | */ |
979 | GtkFileFilter * |
980 | gtk_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 | |