A new file selector for GTK+

Owen Taylor


Table of Contents

Introduction
Reviewing existing file selections
The current GTK+ file selection
Other file selections
General considerations
Loading vs. Saving
Virtual file systems
Multiple roots
Integrating with the desktop
Application customization
API Overview
Preview API
Filter API
The file system object
Completion
Static methods
Moving forward
Tasks
The user interface design process
Conclusions

Introduction

Over the last several releases of GTK+, by far the most commonly asked question is "is there a new file selection?". The current GTK+ file selection widget, GtkFileSelection, dates back to the very early days of the GTK+ project. Except for small details, the current widget looks very much like the one in the first GTK+ releases 6 years ago.

The principle reason for the lack of incremental improvement in this area is was a design mistake in the old file selector: many of the details of the implementation of the file selector were exposed to applications. Applications directly dug into the internals of the file selector to pack in additional widgets, and even to hide or rearrange the standard elements. In fact, they had good reason to do this, because there was no standard way of achieving such common things as adding a preview widget or selecting entire directories instead of individual files. But because of this, it's never been possible to make any significant change to the file selector without causing compatibility problems.

So, for the upcoming 2.4 release, the plan is to create an entirely new file selection widget from scratch and ship it along side the existing GtkFileSelection. The existing file selection widget will be deprecated, and at some future point when we do a ABI-incompatible release, removed.

Reviewing existing file selections

When designing the user interface the primary inspiration will be existing file selections; it's worth taking a good look at these to get idea of the potential elements that might be useful in the user interface, and the impact that these elements will have on the programmatic interfaces. Because it's close to hand, our primary example will be the old GTK+ file selection, even though it probably doesn't resemble the right design for a replacement to any extent.

The current GTK+ file selection

Figure 1. GtkFileSelection

The basic elements of GtkFileSelection widget are:

A drop-down menu to select parent directories of the current directory
list of subdirectories of the current directory
A list of files in the current directory
An entry to type in the name of the file to load or save
Optionally, a set of buttons to create new directories and to rename or delete files

Problems with this scheme are easy to come up with; just a few that immediately spring to mind:

  • There is no obvious way of easily navigating to places where files are typically stored. If the file selector is opened in /usr/bin, the user needs to go to / then to /home and then /home/jdoe to get their home directory. Users should never need to go into directories like / or /home.

  • The use of space is inefficient; if the directory has only a few subdirectories, then the entire left side is wasted. Only vertical expansion can be used to see more files.

  • There is no additional information about files available such as icon, size, or modification time.

But there are also a few virtues:

  • It's simple. There are only 4 controls. As an extreme example of comparison, the KDE file selector has 16 separate controls.

  • There is very good usability for experts. The hidden power of the GTK+ file selector is in the "tab completion" in the filename entry. For an expert, going to the home directory is 3 keystrokes ~/<tab>. Viewing only JPEG files is *.jpg<tab> And so forth.

    It turns out that almost all of the code in the GTK+ file selector is concerned with tab completion; the GTK+ file selector is a file selection command line masquerading as a GUI File selector.

Other file selections

Certainly, there is no lack of prior art for file selection widgets; almost any toolkit or operating system will have one. Just on X11, aside from the GTK+ file selector, Mozilla, OpenOffice, Java, Qt, and KDE all have their own distinct file selector widgets. Other file selector widgets that many GTK+ users will be familiar with are the Windows 95 file selector, the Windows XP file selector, and the OS X file selector.

Certain elements show up in all of these file selectors (the Mozilla, OpenOffice, Java, and Qt file selectors are basically clones of the Windows 95 file selector.)

  • An area for displaying files and directories. Typically, this area provides a list or icon view of a single directory, but the OS X file selector (borrowing ideas from NeXTStep) instead provides a horizontally scrolled set of lists corresponding to the hierarchy.

  • Navigation controls; 'back' and 'up' buttons are common. The KDE file selection adds 'forward'.

  • A menu or combobox for quickly jumping to certain directories; the Windows 95 clones tend to make this ancestor directories of the current directory. (Similar to GtkFileSelection), a more contemporary version is to provide a more useful list of recently accessed directories, user favorites, and important locations in the file system (such as the desktop and file system roots.)

