This page documents the internal workings of the rather complex GtkFileChooser widget.

GType Interfaces and utilities

"GtkFileChooser" is really an abstract GTypeInterface. Objects that implement that interface can be used with the standard gtk_file_chooser_*() API. When people think of a file chooser, they generally think of the GtkFileChooserDialog object, which implements that interface.

GtkFileChooserDialog creates a GtkFileChooserWidget object, and delegates its work to it, via _gtk_file_chooser_delegate_iface_init(), and _gtk_file_chooser_set_delegate(). In turn, GtkFileChooserWidget creates an internal GtkFileChooserDefault object and delegates everything to it. That's gtkfilechooserdefault.c, which is a large source file with the bulk of the file chooser's GUI.

Implementors of GtkFileChooser:

Internal interfaces

GtkFileChooserEmbed

To have a good dialog box, there needs to be communication between the internal GtkFileChooserDefault and the toplevel GtkFileChooserDialog. This communication happens through the internal GtkFileChooserEmbed interface. This interface lets the dialog do things like these:

  • Query the internal widget for a reasonable size, which is particularly tricky for the file chooser.
  • Ask the internal widget whether it is appropriate to close the dialog when the user presses the "OK" or main button: if you click on a folder and then click OK, you want to open the folder, not terminate the dialog.
  • Ask the internal widget to focus the file list when the dialog first pops up.

Implementors of GtkFileChooserEmbed:

TODO: In the future, we can use the Embed interface to actually enable or disable the "OK" or main button based on the state of the selection in the internal widget.

File system access

GTK+ has some particular constraints:

  • It must be multi-platform.
  • Even within a single platform (Unix), there are sub-platforms (plain Unix, or a GNOME desktop)

So, we have three different ways to access the file system:

These are implementations of the semi-private GtkFileSystem interface.

TODO: put GtkFileSystem in the reference docs.

GtkFileSystem is not a generic VFS interface. It only supports very few operations; in particular, it doesn't support reading or writing a file's contents, and it is really just about listing files inside folders and getting basic information about them.

FIXME: write down the expectations from this interface, in particular the sync/async semantics. Put all this in the API reference.

GtkFilePath

(Not sure about this. Correct me if I'm wrong. murrayc)

GtkFileChooser vfuncs such as get_paths() return GtkFilePath objects. These objects generally contain URI strings, though I (murrayc) suspect that their actual contents depends on the particular GtkFileSystem implementation with which they are used.

To convert between GtkFilePaths and regular (non-URI) filepath strings, gtk_file_chooser_get_filenames() uses gtk_file_system_path_to_filename), which calls the GtkFileSystem's path_to_filename vfunc.

Displaying the file system

TODO: GtkFileSystemModel

Extra widgets

FIXME: GtkPathBar

FIXME: GtkFileChooserEntry

Bookmarks

FIXME

Problems

Solving sync/async issues

The GtkFileChooser and the various backends currently do a bunch of synchronous calls to get information about the underlying filesystems. While this mostly work fine for the local filesystem it doesn't for remote filesystems that are by nature slow or in some cases even unreachable.

Unfortunately there are some problems in the GtkFileSystem API that makes synchronous calls required, for example:

  • create_folder (synchronous call to create a folder in the underlying fs)
  • get_folder (needs to check if the folder exists)
  • render_icon (needs to retrieve information about the file to know how to render it)

Part of the GtkFileSystem API seems to be designed to be asynchronous though, for example get_folder is not supposed to actually retrieve the list of files, only start the operation and report back using "files-added" signal, and when done "finished-loading".

The first change planned is to change gtk_file_system_get_folder/gtk_file_folder_list_children to be more asynchronous. This will be done by:

  1. get_folder not return an error if URI doesn't exist or is folder
  2. add an argument that can tell whether a forced reload should happen
  3. starts the loading if either it's not loaded or forced_reload = TRUE

get_folder will always return a GtkFileFolder object but this doesn't mean the actual directory exists. Upper layer will have to be modified to wait until finished_loading has been emitted before they know. list_children will only show the cached contents of a folder and will return directly.

Since get_folder is currently also used to check whether an URI is a folder a new call will have to be added for this. Something like gtk_file_system_get_file_info would probably be a good name for this.

Multiple loads on showing the widget

