Index: gtk/gtkfilechooserdefault.c
===================================================================
--- gtk/gtkfilechooserdefault.c	(revision 17792)
+++ gtk/gtkfilechooserdefault.c	(working copy)
@@ -56,6 +56,8 @@
 #include "gtkpathbar.h"
 #include "gtkprivate.h"
 #include "gtkradiobutton.h"
+#include "gtkrecentfilter.h"
+#include "gtkrecentmanager.h"
 #include "gtkscrolledwindow.h"
 #include "gtkseparatormenuitem.h"
 #include "gtksizegroup.h"
@@ -185,7 +187,8 @@ typedef enum {
   SHORTCUT_TYPE_PATH,
   SHORTCUT_TYPE_VOLUME,
   SHORTCUT_TYPE_SEPARATOR,
-  SHORTCUT_TYPE_SEARCH
+  SHORTCUT_TYPE_SEARCH,
+  SHORTCUT_TYPE_RECENT
 } ShortcutType;
 
 /* Column numbers for the file list */
@@ -206,6 +209,14 @@ enum {
   SEARCH_MODEL_COL_STAT
 };
 
+/* Column numbers for the recent files model. */
+enum {
+  RECENT_MODEL_COL_PATH,
+  RECENT_MODEL_COL_DISPLAY_NAME,
+  RECENT_MODEL_COL_INFO,
+  RECENT_MODEL_COL_NUM_COLUMNS
+};
+
 /* Identifiers for target types */
 enum {
   GTK_TREE_MODEL_ROW,
@@ -217,8 +228,7 @@ static const GtkTargetEntry shortcuts_so
   { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW }
 };
 
-static const int num_shortcuts_source_targets = (sizeof (shortcuts_source_targets)
-						 / sizeof (shortcuts_source_targets[0]));
+static const int num_shortcuts_source_targets = G_N_ELEMENTS (shortcuts_source_targets);
 
 /* Target types for dropping into the shortcuts list */
 static const GtkTargetEntry shortcuts_dest_targets[] = {
@@ -226,24 +236,21 @@ static const GtkTargetEntry shortcuts_de
   { "text/uri-list", 0, TEXT_URI_LIST }
 };
 
-static const int num_shortcuts_dest_targets = (sizeof (shortcuts_dest_targets)
-					       / sizeof (shortcuts_dest_targets[0]));
+static const int num_shortcuts_dest_targets = G_N_ELEMENTS (shortcuts_dest_targets);
 
 /* Target types for DnD from the file list */
 static const GtkTargetEntry file_list_source_targets[] = {
   { "text/uri-list", 0, TEXT_URI_LIST }
 };
 
-static const int num_file_list_source_targets = (sizeof (file_list_source_targets)
-						 / sizeof (file_list_source_targets[0]));
+static const int num_file_list_source_targets = G_N_ELEMENTS (file_list_source_targets);
 
 /* Target types for dropping into the file list */
 static const GtkTargetEntry file_list_dest_targets[] = {
   { "text/uri-list", 0, TEXT_URI_LIST }
 };
 
-static const int num_file_list_dest_targets = (sizeof (file_list_dest_targets)
-					       / sizeof (file_list_dest_targets[0]));
+static const int num_file_list_dest_targets = G_N_ELEMENTS (file_list_dest_targets); 
 
 static gboolean
 search_is_possible (GtkFileChooserDefault *impl)
