thunar-usagebars/thunar/thunar-chooser-button.c

597 lines
20 KiB
C
Raw Permalink Normal View History

/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
* Copyright (c) 2010 Nick Schermer <nick@xfce.org>
* Copyright (c) 2009-2011 Jannis Pohlmann <jannis@xfce.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "thunar/thunar-chooser-button.h"
#include "thunar/thunar-chooser-dialog.h"
#include "thunar/thunar-dialogs.h"
#include "thunar/thunar-gobject-extensions.h"
#include "thunar/thunar-gtk-extensions.h"
#include "thunar/thunar-icon-factory.h"
#include "thunar/thunar-pango-extensions.h"
#include "thunar/thunar-private.h"
/* Property identifiers */
enum
{
PROP_0,
PROP_FILE,
};
enum
{
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_NAME,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_ICON,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_APPLICATION,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_SENSITIVE,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_STYLE,
THUNAR_CHOOSER_BUTTON_STORE_N_COLUMNS
};
static void
thunar_chooser_button_finalize (GObject *object);
static void
thunar_chooser_button_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void
thunar_chooser_button_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static gboolean
thunar_chooser_button_scroll_event (GtkWidget *widget,
GdkEventScroll *event);
static void
thunar_chooser_button_changed (GtkComboBox *combo_box);
static void
thunar_chooser_button_popup (ThunarChooserButton *chooser_button);
static gint
thunar_chooser_button_sort_applications (gconstpointer a,
gconstpointer b);
static gboolean
thunar_chooser_button_row_separator (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data);
static void
thunar_chooser_button_chooser_dialog (ThunarChooserButton *chooser_button);
static void
thunar_chooser_button_file_changed (ThunarChooserButton *chooser_button,
ThunarFile *file);
struct _ThunarChooserButtonClass
{
GtkComboBoxClass __parent__;
};
struct _ThunarChooserButton
{
GtkComboBox __parent__;
GtkListStore *store;
ThunarFile *file;
gboolean has_default_application;
};
G_DEFINE_TYPE (ThunarChooserButton, thunar_chooser_button, GTK_TYPE_COMBO_BOX)
static void
thunar_chooser_button_class_init (ThunarChooserButtonClass *klass)
{
GObjectClass *gobject_class;
GtkWidgetClass *gtkwidget_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = thunar_chooser_button_finalize;
gobject_class->get_property = thunar_chooser_button_get_property;
gobject_class->set_property = thunar_chooser_button_set_property;
gtkwidget_class = GTK_WIDGET_CLASS (klass);
gtkwidget_class->scroll_event = thunar_chooser_button_scroll_event;
/**
* ThunarChooserButton:file:
*
* The #ThunarFile for which a preferred application should
* be chosen.
**/
g_object_class_install_property (gobject_class,
PROP_FILE,
g_param_spec_object ("file", "file", "file",
THUNAR_TYPE_FILE,
EXO_PARAM_READWRITE));
}
static void
thunar_chooser_button_init (ThunarChooserButton *chooser_button)
{
GtkCellRenderer *renderer;
/* allocate a new store for the combo box */
chooser_button->store = gtk_list_store_new (THUNAR_CHOOSER_BUTTON_STORE_N_COLUMNS,
G_TYPE_STRING,
G_TYPE_ICON,
G_TYPE_OBJECT,
G_TYPE_BOOLEAN,
PANGO_TYPE_STYLE);
gtk_combo_box_set_model (GTK_COMBO_BOX (chooser_button),
GTK_TREE_MODEL (chooser_button->store));
g_signal_connect (chooser_button, "changed",
G_CALLBACK (thunar_chooser_button_changed), NULL);
g_signal_connect (chooser_button, "popup",
G_CALLBACK (thunar_chooser_button_popup), NULL);
/* set separator function */
gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser_button),
thunar_chooser_button_row_separator,
NULL, NULL);
/* add renderer for the application icon */
renderer = gtk_cell_renderer_pixbuf_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser_button), renderer, FALSE);
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser_button), renderer,
"gicon",
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_ICON,
"sensitive",
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_SENSITIVE,
NULL);
/* add renderer for the application name */
renderer = gtk_cell_renderer_text_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser_button), renderer, FALSE);
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser_button), renderer,
"text",
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_NAME,
"sensitive",
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_SENSITIVE,
"style",
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_STYLE,
NULL);
}
static void
thunar_chooser_button_finalize (GObject *object)
{
ThunarChooserButton *chooser_button = THUNAR_CHOOSER_BUTTON (object);
/* reset the "file" property */
thunar_chooser_button_set_file (chooser_button, NULL);
/* release the store */
g_object_unref (G_OBJECT (chooser_button->store));
(*G_OBJECT_CLASS (thunar_chooser_button_parent_class)->finalize) (object);
}
static void
thunar_chooser_button_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ThunarChooserButton *chooser_button = THUNAR_CHOOSER_BUTTON (object);
switch (prop_id)
{
case PROP_FILE:
g_value_set_object (value, chooser_button->file);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
thunar_chooser_button_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ThunarChooserButton *chooser_button = THUNAR_CHOOSER_BUTTON (object);
switch (prop_id)
{
case PROP_FILE:
thunar_chooser_button_set_file (chooser_button, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
thunar_chooser_button_scroll_event (GtkWidget *widget,
GdkEventScroll *event)
{
ThunarChooserButton *chooser_button = THUNAR_CHOOSER_BUTTON (widget);
GtkTreeIter iter;
GObject *application;
GtkTreeModel *model = GTK_TREE_MODEL (chooser_button->store);
g_return_val_if_fail (THUNAR_IS_CHOOSER_BUTTON (chooser_button), FALSE);
/* check if the next application in the store is valid if we scroll down,
* else drop the event so we don't popup the chooser dailog */
if (event->direction != GDK_SCROLL_UP
&& gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter)
&& gtk_tree_model_iter_next (model, &iter))
{
gtk_tree_model_get (model, &iter,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_APPLICATION,
&application, -1);
if (application == NULL)
return FALSE;
g_object_unref (G_OBJECT (application));
}
return (*GTK_WIDGET_CLASS (thunar_chooser_button_parent_class)->scroll_event) (widget, event);
}
static void
thunar_chooser_button_changed (GtkComboBox *combo_box)
{
ThunarChooserButton *chooser_button = THUNAR_CHOOSER_BUTTON (combo_box);
GtkTreeIter iter;
const gchar *content_type;
GAppInfo *app_info;
GError *error = NULL;
_thunar_return_if_fail (THUNAR_IS_CHOOSER_BUTTON (chooser_button));
_thunar_return_if_fail (GTK_IS_LIST_STORE (chooser_button->store));
/* verify that we still have a valid file */
if (G_UNLIKELY (chooser_button->file == NULL))
return;
/* get the selected item in the combo box */
if (!gtk_combo_box_get_active_iter (combo_box, &iter))
return;
/* determine the application that was set for the item */
gtk_tree_model_get (GTK_TREE_MODEL (chooser_button->store), &iter,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_APPLICATION,
&app_info, -1);
if (G_LIKELY (app_info != NULL))
{
/* determine the mime info for the file */
content_type = thunar_file_get_content_type (chooser_button->file);
/* try to set application as default for these kind of file */
if (!g_app_info_set_as_default_for_type (app_info, content_type, &error))
{
/* tell the user that it didn't work */
if (g_strcmp0 (thunar_file_get_display_name (chooser_button->file), thunar_file_get_basename (chooser_button->file)) != 0)
thunar_dialogs_show_error (GTK_WIDGET (chooser_button), error,
_("Failed to set default application for \"%s\" (%s)"),
thunar_file_get_display_name (chooser_button->file),
thunar_file_get_basename (chooser_button->file));
else
thunar_dialogs_show_error (GTK_WIDGET (chooser_button), error,
_("Failed to set default application for \"%s\""),
thunar_file_get_display_name (chooser_button->file));
g_error_free (error);
}
else
{
/* emit "changed" on the file, so everybody updates its state */
thunar_file_changed (chooser_button->file);
}
/* release the application */
g_object_unref (app_info);
}
else
{
/* no application was found in the store, looks like the other... option */
thunar_chooser_button_chooser_dialog (chooser_button);
}
}
static void
thunar_chooser_button_popup (ThunarChooserButton *chooser_button)
{
_thunar_return_if_fail (THUNAR_IS_CHOOSER_BUTTON (chooser_button));
if (!chooser_button->has_default_application)
{
/* don't show the menu */
gtk_combo_box_popdown (GTK_COMBO_BOX (chooser_button));
/* open the chooser dialog if the filetype has no default action */
thunar_chooser_button_chooser_dialog (chooser_button);
}
}
static gint
thunar_chooser_button_sort_applications (gconstpointer a,
gconstpointer b)
{
_thunar_return_val_if_fail (G_IS_APP_INFO (a), -1);
_thunar_return_val_if_fail (G_IS_APP_INFO (b), -1);
return g_utf8_collate (g_app_info_get_name (G_APP_INFO (a)),
g_app_info_get_name (G_APP_INFO (b)));
}
static gboolean
thunar_chooser_button_row_separator (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
gchar *name;
/* determine the value of the "name" column */
gtk_tree_model_get (model, iter, THUNAR_CHOOSER_BUTTON_STORE_COLUMN_NAME, &name, -1);
if (G_LIKELY (name != NULL))
{
g_free (name);
return FALSE;
}
return TRUE;
}
static void
thunar_chooser_button_chooser_dialog (ThunarChooserButton *chooser_button)
{
GtkWidget *toplevel;
GtkWidget *dialog;
_thunar_return_if_fail (THUNAR_IS_CHOOSER_BUTTON (chooser_button));
/* determine the toplevel window for the chooser */
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (chooser_button));
if (G_UNLIKELY (toplevel == NULL))
return;
/* popup the application chooser dialog */
dialog = g_object_new (THUNAR_TYPE_CHOOSER_DIALOG, "open", FALSE, NULL);
g_object_bind_property (G_OBJECT (chooser_button), "file",
G_OBJECT (dialog), "file",
G_BINDING_SYNC_CREATE);
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (toplevel));
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT)
thunar_chooser_button_file_changed (chooser_button, chooser_button->file);
gtk_widget_destroy (dialog);
}
static void
thunar_chooser_button_file_changed (ThunarChooserButton *chooser_button,
ThunarFile *file)
{
const gchar *content_type;
GtkTreeIter iter;
GAppInfo *app_info;
GList *app_infos;
GList *lp;
gchar *description;
guint i = 0;
_thunar_return_if_fail (THUNAR_IS_CHOOSER_BUTTON (chooser_button));
_thunar_return_if_fail (chooser_button->file == file);
_thunar_return_if_fail (THUNAR_IS_FILE (file));
/* clear the store */
gtk_list_store_clear (chooser_button->store);
/* reset the default application flag */
chooser_button->has_default_application = FALSE;
/* block the changed signal for a moment */
g_signal_handlers_block_by_func (chooser_button,
thunar_chooser_button_changed,
NULL);
/* determine the content type of the file */
content_type = thunar_file_get_content_type (file);
if (content_type != NULL)
{
/* setup a useful tooltip for the button */
description = g_content_type_get_description (content_type);
thunar_gtk_widget_set_tooltip (GTK_WIDGET (chooser_button),
_("The selected application is used to open "
"this and other files of type \"%s\"."),
description);
g_free (description);
/* determine the default application for that content type */
app_info = g_app_info_get_default_for_type (content_type, FALSE);
if (G_LIKELY (app_info != NULL))
{
/* determine all applications that claim to be able to handle the file */
app_infos = g_app_info_get_all_for_type (content_type);
app_infos = g_list_sort (app_infos, thunar_chooser_button_sort_applications);
/* add all possible applications */
for (lp = app_infos, i = 0; lp != NULL; lp = lp->next, ++i)
{
if (thunar_g_app_info_should_show (lp->data))
{
/* insert the item into the store */
gtk_list_store_insert_with_values (chooser_button->store, &iter, i,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_NAME,
g_app_info_get_name (lp->data),
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_APPLICATION,
lp->data,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_ICON,
g_app_info_get_icon (lp->data),
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_SENSITIVE,
TRUE,
-1);
/* pre-select the default application */
if (g_app_info_equal (lp->data, app_info))
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser_button), &iter);
}
/* release the application */
g_object_unref (lp->data);
}
/* release the application list */
g_list_free (app_infos);
/* release the default application */
g_object_unref (app_info);
/* assume we have some applications in the list */
chooser_button->has_default_application = TRUE;
}
}
if (content_type == NULL || !chooser_button->has_default_application)
{
/* add the "No application selected" item and set as active */
gtk_list_store_insert_with_values (chooser_button->store, &iter, 0,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_NAME,
_("No application selected"),
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_STYLE,
PANGO_STYLE_ITALIC,
-1);
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser_button), &iter);
}
/* insert empty row that will appear as a separator */
gtk_list_store_insert_with_values (chooser_button->store, NULL, ++i, -1);
/* add the "Other Application..." option */
gtk_list_store_insert_with_values (chooser_button->store, NULL, ++i,
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_NAME,
_("Other Application..."),
THUNAR_CHOOSER_BUTTON_STORE_COLUMN_SENSITIVE,
TRUE,
-1);
/* unblock the changed signal */
g_signal_handlers_unblock_by_func (chooser_button,
thunar_chooser_button_changed,
NULL);
}
/**
* thunar_chooser_button_new:
*
* Allocates a new #ThunarChooserButton instance.
*
* Return value: the newly allocated #ThunarChooserButton.
**/
GtkWidget *
thunar_chooser_button_new (void)
{
return g_object_new (THUNAR_TYPE_CHOOSER_BUTTON, NULL);
}
/**
* thunar_chooser_button_set_file:
* @chooser_button : a #ThunarChooserButton instance.
* @file : a #ThunarFile or %NULL.
*
* Associates @chooser_button with the specified @file.
**/
void
thunar_chooser_button_set_file (ThunarChooserButton *chooser_button,
ThunarFile *file)
{
_thunar_return_if_fail (THUNAR_IS_CHOOSER_BUTTON (chooser_button));
_thunar_return_if_fail (file == NULL || THUNAR_IS_FILE (file));
/* check if we already use that file */
if (G_UNLIKELY (chooser_button->file == file))
return;
/* disconnect from the previous file */
if (G_UNLIKELY (chooser_button->file != NULL))
{
g_signal_handlers_disconnect_by_func (G_OBJECT (chooser_button->file), thunar_chooser_button_file_changed, chooser_button);
g_object_unref (G_OBJECT (chooser_button->file));
}
/* activate the new file */
chooser_button->file = file;
/* connect to the new file */
if (G_LIKELY (file != NULL))
{
/* take a reference */
g_object_ref (G_OBJECT (file));
/* stay informed about changes */
g_signal_connect_swapped (G_OBJECT (file), "changed", G_CALLBACK (thunar_chooser_button_file_changed), chooser_button);
/* update our state now */
thunar_chooser_button_file_changed (chooser_button, file);
}
/* notify listeners */
g_object_notify (G_OBJECT (chooser_button), "file");
}