When opening an initial filechooser dialog for another location than $cwd it actually retrieves the list of files four times.

  1. Setting the directory to $cwd
  2. Setting the directory to given URI
  3. Updating the given URI
  4. Loading the list in the filechooser save entry (which gets updated every directory change even if it's a open dialog.

[ 1]===========================================================================
#0  load_dir (folder_vfs=0x812ce68) at gtkfilesystemgnomevfs.c:632
#1  0xb749d968 in gtk_file_folder_gnome_vfs_list_children (folder=0x48,
    children=0xbffff138, error=0x0) at gtkfilesystemgnomevfs.c:2482
#2  0xb7db4586 in IA__gtk_file_folder_list_children (folder=0x812ce68,
    children=0xbffff168, error=0x0) at gtkfilesystem.c:918
#3  0xb7db5aa6 in _gtk_file_system_model_new (file_system=0x80ac9f0,
    root_path=0x812b700, max_depth=0, types=GTK_FILE_INFO_ALL, error=0x0)
    at gtkfilesystemmodel.c:756
#4  0xb7da5bcc in set_list_model (impl=0x8096ad0, error=0x48)
    at gtkfilechooserdefault.c:5210
#5  0xb7da5fb0 in gtk_file_chooser_default_update_current_folder (
    chooser=0x48, path=0x8090a48, keep_trail=-1215518228, error=0x0)
    at gtkfilechooserdefault.c:5346
#6  0xb7da5e45 in gtk_file_chooser_default_set_current_folder (chooser=0x48,
    path=0x48, error=0x48) at gtkfilechooserdefault.c:5277
#7  0xb7d92dbe in _gtk_file_chooser_set_current_folder_path (
    chooser=0x8096ad0, path=0x8090a48, error=0x0) at gtkfilechooser.c:1059
#8  0xb7d9258a in IA__gtk_file_chooser_set_current_folder (chooser=0x8096ad0,
    filename=0x8090a48 "file:///home/micke/Source/Snippets")
    at gtkfilechooser.c:678
#9  0xb7daa8d0 in gtk_file_chooser_widget_constructor (type=72,
    n_construct_properties=72, construct_params=0x48)
    at gtkfilechooserwidget.c:162

[ 2]===========================================================================
#0  load_dir (folder_vfs=0x812d028) at gtkfilesystemgnomevfs.c:632
#1  0xb749d968 in gtk_file_folder_gnome_vfs_list_children (folder=0x48,
    children=0xbffff4a8, error=0x0) at gtkfilesystemgnomevfs.c:2482
#2  0xb7db4586 in IA__gtk_file_folder_list_children (folder=0x812d028,
    children=0xbffff4d8, error=0x0) at gtkfilesystem.c:918
#3  0xb7db5aa6 in _gtk_file_system_model_new (file_system=0x80ac9f0,
    root_path=0x812b700, max_depth=0, types=GTK_FILE_INFO_ALL, error=0x0)
    at gtkfilesystemmodel.c:756
#4  0xb7da5bcc in set_list_model (impl=0x8096ad0, error=0x48)
    at gtkfilechooserdefault.c:5210
#5  0xb7da5fb0 in gtk_file_chooser_default_update_current_folder (
    chooser=0x48, path=0x8171210, keep_trail=-1215518228, error=0x0)
    at gtkfilechooserdefault.c:5346
#6  0xb7da5e45 in gtk_file_chooser_default_set_current_folder (chooser=0x48,
    path=0x48, error=0x48) at gtkfilechooserdefault.c:5277
#7  0xb7d92dbe in _gtk_file_chooser_set_current_folder_path (
    chooser=0x8096ad0, path=0x8171210, error=0x0) at gtkfilechooser.c:1059
#8  0xb7daa425 in delegate_set_current_folder (chooser=0x48, path=0xb78ca9ec,
    error=0xb78ca9ec) at gtkfilechooserutils.c:296
#9  0xb7d92dbe in _gtk_file_chooser_set_current_folder_path (
    chooser=0x80955a8, path=0x8171210, error=0x0) at gtkfilechooser.c:1059
#10 0xb7daa425 in delegate_set_current_folder (chooser=0x48, path=0xb78ca9ec,
    error=0xb78ca9ec) at gtkfilechooserutils.c:296