@@ -258,6 +265,8 @@ search_is_possible (GtkFileChooserDefaul
 typedef enum {
   SHORTCUTS_SEARCH,
   SHORTCUTS_SEARCH_SEPARATOR,
+  SHORTCUTS_RECENT,
+  SHORTCUTS_RECENT_SEPARATOR,
   SHORTCUTS_HOME,
   SHORTCUTS_DESKTOP,
   SHORTCUTS_VOLUMES,
@@ -406,11 +415,11 @@ static void select_func (GtkFileSystemMo
 			 GtkTreeIter        *iter,
 			 gpointer            user_data);
 
-static void path_bar_clicked           (GtkPathBar            *path_bar,
-					GtkFilePath           *file_path,
-					GtkFilePath	      *child_path,
-					gboolean               child_is_hidden,
-					GtkFileChooserDefault *impl);
+static void path_bar_clicked (GtkPathBar            *path_bar,
+			      GtkFilePath           *file_path,
+			      GtkFilePath           *child_path,
+			      gboolean               child_is_hidden,
+			      GtkFileChooserDefault *impl);
 
 static void add_bookmark_button_clicked_cb    (GtkButton             *button,
 					       GtkFileChooserDefault *impl);
@@ -461,6 +470,14 @@ static GSList  *search_get_selected_path
 static void     search_entry_activate_cb     (GtkEntry              *entry, 
 					      gpointer               data);
 
+static void     update_recent_manager        (GtkFileChooserDefault *impl);
+static void     recent_stop_loading          (GtkFileChooserDefault *impl);
+static void     recent_clear_model           (GtkFileChooserDefault *impl,
+                                              gboolean               remove_from_treeview);
+static gboolean recent_should_respond        (GtkFileChooserDefault *impl);
+static void     recent_switch_to_browse_mode (GtkFileChooserDefault *impl);
+static GSList  *recent_get_selected_paths    (GtkFileChooserDefault *impl);
+
 
 
 /* Drag and drop interface declarations */
@@ -893,6 +910,8 @@ gtk_file_chooser_default_finalize (GObje
 
   search_clear_model (impl, FALSE);
 
+  recent_clear_model (impl, FALSE);
+
   g_free (impl->preview_display_name);
 
   g_free (impl->edited_new_text);
@@ -1149,9 +1168,14 @@ set_preview_widget (GtkFileChooserDefaul
 static GdkPixbuf *
 render_search_icon (GtkFileChooserDefault *impl)
 {
-  return gtk_widget_render_icon (GTK_WIDGET (impl), GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR, NULL);
+  return gtk_widget_render_icon (GTK_WIDGET (impl), GTK_STOCK_FIND, GTK_ICON_SIZE_MENU, NULL);
 }
 
+static GdkPixbuf *
+render_recent_icon (GtkFileChooserDefault *impl)
+{
+  return gtk_widget_render_icon (GTK_WIDGET (impl), GTK_STOCK_FILE, GTK_ICON_SIZE_MENU, NULL);
+}
 
 /* Re-reads all the icons for the shortcuts, used when the theme changes */
 struct ReloadIconsData
@@ -1299,6 +1323,10 @@ shortcuts_reload_icons (GtkFileChooserDe
 	    {
 	      pixbuf = render_search_icon (impl);
 	    }
+          else if (shortcut_type == SHORTCUT_TYPE_RECENT)
+            {
+              pixbuf = render_recent_icon (impl);
+            }
 	}
     }
   while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model),&iter));
@@ -1757,6 +1785,30 @@ shortcuts_append_search (GtkFileChooserD
   impl->has_search = TRUE;
 }
 
+static void
+shortcuts_append_recent (GtkFileChooserDefault *impl)
+{
+  GdkPixbuf *pixbuf;
+  GtkTreeIter iter;
+
+  pixbuf = render_recent_icon (impl);
+
+  gtk_list_store_append (impl->shortcuts_model, &iter);
+  gtk_list_store_set (impl->shortcuts_model, &iter,
+                      SHORTCUTS_COL_PIXBUF, pixbuf,
+                      SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE,
+                      SHORTCUTS_COL_NAME, _("Recently Used"),
+                      SHORTCUTS_COL_DATA, NULL,
+                      SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_RECENT,
+                      SHORTCUTS_COL_REMOVABLE, FALSE,
+                      -1);
+
+  if (pixbuf)
+    g_object_unref (pixbuf);
+
+  impl->has_recent = TRUE;
+}
+
 /* Appends an item for the user's home directory to the shortcuts model */
 static void
 shortcuts_append_home (GtkFileChooserDefault *impl)