Other controls that are sometimes present:

  • A way of selecting between different filters on file types.

  • A way of choosing between multiple views (list, icon, thumbnail, etc.) in the file system.

  • A button for creating a new directory.

  • A way of saving directories as a favorites.

Figure 2. Qt file selection

Figure 3. KDE file selection

Figure 4. OS X file open dialog (from Aqua Human Interface Guidelines)

General considerations

Loading vs. Saving

Frequently, the load and save dialogs are close to identical. However, there are substantial differences between the two operations in the details of what the user may want to do. For loading, we want to be able to browse to anywhere on the file system, and get detailed information about the files that exist on the file system. On the other hand, for saving, browsing the file system is less important, but we need to specify a filename for the file we are saving, and may want to do things like create new directories.

OS X exploits this distinction quite effectively by providing a save dialog, that in it's default configuration is much simplified from the open dialog ... the only controls are a file entry and a drop-down list of common save locations; the dialog can be expanded to add more controls for browsing the file system.

Figure 5. OS X file save dialog, initial view (from Aqua Human Interface Guidelines)

Figure 6. OS X file save dialog, expanded (from Aqua Human Interface Guidelines)

Virtual file systems

A common idea in current systems is the idea of the "virtual file system". In addition to the file systems that are exported as classical Unix file systems via the system kernel, libraries make network resources such as DAV or FTP servers available. Other resources, such as the menu system, installed fonts on the system, and archive files may also be browsable as virtual file systems.

