From: Owen Taylor Date: Wed, 23 Jul 2003 15:31:10 +0000 (+0000) Subject: File filter objects. X-Git-Url: http://pileus.org/git/?a=commitdiff_plain;h=733f4489ade5b79227af871e1cbe694806ec367e;p=~andy%2Fgtk File filter objects. Wed Jul 23 11:23:43 2003 Owen Taylor * gtkfilefilter.[ch]: File filter objects. * gtkfilechooser.[ch] gtkfilechooserutils.[ch]: Add file filtering to API. * gtkfilechooserimpldefault.c: Implement file filters. * testfilechooser.c: Try out the filter functionality. * gtkfilesystemmodel.c: Add _gtk_file_system_model_set_filter() to set a callback function for filtering. * gtkfilechooserutils.c: Propagate property notification to the receiver. * fnmatch.c: Copy this from GTK+ temporarily to get UTF-8 pattern matching functionality. --- diff --git a/gtk/gtkfilechooser.c b/gtk/gtkfilechooser.c index 8daedd812..d1a6651d8 100644 --- a/gtk/gtkfilechooser.c +++ b/gtk/gtkfilechooser.c @@ -91,6 +91,12 @@ gtk_file_chooser_base_init (gpointer g_iface) _("File system object to use"), GTK_TYPE_FILE_SYSTEM, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + g_object_interface_install_property (g_iface, + g_param_spec_object ("filter", + _("Filter"), + _("The current filter for selecting which files are displayed"), + GTK_TYPE_FILE_FILTER, + G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_boolean ("folder-mode", _("Folder Mode"), @@ -932,6 +938,13 @@ gtk_file_chooser_get_preview_widget (GtkFileChooser *chooser) g_return_val_if_fail (GTK_IS_FILE_CHOOSER (chooser), NULL); g_object_get (chooser, "preview-widget", &preview_widget, NULL); + + /* Horrid hack; g_object_get() refs returned objects but + * that contradicts the memory management conventions + * for accessors. + */ + if (preview_widget) + g_object_unref (preview_widget); return preview_widget; } @@ -1046,3 +1059,107 @@ gtk_file_chooser_get_preview_uri (GtkFileChooser *chooser) return result; } + +/** + * gtk_file_chooser_add_filter: + * @chooser: a #GtkFileChooser + * @filter: a #GtkFileFilter + * + * Adds @filter to the list of filters that the user can select between. + * When a filter is selected, only files that are passed by that + * filter are displayed. + **/ +void +gtk_file_chooser_add_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + g_return_if_fail (GTK_IS_FILE_CHOOSER (chooser)); + + GTK_FILE_CHOOSER_GET_IFACE (chooser)->add_filter (chooser, filter); +} + +/** + * gtk_file_chooser_add_filter: + * @chooser: a #GtkFileChooser + * @filter: a #GtkFileFilter + * + * Removes @filter from the list of filters that the user can select between. + **/ +void +gtk_file_chooser_remove_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + g_return_if_fail (GTK_IS_FILE_CHOOSER (chooser)); + + GTK_FILE_CHOOSER_GET_IFACE (chooser)->remove_filter (chooser, filter); +} + +/** + * gtk_file_chooser_list_filters: + * @choooser: a #GtkFileChooser + * + * Lists the current set of user-selectable filters; see + * gtk_file_chooser_add_filter(), gtk_file_chooser_remove_filter(). + * + * Return value: a #GSList containing the current set of + * user selectable filters. The contents of the list are + * owned by GTK+, but you must free the list itself with + * g_slist_free() when you are done with it. + **/ +GSList * +gtk_file_chooser_list_filters (GtkFileChooser *chooser) +{ + g_return_val_if_fail (GTK_IS_FILE_CHOOSER (chooser), NULL); + + return GTK_FILE_CHOOSER_GET_IFACE (chooser)->list_filters (chooser); +} + +/** + * gtk_file_chooser_set_filter: + * @chooser: a #GtkFileChooser + * @filter: a #GtkFileFilter + * + * Sets the current filter; only the files that pass the + * filter will be displayed. If the user-selectable list of filters + * is non-empty, then the filter should be one of the filters + * in that list. Setting the current filter when the list of + * filters is empty is useful if you want to restrict the displayed + * set of files without letting the user change it. + **/ +void +gtk_file_chooser_set_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + g_return_if_fail (GTK_IS_FILE_CHOOSER (chooser)); + g_return_if_fail (GTK_IS_FILE_FILTER (filter)); + + g_object_set (chooser, "filter", filter, NULL); +} + +/** + * gtk_file_chooser_get_filter: + * @chooser: a #GtkFileChooser + * + * Gets the current filter; see gtk_file_chooser_set_filter(). + * + * Return value: the current filter, or %NULL + **/ +GtkFileFilter * +gtk_file_chooser_get_filter (GtkFileChooser *chooser) +{ + GtkFileFilter *filter; + + g_return_val_if_fail (GTK_IS_FILE_CHOOSER (chooser), NULL); + + g_object_get (chooser, "filter", &filter, NULL); + /* Horrid hack; g_object_get() refs returned objects but + * that contradicts the memory management conventions + * for accessors. + */ + if (filter) + g_object_unref (filter); + + return filter; +} + + diff --git a/gtk/gtkfilechooser.h b/gtk/gtkfilechooser.h index 145cb2c1f..1b708713d 100644 --- a/gtk/gtkfilechooser.h +++ b/gtk/gtkfilechooser.h @@ -21,6 +21,7 @@ #ifndef __GTK_FILE_CHOOSER_H__ #define __GTK_FILE_CHOOSER_H__ +#include "gtkfilefilter.h" #include G_BEGIN_DECLS @@ -100,29 +101,19 @@ gboolean gtk_file_chooser_get_preview_widget_active (GtkFileChooser *chooser); const char *gtk_file_chooser_get_preview_filename (GtkFileChooser *file_chooser); const char *gtk_file_chooser_get_preview_uri (GtkFileChooser *file_chooser); - -#if 0 -/* Filters +/* List of user selectable filters */ -void gtk_file_chooser_add_filter (GtkFileChooser *chooser, - GtkFileFilter *filter); -void gtk_file_chooser_remove_filter (GtkFileChooser *chooser, - GtkFileFilter *filter); -GList *gtk_file_chooser_get_filters (GtkFileChooser *chooser); - - -/************************************************/ +void gtk_file_chooser_add_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +void gtk_file_chooser_remove_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +GSList *gtk_file_chooser_list_filters (GtkFileChooser *chooser); -static gboolean (*GtkFileFilterFunc) (const char *uri, - const char *filename, - gpointer data); - -GtkFileFilter *gtk_file_filter_new_pattern (const char *pattern); -GtkFileFilter *gtk_file_filter_new_mime_type (const char *mime_type); -GtkFileFilter *gtk_file_filter_new_callback (GtkFileFilterFunction *func, - gpointer data, - GDestroyNotify notify); -#endif +/* Current filter + */ +void gtk_file_chooser_set_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +GtkFileFilter *gtk_file_chooser_get_filter (GtkFileChooser *chooser); G_END_DECLS diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index 9c5222e79..3a3c378e4 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -25,6 +25,7 @@ #include "gtkfilechooser.h" #include "gtkfilesystemmodel.h" +#include #include #include #include @@ -32,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -62,6 +65,9 @@ struct _GtkFileChooserImplDefault GtkFileChooserAction action; + GtkFileFilter *current_filter; + GSList *filters; + GtkFilePath *current_folder; guint folder_mode : 1; @@ -70,6 +76,8 @@ struct _GtkFileChooserImplDefault guint select_multiple : 1; guint show_hidden : 1; + GtkWidget *filter_alignment; + GtkWidget *filter_option_menu; GtkWidget *tree_scrollwin; GtkWidget *tree; GtkWidget *list_scrollwin; @@ -94,6 +102,7 @@ static void gtk_file_chooser_impl_default_get_property (GObject guint prop_id, GValue *value, GParamSpec *pspec); +static void gtk_file_chooser_impl_default_show_all (GtkWidget *widget); static void gtk_file_chooser_impl_default_set_current_folder (GtkFileChooser *chooser, const GtkFilePath *path); @@ -108,13 +117,23 @@ static void gtk_file_chooser_impl_default_select_all (GtkFileC static void gtk_file_chooser_impl_default_unselect_all (GtkFileChooser *chooser); static GSList * gtk_file_chooser_impl_default_get_paths (GtkFileChooser *chooser); static GtkFileSystem *gtk_file_chooser_impl_default_get_file_system (GtkFileChooser *chooser); - -static void tree_selection_changed (GtkTreeSelection *tree_selection, - GtkFileChooserImplDefault *impl); -static void list_selection_changed (GtkTreeSelection *tree_selection, - GtkFileChooserImplDefault *impl); -static void entry_activate (GtkEntry *entry, - GtkFileChooserImplDefault *impl); +static void gtk_file_chooser_impl_default_add_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +static void gtk_file_chooser_impl_default_remove_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +static GSList * gtk_file_chooser_impl_default_list_filters (GtkFileChooser *chooser); + +static void set_current_filter (GtkFileChooserImplDefault *impl, + GtkFileFilter *filter); + +static void filter_option_menu_changed (GtkOptionMenu *option_menu, + GtkFileChooserImplDefault *impl); +static void tree_selection_changed (GtkTreeSelection *tree_selection, + GtkFileChooserImplDefault *impl); +static void list_selection_changed (GtkTreeSelection *tree_selection, + GtkFileChooserImplDefault *impl); +static void entry_activate (GtkEntry *entry, + GtkFileChooserImplDefault *impl); static void tree_name_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, @@ -180,6 +199,7 @@ static void gtk_file_chooser_impl_default_class_init (GtkFileChooserImplDefaultClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); parent_class = g_type_class_peek_parent (class); @@ -188,6 +208,8 @@ gtk_file_chooser_impl_default_class_init (GtkFileChooserImplDefaultClass *class) gobject_class->set_property = gtk_file_chooser_impl_default_set_property; gobject_class->get_property = gtk_file_chooser_impl_default_get_property; + widget_class->show_all = gtk_file_chooser_impl_default_show_all; + _gtk_file_chooser_install_properties (gobject_class); } @@ -203,6 +225,9 @@ gtk_file_chooser_impl_default_iface_init (GtkFileChooserIface *iface) iface->set_current_folder = gtk_file_chooser_impl_default_set_current_folder; iface->get_current_folder = gtk_file_chooser_impl_default_get_current_folder; iface->set_current_name = gtk_file_chooser_impl_default_set_current_name; + iface->add_filter = gtk_file_chooser_impl_default_add_filter; + iface->remove_filter = gtk_file_chooser_impl_default_remove_filter; + iface->list_filters = gtk_file_chooser_impl_default_list_filters; } static void @@ -274,6 +299,30 @@ gtk_file_chooser_impl_default_constructor (GType type, gtk_widget_push_composite_child (); + impl->filter_alignment = gtk_alignment_new (0.0, 0.5, 0.0, 1.0); + gtk_alignment_set_padding (GTK_ALIGNMENT (impl->filter_alignment), 0, 6, 0, 0); + gtk_box_pack_start (GTK_BOX (impl), impl->filter_alignment, FALSE, FALSE, 0); + /* Don't show filter initially */ + + hbox = gtk_hbox_new (FALSE, 6); + gtk_container_add (GTK_CONTAINER (impl->filter_alignment), hbox); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic ("Files of _type:"); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + impl->filter_option_menu = gtk_option_menu_new (); + gtk_option_menu_set_menu (GTK_OPTION_MENU (impl->filter_option_menu), + gtk_menu_new ()); + gtk_box_pack_start (GTK_BOX (hbox), impl->filter_option_menu, FALSE, FALSE, 0); + gtk_widget_show (impl->filter_option_menu); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), impl->filter_option_menu); + + g_signal_connect (impl->filter_option_menu, "changed", + G_CALLBACK (filter_option_menu_changed), impl); + hpaned = gtk_hpaned_new (); gtk_box_pack_start (GTK_BOX (impl), hpaned, TRUE, TRUE, 0); gtk_widget_show (hpaned); @@ -415,6 +464,9 @@ gtk_file_chooser_impl_default_set_property (GObject *object, } } break; + case GTK_FILE_CHOOSER_PROP_FILTER: + set_current_filter (impl, g_value_get_object (value)); + break; case GTK_FILE_CHOOSER_PROP_FOLDER_MODE: { gboolean folder_mode = g_value_get_boolean (value); @@ -481,6 +533,9 @@ gtk_file_chooser_impl_default_get_property (GObject *object, case GTK_FILE_CHOOSER_PROP_ACTION: g_value_set_enum (value, impl->action); break; + case GTK_FILE_CHOOSER_PROP_FILTER: + g_value_set_object (value, impl->current_filter); + break; case GTK_FILE_CHOOSER_PROP_FOLDER_MODE: g_value_set_boolean (value, impl->folder_mode); break; @@ -505,6 +560,16 @@ gtk_file_chooser_impl_default_get_property (GObject *object, } } +/* We override show-all since we have internal widgets that + * shouldn't be shown when you call show_all(), like the filter + * option menu. + */ +static void +gtk_file_chooser_impl_default_show_all (GtkWidget *widget) +{ + gtk_widget_show (widget); +} + static void expand_and_select_func (GtkFileSystemModel *model, GtkTreePath *path, @@ -587,7 +652,7 @@ gtk_file_chooser_impl_default_select_path (GtkFileChooser *chooser, _gtk_file_chooser_set_current_folder_path (chooser, parent_path); gtk_file_path_free (parent_path); _gtk_file_system_model_path_do (impl->list_model, path, - select_func, impl); + select_func, impl); } } @@ -691,6 +756,209 @@ gtk_file_chooser_impl_default_get_file_system (GtkFileChooser *chooser) return impl->file_system; } +static GtkWidget * +find_filter_menu_item (GtkFileChooserImplDefault *impl, + GtkFileFilter *filter, + gint *index_return) +{ + GtkWidget *menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (impl->filter_option_menu)); + GList *children = gtk_container_get_children (GTK_CONTAINER (menu)); + GList *tmp_list; + int index = 0; + + if (index_return) + *index_return = -1; + + for (tmp_list = children; tmp_list; tmp_list = tmp_list->next) + { + if (g_object_get_data (tmp_list->data, "gtk-file-filter") == filter) + { + if (index_return) + *index_return = index; + return tmp_list->data; + } + index++; + } + + g_list_free (children); + + return NULL; +} + +static void +gtk_file_chooser_impl_default_add_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser); + GtkWidget *menu; + GtkWidget *menu_item; + const gchar *name; + + if (g_slist_find (impl->filters, filter)) + { + g_warning ("gtk_file_chooser_add_filter() called on filter already in list\n"); + return; + } + + g_object_ref (filter); + gtk_object_sink (GTK_OBJECT (filter)); + impl->filters = g_slist_append (impl->filters, filter); + + name = gtk_file_filter_get_name (filter); + if (!name) + name = "Untitled filter"; /* Place-holder, doesn't need to be marked for translation */ + + menu_item = gtk_menu_item_new_with_label (name); + g_object_set_data (G_OBJECT (menu_item), "gtk-file-filter", filter); + gtk_widget_show (menu_item); + + menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (impl->filter_option_menu)); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + /* Option menus don't react to menu size changes properly */ + gtk_widget_size_request (menu, NULL); + + if (!g_slist_find (impl->filters, impl->current_filter)) + set_current_filter (impl, filter); + + gtk_widget_show (impl->filter_alignment); +} + +static void +gtk_file_chooser_impl_default_remove_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser); + GtkWidget *menu; + GtkWidget *menu_item; + + if (!g_slist_find (impl->filters, filter)) + { + g_warning ("gtk_file_chooser_remove_filter() called on filter not in list\n"); + return; + } + + impl->filters = g_slist_remove (impl->filters, filter); + + if (filter == impl->current_filter) + { + if (impl->filters) + set_current_filter (impl, impl->filters->data); + else + set_current_filter (impl, NULL); + } + + menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (impl->filter_option_menu)); + menu_item = find_filter_menu_item (impl, filter, NULL); + g_assert (menu_item); + gtk_widget_destroy (menu_item); + /* Option menus don't react to menu size changes properly */ + gtk_widget_size_request (menu, NULL); + + g_object_unref (filter); + + if (!impl->filters) + gtk_widget_hide (impl->filter_alignment); +} + +static GSList * +gtk_file_chooser_impl_default_list_filters (GtkFileChooser *chooser) +{ + GtkFileChooserImplDefault *impl = GTK_FILE_CHOOSER_IMPL_DEFAULT (chooser); + + return g_slist_copy (impl->filters); +} + +static gboolean +list_model_filter_func (GtkFileSystemModel *model, + GtkFilePath *path, + const GtkFileInfo *file_info, + gpointer user_data) +{ + GtkFileChooserImplDefault *impl = user_data; + GtkFileFilterInfo filter_info; + GtkFileFilterFlags needed; + gboolean result; + + if (!impl->current_filter) + return TRUE; + + filter_info.contains = GTK_FILE_FILTER_DISPLAY_NAME | GTK_FILE_FILTER_MIME_TYPE; + + needed = gtk_file_filter_get_needed (impl->current_filter); + + filter_info.display_name = gtk_file_info_get_display_name (file_info); + filter_info.mime_type = gtk_file_info_get_mime_type (file_info); + + if (needed & GTK_FILE_FILTER_FILENAME) + { + filter_info.filename = gtk_file_system_path_to_filename (impl->file_system, path); + if (filter_info.filename) + filter_info.contains |= GTK_FILE_FILTER_FILENAME; + } + else + filter_info.filename = NULL; + + if (needed & GTK_FILE_FILTER_URI) + { + filter_info.uri = gtk_file_system_path_to_uri (impl->file_system, path); + if (filter_info.filename) + filter_info.contains |= GTK_FILE_FILTER_URI; + } + else + filter_info.uri = NULL; + + result = gtk_file_filter_filter (impl->current_filter, &filter_info); + + if (filter_info.filename) + g_free ((gchar *)filter_info.filename); + if (filter_info.uri) + g_free ((gchar *)filter_info.uri); + + return result; +} + +static void +install_list_model_filter (GtkFileChooserImplDefault *impl) +{ + if (impl->current_filter) + _gtk_file_system_model_set_filter (impl->list_model, + list_model_filter_func, + impl); +} + +static void +set_current_filter (GtkFileChooserImplDefault *impl, + GtkFileFilter *filter) +{ + if (impl->current_filter != filter) + { + int menu_item_index; + + /* If we have filters, new filter must be one of them + */ + find_filter_menu_item (impl, filter, &menu_item_index); + if (impl->filters && menu_item_index < 0) + return; + + if (impl->current_filter) + g_object_unref (impl->current_filter); + impl->current_filter = filter; + if (impl->current_filter) + { + g_object_ref (impl->current_filter); + gtk_object_sink (GTK_OBJECT (filter)); + } + + if (impl->filters) + gtk_option_menu_set_history (GTK_OPTION_MENU (impl->filter_option_menu), + menu_item_index); + + install_list_model_filter (impl); + + g_object_notify (G_OBJECT (impl), "filter"); + } +} + static gint name_sort_func (GtkTreeModel *model, GtkTreeIter *a, @@ -797,6 +1065,16 @@ update_chooser_entry (GtkFileChooserImplDefault *impl) gtk_file_info_get_display_name (info)); } +static void +filter_option_menu_changed (GtkOptionMenu *option_menu, + GtkFileChooserImplDefault *impl) +{ + gint new_index = gtk_option_menu_get_history (GTK_OPTION_MENU (option_menu)); + GtkFileFilter *new_filter = g_slist_nth_data (impl->filters, new_index); + + set_current_filter (impl, new_filter); +} + static void tree_selection_changed (GtkTreeSelection *selection, GtkFileChooserImplDefault *impl) @@ -843,6 +1121,7 @@ tree_selection_changed (GtkTreeSelection *selection, GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_SIZE); _gtk_file_system_model_set_show_folders (impl->list_model, FALSE); + install_list_model_filter (impl); impl->sort_model = (GtkTreeModelSort *)gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (impl->list_model)); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->sort_model), 0, name_sort_func, impl, NULL); diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index d3b73f1ec..6511b6903 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -34,18 +34,6 @@ struct _GtkFileChooserIface { GTypeInterface base_iface; - /* GtkFileChooser interface has the following properties: - * - * action: GtkFileChooserAction - * folder_mode: boolean - * select_multiple: boolean - * show_hidden: boolean - * local_only: boolean - * - * preview_widget: GtkWidget - * preview_widget_active: boolean - */ - /* Methods */ void (*set_current_folder) (GtkFileChooser *chooser, @@ -61,6 +49,12 @@ struct _GtkFileChooserIface void (*unselect_all) (GtkFileChooser *chooser); GSList * (*get_paths) (GtkFileChooser *chooser); GtkFileSystem *(*get_file_system) (GtkFileChooser *chooser); + void (*add_filter) (GtkFileChooser *chooser, + GtkFileFilter *filter); + void (*remove_filter) (GtkFileChooser *chooser, + GtkFileFilter *filter); + GSList * (*list_filters) (GtkFileChooser *chooser); + /* Signals */ diff --git a/gtk/gtkfilechooserutils.c b/gtk/gtkfilechooserutils.c index 9c0e51ced..b6648f21c 100644 --- a/gtk/gtkfilechooserutils.c +++ b/gtk/gtkfilechooserutils.c @@ -37,6 +37,14 @@ static void delegate_select_all (GtkFileChooser *choose static void delegate_unselect_all (GtkFileChooser *chooser); static GSList * delegate_get_paths (GtkFileChooser *chooser); static GtkFileSystem *delegate_get_file_system (GtkFileChooser *chooser); +static void delegate_add_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +static void delegate_remove_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +static GSList * delegate_list_filters (GtkFileChooser *chooser); +static void delegate_notify (GObject *object, + GParamSpec *pspec, + gpointer data); static void delegate_current_folder_changed (GtkFileChooser *chooser, gpointer data); static void delegate_selection_changed (GtkFileChooser *chooser, @@ -66,6 +74,11 @@ _gtk_file_chooser_install_properties (GObjectClass *klass) g_param_spec_override ("file-system", GTK_TYPE_FILE_SYSTEM, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (klass, + GTK_FILE_CHOOSER_PROP_FILTER, + g_param_spec_override ("filter", + GTK_TYPE_FILE_FILTER, + G_PARAM_READWRITE)); g_object_class_install_property (klass, GTK_FILE_CHOOSER_PROP_FOLDER_MODE, g_param_spec_override ("folder-mode", @@ -121,6 +134,9 @@ _gtk_file_chooser_delegate_iface_init (GtkFileChooserIface *iface) iface->unselect_all = delegate_unselect_all; iface->get_paths = delegate_get_paths; iface->get_file_system = delegate_get_file_system; + iface->add_filter = delegate_add_filter; + iface->remove_filter = delegate_remove_filter; + iface->list_filters = delegate_list_filters; } /** @@ -143,6 +159,8 @@ _gtk_file_chooser_set_delegate (GtkFileChooser *receiver, g_object_set_data (G_OBJECT (receiver), "gtk-file-chooser-delegate", delegate); + g_signal_connect (delegate, "notify", + G_CALLBACK (delegate_notify), receiver); g_signal_connect (delegate, "current-folder-changed", G_CALLBACK (delegate_current_folder_changed), receiver); g_signal_connect (delegate, "selection-changed", @@ -193,6 +211,26 @@ delegate_get_file_system (GtkFileChooser *chooser) return _gtk_file_chooser_get_file_system (get_delegate (chooser)); } +static void +delegate_add_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + gtk_file_chooser_add_filter (get_delegate (chooser), filter); +} + +static void +delegate_remove_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + gtk_file_chooser_remove_filter (get_delegate (chooser), filter); +} + +static GSList * +delegate_list_filters (GtkFileChooser *chooser) +{ + return gtk_file_chooser_list_filters (get_delegate (chooser)); +} + static void delegate_set_current_folder (GtkFileChooser *chooser, const GtkFilePath *path) @@ -213,6 +251,18 @@ delegate_set_current_name (GtkFileChooser *chooser, gtk_file_chooser_set_current_name (get_delegate (chooser), name); } +static void +delegate_notify (GObject *object, + GParamSpec *pspec, + gpointer data) +{ + if (pspec->param_id >= GTK_FILE_CHOOSER_PROP_FIRST && + pspec->param_id <= GTK_FILE_CHOOSER_PROP_LAST) + { + g_object_notify (data, pspec->name); + } +} + static void delegate_selection_changed (GtkFileChooser *chooser, gpointer data) diff --git a/gtk/gtkfilechooserutils.h b/gtk/gtkfilechooserutils.h index 56e47d55d..874e56da3 100644 --- a/gtk/gtkfilechooserutils.h +++ b/gtk/gtkfilechooserutils.h @@ -27,14 +27,17 @@ G_BEGIN_DECLS typedef enum { - GTK_FILE_CHOOSER_PROP_ACTION = 0x1000, + GTK_FILE_CHOOSER_PROP_FIRST = 0x1000, + GTK_FILE_CHOOSER_PROP_ACTION = GTK_FILE_CHOOSER_PROP_FIRST, GTK_FILE_CHOOSER_PROP_FILE_SYSTEM, + GTK_FILE_CHOOSER_PROP_FILTER, GTK_FILE_CHOOSER_PROP_FOLDER_MODE, GTK_FILE_CHOOSER_PROP_LOCAL_ONLY, GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET, GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE, GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE, - GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN + GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN, + GTK_FILE_CHOOSER_PROP_LAST = GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN, } GtkFileChooserProp; void _gtk_file_chooser_install_properties (GObjectClass *klass); diff --git a/gtk/gtkfilefilter.c b/gtk/gtkfilefilter.c new file mode 100644 index 000000000..b57bf2557 --- /dev/null +++ b/gtk/gtkfilefilter.c @@ -0,0 +1,372 @@ +/* GTK - The GIMP Toolkit + * gtkfilefilter.c: Filters for selecting a file subset + * Copyright (C) 2003, 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gtkfilefilter.h" + +#include + +#include + +typedef struct _GtkFileFilterClass GtkFileFilterClass; +typedef struct _FilterRule FilterRule; + +#define GTK_FILE_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_FILTER, GtkFileFilterClass)) +#define GTK_IS_FILE_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_FILTER)) +#define GTK_FILE_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_FILTER, GtkFileFilterClass)) + +typedef enum { + FILTER_RULE_PATTERN, + FILTER_RULE_MIME_TYPE, + FILTER_RULE_CUSTOM, +} FilterRuleType; + +struct _GtkFileFilterClass +{ + GtkObjectClass parent_class; +}; + +struct _GtkFileFilter +{ + GtkObject parent_instance; + + gchar *name; + GSList *rules; + + GtkFileFilterFlags needed; +}; + +struct _FilterRule +{ + FilterRuleType type; + GtkFileFilterFlags needed; + + union { + gchar *pattern; + gchar *mime_type; + struct { + GtkFileFilterFunc func; + gpointer data; + GDestroyNotify notify; + } custom; + } u; +}; + +static void gtk_file_filter_class_init (GtkFileFilterClass *class); +static void gtk_file_filter_finalize (GObject *object); + +static GObjectClass *parent_class; + +GType +gtk_file_filter_get_type (void) +{ + static GType file_filter_type = 0; + + if (!file_filter_type) + { + static const GTypeInfo file_filter_info = + { + sizeof (GtkFileFilterClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_file_filter_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkFileFilter), + 0, /* n_preallocs */ + NULL /* init */ + }; + + file_filter_type = g_type_register_static (GTK_TYPE_OBJECT, "GtkFileFilter", + &file_filter_info, 0); + } + + return file_filter_type; +} + +static void +gtk_file_filter_class_init (GtkFileFilterClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = gtk_file_filter_finalize; +} + +static void +filter_rule_free (FilterRule *rule) +{ + switch (rule->type) + { + case FILTER_RULE_MIME_TYPE: + g_free (rule->u.mime_type); + break; + case FILTER_RULE_PATTERN: + g_free (rule->u.pattern); + break; + case FILTER_RULE_CUSTOM: + if (rule->u.custom.notify) + rule->u.custom.notify (rule->u.custom.data); + break; + } + + g_free (rule); +} + +static void +gtk_file_filter_finalize (GObject *object) +{ + GtkFileFilter *filter = GTK_FILE_FILTER (filter); + + g_slist_foreach (filter->rules, (GFunc)filter_rule_free, NULL); + + parent_class->finalize (object); +} + +/** + * gtk_file_filter_new: + * + * Creates a new #GtkFileFilter with no rules added to it. + * Such a filter doesn't accept any files, so is not + * particularly useful until you add rules with + * gtk_file_filter_add_mime_type(), gtk_file_filter_add_pattern(), + * or gtk_file_filter_add_custom(). To create a filter + * that accepts any file, use: + * + * + * GtkFileFilter *filter = gtk_file_filter_new (); + * gtk_file_filter_add_pattern (filter, "*"); + * + * + * Return value: a new #GtkFileFilter + **/ +GtkFileFilter * +gtk_file_filter_new (void) +{ + return g_object_new (GTK_TYPE_FILE_FILTER, NULL); +} + +/** + * gtk_file_filter_set_name: + * @filter: a #GtkFileFilter + * @name: the human-readable-name for the filter, or %NULL + * to remove any existing name. + * + * Sets the human-readable name of the filter; this is the string + * that will be displayed in the file selector user interface if + * there is a selectable list of filters. + **/ +void +gtk_file_filter_set_name (GtkFileFilter *filter, + const gchar *name) +{ + g_return_if_fail (GTK_IS_FILE_FILTER (filter)); + + if (filter->name) + g_free (filter->name); + + filter->name = g_strdup (name); +} + +/** + * gtk_file_filter_get_name: + * @filter: a #GtkFileFilter + * + * Gets the human-readable name for the filter. See gtk_file_filter_set_name(). + * + * Return value: The human-readable name of the filter, + * or %NULL. This value is owned by GTK+ and must not + * be modified or freed. + **/ +G_CONST_RETURN gchar * +gtk_file_filter_get_name (GtkFileFilter *filter) +{ + g_return_val_if_fail (GTK_IS_FILE_FILTER (filter), NULL); + + return filter->name; +} + +static void +file_filter_add_rule (GtkFileFilter *filter, + FilterRule *rule) +{ + filter->needed |= rule->needed; + filter->rules = g_slist_append (filter->rules, rule); +} + +/** + * gtk_file_filter_add_mime_type: + * @filter: A #GtkFileFilter + * @mime_type: name of a MIME type + * + * Adds a rule allowing a given mime type to @filter. + **/ +void +gtk_file_filter_add_mime_type (GtkFileFilter *filter, + const gchar *mime_type) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_FILE_FILTER (filter)); + g_return_if_fail (mime_type != NULL); + + rule = g_new (FilterRule, 1); + rule->type = FILTER_RULE_MIME_TYPE; + rule->needed = GTK_FILE_FILTER_MIME_TYPE; + rule->u.mime_type = g_strdup (mime_type); + + file_filter_add_rule (filter, rule); +} + +/** + * gtk_file_filter_add_pattern: + * @filter: a #GtkFileFilter + * @pattern: a shell style glob + * + * Adds a rule allowing a shell style glob to a filter. + **/ +void +gtk_file_filter_add_pattern (GtkFileFilter *filter, + const gchar *pattern) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_FILE_FILTER (filter)); + g_return_if_fail (pattern != NULL); + + rule = g_new (FilterRule, 1); + rule->type = FILTER_RULE_PATTERN; + rule->needed = GTK_FILE_FILTER_DISPLAY_NAME; + rule->u.pattern = g_strdup (pattern); + + file_filter_add_rule (filter, rule); +} + +/** + * gtk_file_filter_add_custom: + * @filter: a #GtkFileFilter + * @needed: bitfield of flags indicating the information that the custom + * filter function needs. + * @func: callback function; if the function returns %TRUE, then + * the file will be displayed. + * @data: data to pass to @func + * @notify: function to call to free @data when it is no longer needed. + * + * Adds rule to a filter that allows files based on a custom callback + * function. The bitfield @needed which is passed in provides information + * about what sorts of information that the filter function needs; + * this allows GTK+ to avoid retrieving expensive information when + * it isn't needed by the filter. + **/ +void +gtk_file_filter_add_custom (GtkFileFilter *filter, + GtkFileFilterFlags needed, + GtkFileFilterFunc func, + gpointer data, + GDestroyNotify notify) +{ + FilterRule *rule; + + g_return_if_fail (GTK_IS_FILE_FILTER (filter)); + g_return_if_fail (func != NULL); + + rule = g_new (FilterRule, 1); + rule->type = FILTER_RULE_CUSTOM; + rule->needed = needed; + rule->u.custom.func = func; + rule->u.custom.data = data; + rule->u.custom.notify = notify; + + file_filter_add_rule (filter, rule); +} + +/** + * gtk_file_filter_get_needed: + * @filter: a #GtkFileFilter + * + * Gets the fields that need to be filled in for the structure + * passed to gtk_file_filter_filter() + * + * This function will not typically be used by applications; it + * is intended principally for use in the implementation of + * #GtkFileChooser. + * + * Return value: bitfield of flags indicating needed fields when + * calling gtk_file_filter_filter() + **/ +GtkFileFilterFlags +gtk_file_filter_get_needed (GtkFileFilter *filter) +{ + return filter->needed; +} + +/* Remove once we merge into GTK+ and use _gtk_fnmatch(). + */ +gboolean _gtk_file_chooser_fnmatch (const char *pattern, + const char *string); + +/** + * gtk_file_filter_filter: + * @filter: a #GtkFileFilter + * @filter_info: a #GtkFileFilterInfo structure containing information + * about a file. + * + * Tests whether a file should be displayed according to @filter. + * The #GtkFileFilterInfo structure @filter_info should include + * the fields returned feom gtk_file_filter_get_needed(). + * + * This function will not typically be used by applications; it + * is intended principally for use in the implementation of + * #GtkFileChooser. + * + * Return value: %TRUE if the file should be displayed + **/ +gboolean +gtk_file_filter_filter (GtkFileFilter *filter, + const GtkFileFilterInfo *filter_info) +{ + GSList *tmp_list; + + for (tmp_list = filter->rules; tmp_list; tmp_list = tmp_list->next) + { + FilterRule *rule = tmp_list->data; + + if ((filter_info->contains & rule->needed) != rule->needed) + continue; + + switch (rule->type) + { + case FILTER_RULE_MIME_TYPE: + if (strcmp (rule->u.mime_type, filter_info->mime_type) == 0) + return TRUE; + break; + case FILTER_RULE_PATTERN: + if (_gtk_file_chooser_fnmatch (rule->u.pattern, filter_info->display_name)) + return TRUE; + break; + case FILTER_RULE_CUSTOM: + if (rule->u.custom.func (filter_info, rule->u.custom.data)) + return TRUE; + break; + } + } + + return FALSE; +} diff --git a/gtk/gtkfilefilter.h b/gtk/gtkfilefilter.h new file mode 100644 index 000000000..01b70b3ba --- /dev/null +++ b/gtk/gtkfilefilter.h @@ -0,0 +1,78 @@ +/* GTK - The GIMP Toolkit + * gtkfilefilter.h: Filters for selecting a file subset + * Copyright (C) 2003, 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_FILE_FILTER_H__ +#define __GTK_FILE_FILTER_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_FILE_FILTER (gtk_file_filter_get_type ()) +#define GTK_FILE_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FILE_FILTER, GtkFileFilter)) +#define GTK_IS_FILE_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FILE_FILTER)) + +typedef struct _GtkFileFilter GtkFileFilter; +typedef struct _GtkFileFilterInfo GtkFileFilterInfo; + +typedef enum { + GTK_FILE_FILTER_FILENAME = 1 << 0, + GTK_FILE_FILTER_URI = 1 << 1, + GTK_FILE_FILTER_DISPLAY_NAME = 1 << 2, + GTK_FILE_FILTER_MIME_TYPE = 1 << 3 +} GtkFileFilterFlags; + +typedef gboolean (*GtkFileFilterFunc) (const GtkFileFilterInfo *filter_info, + gpointer data); + +struct _GtkFileFilterInfo +{ + GtkFileFilterFlags contains; + + const gchar *filename; + const gchar *uri; + const gchar *display_name; + const gchar *mime_type; +}; + +GType gtk_file_filter_get_type (void); + +GtkFileFilter * gtk_file_filter_new (void); +void gtk_file_filter_set_name (GtkFileFilter *filter, + const gchar *name); +G_CONST_RETURN gchar *gtk_file_filter_get_name (GtkFileFilter *filter); + +void gtk_file_filter_add_mime_type (GtkFileFilter *filter, + const gchar *mime_type); +void gtk_file_filter_add_pattern (GtkFileFilter *filter, + const gchar *pattern); +void gtk_file_filter_add_custom (GtkFileFilter *filter, + GtkFileFilterFlags needed, + GtkFileFilterFunc func, + gpointer data, + GDestroyNotify notify); + +GtkFileFilterFlags gtk_file_filter_get_needed (GtkFileFilter *filter); +gboolean gtk_file_filter_filter (GtkFileFilter *filter, + const GtkFileFilterInfo *filter_info); + +G_END_DECLS + +#endif /* __GTK_FILE_FILTER_H__ */ diff --git a/gtk/gtkfilesystemmodel.c b/gtk/gtkfilesystemmodel.c index ed738481b..2a28d4415 100644 --- a/gtk/gtkfilesystemmodel.c +++ b/gtk/gtkfilesystemmodel.c @@ -44,6 +44,9 @@ struct _GtkFileSystemModel FileModelNode *roots; GtkFileFolder *root_folder; + GtkFileSystemModelFilter filter_func; + gpointer filter_data; + GSList *idle_clears; GSource *idle_clear_source; @@ -561,7 +564,8 @@ _gtk_file_system_model_new (GtkFileSystem *file_system, GtkFileInfoType types) { GtkFileSystemModel *model; - GSList *roots, *tmp_list; + GSList *roots = NULL; + GSList *tmp_list; g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); @@ -686,6 +690,16 @@ model_refilter_recurse (GtkFileSystemModel *model, } } +static void +model_refilter_all (GtkFileSystemModel *model) +{ + GtkTreePath *path; + + path = gtk_tree_path_new (); + model_refilter_recurse (model, NULL, path); + gtk_tree_path_free (path); +} + /** * _gtk_file_system_model_set_show_hidden: * @model: a #GtkFileSystemModel @@ -702,13 +716,8 @@ _gtk_file_system_model_set_show_hidden (GtkFileSystemModel *model, if (show_hidden != model->show_hidden) { - GtkTreePath *path; - model->show_hidden = show_hidden; - - path = gtk_tree_path_new (); - model_refilter_recurse (model, NULL, path); - gtk_tree_path_free (path); + model_refilter_all (model); } } @@ -728,13 +737,8 @@ _gtk_file_system_model_set_show_folders (GtkFileSystemModel *model, if (show_folders != model->show_folders) { - GtkTreePath *path; - model->show_folders = show_folders; - - path = gtk_tree_path_new (); - model_refilter_recurse (model, NULL, path); - gtk_tree_path_free (path); + model_refilter_all (model); } } @@ -755,13 +759,8 @@ _gtk_file_system_model_set_show_files (GtkFileSystemModel *model, if (show_files != model->show_files) { - GtkTreePath *path; - model->show_files = show_files; - - path = gtk_tree_path_new (); - model_refilter_recurse (model, NULL, path); - gtk_tree_path_free (path); + model_refilter_all (model); } } @@ -897,6 +896,29 @@ find_and_ref_path (GtkFileSystemModel *model, return NULL; } +/** + * _gtk_file_system_model_set_filter: + * @mode: a #GtkFileSystemModel + * @filter: function to be called for each file + * @user_data: data to pass to @filter + * + * Sets a callback called for each file/directory to see whether + * it should be included in model. If this function was made + * public, we'd want to include a GDestroyNotify as well. + **/ +void +_gtk_file_system_model_set_filter (GtkFileSystemModel *model, + GtkFileSystemModelFilter filter, + gpointer user_data) +{ + g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); + + model->filter_func = filter; + model->filter_data = user_data; + + model_refilter_all (model); +} + /** * _gtk_file_system_model_path_do: * @model: a #GtkFileSystemModel @@ -1024,6 +1046,8 @@ file_model_node_is_visible (GtkFileSystemModel *model, return FALSE; if (!model->show_hidden && gtk_file_info_get_is_hidden (info)) return FALSE; + if (model->filter_func && !model->filter_func (model, node->path, info, model->filter_data)) + return FALSE; return TRUE; } diff --git a/gtk/gtkfilesystemmodel.h b/gtk/gtkfilesystemmodel.h index e84d004de..759c1ab3c 100644 --- a/gtk/gtkfilesystemmodel.h +++ b/gtk/gtkfilesystemmodel.h @@ -56,6 +56,14 @@ void _gtk_file_system_model_set_show_folders (GtkFileSystemModel void _gtk_file_system_model_set_show_files (GtkFileSystemModel *model, gboolean show_files); +typedef gboolean (*GtkFileSystemModelFilter) (GtkFileSystemModel *model, + GtkFilePath *path, + const GtkFileInfo *info, + gpointer user_data); + +void _gtk_file_system_model_set_filter (GtkFileSystemModel *model, + GtkFileSystemModelFilter filter, + gpointer user_data); typedef void (*GtkFileSystemModelPathFunc) (GtkFileSystemModel *model, GtkTreePath *path, diff --git a/tests/testfilechooser.c b/tests/testfilechooser.c index 95696dbb8..51f91e918 100644 --- a/tests/testfilechooser.c +++ b/tests/testfilechooser.c @@ -1,3 +1,5 @@ +#include + #include #include "gtkfilechooserdialog.h" #include "gtkfilechooser.h" @@ -43,6 +45,17 @@ response_cb (GtkDialog *dialog, gtk_main_quit (); } +static gboolean +no_backup_files_filter (const GtkFileFilterInfo *filter_info, + gpointer data) +{ + gsize len = strlen (filter_info->display_name); + if (len > 0 && filter_info->display_name[len - 1] == '~') + return 0; + else + return 1; +} + int main (int argc, char **argv) { @@ -52,6 +65,7 @@ main (int argc, char **argv) GtkWidget *dialog; GtkWidget *prop_editor; GtkFileSystem *file_system; + GtkFileFilter *filter; gtk_init (&argc, &argv); @@ -79,9 +93,32 @@ main (int argc, char **argv) G_CALLBACK (print_current_folder), NULL); g_signal_connect (dialog, "response", G_CALLBACK (response_cb), NULL); + + /* Filters */ + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, "All Files"); + gtk_file_filter_add_pattern (filter, "*"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, "No backup files"); + gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_DISPLAY_NAME, + no_backup_files_filter, NULL, NULL); + gtk_file_filter_add_mime_type (filter, "image/png"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + /* Make this filter the default */ + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, "PNG and JPEG"); + gtk_file_filter_add_mime_type (filter, "image/jpeg"); + gtk_file_filter_add_mime_type (filter, "image/png"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 400); - gtk_widget_show (dialog); + /* show_all() to reveal bugs in composite widget handling */ + gtk_widget_show_all (dialog); prop_editor = create_prop_editor (G_OBJECT (dialog), GTK_TYPE_FILE_CHOOSER);