@@ -1880,6 +1932,16 @@ shortcuts_get_index (GtkFileChooserDefau
 
   n += impl->has_search ? 1 : 0;
 
+  if (where == SHORTCUTS_RECENT)
+    goto out;
+
+  n += impl->has_recent ? 1 : 0;
+
+  if (where == SHORTCUTS_RECENT_SEPARATOR)
+    goto out;
+
+  n += impl->has_recent ? 1 : 0;
+
   if (where == SHORTCUTS_HOME)
     goto out;
 
@@ -2002,7 +2064,8 @@ shortcuts_insert_separator (GtkFileChoos
   GtkTreeIter iter;
 
   g_assert (where == SHORTCUTS_SEARCH_SEPARATOR || 
-	    where == SHORTCUTS_BOOKMARKS_SEPARATOR || 
+	    where == SHORTCUTS_RECENT_SEPARATOR ||
+            where == SHORTCUTS_BOOKMARKS_SEPARATOR || 
 	    where == SHORTCUTS_CURRENT_FOLDER_SEPARATOR);
 
   gtk_list_store_insert (impl->shortcuts_model, &iter,
@@ -2217,6 +2280,12 @@ shortcuts_model_create (GtkFileChooserDe
       shortcuts_append_search (impl);
       shortcuts_insert_separator (impl, SHORTCUTS_SEARCH_SEPARATOR);
     }
+
+  if (impl->recent_manager)
+    {
+      shortcuts_append_recent (impl);
+      shortcuts_insert_separator (impl, SHORTCUTS_RECENT_SEPARATOR);
+    }
   
   if (impl->file_system)
     {
@@ -2372,7 +2441,7 @@ renderer_edited_cb (GtkCellRendererText 
  /* work around bug #154921 */
   g_object_set (cell_renderer_text, 
 		"mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
-   queue_edited_idle (impl, new_text);
+  queue_edited_idle (impl, new_text);
 }
 
 /* Callback used from the text cell renderer when the new folder edition gets
@@ -2385,7 +2454,7 @@ renderer_editing_canceled_cb (GtkCellRen
  /* work around bug #154921 */
   g_object_set (cell_renderer_text, 
 		"mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
-   queue_edited_idle (impl, NULL);
+  queue_edited_idle (impl, NULL);
 }
 
 /* Creates the widgets for the filter combo box */
@@ -2403,11 +2472,11 @@ filter_create (GtkFileChooserDefault *im
 
 static GtkWidget *
 button_new (GtkFileChooserDefault *impl,
-	    const char *text,
-	    const char *stock_id,
-	    gboolean    sensitive,
-	    gboolean    show,
-	    GCallback   callback)
+	    const char            *text,
+	    const char            *stock_id,
+	    gboolean               sensitive,
+	    gboolean               show,
+	    GCallback              callback)
 {
   GtkWidget *button;
   GtkWidget *image;
@@ -2764,7 +2833,8 @@ bookmarks_check_add_sensitivity (GtkFile
   gboolean active;
   gchar *tip;
 
-  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+  if (impl->operation_mode == OPERATION_MODE_SEARCH ||
+      impl->operation_mode == OPERATION_MODE_RECENT)
     {
       gtk_widget_set_sensitive (impl->browse_shortcuts_add_button, FALSE);
       
@@ -4092,7 +4162,7 @@ file_list_update_popup_menu (GtkFileChoo
 {
   file_list_build_popup_menu (impl);
 
-  /* FMQ: handle OPERATION_MODE_SEARCH */
+  /* FMQ: handle OPERATION_MODE_SEARCH || OPERATION_MODE_RECENT */
 
   /* The sensitivity of the Add to Bookmarks item is set in
    * bookmarks_check_add_sensitivity()
@@ -4199,15 +4269,22 @@ file_list_set_sort_column_ids (GtkFileCh
 {
   int name_id, mtime_id;
 
-  if (impl->operation_mode == OPERATION_MODE_BROWSE)
+  switch (impl->operation_mode)
     {
+    case OPERATION_MODE_BROWSE:
       name_id = FILE_LIST_COL_NAME;
       mtime_id = FILE_LIST_COL_MTIME;
-    }
-  else
-    {
+      break;
+    case OPERATION_MODE_SEARCH:
       name_id = SEARCH_MODEL_COL_PATH;
       mtime_id = SEARCH_MODEL_COL_STAT;
+      break;
+    case OPERATION_MODE_RECENT:
+      name_id = RECENT_MODEL_COL_DISPLAY_NAME;
+      mtime_id = RECENT_MODEL_COL_INFO;
+      break;
+    default:
+      g_assert_not_reached ();
     }
 
   gtk_tree_view_column_set_sort_column_id (impl->list_name_column, name_id);
@@ -4488,6 +4565,19 @@ shortcuts_combo_filter_func (GtkTreeMode
 	    retval = FALSE;
         }
     }
+  
+  if (impl->has_recent)
+    {
+      idx = shortcuts_get_index (impl, SHORTCUTS_RECENT);
+      if (idx == indices[0])
+        retval = FALSE;
+      else
+        {
+          idx = shortcuts_get_index (impl, SHORTCUTS_RECENT_SEPARATOR);
+          if (idx == indices[0])
+            retval = FALSE;
+        }
+    }
 
   gtk_tree_path_free (tree_path);
 
@@ -4936,8 +5026,10 @@ gtk_file_chooser_default_constructor (GT
 
   gtk_widget_push_composite_child ();
 
-  /* Shortcuts model */
+  /* recent files manager */
+  update_recent_manager (impl);
 
+  /* Shortcuts model */
   shortcuts_model_create (impl);
 
   /* The browse widgets */
@@ -5463,6 +5555,9 @@ gtk_file_chooser_default_dispose (GObjec
   if (impl->operation_mode == OPERATION_MODE_SEARCH)
     search_stop_searching (impl);
 
+  if (impl->operation_mode == OPERATION_MODE_RECENT)
+    recent_stop_loading (impl);
+
   remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl)));
 
   G_OBJECT_CLASS (_gtk_file_chooser_default_parent_class)->dispose (object);
@@ -5595,6 +5690,49 @@ check_icon_theme (GtkFileChooserDefault 
 }
 
 static void
+recent_changed_cb (GtkRecentManager *manager,
+                   gpointer          user_data)
+{
+  /* Fall through */
+}
+
+static void
+update_recent_manager (GtkFileChooserDefault *impl)
+{
+  GtkRecentManager *manager;
+  profile_start ("start", NULL);
+
+  if (gtk_widget_has_screen (GTK_WIDGET (impl)))
+    {
+      manager = gtk_recent_manager_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl)));
+    }
+  else
+    {
+      manager = gtk_recent_manager_get_default ();
+    }
+
+  if (impl->recent_manager == manager)
+    {
+      profile_end ("end", NULL);
+      return;
+    }
+
+  if (impl->recent_changed_id)
+    {
+      g_signal_handler_disconnect (impl->recent_manager,
+                                   impl->recent_changed_id);
+      impl->recent_changed_id = 0;
+    }
+
+  impl->recent_manager = manager;
+  impl->recent_changed_id = g_signal_connect (manager, "changed",
+                                              G_CALLBACK (recent_changed_cb),
+                                              impl);
+
+  profile_end ("end", NULL);
+}
+
+static void
 gtk_file_chooser_default_style_set (GtkWidget *widget,
 				    GtkStyle  *previous_style)
 {
@@ -5634,6 +5772,7 @@ gtk_file_chooser_default_screen_changed 
 
   remove_settings_signal (impl, previous_screen);
   check_icon_theme (impl);
+  update_recent_manager (impl);
 
   g_signal_emit_by_name (widget, "default-size-changed");
 
@@ -6391,7 +6530,9 @@ update_chooser_entry (GtkFileChooserDefa
   struct update_chooser_entry_selected_foreach_closure closure;
   const char *file_part;
 
-  if (impl->operation_mode == OPERATION_MODE_SEARCH || !impl->location_entry)
+  if (impl->operation_mode == OPERATION_MODE_SEARCH ||
+      impl->operation_mode == OPERATION_MODE_RECENT ||
+      !impl->location_entry)
     return;
 
   if (!(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE
@@ -6448,18 +6589,25 @@ update_chooser_entry (GtkFileChooserDefa
                                                    impl->browse_files_last_selected_name);
 
         }
-      else
+      else if (impl->operation_mode == OPERATION_MODE_SEARCH)
         {
           gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model),
                               &closure.first_selected_iter,
                               SEARCH_MODEL_COL_DISPLAY_NAME, &file_part,
                               -1);
         }
+      else if (impl->operation_mode == OPERATION_MODE_RECENT)
+        {
+          gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model),
+                              &closure.first_selected_iter,
+                              RECENT_MODEL_COL_DISPLAY_NAME, &file_part,
+                              -1);
+        }
     }
   else
     {
-      g_assert (!(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE
- 		  || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER));
+      g_assert (!(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
+ 		  impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER));
 
       /* Multiple selection, so just clear the entry. */
 
@@ -6668,7 +6816,17 @@ gtk_file_chooser_default_update_current_
 
   profile_start ("start", (char *) path);
 
- search_switch_to_browse_mode (impl);
+  switch (impl->operation_mode)
+    {
+    case OPERATION_MODE_SEARCH:
+      search_switch_to_browse_mode (impl);
+      break;
+    case OPERATION_MODE_RECENT:
+      recent_switch_to_browse_mode (impl);
+      break;
+    case OPERATION_MODE_BROWSE:
+      break;
+    }
 
   g_assert (path != NULL);
 
@@ -6712,7 +6870,8 @@ gtk_file_chooser_default_get_current_fol
 {
   GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser);
 
-  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+  if (impl->operation_mode == OPERATION_MODE_SEARCH ||
+      impl->operation_mode == OPERATION_MODE_RECENT)
     return NULL;
  
   if (impl->reload_state == RELOAD_EMPTY)
@@ -6777,8 +6936,12 @@ gtk_file_chooser_default_select_path (Gt
   if (!parent_path)
    return _gtk_file_chooser_set_current_folder_path (chooser, path, error);
 
-  if (impl->operation_mode == OPERATION_MODE_SEARCH || impl->load_state == LOAD_EMPTY)
-    same_path = FALSE;
+  if (impl->operation_mode == OPERATION_MODE_SEARCH ||
+      impl->operation_mode == OPERATION_MODE_RECENT ||
+      impl->load_state == LOAD_EMPTY)
+    {
+      same_path = FALSE;
+    }
   else
     {
       g_assert (impl->current_folder != NULL);
@@ -6874,7 +7037,8 @@ gtk_file_chooser_default_select_all (Gtk
 {
   GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser);
 
-  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+  if (impl->operation_mode == OPERATION_MODE_SEARCH ||
+      impl->operation_mode == OPERATION_MODE_RECENT)
     {
       GtkTreeSelection *selection;
       
@@ -7027,6 +7191,9 @@ gtk_file_chooser_default_get_paths (GtkF
   if (impl->operation_mode == OPERATION_MODE_SEARCH)
     return search_get_selected_paths (impl);
 
+  if (impl->operation_mode == OPERATION_MODE_RECENT)
+    return recent_get_selected_paths (impl);
+
   info.impl = impl;
   info.result = NULL;
   info.path_from_entry = NULL;
@@ -8064,6 +8231,9 @@ gtk_file_chooser_default_should_respond 
       if (impl->operation_mode == OPERATION_MODE_SEARCH)
 	return search_should_respond (impl);
 
+      if (impl->operation_mode == OPERATION_MODE_RECENT)
+        return recent_should_respond (impl);
+
       selection_check (impl, &num_selected, &all_files, &all_folders);
 
       if (num_selected > 2)
@@ -8227,8 +8397,8 @@ gtk_file_chooser_default_should_respond 
     }
   else
     /* The focus is on a dialog's action area button or something else */
-    if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE
-	|| impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
+    if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
+        impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
       goto save_entry;
     else
       goto file_list; 
@@ -8478,8 +8648,7 @@ search_stop_searching (GtkFileChooserDef
 static void
 search_switch_to_browse_mode (GtkFileChooserDefault *impl)
 {
-  if (impl->operation_mode == OPERATION_MODE_BROWSE)
-    return;
+  g_assert (impl->operation_mode != OPERATION_MODE_BROWSE);
 
   search_stop_searching (impl);
   search_clear_model (impl, TRUE);
@@ -8705,6 +8874,330 @@ search_activate (GtkFileChooserDefault *
   file_list_set_sort_column_ids (impl);
 }
 
+/*
+ * Recent Files support
+ */
+
+/* Frees the data in the search_model */
+static void
+recent_clear_model (GtkFileChooserDefault *impl, 
+		    gboolean               remove_from_treeview)
+{
+  GtkTreeIter iter;
+
+  if (!impl->recent_model)
+    return;
+
+  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->recent_model), &iter))
+    do
+      {
+	GtkFilePath *path;
+        GtkRecentInfo *info;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &iter,
+			    RECENT_MODEL_COL_PATH, &path,
+                            RECENT_MODEL_COL_INFO, &info,
+			    -1);
+
+	gtk_file_path_free (path);
+        gtk_recent_info_unref (info);
+      }
+    while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->recent_model), &iter));
+  
+  g_object_unref (impl->recent_model);
+  impl->recent_model = NULL;
+
+  if (remove_from_treeview)
+    gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), NULL);
+}
+
+/* Stops any ongoing loading of the recent files list; does
+ * not touch the recent_model
+ */
+static void
+recent_stop_loading (GtkFileChooserDefault *impl)
+{
+  if (impl->load_recent_id)
+    {
+      g_source_remove (impl->load_recent_id);
+      impl->load_recent_id = 0;
+    }
+}
+
+/* Stops any pending load, clears the file list, and switches
+ * back to OPERATION_MODE_BROWSE
+ */
+static void
+recent_switch_to_browse_mode (GtkFileChooserDefault *impl)
+{
+  g_assert (impl->operation_mode != OPERATION_MODE_BROWSE);
+
+  recent_stop_loading (impl);
+  recent_clear_model (impl, TRUE);
+
+  gtk_widget_show (impl->browse_path_bar);
+  gtk_widget_show (impl->browse_new_folder_button);
+
+  if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+      impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+    {
+      gtk_widget_show (impl->location_button);
+
+      if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY)
+	gtk_widget_show (impl->location_entry_box);
+    }
+
+  impl->operation_mode = OPERATION_MODE_BROWSE;
+
+  file_list_set_sort_column_ids (impl);
+}
+
+/* Sort callback from the modification time column */
+static gint
+recent_column_mtime_sort_func (GtkTreeModel *model,
+			       GtkTreeIter  *a,
+			       GtkTreeIter  *b,
+			       gpointer      user_data)
+{
+  GtkRecentInfo *info_a, *info_b;
+
+  /* Note that although we store a whole struct stat in the model, we only
+   * compare the mtime here.  If we add another column relative to a struct stat
+   * (e.g. a file size column), we'll want another sort callback similar to this
+   * one as well.
+   */
+
+  gtk_tree_model_get (model, a, RECENT_MODEL_COL_INFO, &info_a, -1);
+  gtk_tree_model_get (model, b, RECENT_MODEL_COL_INFO, &info_b, -1);
+
+  if (gtk_recent_info_get_modified (info_a) < gtk_recent_info_get_modified (info_b))
+    return -1;
+  else if (gtk_recent_info_get_modified (info_a) > gtk_recent_info_get_modified (info_b))
+    return 1;
+  else
+    return 0;
+}
+
+
+static void
+recent_setup_model (GtkFileChooserDefault *impl)
+{
+  g_assert (impl->recent_model == NULL);
+
+  /* We store these columns in the search model:
+   *
+   * RECENT_MODEL_COL_PATH - a pointer to GtkFilePath for the hit's URI
+   * RECENT_MODEL_COL_DISPLAY_NAME - a string with the display name
+   * RECENT_MODEL_COL_INFO - GtkRecentInfo
+   *
+   * Keep this in sync with the enumeration defined near the beginning of
+   * this file.
+   */
+  impl->recent_model = gtk_list_store_new (RECENT_MODEL_COL_NUM_COLUMNS,
+					   G_TYPE_POINTER,
+					   G_TYPE_STRING,
+					   GTK_TYPE_RECENT_INFO);
+  
+  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->recent_model),
+				   RECENT_MODEL_COL_INFO,
+				   recent_column_mtime_sort_func,
+				   impl,
+				   NULL);
+
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (impl->recent_model),
+					RECENT_MODEL_COL_INFO,
+					GTK_SORT_DESCENDING);
+  
+  gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view),
+                           GTK_TREE_MODEL (impl->recent_model));
+}
+
+typedef struct
+{
+  GtkFileChooserDefault *impl;
+  GList *items;
+  gint n_items;
+  gint n_loaded_items;
+} RecentLoadData;
+
+static void
+recent_idle_cleanup (gpointer data)
+{
+  RecentLoadData *load_data = data;
+
+  set_busy_cursor (load_data->impl, FALSE);
+  
+  if (load_data->items)
+    {
+      g_list_foreach (load_data->items, (GFunc) gtk_recent_info_unref, NULL);
+      g_list_free (load_data->items);
+    }
+
+  g_free (load_data);
+}
+
+static gboolean
+recent_idle_load (gpointer data)
+{
+  RecentLoadData *load_data = data;
+  GtkFileChooserDefault *impl = load_data->impl;
+  GtkTreeIter iter;
+  GtkRecentInfo *info;
+  const gchar *uri, *display_name;
+  GtkFilePath *path;
+
+  if (!impl->recent_manager)
+    return FALSE;
+
+  if (!load_data->items)
+    {
+      load_data->items = gtk_recent_manager_get_items (impl->recent_manager);
+      load_data->n_items = g_list_length (load_data->items);
+      load_data->n_loaded_items = 0;
+
+      return TRUE;
+    }
+
+  info = g_list_nth_data (load_data->items, load_data->n_loaded_items);
+  g_assert (info != NULL);
+
+  uri = gtk_recent_info_get_uri (info);
+  display_name = gtk_recent_info_get_display_name (info);
+  path = gtk_file_system_uri_to_path (impl->file_system, uri);
+
+  gtk_list_store_append (impl->recent_model, &iter);
+  gtk_list_store_set (impl->recent_model, &iter,
+                      RECENT_MODEL_COL_PATH, path,
+                      RECENT_MODEL_COL_DISPLAY_NAME, display_name,
+                      RECENT_MODEL_COL_INFO, gtk_recent_info_ref (info),
+                      -1);
+
+  load_data->n_loaded_items += 1;
+
+  /* finished loading items */
+  if (load_data->n_loaded_items == load_data->n_items)
+    {
+      g_list_foreach (load_data->items, (GFunc) gtk_recent_info_unref, NULL);
+      g_list_free (load_data->items);
+      load_data->items = NULL;
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+recent_start_loading (GtkFileChooserDefault *impl)
+{
+  RecentLoadData *load_data;
+
+  recent_stop_loading (impl);
+  recent_clear_model (impl, TRUE);
+  recent_setup_model (impl);
+  set_busy_cursor (impl, TRUE);
+
+  if (!impl->recent_manager)
+    {
+      update_recent_manager (impl);
+      return;
+    }
+
+  load_data = g_new (RecentLoadData, 1);
+  load_data->impl = impl;
+  load_data->items = NULL;
+  load_data->n_items = 0;
+  load_data->n_loaded_items = 0;
+
+  /* begin lazy loading the recent files into the model */
+  impl->load_recent_id = gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE + 30,
+                                                    recent_idle_load,
+                                                    load_data,
+                                                    recent_idle_cleanup);
+}
+
+static void
+recent_selected_foreach_get_path_cb (GtkTreeModel *model,
+				     GtkTreePath  *path,
+				     GtkTreeIter  *iter,
+				     gpointer      data)
+{
+  GSList **list;
+  const GtkFilePath *file_path;
+  GtkFilePath *file_path_copy;
+
+  list = data;
+
+  gtk_tree_model_get (model, iter, RECENT_MODEL_COL_PATH, &file_path, -1);
+  file_path_copy = gtk_file_path_copy (file_path);
+  *list = g_slist_prepend (*list, file_path_copy);
+}
+
+/* Constructs a list of the selected paths in recent files mode */
+static GSList *
+recent_get_selected_paths (GtkFileChooserDefault *impl)
+{
+  GSList *result;
+  GtkTreeSelection *selection;
+
+  result = NULL;
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view));
+  gtk_tree_selection_selected_foreach (selection, recent_selected_foreach_get_path_cb, &result);
+  result = g_slist_reverse (result);
+
+  return result;
+}
+
+/* Called from ::should_respond().  We return whether there are selected files
+ * in the search list.
+ */
+static gboolean
+recent_should_respond (GtkFileChooserDefault *impl)
+{
+  GtkTreeSelection *selection;
+
+  g_assert (impl->operation_mode == OPERATION_MODE_RECENT);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view));
+  return (gtk_tree_selection_count_selected_rows (selection) != 0);
+}
+
+/* Hide the location widgets temporarily */
+static void
+recent_hide_entry (GtkFileChooserDefault *impl)
+{
+  gtk_widget_hide (impl->browse_path_bar);
+  gtk_widget_hide (impl->browse_new_folder_button);
+  
+  if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+      impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+    {
+      gtk_widget_hide (impl->location_button);
+      gtk_widget_hide (impl->location_entry_box);
+    }
+
+  /* EB: hide the filter combo? */
+}
+
+/* Main entry point to the recent files functions; this gets called when
+ * the user activates the Recently Used shortcut.
+ */
+static void
+recent_activate (GtkFileChooserDefault *impl)
+{
+  if (impl->operation_mode == OPERATION_MODE_RECENT)
+    return;
+
+  impl->operation_mode = OPERATION_MODE_RECENT;
+
+  stop_loading_and_clear_list_model (impl);
+  recent_hide_entry (impl);
+  file_list_set_sort_column_ids (impl);
+  recent_start_loading (impl);
+}
+
+
 static void
 set_current_filter (GtkFileChooserDefault *impl,
 		    GtkFileFilter         *filter)