In the GNOME desktop, the virtual file system interfaces are part of the gnome-vfs library. The gnome-vfs library provides access to a wide range of different possible backends, with a asynchronous API for file system access implemented with behind the scenes threading. Since GTK+ does not depend on gnome-vfs (and gnome-vfs hasn't been ported to all platforms where GTK+ runs), support in the GTK+ file selector for gnome-vfs has to be done on a plugin basis.

We want to use the gnome-vfs plugin in two circumstances: First, when we are using an application using gnome-vfs internally, but also when we are using a plain GTK+ application (that only understands local URLs) in the GNOME desktop. The reason for using the gnome-vfs file system in the second case is so that we get a consistent view of icons, mime types, and thumbnails across all applications. There may also be intermediate cases where an application doesn't use gnome-vfs, but can understand some subset of remote URLs. (Think of a web browser, or, as another example, the GIMP currently has some support for loading images from http: URLs.) There needs to be a method for the application to tell the file selector about the types of file system URIs it supports.

One area of some concern is that names of URI schemes for virtual file systems. For standard schemes like http: or ftp:, there is no problem, but both GNOME and KDE have created many new non-standard names, which are not shared. In some cases, different names are used for the same thing; the ssh file system is fish: in KDE, but ssh: on GNOME. If names are not standardized, things like drag and drop, and a shared recently used location history will not work. It's probably best to simply ignore this problem for the file selector and try to get the names standardized upstream.

Multiple roots

A virtual file system layer is one way that the file system view presented by the file selector will differ from the literal Unix file system. But another way that it will differ is the concept of multiple roots. Instead of a single '/' point where the entire file system branches from, the user will have a choice of starting points.

Some operating systems, such as Windows, actually have multiple actual roots to the file system; in Windows, the drive letters. But in other cases, the multiple roots are user interface constructs. User shouldn't have to know that their home directory is buried deep inside a file system hierarchy. Users probably shouldn't need to know that their desktop is a subdirectory of their home directory. The set of roots displayed to the user should correspond to the ones they see in the desktop file manager.

Integrating with the desktop

It's important that the file selection feels like an integral part of the desktop; this applies whether the desktop that the GTK+ application is running under is GNOME, KDE, or Windows. Some of the forms of integration with the desktop that are needed have been mentioned above; icons, mime types and thumbnails. Another area where integration is needed is for most recently used directories and favorite directories.

More dramatic forms of application integration involve replacing the file selection with a different implementation. Different variants of this would be:

Replacing the file selector with a version that has been rearranged or enhanced to look and work more like the native file selector for a platform.
Using an entirely different file selection implementation for the platform.
Using the native file selector for a platform.

Each of these options has related considerations for the API or implementation. Using a customized version of the file selector for a platform is relatively straightforward. The main design impact of allowing for this is that we must separate the public API for the file selection from the actual implementation; they need to be separate objects.

If we go further and allow total replacement of the file selection then there are more API considerations. One such replacement that has been suggested is to implement the file selector within the GNOME file manager, Nautilus, so that the file selector is done with interprocess-embedding. If we do something like this, then such things as allowing the application to override icons for individual files or to have a filter that involves a per-file callback would likely involve performance overhead and possibly be impossible to implement altogether.

The most radical form of desktop integration is to use the platform native file selector instead of the GTK+ file selector. However, this presents severe challenges:

  • Many systems only have a modal API for their file selector. Only a single call that results in getting a filename is available. This would result in an API that was different from the standard GtkDialog API where gtk_dialog_run() invokes a recursive main loop.

  • Customization facilities will be different from those available in the standard GTK+ file selector. In particular customization APIs that allow embedding application widgets in the file selector probably can't be implemented.

The likely result of allowing using native widgets would be that there would be some cases where a GTK+ program running on such a platform would use the platform native widget, and other cases in which the standard GTK+ file selector would be used; an inconsistency potentially worse than the inconsistency between GTK+ applications and other applications that comes from not using the native widgets.

An argument could be made that any time that the user spends in the file selector actually represents a failure of integration; a single file dialog will never be as efficient way of locating a file or browsing files as the entire file manager. Forcing the user to work in a "open a file" or "save a file" mode goes against a lot of traditional wisdom that says that modes are bad. We should be emphasizing a non-intrusive file selector that the user can get in and out of fast, rather than one with all the bells and whistles.

Application customization

As mentioned above, the approach that the classic GTK+ file selection took of exposing the entire widget layout of the file selector to applications has caused major problems with being able to enhance the file selection API in a compatible fashion. So, a much more well defined set of points where application customization needs to be defined. Some reasonable things that an application might want to be able to do to customize the widget are:

A widget to preview files before opening
A file type filter for loading
A file type selection combo for saving
Extra simple controls such as an "Open Read Only" checkbutton.

One thing to note is that desktop integration and application customization frequently conflict. The more ways we allow the application to change the appearance of the application, the less we can provide different file selectors in different environments, and vice versa.

API Overview

Now that we have some idea of the user and application features that are needed, we can go ahead and sketch out a proposed API. For a name, we'll use GtkFileChooser, since GtkFileSelection needs to be reserved for the old widget, and GtkFileSelector is confusingly similar to GtkFileSelection.

On a large scale, one distinguishing feature of any API is that the same set of methods, signals and properties is needed in several places. We need these aspects of the file selection interface in the file selection dialog, which is where people will most often use them. But, to correspond to GtkFontSelection and GtkColorSelection, we'll also need an embeddable widget that has the same signature, and to allow replacements of the dialog, we'll need a split between the public object that the the user creates and the private implementation objects.

What this implies is that we should have an interface, GtkFileChooser, that has the necessary methods and signals, and let the various objects and interfaces implement it. (GtkIMContext is somewhat similar in concept, in that the GtkIMMultiContext object frontends for a backend such as GtkIMContextSimple or GtkIMContextXIM; however, derivation is used there instead of an interface, because there was no need for the objects to be different types of GtkWidget, as there is here.) Currently, interfaces can't have properties, but this can be dealt with in one of two ways: first, we could simply use a conventional association; an object implementing GtkFileChooser also must have a set of related properties, or we could just go head and add properties to GObject interfaces. Properties on interfaces conceptually makes a lot of sense and would be useful elsewhere.

Figure 7. Objects with parallel APIs

Preview API

For the preview portion of the API, we want to allow both standard previews without application intervention, and also the application to provide it's own previews. The API should then contain two things to set: whether there is a preview widget, and whether the preview widget handles the currently selected filename.

void gtk_file_chooser_set_preview_widget        (GtkFileChooser *file_chooser,
                                                 GtkWidget      *widget);
void gtk_file_chooser_set_preview_widget_active (GtkFileChooser *file_chooser,
                                                 gboolean        active);

A signal then tells the application to update these two settings.

void ::update-preview (GtkFileChooser *file_chooser);
const char *gtk_file_chooser_get_preview_filename (GtkFileChooser *file_chooser);
const char *gtk_file_chooser_get_preview_uri      (GtkFileChooser *file_chooser);

In response to the ::update-preview signal, the application calls get_preview_filename () or get_preview_uri (), then either updates the preview widget and calls:

gtk_file_chooser_set_preview_widget_active (chooser, FALSE);
	
or simply calls: gtk_file_chooser_set_preview_widget_active (chooser, TRUE);

Qt actually extends the idea of a preview display to also include the idea of an information display -- the user can choose between viewing the preview or viewing extended information about the file. Both the preview and the information display are under application control. The information display isn't a a high priority immediate feature, but could be added later if desired.

Filter API

The ability to allow the user to choose between a choice of filters seems necessary to have. The properties of a filter include at least the filter criteria -- a list of mime types or filename patterns is most common, and a human readable name.

Since we need to be able to remove filter objects as well as add them, making them first class objects (GtkFileFilter) seems to make sense.

GtkFileFilter *filter = gtk_file_filter_new ();
gtk_file_filter_add_mimetype (filter, "image/jpeg");
gtk_file_filter_add_mimetype (filter, "image/png");
gtk_file_filter_add_mimetype (filter, "image/x-xcf");
gtk_file_filter_set_label (filter, "Images (JPEG,PNG,XCF)");

gtk_file_chooser_add_filter (chooser, filter);
g_object_unref (filter);

Deriving GtkObject for the "sink" capability would make the API a little more convenient for simple uses, by allowing eliding the unref.

Other possible open questions about the filter API:

Should mixed mime types and file patterns be allowed in a single file filter, or should there be distinct types of file filter? (Conceptually elegant is GtkFileFilterUnion which is the union of a set of other file filters. However, practically speaking, it's probably just inefficient.)
Should we allow file filters that work as a callback? This is almost certainly useful in a lot of cases, but makes a file selection running in a separate process trickier.
Should GtkFileFilter be an opaque object, or should it be an exposed base type that anybody can implement a new subclass of? Again, allowing new implementations is conceptually elegant, but probably not useful in practice.)

The file system object

The primary form of customization of the file selector will be replacement of a file-system object. Actually the "file system object" provides a number of conceptually different services. Different file system objects will work a bit like themes ... they would be loaded as dynamic modules based on a GtkSetting key. (Though there also needs to be some application influence here ... if the application is using gnome-vfs, it should be able to force the gnome-vfs backend, for example.)

  • Browsing the file system. Presumably this will involve an iterator abstraction of some sort, a function to get a list of available file system roots, and

  • Getting icons, thumbnails, and mime types for the files on the file system.

  • Getting a list of most-recently-used or favorite locations that should be quickly available for the user of the file system.

  • Creating new directories, and doing any other manipulation of the file system that is allowed in the file selector interface.

The file system object needs to allow for spontaneous notification of changes to the file system, since when available this is simply a better way of doing things than an explicit reload. If the user interface includes a reload button, probably there also needs to be a way for the file system object

When dealing with remote file systems, such as FTP or HTTP, and also when dealing with large local directories, asynchronous operations become desirable, especially for getting icons and mime-types, which may require actual access to the files. So, we may want ways to ask for the files in a particular directory and get them filled incrementally as they become available. (Note that it's important in this case not to make the update process for a fixed size chunk not to increase as the list gets longer.)

If we design things right, it seems likely that incremental filling can be done simply as change notification. When a directory is first accessed, the backend presents it as empty with dummy icons, then gradually fills in the directory and changes the icons as it gets more information.

Completion

Completion will be one of the more difficult parts of the implementation of the file selector, but it should appear minimally in the application API, if it all. There are two obvious sources for text to complete against: the file system and previously selected directories. Completing against previously selected directories has the possible advantage that we can first save the user typing and second provide memory for hard to remember file system paths.

The current GTK+ file selection uses tab completion; because this is both completely hidden from users and second interferes with standard navigation, it is not suitable for being the default form of navigation; automatic completion with highlighting should be the default mode of completion. It's possible that tab completion should also be available as a hidden user option, but if autocompletion is done well enough, it probably isn't necessary.

Static methods

One small API issue whether to provide static convenience methods to get file names without having to create an explicit object. To be able to replace:

GtkWidget *chooser = gtk_file_chooser_dialog_new (GTK_FILE_CHOOSER_OPEN);
gtk_window_set_parent (GTK_WINDOW (chooser), parent_window);
gtk_window_set_title (GTK_WINDOW (chooser), "Select a file");
if (gtk_dialog_run (GTK_DIALOG (chooser) == GTK_RESPONSE_OK)) 
  name = gtk_file_chooser_dialog_get_name (GTK_FILE_CHOOSER (chooser));
gtk_widget_destroy (chooser);

with:

name = gtk_file_chooser_run_open (parent_window, "Select a file");

Such an convenience API does reduce the amount of code for the most frequent operations. However, it is an inherently non-extensible API. A fixed set of decisions have to be made as to what options the caller will need to be set, and if the users needs more, they have to switch to a completely different API.

Moving forward

Tasks

  • Convert the above API sketch into a set of header files.

  • Get an initial implementation working. (For both this and the above, Bastien Nocera's work in libegg may be useful. For an initial implementation, the application interfaces are most important ... features such as completion can be gradually added over time.

  • Get a tentative UI design working together with the GNOME UI team.

  • Implement UI design.

  • Implement gnome-vfs and Win32 file system objects to make sure that the file system module interfaces are reasonable.

  • Iterate UI and API designs as necessary.

The user interface design process

While most existing file selectors are similar in broad outline, there is also quite a bit of divergence in the details. Do you include a "back" button? Do you include a forward button in addition to the "back" button? What form does the main file display take? A vertically scrolled list? A horizontally scrolled list? Should multiple different types of icon views be available?

If we want to avoid simple blind guessing when answering these questions, we should try to get some concrete ideas about who our user base is and what they'll be doing with the file selector. If the answers to those questions are "everybody" and "everything", as they likely will be, then we need to decide who the most important users are to us, and what features are essential to the broad set of tasks or important for the most common tasks.

Conclusions

The above discussion covers a lot of topics. It's worth going over the main conclusions briefly:

  • Desktop customizability should be achieved primarily through a dynamically replaceable file system object.

  • While the initial focus of development will be on a single well designed user interface, it seems desirable to make provisions for customized and possibly even complete replacement versions of the file selector for the future.

  • By using a GObject interface, GtkFileChooser, we can present a multi-layer API without an explosion of identical API in different objects.

  • The main points of application customization are: customized sets of file filters, custom previews, and the ability to pack in a small number of custom widgets.

  • The API design will allow a wide set of possible user interfaces; this allows work to start on implementation before finalization of the UI design.