+ if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
+ (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (widget,
+ old_screen);
+
+ change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
+}
+
+
+/* ******************* *
+ * Utility Functions *
+ * ******************* */
+
+/* General */
+static GtkIconTheme *
+get_icon_theme (GtkWidget *widget)
+{
+ if (gtk_widget_has_screen (widget))
+ return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
+
+ return gtk_icon_theme_get_default ();
+}
+
+
+struct SetDisplayNameData
+{
+ GtkFileChooserButton *button;
+ char *label;
+ GtkTreeRowReference *row_ref;
+};
+
+static void
+set_info_get_info_cb (GtkFileSystemHandle *handle,
+ const GtkFileInfo *info,
+ const GError *error,
+ gpointer callback_data)
+{
+ gboolean cancelled = handle->cancelled;
+ GdkPixbuf *pixbuf;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkFileSystemHandle *model_handle;
+ struct SetDisplayNameData *data = callback_data;
+
+ if (!data->button->priv->model)
+ /* button got destroyed */
+ goto out;
+
+ path = gtk_tree_row_reference_get_path (data->row_ref);
+ if (!path)
+ /* Handle doesn't exist anymore in the model */
+ goto out;
+
+ gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
+ gtk_tree_path_free (path);
+
+ /* Validate the handle */
+ gtk_tree_model_get (data->button->priv->model, &iter,
+ HANDLE_COLUMN, &model_handle,
+ -1);
+ if (handle != model_handle)
+ goto out;
+
+ gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
+ HANDLE_COLUMN, NULL,
+ -1);
+
+ if (cancelled || error)
+ /* There was an error, leave the fallback name in there */
+ goto out;
+
+ pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (data->button),
+ data->button->priv->icon_size, NULL);
+
+ if (!data->label)
+ data->label = g_strdup (gtk_file_info_get_display_name (info));
+
+ gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
+ ICON_COLUMN, pixbuf,
+ DISPLAY_NAME_COLUMN, data->label,
+ IS_FOLDER_COLUMN, gtk_file_info_get_is_folder (info),
+ -1);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+out:
+ g_object_unref (data->button);
+ g_free (data->label);
+ gtk_tree_row_reference_free (data->row_ref);
+ g_free (data);
+
+ g_object_unref (handle);
+}
+
+static void
+set_info_for_path_at_iter (GtkFileChooserButton *button,
+ const GtkFilePath *path,
+ GtkTreeIter *iter)
+{
+ struct SetDisplayNameData *data;
+ GtkTreePath *tree_path;
+ GtkFileSystemHandle *handle;
+
+ data = g_new0 (struct SetDisplayNameData, 1);
+ data->button = g_object_ref (button);
+ data->label = gtk_file_system_get_bookmark_label (button->priv->fs, path);
+
+ tree_path = gtk_tree_model_get_path (button->priv->model, iter);
+ data->row_ref = gtk_tree_row_reference_new (button->priv->model, tree_path);
+ gtk_tree_path_free (tree_path);
+
+ handle = gtk_file_system_get_info (button->priv->fs, path,
+ GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER | GTK_FILE_INFO_ICON,
+ set_info_get_info_cb, data);
+
+ gtk_list_store_set (GTK_LIST_STORE (button->priv->model), iter,
+ HANDLE_COLUMN, handle,
+ -1);
+}
+
+/* Shortcuts Model */
+static gint
+model_get_type_position (GtkFileChooserButton *button,
+ RowType row_type)
+{
+ gint retval = 0;
+
+ if (row_type == ROW_TYPE_SPECIAL)
+ return retval;
+
+ retval += button->priv->n_special;
+
+ if (row_type == ROW_TYPE_VOLUME)
+ return retval;
+
+ retval += button->priv->n_volumes;
+
+ if (row_type == ROW_TYPE_SHORTCUT)
+ return retval;
+
+ retval += button->priv->n_shortcuts;
+
+ if (row_type == ROW_TYPE_BOOKMARK_SEPARATOR)
+ return retval;
+
+ retval += button->priv->has_bookmark_separator;
+
+ if (row_type == ROW_TYPE_BOOKMARK)
+ return retval;
+
+ retval += button->priv->n_bookmarks;
+
+ if (row_type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR)
+ return retval;
+
+ retval += button->priv->has_current_folder_separator;
+
+ if (row_type == ROW_TYPE_CURRENT_FOLDER)
+ return retval;
+
+ retval += button->priv->has_current_folder;
+
+ if (row_type == ROW_TYPE_OTHER_SEPARATOR)
+ return retval;
+
+ retval += button->priv->has_other_separator;
+
+ if (row_type == ROW_TYPE_OTHER)
+ return retval;
+
+ g_assert_not_reached ();
+ return -1;
+}
+
+static void
+model_free_row_data (GtkFileChooserButton *button,
+ GtkTreeIter *iter)
+{
+ gchar type;
+ gpointer data;
+ GtkFileSystemHandle *handle;
+
+ gtk_tree_model_get (button->priv->model, iter,
+ TYPE_COLUMN, &type,
+ DATA_COLUMN, &data,
+ HANDLE_COLUMN, &handle,
+ -1);
+
+ if (handle)
+ gtk_file_system_cancel_operation (handle);
+
+ switch (type)
+ {
+ case ROW_TYPE_SPECIAL:
+ case ROW_TYPE_SHORTCUT:
+ case ROW_TYPE_BOOKMARK:
+ case ROW_TYPE_CURRENT_FOLDER:
+ gtk_file_path_free (data);
+ break;
+ case ROW_TYPE_VOLUME:
+ gtk_file_system_volume_free (button->priv->fs, data);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+model_add_special_get_info_cb (GtkFileSystemHandle *handle,
+ const GtkFileInfo *info,
+ const GError *error,
+ gpointer user_data)
+{
+ gboolean cancelled = handle->cancelled;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GdkPixbuf *pixbuf;
+ GtkFileSystemHandle *model_handle;
+ struct ChangeIconThemeData *data = user_data;
+
+ if (!data->button->priv->model)
+ /* button got destroyed */
+ goto out;
+
+ path = gtk_tree_row_reference_get_path (data->row_ref);
+ if (!path)
+ /* Handle doesn't exist anymore in the model */
+ goto out;
+
+ gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (data->button->priv->model, &iter,
+ HANDLE_COLUMN, &model_handle,
+ -1);
+ if (handle != model_handle)
+ goto out;
+
+ gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
+ HANDLE_COLUMN, NULL,
+ -1);
+
+ if (cancelled || error)
+ goto out;
+
+ pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (data->button),
+ data->button->priv->icon_size, NULL);
+
+ if (pixbuf)
+ {
+ gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
+ ICON_COLUMN, pixbuf,
+ -1);
+ g_object_unref (pixbuf);
+ }
+
+ gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
+ DISPLAY_NAME_COLUMN, gtk_file_info_get_display_name (info),
+ -1);
+
+out:
+ g_object_unref (data->button);
+ gtk_tree_row_reference_free (data->row_ref);
+ g_free (data);
+
+ g_object_unref (handle);
+}
+
+static inline void
+model_add_special (GtkFileChooserButton *button)
+{
+ const gchar *homedir;
+ gchar *desktopdir = NULL;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GtkFilePath *path;
+ gint pos;
+
+ store = GTK_LIST_STORE (button->priv->model);
+ pos = model_get_type_position (button, ROW_TYPE_SPECIAL);
+
+ homedir = g_get_home_dir ();
+
+ if (homedir)
+ {
+ GtkTreePath *tree_path;
+ GtkFileSystemHandle *handle;
+ struct ChangeIconThemeData *info;
+
+ path = gtk_file_system_filename_to_path (button->priv->fs, homedir);
+ gtk_list_store_insert (store, &iter, pos);
+ pos++;
+
+ info = g_new0 (struct ChangeIconThemeData, 1);
+ info->button = g_object_ref (button);
+ tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+ info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store),
+ tree_path);
+ gtk_tree_path_free (tree_path);
+
+ handle = gtk_file_system_get_info (button->priv->fs, path,
+ GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_ICON,
+ model_add_special_get_info_cb, info);
+
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, NULL,
+ DISPLAY_NAME_COLUMN, NULL,
+ TYPE_COLUMN, ROW_TYPE_SPECIAL,
+ DATA_COLUMN, path,
+ IS_FOLDER_COLUMN, TRUE,
+ HANDLE_COLUMN, handle,
+ -1);
+
+ button->priv->n_special++;
+
+#ifndef G_OS_WIN32
+ desktopdir = g_build_filename (homedir, DESKTOP_DISPLAY_NAME, NULL);
+#endif
+ }
+
+#ifdef G_OS_WIN32
+ desktopdir = _gtk_file_system_win32_get_desktop ();
+#endif
+
+ if (desktopdir)
+ {
+ GtkTreePath *tree_path;
+ GtkFileSystemHandle *handle;
+ struct ChangeIconThemeData *info;
+
+ path = gtk_file_system_filename_to_path (button->priv->fs, desktopdir);
+ g_free (desktopdir);
+ gtk_list_store_insert (store, &iter, pos);
+ pos++;
+
+ info = g_new0 (struct ChangeIconThemeData, 1);
+ info->button = g_object_ref (button);
+ tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+ info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store),
+ tree_path);
+ gtk_tree_path_free (tree_path);
+
+ handle = gtk_file_system_get_info (button->priv->fs, path,
+ GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_ICON,
+ model_add_special_get_info_cb, info);
+
+ gtk_list_store_set (store, &iter,
+ TYPE_COLUMN, ROW_TYPE_SPECIAL,
+ ICON_COLUMN, NULL,
+ DISPLAY_NAME_COLUMN, _(DESKTOP_DISPLAY_NAME),
+ DATA_COLUMN, path,
+ IS_FOLDER_COLUMN, TRUE,
+ HANDLE_COLUMN, handle,
+ -1);
+
+ button->priv->n_special++;
+ }
+}
+
+static void
+model_add_volumes (GtkFileChooserButton *button,
+ GSList *volumes)
+{
+ GtkListStore *store;
+ gint pos;
+ gboolean local_only;
+ GtkFileSystem *file_system;
+ GSList *l;
+
+ if (!volumes)
+ return;
+
+ store = GTK_LIST_STORE (button->priv->model);
+ pos = model_get_type_position (button, ROW_TYPE_VOLUME);
+ local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (button->priv->dialog));
+ file_system = button->priv->fs;
+
+ for (l = volumes; l; l = l->next)
+ {
+ GtkFileSystemVolume *volume;
+ GtkTreeIter iter;
+ GdkPixbuf *pixbuf;
+ gchar *display_name;
+
+ volume = l->data;
+
+ if (local_only)
+ {
+ if (gtk_file_system_volume_get_is_mounted (file_system, volume))
+ {
+ GtkFilePath *base_path;
+
+ base_path = gtk_file_system_volume_get_base_path (file_system, volume);
+ if (base_path != NULL)
+ {
+ gboolean is_local = gtk_file_system_path_is_local (file_system, base_path);
+ gtk_file_path_free (base_path);
+
+ if (!is_local)
+ {
+ gtk_file_system_volume_free (file_system, volume);
+ continue;
+ }
+ }
+ }
+ }
+
+ pixbuf = gtk_file_system_volume_render_icon (file_system,
+ volume,
+ GTK_WIDGET (button),
+ button->priv->icon_size,
+ NULL);
+ display_name = gtk_file_system_volume_get_display_name (file_system, volume);
+
+ gtk_list_store_insert (store, &iter, pos);
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, pixbuf,
+ DISPLAY_NAME_COLUMN, display_name,
+ TYPE_COLUMN, ROW_TYPE_VOLUME,
+ DATA_COLUMN, volume,
+ IS_FOLDER_COLUMN, TRUE,
+ -1);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+ g_free (display_name);
+
+ button->priv->n_volumes++;
+ pos++;
+ }
+}
+
+extern gchar * _gtk_file_chooser_label_for_uri (const gchar *uri);
+
+static void
+model_add_bookmarks (GtkFileChooserButton *button,
+ GSList *bookmarks)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gint pos;
+ gboolean local_only;
+ GSList *l;
+
+ if (!bookmarks)
+ return;
+
+ store = GTK_LIST_STORE (button->priv->model);
+ pos = model_get_type_position (button, ROW_TYPE_BOOKMARK);
+ local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (button->priv->dialog));
+
+ for (l = bookmarks; l; l = l->next)
+ {
+ GtkFilePath *path;
+
+ path = l->data;
+
+ if (gtk_file_system_path_is_local (button->priv->fs, path))
+ {
+ gtk_list_store_insert (store, &iter, pos);
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, NULL,
+ DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
+ TYPE_COLUMN, ROW_TYPE_BOOKMARK,
+ DATA_COLUMN, gtk_file_path_copy (path),
+ IS_FOLDER_COLUMN, FALSE,
+ -1);
+ set_info_for_path_at_iter (button, path, &iter);
+ }
+ else
+ {
+ gchar *label;
+ GtkIconTheme *icon_theme;
+ GdkPixbuf *pixbuf;
+
+ if (local_only)
+ continue;
+
+ /* Don't call get_info for remote paths to avoid latency and
+ * auth dialogs.
+ * If we switch to a better bookmarks file format (XBEL), we
+ * should use mime info to get a better icon.
+ */
+ label = gtk_file_system_get_bookmark_label (button->priv->fs, path);
+ if (!label)
+ {
+ gchar *uri;
+
+ uri = gtk_file_system_path_to_uri (button->priv->fs, path);
+ label = _gtk_file_chooser_label_for_uri (uri);
+ g_free (uri);
+ }
+
+ icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
+ pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory",
+ button->priv->icon_size, 0, NULL);
+
+ gtk_list_store_insert (store, &iter, pos);
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, pixbuf,
+ DISPLAY_NAME_COLUMN, label,
+ TYPE_COLUMN, ROW_TYPE_BOOKMARK,
+ DATA_COLUMN, gtk_file_path_copy (path),
+ IS_FOLDER_COLUMN, TRUE,
+ -1);
+
+ g_free (label);
+ g_object_unref (pixbuf);
+ }
+
+ button->priv->n_bookmarks++;
+ pos++;
+ }
+
+ if (button->priv->n_bookmarks > 0 &&
+ !button->priv->has_bookmark_separator)
+ {
+ pos = model_get_type_position (button, ROW_TYPE_BOOKMARK_SEPARATOR);
+
+ gtk_list_store_insert (store, &iter, pos);
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, NULL,
+ DISPLAY_NAME_COLUMN, NULL,
+ TYPE_COLUMN, ROW_TYPE_BOOKMARK_SEPARATOR,
+ DATA_COLUMN, NULL,
+ IS_FOLDER_COLUMN, FALSE,
+ -1);
+ button->priv->has_bookmark_separator = TRUE;
+ }
+}
+
+static void
+model_update_current_folder (GtkFileChooserButton *button,
+ const GtkFilePath *path)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gint pos;
+
+ if (!path)
+ return;
+
+ store = GTK_LIST_STORE (button->priv->model);
+
+ if (!button->priv->has_current_folder_separator)
+ {
+ pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER_SEPARATOR);
+ gtk_list_store_insert (store, &iter, pos);
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, NULL,
+ DISPLAY_NAME_COLUMN, NULL,
+ TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER_SEPARATOR,
+ DATA_COLUMN, NULL,
+ IS_FOLDER_COLUMN, FALSE,
+ -1);
+ button->priv->has_current_folder_separator = TRUE;
+ }
+
+ pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
+ if (!button->priv->has_current_folder)
+ {
+ gtk_list_store_insert (store, &iter, pos);
+ button->priv->has_current_folder = TRUE;
+ }
+ else
+ {
+ gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos);
+ model_free_row_data (button, &iter);
+ }
+
+ if (gtk_file_system_path_is_local (button->priv->fs, path))
+ {
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, NULL,
+ DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
+ TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER,
+ DATA_COLUMN, gtk_file_path_copy (path),
+ IS_FOLDER_COLUMN, FALSE,
+ -1);
+ set_info_for_path_at_iter (button, path, &iter);
+ }
+ else
+ {
+ gchar *label;
+ GtkIconTheme *icon_theme;
+ GdkPixbuf *pixbuf;
+
+ /* Don't call get_info for remote paths to avoid latency and
+ * auth dialogs.
+ * If we switch to a better bookmarks file format (XBEL), we
+ * should use mime info to get a better icon.
+ */
+ label = gtk_file_system_get_bookmark_label (button->priv->fs, path);
+ if (!label)
+ {
+ gchar *uri;
+
+ uri = gtk_file_system_path_to_uri (button->priv->fs, path);
+ label = _gtk_file_chooser_label_for_uri (uri);
+ g_free (uri);
+ }
+
+ icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
+ pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory",
+ button->priv->icon_size, 0, NULL);
+
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, pixbuf,
+ DISPLAY_NAME_COLUMN, label,
+ TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER,
+ DATA_COLUMN, gtk_file_path_copy (path),
+ IS_FOLDER_COLUMN, TRUE,
+ -1);
+
+ g_free (label);
+ g_object_unref (pixbuf);
+ }
+}
+
+static inline void
+model_add_other (GtkFileChooserButton *button)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gint pos;
+
+ store = GTK_LIST_STORE (button->priv->model);
+ pos = model_get_type_position (button, ROW_TYPE_OTHER_SEPARATOR);
+
+ gtk_list_store_insert (store, &iter, pos);
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, NULL,
+ DISPLAY_NAME_COLUMN, NULL,
+ TYPE_COLUMN, ROW_TYPE_OTHER_SEPARATOR,
+ DATA_COLUMN, NULL,
+ IS_FOLDER_COLUMN, FALSE,
+ -1);
+ button->priv->has_other_separator = TRUE;
+ pos++;
+
+ gtk_list_store_insert (store, &iter, pos);
+ gtk_list_store_set (store, &iter,
+ ICON_COLUMN, NULL,
+ DISPLAY_NAME_COLUMN, _("Other..."),
+ TYPE_COLUMN, ROW_TYPE_OTHER,
+ DATA_COLUMN, NULL,
+ IS_FOLDER_COLUMN, FALSE,
+ -1);
+}
+
+static void
+model_remove_rows (GtkFileChooserButton *button,
+ gint pos,
+ gint n_rows)
+{
+ GtkListStore *store;
+
+ if (!n_rows)
+ return;
+
+ store = GTK_LIST_STORE (button->priv->model);
+
+ do
+ {
+ GtkTreeIter iter;
+
+ if (!gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos))
+ g_assert_not_reached ();
+
+ model_free_row_data (button, &iter);
+ gtk_list_store_remove (store, &iter);
+ n_rows--;
+ }
+ while (n_rows);
+}
+
+/* Filter Model */
+static inline gboolean
+test_if_path_is_visible (GtkFileSystem *fs,
+ const GtkFilePath *path,
+ gboolean local_only,
+ gboolean is_folder)
+{
+ if (!path)
+ return FALSE;
+
+ if (local_only && !gtk_file_system_path_is_local (fs, path))
+ return FALSE;
+
+ if (!is_folder)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+filter_model_visible_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
+ GtkFileChooserButtonPrivate *priv = button->priv;
+ gchar type;
+ gpointer data;
+ gboolean local_only, retval, is_folder;
+
+ type = ROW_TYPE_INVALID;
+ data = NULL;
+ local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->dialog));
+
+ gtk_tree_model_get (model, iter,
+ TYPE_COLUMN, &type,
+ DATA_COLUMN, &data,
+ IS_FOLDER_COLUMN, &is_folder,
+ -1);
+
+ switch (type)
+ {
+ case ROW_TYPE_CURRENT_FOLDER:
+ retval = TRUE;
+ break;
+ case ROW_TYPE_SPECIAL:
+ case ROW_TYPE_SHORTCUT:
+ case ROW_TYPE_BOOKMARK:
+ retval = test_if_path_is_visible (priv->fs, data, local_only, is_folder);
+ break;
+ case ROW_TYPE_VOLUME:
+ {
+ retval = TRUE;
+ if (local_only)
+ {
+ if (gtk_file_system_volume_get_is_mounted (priv->fs, data))
+ {
+ GtkFilePath *base_path;
+
+ base_path = gtk_file_system_volume_get_base_path (priv->fs, data);
+ if (base_path)
+ {
+ gboolean is_local = gtk_file_system_path_is_local (priv->fs, base_path);
+
+ gtk_file_path_free (base_path);
+
+ if (!is_local)
+ retval = FALSE;
+ }
+ else
+ retval = FALSE;
+ }
+ }
+ }
+ break;
+ default:
+ retval = TRUE;
+ break;
+ }
+
+ return retval;
+}
+
+/* Combo Box */
+static void
+name_cell_data_func (GtkCellLayout *layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ gchar type;
+
+ type = 0;
+ gtk_tree_model_get (model, iter,
+ TYPE_COLUMN, &type,
+ -1);
+
+ if (type == ROW_TYPE_CURRENT_FOLDER)
+ g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ else
+ g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL);
+}
+
+static gboolean
+combo_box_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ gchar type = ROW_TYPE_INVALID;
+
+ gtk_tree_model_get (model, iter, TYPE_COLUMN, &type, -1);
+
+ return (type == ROW_TYPE_BOOKMARK_SEPARATOR ||
+ type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR ||
+ type == ROW_TYPE_OTHER_SEPARATOR);
+}
+
+static void
+update_combo_box (GtkFileChooserButton *button)
+{
+ GtkFileChooserButtonPrivate *priv = button->priv;
+ GSList *paths;
+ GtkTreeIter iter;
+ gboolean row_found;
+
+ gtk_tree_model_get_iter_first (priv->filter_model, &iter);
+
+ paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
+
+ row_found = FALSE;
+
+ do
+ {
+ gchar type;
+ gpointer data;
+
+ type = ROW_TYPE_INVALID;
+ data = NULL;
+
+ gtk_tree_model_get (priv->filter_model, &iter,
+ TYPE_COLUMN, &type,
+ DATA_COLUMN, &data,
+ -1);
+
+ switch (type)
+ {
+ case ROW_TYPE_SPECIAL:
+ case ROW_TYPE_SHORTCUT:
+ case ROW_TYPE_BOOKMARK:
+ case ROW_TYPE_CURRENT_FOLDER:
+ row_found = (paths &&
+ paths->data &&
+ gtk_file_path_compare (data, paths->data) == 0);
+ break;
+ case ROW_TYPE_VOLUME:
+ {
+ GtkFilePath *base_path;
+
+ base_path = gtk_file_system_volume_get_base_path (priv->fs, data);
+ if (base_path)
+ {
+ row_found = (paths &&
+ paths->data &&
+ gtk_file_path_compare (base_path, paths->data) == 0);
+ gtk_file_path_free (base_path);
+ }
+ }
+ break;
+ default:
+ row_found = FALSE;
+ break;
+ }
+
+ if (row_found)
+ {
+ g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box),
+ &iter);
+ g_signal_handler_unblock (priv->combo_box,
+ priv->combo_box_changed_id);
+ }
+ }
+ while (!row_found && gtk_tree_model_iter_next (priv->filter_model, &iter));
+
+ /* If it hasn't been found already, update & select the current-folder row. */
+ if (!row_found && paths && paths->data)
+ {
+ GtkTreeIter filter_iter;
+ gint pos;
+
+ model_update_current_folder (button, paths->data);
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
+
+ pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
+ gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
+
+ gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
+ &filter_iter, &iter);
+
+ g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box), &filter_iter);
+ g_signal_handler_unblock (priv->combo_box, priv->combo_box_changed_id);
+ }
+
+ gtk_file_paths_free (paths);
+}
+
+/* Button */
+static void
+update_label_get_info_cb (GtkFileSystemHandle *handle,
+ const GtkFileInfo *info,
+ const GError *error,
+ gpointer data)
+{
+ gboolean cancelled = handle->cancelled;
+ GdkPixbuf *pixbuf;
+ GtkFileChooserButton *button = data;
+ GtkFileChooserButtonPrivate *priv = button->priv;
+
+ if (handle != priv->update_button_handle)
+ goto out;
+
+ priv->update_button_handle = NULL;
+
+ if (cancelled || error)
+ goto out;
+
+ gtk_label_set_text (GTK_LABEL (priv->label), gtk_file_info_get_display_name (info));
+
+ pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (priv->image),
+ priv->icon_size, NULL);
+ if (!pixbuf)
+ pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (priv->image)),
+ FALLBACK_ICON_NAME,
+ priv->icon_size, 0, NULL);
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+out:
+ g_object_unref (button);
+ g_object_unref (handle);
+}
+
+static void
+update_label_and_image (GtkFileChooserButton *button)
+{
+ GtkFileChooserButtonPrivate *priv = button->priv;
+ GdkPixbuf *pixbuf;
+ gchar *label_text;
+ GSList *paths;
+
+ paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
+ label_text = NULL;
+ pixbuf = NULL;
+
+ if (paths && paths->data)
+ {
+ GtkFilePath *path;
+ GtkFileSystemVolume *volume = NULL;
+
+ path = paths->data;
+
+ volume = gtk_file_system_get_volume_for_path (priv->fs, path);
+ if (volume)
+ {
+ GtkFilePath *base_path;
+
+ base_path = gtk_file_system_volume_get_base_path (priv->fs, volume);
+ if (base_path && gtk_file_path_compare (base_path, path) == 0)
+ {
+ label_text = gtk_file_system_volume_get_display_name (priv->fs,
+ volume);
+ pixbuf = gtk_file_system_volume_render_icon (priv->fs, volume,
+ GTK_WIDGET (button),
+ priv->icon_size,
+ NULL);
+ }
+
+ if (base_path)
+ gtk_file_path_free (base_path);
+
+ gtk_file_system_volume_free (priv->fs, volume);
+
+ if (label_text)
+ goto out;
+ }
+
+ if (priv->update_button_handle)
+ {
+ gtk_file_system_cancel_operation (priv->update_button_handle);
+ priv->update_button_handle = NULL;
+ }
+
+ if (gtk_file_system_path_is_local (priv->fs, path))
+ {
+ priv->update_button_handle =
+ gtk_file_system_get_info (priv->fs, path,
+ GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_ICON,
+ update_label_get_info_cb,
+ g_object_ref (button));
+ }
+ else
+ {
+ GdkPixbuf *pixbuf;
+
+ label_text = gtk_file_system_get_bookmark_label (button->priv->fs, path);
+
+ pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (priv->image)),
+ "gnome-fs-regular",
+ priv->icon_size, 0, NULL);
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+ }
+ }
+out:
+ gtk_file_paths_free (paths);
+
+ if (label_text)
+ {
+ gtk_label_set_text (GTK_LABEL (priv->label), label_text);
+ g_free (label_text);
+ }
+ else
+ gtk_label_set_text (GTK_LABEL (priv->label), _(FALLBACK_DISPLAY_NAME));
+}
+
+
+/* ************************ *
+ * Child Object Callbacks *
+ * ************************ */
+
+/* File System */
+static void
+fs_volumes_changed_cb (GtkFileSystem *fs,
+ gpointer user_data)
+{
+ GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
+ GtkFileChooserButtonPrivate *priv = button->priv;
+ GSList *volumes;
+
+ model_remove_rows (user_data,
+ model_get_type_position (user_data, ROW_TYPE_VOLUME),
+ priv->n_volumes);
+
+ priv->n_volumes = 0;
+
+ volumes = gtk_file_system_list_volumes (fs);
+ model_add_volumes (user_data, volumes);
+ g_slist_free (volumes);
+
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
+
+ update_label_and_image (user_data);
+ update_combo_box (user_data);
+}
+
+static void
+fs_bookmarks_changed_cb (GtkFileSystem *fs,
+ gpointer user_data)
+{
+ GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
+ GtkFileChooserButtonPrivate *priv = button->priv;
+ GSList *bookmarks;
+
+ bookmarks = gtk_file_system_list_bookmarks (fs);
+ model_remove_rows (user_data,
+ model_get_type_position (user_data,
+ ROW_TYPE_BOOKMARK_SEPARATOR),
+ (priv->n_bookmarks + priv->has_bookmark_separator));
+ priv->has_bookmark_separator = FALSE;
+ priv->n_bookmarks = 0;
+ model_add_bookmarks (user_data, bookmarks);
+ gtk_file_paths_free (bookmarks);
+
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
+
+ update_label_and_image (user_data);
+ update_combo_box (user_data);
+}
+
+/* Dialog */
+static void
+open_dialog (GtkFileChooserButton *button)
+{
+ GtkFileChooserButtonPrivate *priv = button->priv;
+
+ /* Setup the dialog parent to be chooser button's toplevel, and be modal
+ as needed. */
+ if (!GTK_WIDGET_VISIBLE (priv->dialog))
+ {
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
+
+ if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
+ {
+ if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
+ gtk_window_set_transient_for (GTK_WINDOW (priv->dialog),
+ GTK_WINDOW (toplevel));
+
+ gtk_window_set_modal (GTK_WINDOW (priv->dialog),
+ gtk_window_get_modal (GTK_WINDOW (toplevel)));
+ }
+ }
+
+ if (!priv->active)
+ {
+ GSList *paths;
+
+ g_signal_handler_block (priv->dialog,
+ priv->dialog_folder_changed_id);
+ g_signal_handler_block (priv->dialog,
+ priv->dialog_file_activated_id);
+ g_signal_handler_block (priv->dialog,
+ priv->dialog_selection_changed_id);
+ paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
+ if (paths)
+ {
+ if (paths->data)
+ priv->old_path = gtk_file_path_copy (paths->data);
+
+ gtk_file_paths_free (paths);
+ }
+
+ priv->active = TRUE;
+ }
+
+ gtk_widget_set_sensitive (priv->combo_box, FALSE);
+ gtk_window_present (GTK_WINDOW (priv->dialog));
+}
+
+/* Combo Box */
+static void
+combo_box_changed_cb (GtkComboBox *combo_box,
+ gpointer user_data)
+{
+ GtkTreeIter iter;
+
+ if (gtk_combo_box_get_active_iter (combo_box, &iter))
+ {
+ GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
+ GtkFileChooserButtonPrivate *priv = button->priv;
+ gchar type;
+ gpointer data;
+
+ type = ROW_TYPE_INVALID;
+ data = NULL;
+
+ gtk_tree_model_get (priv->filter_model, &iter,
+ TYPE_COLUMN, &type,
+ DATA_COLUMN, &data,
+ -1);
+
+ switch (type)
+ {
+ case ROW_TYPE_SPECIAL:
+ case ROW_TYPE_SHORTCUT:
+ case ROW_TYPE_BOOKMARK:
+ case ROW_TYPE_CURRENT_FOLDER:
+ gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
+ if (data)
+ _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
+ data, NULL);
+ break;
+ case ROW_TYPE_VOLUME:
+ {
+ GtkFilePath *base_path;
+
+ gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
+ base_path = gtk_file_system_volume_get_base_path (priv->fs, data);
+ if (base_path)
+ {
+ _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
+ base_path, NULL);
+ gtk_file_path_free (base_path);
+ }
+ }
+ break;
+ case ROW_TYPE_OTHER:
+ open_dialog (user_data);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/* Button */
+static void
+button_clicked_cb (GtkButton *real_button,
+ gpointer user_data)
+{
+ open_dialog (user_data);
+}
+
+/* Dialog */
+static void
+dialog_current_folder_changed_cb (GtkFileChooser *dialog,
+ gpointer user_data)
+{
+ GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
+ GtkFileChooserButtonPrivate *priv = button->priv;
+
+ priv->folder_has_been_set = TRUE;
+
+ g_signal_emit_by_name (button, "current-folder-changed");
+}
+
+static void
+dialog_file_activated_cb (GtkFileChooser *dialog,
+ gpointer user_data)
+{
+ g_signal_emit_by_name (user_data, "file-activated");
+}
+
+static void
+dialog_selection_changed_cb (GtkFileChooser *dialog,
+ gpointer user_data)
+{
+ update_label_and_image (user_data);
+ update_combo_box (user_data);
+ g_signal_emit_by_name (user_data, "selection-changed");
+}
+
+static void
+dialog_update_preview_cb (GtkFileChooser *dialog,
+ gpointer user_data)
+{
+ g_signal_emit_by_name (user_data, "update-preview");
+}
+
+static void
+dialog_notify_cb (GObject *dialog,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ gpointer iface;
+
+ iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
+ GTK_TYPE_FILE_CHOOSER);
+ if (g_object_interface_find_property (iface, pspec->name))
+ g_object_notify (user_data, pspec->name);
+
+ if (g_ascii_strcasecmp (pspec->name, "local-only") == 0)
+ {
+ GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
+ GtkFileChooserButtonPrivate *priv = button->priv;
+
+ if (priv->has_current_folder)
+ {
+ GtkTreeIter iter;
+ gint pos;
+ gpointer data;
+
+ pos = model_get_type_position (user_data,
+ ROW_TYPE_CURRENT_FOLDER);
+ gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
+
+ data = NULL;
+ gtk_tree_model_get (priv->model, &iter, DATA_COLUMN, &data, -1);
+
+ /* If the path isn't local but we're in local-only mode now, remove
+ * the custom-folder row */
+ if (data &&
+ (!gtk_file_system_path_is_local (priv->fs, data) &&
+ gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->dialog))))
+ {
+ pos--;
+ model_remove_rows (user_data, pos, 2);
+ }
+ }
+
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
+ update_combo_box (user_data);
+ }
+}
+
+static gboolean
+dialog_delete_event_cb (GtkWidget *dialog,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
+
+ return TRUE;
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog,
+ gint response,
+ gpointer user_data)
+{
+ GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
+ GtkFileChooserButtonPrivate *priv = button->priv;