@@ -8779,7 +9272,7 @@ check_preview_change (GtkFileChooserDefa
 	        new_display_name = gtk_file_info_get_display_name (new_info);
 	    }
 	}
-      else
+      else if (impl->operation_mode == OPERATION_MODE_SEARCH)
 	{
 	  GtkTreeIter iter;
 
@@ -8791,6 +9284,18 @@ check_preview_change (GtkFileChooserDefa
 			      SEARCH_MODEL_COL_DISPLAY_NAME, &new_display_name,
 			      -1);
 	}
+      else if (impl->operation_mode == OPERATION_MODE_RECENT)
+        {
+          GtkTreeIter iter;
+
+          gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->recent_model), &iter, cursor_path);
+          gtk_tree_path_free (cursor_path);
+
+          gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &iter,
+                              RECENT_MODEL_COL_PATH, &new_path,
+                              RECENT_MODEL_COL_DISPLAY_NAME, &new_display_name,
+                              -1);
+        }
     }
 
   if (new_path != impl->preview_path &&
@@ -8877,7 +9382,17 @@ shortcuts_activate_volume (GtkFileChoose
 {
   GtkFilePath *path;
 
-  search_switch_to_browse_mode (impl);
+  switch (impl->operation_mode)
+    {
+    case OPERATION_MODE_SEARCH:
+      search_switch_to_browse_mode (impl);
+      break;
+    case OPERATION_MODE_RECENT:
+      recent_switch_to_browse_mode (impl);
+      break;
+    case OPERATION_MODE_BROWSE:
+      break;
+    }
 
   /* We ref the file chooser since volume_mount() may run a main loop, and the
    * user could close the file chooser window in the meantime.
@@ -8996,6 +9511,10 @@ shortcuts_activate_iter (GtkFileChooserD
     {
       search_activate (impl);
     }
+  else if (shortcut_type == SHORTCUT_TYPE_RECENT)
+    {
+      recent_activate (impl);
+    }
 }
 
 /* Callback used when a row in the shortcuts list is activated */
@@ -9137,12 +9656,14 @@ list_row_activated (GtkTreeView         
   GtkTreeIter iter, child_iter;
   const GtkFileInfo *info;
 
-  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+  if (impl->operation_mode == OPERATION_MODE_SEARCH ||
+      impl->operation_mode == OPERATION_MODE_RECENT)
     {
       g_signal_emit_by_name (impl, "file-activated");
       return;
     }
 
+
   if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, path))
     return;
 
@@ -9208,49 +9729,67 @@ list_icon_data_func (GtkTreeViewColumn *
 {
   GtkFileChooserDefault *impl = data;
   GtkTreeIter child_iter;
-  const GtkFilePath *path;
-  GdkPixbuf *pixbuf;
-  const GtkFileInfo *info; 
+  GdkPixbuf *pixbuf = NULL;
   gboolean sensitive = TRUE;
 
-  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+  profile_start ("start", NULL);
+  
+  switch (impl->operation_mode)
     {
+    case OPERATION_MODE_SEARCH:
       g_object_set (cell,
 		    "pixbuf", NULL,
-		    "sensitive", TRUE,
+		    "sensitive", sensitive,
 		    NULL);
-      return;
-    }
-  
-  profile_start ("start", NULL);
-  
-  info = get_list_file_info (impl, iter);
+      break;
+    case OPERATION_MODE_RECENT:
+      {
+        GtkRecentInfo *info;
 
-  gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model,
-						  &child_iter,
-						  iter);
-  path = _gtk_file_system_model_get_path (impl->browse_files_model, &child_iter);
+        gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), iter,
+                            RECENT_MODEL_COL_INFO, &info,
+                            -1);
+
+        pixbuf = gtk_recent_info_get_icon (info, impl->icon_size);
+        if (impl->local_only && !gtk_recent_info_is_local (info))
+          sensitive = FALSE;
+      }
+      break;
+    case OPERATION_MODE_BROWSE:
+      {
+        const GtkFileInfo *info;
+        const GtkFilePath *path;
 
-  if (path)
-    {
-      pixbuf = NULL;
+        info = get_list_file_info (impl, iter);
 
-      if (info)
-        {
-          /* FIXME: NULL GError */
-	  pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (impl),
-					      impl->icon_size, NULL);
-	}
-    }
-  else
-    {
-      /* We are on the editable row */
-      pixbuf = NULL;
+        gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model,
+                                                        &child_iter,
+                                                        iter);
+        path = _gtk_file_system_model_get_path (impl->browse_files_model, &child_iter);
+
+        if (path)
+          {
+            pixbuf = NULL;
+            if (info)
+              {
+                /* FIXME: NULL GError */
+                pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (impl),
+                                                    impl->icon_size, NULL);
+              }
+          }
+        else
+          {
+            /* We are on the editable row */
+            pixbuf = NULL;
+          }
+
+        if (info &&
+            (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
+             impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER))
+          sensitive =  gtk_file_info_get_is_folder (info);    
+      }
+      break;
     }
