forked from AuroraMiddleware/gtk
Merge branch 'ci-file-filters' into 'master'
GtkFileFilter: Allow case-insensitive patterns Closes #3705 See merge request GNOME/gtk!359
This commit is contained in:
commit
1605ec0a44
@ -253,3 +253,67 @@ _gtk_fnmatch (const char *pattern,
|
||||
{
|
||||
return gtk_fnmatch_intern (pattern, string, TRUE, no_leading_period, casefold);
|
||||
}
|
||||
|
||||
/* Turn a glob pattern into a case-insensitive one, by replacing
|
||||
* alphabetic characters by [xX] ranges.
|
||||
*/
|
||||
char *
|
||||
_gtk_make_ci_glob_pattern (const char *pattern)
|
||||
{
|
||||
GString *s;
|
||||
gboolean in_range = FALSE;
|
||||
|
||||
s = g_string_new ("");
|
||||
for (const char *p = pattern; *p; p = g_utf8_next_char (p))
|
||||
{
|
||||
gunichar c = g_utf8_get_char (p);
|
||||
if (in_range)
|
||||
{
|
||||
g_string_append_unichar (s, c);
|
||||
if (c == ']')
|
||||
in_range = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
#if DO_ESCAPE
|
||||
if (c == '\\')
|
||||
{
|
||||
g_string_append (s, "\\");
|
||||
p = g_utf8_next_char (p);
|
||||
if (*p == '\0')
|
||||
break;
|
||||
|
||||
c = g_utf8_get_char (p);
|
||||
g_string_append_unichar (s, c);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (c == '[')
|
||||
{
|
||||
g_string_append (s, "[");
|
||||
p = g_utf8_next_char (p);
|
||||
if (*p == '\0')
|
||||
break;
|
||||
|
||||
c = g_utf8_get_char (p);
|
||||
g_string_append_unichar (s, c);
|
||||
|
||||
in_range = TRUE;
|
||||
continue;
|
||||
}
|
||||
else if (g_unichar_isalpha (c))
|
||||
{
|
||||
g_string_append (s, "[");
|
||||
g_string_append_unichar (s, g_unichar_tolower (c));
|
||||
g_string_append_unichar (s, g_unichar_toupper (c));
|
||||
g_string_append (s, "]");
|
||||
}
|
||||
else
|
||||
{
|
||||
g_string_append_unichar (s, c);
|
||||
}
|
||||
}
|
||||
|
||||
return g_string_free (s, FALSE);
|
||||
}
|
||||
|
@ -23,8 +23,8 @@
|
||||
*
|
||||
* `GtkFileFilter` can be used to restrict the files being shown in a
|
||||
* `GtkFileChooser`. Files can be filtered based on their name (with
|
||||
* [method@Gtk.FileFilter.add_pattern]) or on their mime type (with
|
||||
* [method@Gtk.FileFilter.add_mime_type]).
|
||||
* [method@Gtk.FileFilter.add_pattern] or [method@Gtk.FileFilter.add_suffix])
|
||||
* or on their mime type (with [method@Gtk.FileFilter.add_mime_type]).
|
||||
*
|
||||
* Filtering by mime types handles aliasing and subclassing of mime
|
||||
* types; e.g. a filter for text/plain also matches a file with mime
|
||||
@ -40,11 +40,13 @@
|
||||
* # GtkFileFilter as GtkBuildable
|
||||
*
|
||||
* The `GtkFileFilter` implementation of the `GtkBuildable` interface
|
||||
* supports adding rules using the <mime-types> and <patterns>
|
||||
* elements and listing the rules within. Specifying a <mime-type>
|
||||
* or <pattern> has the same effect as as calling
|
||||
* supports adding rules using the `<mime-types>` and `<patterns>` and
|
||||
* `<suffixes>` elements and listing the rules within. Specifying a
|
||||
* `<mime-type>` or `<pattern>` or `<suffix>` has the same effect as
|
||||
* as calling
|
||||
* [method@Gtk.FileFilter.add_mime_type] or
|
||||
* [method@Gtk.FileFilter.add_pattern].
|
||||
* [method@Gtk.FileFilter.add_pattern] or
|
||||
* [method@Gtk.FileFilter.add_suffix].
|
||||
*
|
||||
* An example of a UI definition fragment specifying `GtkFileFilter`
|
||||
* rules:
|
||||
@ -57,8 +59,10 @@
|
||||
* </mime-types>
|
||||
* <patterns>
|
||||
* <pattern>*.txt</pattern>
|
||||
* <pattern>*.png</pattern>
|
||||
* </patterns>
|
||||
* <suffixes>
|
||||
* <suffix>png</suffix>
|
||||
* </suffixes>
|
||||
* </object>
|
||||
* ```
|
||||
*/
|
||||
@ -85,6 +89,7 @@ typedef struct _FilterRule FilterRule;
|
||||
typedef enum {
|
||||
FILTER_RULE_PATTERN,
|
||||
FILTER_RULE_MIME_TYPE,
|
||||
FILTER_RULE_SUFFIX,
|
||||
FILTER_RULE_PIXBUF_FORMATS
|
||||
} FilterRuleType;
|
||||
|
||||
@ -182,6 +187,7 @@ filter_rule_free (FilterRule *rule)
|
||||
switch (rule->type)
|
||||
{
|
||||
case FILTER_RULE_PATTERN:
|
||||
case FILTER_RULE_SUFFIX:
|
||||
g_free (rule->u.pattern);
|
||||
break;
|
||||
case FILTER_RULE_MIME_TYPE:
|
||||
@ -245,7 +251,8 @@ gtk_file_filter_class_init (GtkFileFilterClass *class)
|
||||
typedef enum
|
||||
{
|
||||
PARSE_MIME_TYPES,
|
||||
PARSE_PATTERNS
|
||||
PARSE_PATTERNS,
|
||||
PARSE_SUFFIXES
|
||||
} ParserType;
|
||||
|
||||
typedef struct
|
||||
@ -293,6 +300,13 @@ parser_start_element (GtkBuildableParseContext *context,
|
||||
if (!_gtk_builder_check_parent (data->builder, context, "patterns", error))
|
||||
return;
|
||||
|
||||
data->parsing = TRUE;
|
||||
}
|
||||
else if (strcmp (element_name, "suffix") == 0)
|
||||
{
|
||||
if (!_gtk_builder_check_parent (data->builder, context, "suffixes", error))
|
||||
return;
|
||||
|
||||
data->parsing = TRUE;
|
||||
}
|
||||
else
|
||||
@ -334,6 +348,9 @@ parser_end_element (GtkBuildableParseContext *context,
|
||||
case PARSE_PATTERNS:
|
||||
gtk_file_filter_add_pattern (data->filter, data->string->str);
|
||||
break;
|
||||
case PARSE_SUFFIXES:
|
||||
gtk_file_filter_add_suffix (data->filter, data->string->str);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -379,6 +396,17 @@ gtk_file_filter_buildable_custom_tag_start (GtkBuildable *buildable,
|
||||
data->filter = GTK_FILE_FILTER (buildable);
|
||||
data->builder = builder;
|
||||
|
||||
*parser = sub_parser;
|
||||
*parser_data = data;
|
||||
}
|
||||
else if (strcmp (tagname, "suffixes") == 0)
|
||||
{
|
||||
data = g_slice_new0 (SubParserData);
|
||||
data->string = g_string_new ("");
|
||||
data->type = PARSE_SUFFIXES;
|
||||
data->filter = GTK_FILE_FILTER (buildable);
|
||||
data->builder = builder;
|
||||
|
||||
*parser = sub_parser;
|
||||
*parser_data = data;
|
||||
}
|
||||
@ -422,7 +450,8 @@ gtk_file_filter_buildable_init (GtkBuildableIface *iface)
|
||||
* Such a filter doesn’t accept any files, so is not
|
||||
* particularly useful until you add rules with
|
||||
* [method@Gtk.FileFilter.add_mime_type],
|
||||
* [method@Gtk.FileFilter.add_pattern], or
|
||||
* [method@Gtk.FileFilter.add_pattern],
|
||||
* [method@Gtk.FileFilter.add_suffix] or
|
||||
* [method@Gtk.FileFilter.add_pixbuf_formats].
|
||||
*
|
||||
* To create a filter that accepts any file, use:
|
||||
@ -543,6 +572,10 @@ gtk_file_filter_add_mime_type (GtkFileFilter *filter,
|
||||
* @pattern: a shell style glob
|
||||
*
|
||||
* Adds a rule allowing a shell style glob to a filter.
|
||||
*
|
||||
* Note that it depends on the platform whether pattern
|
||||
* matching ignores case or not. On Windows, it does, on
|
||||
* other platforms, it doesn't.
|
||||
*/
|
||||
void
|
||||
gtk_file_filter_add_pattern (GtkFileFilter *filter,
|
||||
@ -561,6 +594,36 @@ gtk_file_filter_add_pattern (GtkFileFilter *filter,
|
||||
file_filter_add_rule (filter, rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_file_filter_add_suffix:
|
||||
* @filter: a `GtkFileFilter`
|
||||
* @suffix: filename suffix to match
|
||||
*
|
||||
* Adds a suffix match rule to a filter.
|
||||
*
|
||||
* This is similar to adding a match for the pattern
|
||||
* "*.@suffix".
|
||||
*
|
||||
* In contrast to pattern matches, suffix matches
|
||||
* are *always* case-insensitive.
|
||||
*/
|
||||
void
|
||||
gtk_file_filter_add_suffix (GtkFileFilter *filter,
|
||||
const char *suffix)
|
||||
{
|
||||
FilterRule *rule;
|
||||
|
||||
g_return_if_fail (GTK_IS_FILE_FILTER (filter));
|
||||
g_return_if_fail (suffix != NULL);
|
||||
|
||||
rule = g_slice_new (FilterRule);
|
||||
rule->type = FILTER_RULE_SUFFIX;
|
||||
rule->u.pattern = g_strconcat ("*.", suffix, NULL);
|
||||
|
||||
file_filter_add_attribute (filter, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
|
||||
file_filter_add_rule (filter, rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_file_filter_add_pixbuf_formats:
|
||||
* @filter: a `GtkFileFilter`
|
||||
@ -657,6 +720,7 @@ NSArray * _gtk_file_filter_get_as_pattern_nsstrings (GtkFileFilter *filter)
|
||||
break;
|
||||
|
||||
case FILTER_RULE_PATTERN:
|
||||
case FILTER_RULE_SUFFIX:
|
||||
{
|
||||
// patterns will need to be stripped of their leading *.
|
||||
GString *pattern = g_string_new (rule->u.pattern);
|
||||
@ -725,6 +789,10 @@ _gtk_file_filter_get_as_patterns (GtkFileFilter *filter)
|
||||
break;
|
||||
|
||||
case FILTER_RULE_PATTERN:
|
||||
case FILTER_RULE_SUFFIX:
|
||||
/* Note: we don't make the suffix pattern explicitly
|
||||
* case-insensitive, since this is only used on Windows
|
||||
*/
|
||||
g_ptr_array_add (array, g_strdup (rule->u.pattern));
|
||||
break;
|
||||
|
||||
@ -794,9 +862,14 @@ gtk_file_filter_match (GtkFilter *filter,
|
||||
for (tmp_list = file_filter->rules; tmp_list; tmp_list = tmp_list->next)
|
||||
{
|
||||
FilterRule *rule = tmp_list->data;
|
||||
gboolean ignore_case = FALSE;
|
||||
|
||||
switch (rule->type)
|
||||
{
|
||||
case FILTER_RULE_SUFFIX:
|
||||
ignore_case = TRUE;
|
||||
G_GNUC_FALLTHROUGH;
|
||||
|
||||
case FILTER_RULE_PATTERN:
|
||||
{
|
||||
const char *display_name;
|
||||
@ -804,7 +877,7 @@ gtk_file_filter_match (GtkFilter *filter,
|
||||
display_name = g_file_info_get_display_name (info);
|
||||
if (display_name)
|
||||
{
|
||||
if (_gtk_fnmatch (rule->u.pattern, display_name, FALSE, FALSE))
|
||||
if (_gtk_fnmatch (rule->u.pattern, display_name, FALSE, ignore_case))
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@ -863,6 +936,17 @@ gtk_file_filter_to_gvariant (GtkFileFilter *filter)
|
||||
g_variant_builder_add (&builder, "(us)", 0, rule->u.pattern);
|
||||
break;
|
||||
|
||||
case FILTER_RULE_SUFFIX:
|
||||
{
|
||||
/* Tweak the glob, since the filechooser portal has no api
|
||||
* for case-insensitive globs
|
||||
*/
|
||||
char *pattern = _gtk_make_ci_glob_pattern (rule->u.pattern);
|
||||
g_variant_builder_add (&builder, "(us)", 0, pattern);
|
||||
g_free (pattern);
|
||||
}
|
||||
break;
|
||||
|
||||
case FILTER_RULE_MIME_TYPE:
|
||||
case FILTER_RULE_PIXBUF_FORMATS:
|
||||
for (i = 0; rule->u.content_types[i]; i++)
|
||||
|
@ -48,9 +48,15 @@ const char * gtk_file_filter_get_name (GtkFileFilter *filter);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_file_filter_add_mime_type (GtkFileFilter *filter,
|
||||
const char *mime_type);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_file_filter_add_pattern (GtkFileFilter *filter,
|
||||
const char *pattern);
|
||||
|
||||
GDK_AVAILABLE_IN_4_4
|
||||
void gtk_file_filter_add_suffix (GtkFileFilter *filter,
|
||||
const char *suffix);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_file_filter_add_pixbuf_formats (GtkFileFilter *filter);
|
||||
|
||||
|
@ -64,6 +64,9 @@ gboolean _gtk_fnmatch (const char *pattern,
|
||||
gboolean no_leading_period,
|
||||
gboolean casefold);
|
||||
|
||||
char * _gtk_make_ci_glob_pattern (const char *pattern);
|
||||
|
||||
|
||||
char * _gtk_get_lc_ctype (void);
|
||||
|
||||
void _gtk_ensure_resources (void);
|
||||
|
@ -5576,8 +5576,8 @@ native_filter_changed (GtkWidget *combo,
|
||||
case 1: /* pattern */
|
||||
filter = gtk_file_filter_new ();
|
||||
gtk_file_filter_set_name (filter, "Text");
|
||||
gtk_file_filter_add_pattern (filter, "*.doc");
|
||||
gtk_file_filter_add_pattern (filter, "*.txt");
|
||||
gtk_file_filter_add_suffix (filter, "doc");
|
||||
gtk_file_filter_add_suffix (filter, "txt");
|
||||
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (native), filter);
|
||||
g_object_unref (filter);
|
||||
|
||||
|
131
testsuite/gtk/filefilter.c
Normal file
131
testsuite/gtk/filefilter.c
Normal file
@ -0,0 +1,131 @@
|
||||
/* GtkFileFilter tests
|
||||
*
|
||||
* Copyright (C) 2021, Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static void
|
||||
test_basic (void)
|
||||
{
|
||||
GtkFileFilter *filter;
|
||||
|
||||
filter = gtk_file_filter_new ();
|
||||
gtk_file_filter_set_name (filter, "test");
|
||||
g_assert_cmpstr (gtk_file_filter_get_name (filter), ==, "test");
|
||||
|
||||
g_object_unref (filter);
|
||||
}
|
||||
|
||||
static void
|
||||
test_pattern (void)
|
||||
{
|
||||
GtkFileFilter *filter;
|
||||
char *attrs;
|
||||
GFileInfo *info;
|
||||
|
||||
filter = gtk_file_filter_new ();
|
||||
gtk_file_filter_add_pattern (filter, "D*.st[xy]");
|
||||
attrs = g_strjoinv (",", (char **)gtk_file_filter_get_attributes (filter));
|
||||
|
||||
info = g_file_info_new ();
|
||||
|
||||
g_file_info_set_display_name (info, "abracadabra");
|
||||
g_assert_false (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_file_info_set_display_name (info, "dro.stx");
|
||||
g_assert_false (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_file_info_set_display_name (info, "Dro.sty");
|
||||
g_assert_true (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_object_unref (info);
|
||||
g_free (attrs);
|
||||
g_object_unref (filter);
|
||||
}
|
||||
|
||||
static void
|
||||
test_suffix (void)
|
||||
{
|
||||
GtkFileFilter *filter;
|
||||
char *attrs;
|
||||
GFileInfo *info;
|
||||
|
||||
filter = gtk_file_filter_new ();
|
||||
gtk_file_filter_add_suffix (filter, "txt");
|
||||
attrs = g_strjoinv (",", (char **)gtk_file_filter_get_attributes (filter));
|
||||
|
||||
info = g_file_info_new ();
|
||||
|
||||
g_file_info_set_display_name (info, "abracadabra");
|
||||
g_assert_false (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_file_info_set_display_name (info, "dro.txt");
|
||||
g_assert_true (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_file_info_set_display_name (info, "dro.TXT");
|
||||
g_assert_true (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_object_unref (info);
|
||||
g_free (attrs);
|
||||
g_object_unref (filter);
|
||||
}
|
||||
|
||||
static void
|
||||
test_mime_type (void)
|
||||
{
|
||||
GtkFileFilter *filter;
|
||||
char *attrs;
|
||||
GFileInfo *info;
|
||||
|
||||
filter = gtk_file_filter_new ();
|
||||
gtk_file_filter_add_mime_type (filter, "image/png");
|
||||
attrs = g_strjoinv (",", (char **)gtk_file_filter_get_attributes (filter));
|
||||
|
||||
info = g_file_info_new ();
|
||||
|
||||
g_file_info_set_display_name (info, "abracadabra");
|
||||
g_file_info_set_content_type (info, "text/plain");
|
||||
g_assert_false (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_file_info_set_display_name (info, "dro.png");
|
||||
g_file_info_set_content_type (info, "image/png");
|
||||
g_assert_true (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_file_info_set_display_name (info, "dro.PNG");
|
||||
g_file_info_set_content_type (info, "image/png");
|
||||
g_assert_true (gtk_filter_match (GTK_FILTER (filter), info));
|
||||
|
||||
g_object_unref (info);
|
||||
g_free (attrs);
|
||||
g_object_unref (filter);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
(g_test_init) (&argc, &argv, NULL);
|
||||
setlocale (LC_ALL, "C");
|
||||
|
||||
g_test_add_func ("/filefilter/basic", test_basic);
|
||||
g_test_add_func ("/filefilter/pattern", test_pattern);
|
||||
g_test_add_func ("/filefilter/suffix", test_suffix);
|
||||
g_test_add_func ("/filefilter/mimetype", test_mime_type);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@ -125,6 +125,37 @@ test_fnmatch (gconstpointer data)
|
||||
g_assert_true (_gtk_fnmatch (test->pat, test->str, test->no_leading_period, test->ci) == test->result);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *glob;
|
||||
const char *ci;
|
||||
} CITest;
|
||||
|
||||
static CITest citests[] = {
|
||||
{ "*.txt", "*.[tT][xX][tT]" },
|
||||
{ "*.TXT", "*.[tT][xX][tT]" },
|
||||
{ "*?[]-abc]t", "*?[]-abc][tT]" },
|
||||
#ifdef DO_ESCAPE
|
||||
/* Tests of escaping */
|
||||
{ "\\\\", "\\\\" },
|
||||
{ "\\??", "\\??" },
|
||||
{ "\\**", "\\**" },
|
||||
{ "\\[", "\\[" },
|
||||
{ "\\[a-", "\\[[aA]-" },
|
||||
{ "\\[]", "\\[]" },
|
||||
#endif
|
||||
};
|
||||
|
||||
static void
|
||||
test_ci_glob (gconstpointer data)
|
||||
{
|
||||
const CITest *test = data;
|
||||
char *ci;
|
||||
|
||||
ci = _gtk_make_ci_glob_pattern (test->glob);
|
||||
g_assert_cmpstr (ci, ==, test->ci);
|
||||
g_free (ci);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
@ -136,5 +167,11 @@ main (int argc, char *argv[])
|
||||
g_test_add_data_func (path, &tests[i], test_fnmatch);
|
||||
}
|
||||
|
||||
for (int i = 0; i < G_N_ELEMENTS (citests); i++)
|
||||
{
|
||||
char *path = g_strdup_printf ("/ci-glob/test%d", i);
|
||||
g_test_add_data_func (path, &citests[i], test_ci_glob);
|
||||
}
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ tests = [
|
||||
{ 'name': 'defaultvalue' },
|
||||
{ 'name': 'entry' },
|
||||
{ 'name': 'expression' },
|
||||
{ 'name': 'filefilter' },
|
||||
{ 'name': 'filter' },
|
||||
{ 'name': 'filterlistmodel' },
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user