[ 3]===========================================================================
#0  load_dir (folder_vfs=0x8190728) at gtkfilesystemgnomevfs.c:632
#1  0xb749d968 in gtk_file_folder_gnome_vfs_list_children (folder=0x48,
    children=0xbfffe2e8, error=0xbfffe3a8) at gtkfilesystemgnomevfs.c:2482
#2  0xb7db4586 in IA__gtk_file_folder_list_children (folder=0x8190728,
    children=0xbfffe318, error=0xbfffe3a8) at gtkfilesystem.c:918
#3  0xb7db5aa6 in _gtk_file_system_model_new (file_system=0x80ac9f0,
    root_path=0x812b700, max_depth=0, types=GTK_FILE_INFO_ALL,
    error=0xbfffe3a8) at gtkfilesystemmodel.c:756
#4  0xb7da5bcc in set_list_model (impl=0x8096ad0, error=0x48)
    at gtkfilechooserdefault.c:5210
#5  0xb7da5fb0 in gtk_file_chooser_default_update_current_folder (
    chooser=0x48, path=0x8198b00, keep_trail=-1215518228, error=0xbfffe3a8)
    at gtkfilechooserdefault.c:5346
#6  0xb7d9d0c3 in change_folder_and_display_error (impl=0x8096ad0,
    path=0x812b700) at gtkfilechooserdefault.c:1082
#7  0xb7da4980 in gtk_file_chooser_default_map (widget=0x8096ad0)
    at gtkfilechooserdefault.c:4718
#8  0xb7aaa5c6 in IA__g_cclosure_marshal_VOID__VOID (closure=0x8075268,
    return_value=0x0, n_param_values=1, param_values=0x48,
    invocation_hint=0xbfffe558, marshal_data=0xb7da48b0) at gmarshal.c:77
#9  0xb7a97549 in g_type_class_meta_marshal (closure=0x8075268,
    return_value=0x48, n_param_values=72, param_values=0xbfffe670,
    invocation_hint=0x48, marshal_data=0x48) at gclosure.c:686
[ 4]===========================================================================
#0  load_dir (folder_vfs=0x8190728) at gtkfilesystemgnomevfs.c:632
#1  0xb749d968 in gtk_file_folder_gnome_vfs_list_children (folder=0x48,
    children=0xbffff3c8, error=0x0) at gtkfilesystemgnomevfs.c:2482
#2  0xb7db4586 in IA__gtk_file_folder_list_children (folder=0x8190728,
    children=0xbffff3f8, error=0x0) at gtkfilesystem.c:918
#3  0xb7d9b1d4 in load_directory_callback (chooser_entry=0x81039b0)
    at gtkfilechooserentry.c:643
#4  0xb7aabdd7 in source_closure_marshal_BOOLEAN__VOID (closure=0x48,
    return_value=0xbffff4c0, n_param_values=0, param_values=0x0,
    invocation_hint=0x0, marshal_data=0x0) at gsourceclosure.c:81
#5  0xb7a97276 in IA__g_closure_invoke (closure=0x812ae30, return_value=0x48,
    n_param_values=72, param_values=0x48, invocation_hint=0x48)
    at gclosure.c:603
#6  0xb7aabf5e in source_closure_callback (data=0x48) at gsourceclosure.c:123
#7  0xb7a2d2d3 in g_idle_dispatch (source=0x812cf60,
    callback=0xb78ca9ec <__after_morecore_hook+36>, user_data=0x48)
    at gmain.c:3813
#8  0xb7a2a2d2 in g_main_dispatch (context=0x8080128) at gmain.c:1934
#9  0xb7a2b348 in IA__g_main_context_dispatch (context=0x8080128)
    at gmain.c:2484
#10 0xb7a2b680 in g_main_context_iterate (context=0x8080128, block=1,
    dispatch=1, self=0x8096cf0) at gmain.c:2565
#11 0xb7a2bc23 in IA__g_main_loop_run (loop=0x812e078) at gmain.c:2769

Passing the GtkFileFolder around instead of GtkFilePath

Currently the same list of files has to be reloaded several times due to using the GtkFilePath instead of sending a reference to the GtkFileFolder object which sometimes contains the data needed so that it doesn't have to be loaded again.

This is particulary bad in the GnomeVFS backend at this point since it reloads the list which in case the backend doesn't cache means another remote retrieval.

Projects/GTK/GtkFileChooserInternals (last edited 2018-12-05 15:46:57 by EmmanueleBassi)