-
-  if (info && (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
-	       impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER))
-    sensitive =  gtk_file_info_get_is_folder (info);    
     
   g_object_set (cell,
 		"pixbuf", pixbuf,
@@ -9289,6 +9828,22 @@ list_name_data_func (GtkTreeViewColumn *
       return;
     }
 
+  if (impl->operation_mode == OPERATION_MODE_RECENT)
+    {
+      char *display_name;
+
+      gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), iter,
+                          RECENT_MODEL_COL_DISPLAY_NAME, &display_name,
+                          -1);
+
+      g_object_set (cell,
+                    "text", display_name,
+                    "sensitive", TRUE,
+                    "ellipsize", PANGO_ELLIPSIZE_END,
+                    NULL);
+      return;
+    }
+
   info = get_list_file_info (impl, iter);
   sensitive = TRUE;
 
@@ -9304,8 +9859,8 @@ list_name_data_func (GtkTreeViewColumn *
     }
 
 
-  if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
-	 || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
+  if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
+      impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
     {
       sensitive = gtk_file_info_get_is_folder (info);
     } 
@@ -9392,6 +9947,18 @@ list_mtime_data_func (GtkTreeViewColumn 
 			  -1);
       time_mtime = statbuf->st_mtime;
     }
+  else if (impl->operation_mode == OPERATION_MODE_RECENT)
+    {
+      GtkRecentInfo *info = NULL;
+
+      gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), iter,
+                          RECENT_MODEL_COL_INFO, &info,
+                          -1);
+      time_mtime = (GtkFileTime) gtk_recent_info_get_modified (info);
+      
+      if (impl->local_only && !gtk_recent_info_is_local (info))
+        sensitive = FALSE;
+    }
   else
     {
       const GtkFileInfo *info;
@@ -9468,12 +10035,24 @@ static void
 location_popup_handler (GtkFileChooserDefault *impl,
 			const gchar           *path)
 { 
-  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+
+  if (impl->operation_mode != OPERATION_MODE_BROWSE)
     {
       GtkWidget *widget_to_focus;
-      
+
       /* This will give us the location widgets back */
-      search_switch_to_browse_mode (impl);
+      switch (impl->operation_mode)
+        {
+        case OPERATION_MODE_SEARCH:
+          search_switch_to_browse_mode (impl);
+          break;
+        case OPERATION_MODE_BROWSE:
+          recent_switch_to_browse_mode (impl);
+          break;
+        default:
+          break;
+        }
+      
       if (impl->current_folder)
         change_folder_and_display_error (impl, impl->current_folder, FALSE);
 
@@ -9485,8 +10064,9 @@ location_popup_handler (GtkFileChooserDe
       gtk_widget_grab_focus (widget_to_focus);
       return; 
     }
-  if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN
-      || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+
+  if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+      impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
     {
       LocationMode new_mode;
 
Index: gtk/gtkfilechooserprivate.h
===================================================================
--- gtk/gtkfilechooserprivate.h	(revision 17792)
+++ gtk/gtkfilechooserprivate.h	(working copy)
@@ -26,6 +26,7 @@
 #include "gtkfilesystemmodel.h"
 #include "gtkliststore.h"
 #include "gtksearchengine.h"
+#include "gtkrecentmanager.h"
 #include "gtkquery.h"
 #include "gtktooltips.h"
 #include "gtktreemodelsort.h"
@@ -151,7 +152,8 @@ typedef enum {
 
 typedef enum {
   OPERATION_MODE_BROWSE,
-  OPERATION_MODE_SEARCH
+  OPERATION_MODE_SEARCH,
+  OPERATION_MODE_RECENT
 } OperationMode;
 
 struct _GtkFileChooserDefault
@@ -195,6 +197,12 @@ struct _GtkFileChooserDefault
   GtkQuery *search_query;
   GtkListStore *search_model;
 
+  /* Widgets for recent files */
+  GtkRecentManager *recent_manager;
+  GtkListStore *recent_model;
+  guint recent_changed_id;
+  guint load_recent_id;
+
   GtkWidget *filter_combo_hbox;
   GtkWidget *filter_combo;
   GtkWidget *preview_box;
@@ -293,6 +301,7 @@ struct _GtkFileChooserDefault
   guint has_home : 1;
   guint has_desktop : 1;
   guint has_search : 1;
+  guint has_recent : 1;
 
 #if 0
   guint shortcuts_drag_outside : 1;
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 17792)
+++ ChangeLog	(working copy)
@@ -1,3 +1,9 @@
+2007-05-04  Emmanuele Bassi  <ebassi@gnome.org>
+
+	* gtk/gtkfilechooserprivate.h:
+	* gtk/gtkfilechooserdefault.c: Add a "Recently Used" shortcut,
+	showing the list of recently used files.
+
 2007-05-03  Behdad Esfahbod  <behdad@gnome.org>
 
 	* gtk/gtklabel.c (gtk_label_ensure_layout):
