/*
 * gtkopenwithwidget.c: an open-with widget
 *
 * Copyright (C) 2004 Novell, Inc.
 * Copyright (C) 2007, 2010 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the Gnome Library; see the file COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Dave Camp <dave@novell.com>
 *          Alexander Larsson <alexl@redhat.com>
 *          Cosimo Cecchi <ccecchi@redhat.com>
 */

#include <config.h>

#include "gtkopenwithwidget.h"

#include "gtkintl.h"
#include "gtkmarshalers.h"
#include "gtkopenwith.h"
#include "gtkopenwithprivate.h"

#include <string.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <gio/gio.h>

struct _GtkOpenWithWidgetPrivate {
  GAppInfo *selected_app_info;

  char *content_type;
  GtkOpenWithWidgetShowMode show_mode;

  GtkWidget *program_list;
  GtkWidget *show_more;
  GtkListStore *program_list_store;

  GtkCellRenderer *padding_renderer;

  gboolean show_more_clicked;
};

enum {
  COLUMN_APP_INFO,
  COLUMN_GICON,
  COLUMN_NAME,
  COLUMN_DESC,
  COLUMN_EXEC,
  COLUMN_HEADING,
  COLUMN_HEADING_TEXT,
  COLUMN_RECOMMENDED,
  NUM_COLUMNS
};


enum {
  PROP_CONTENT_TYPE = 1,
  PROP_GFILE,
  PROP_SHOW_MODE,
  N_PROPERTIES
};

enum {
  SIGNAL_APPLICATION_SELECTED,
  SIGNAL_APPLICATION_ACTIVATED,
  N_SIGNALS
};

static guint signals[N_SIGNALS] = { 0, };

static void gtk_open_with_widget_iface_init (GtkOpenWithIface *iface);

G_DEFINE_TYPE_WITH_CODE (GtkOpenWithWidget, gtk_open_with_widget, GTK_TYPE_BOX,
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_OPEN_WITH,
						gtk_open_with_widget_iface_init));

static void
refresh_and_emit_app_selected (GtkOpenWithWidget *self,
			       GtkTreeSelection *selection)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  GAppInfo *info = NULL;
  gboolean should_emit = FALSE;

  if (gtk_tree_selection_get_selected (selection, &model, &iter))
    {
      gtk_tree_model_get (model, &iter,
			  COLUMN_APP_INFO, &info,
			  -1);
    }

  if (info == NULL)
    return;

  if (self->priv->selected_app_info)
    {
      if (!g_app_info_equal (self->priv->selected_app_info, info))
	{
	  should_emit = TRUE;
	  g_object_unref (self->priv->selected_app_info);

	  self->priv->selected_app_info = info;
	}
    }
  else
    {
      should_emit = TRUE;
      self->priv->selected_app_info = info;
    }

  if (should_emit)
    g_signal_emit (self, signals[SIGNAL_APPLICATION_SELECTED], 0,
		   self->priv->selected_app_info);
}

static gboolean
path_is_heading (GtkTreeView *view,
		 GtkTreePath *path)
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  gboolean res;

  model = gtk_tree_view_get_model (view);
  gtk_tree_model_get_iter (model, &iter, path);
  gtk_tree_model_get (model, &iter,
		      COLUMN_HEADING, &res,
		      -1);

  return res;
}

static void
program_list_selection_activated (GtkTreeView *view,
				  GtkTreePath *path,
				  GtkTreeViewColumn *column,
				  gpointer user_data)
{
  GtkOpenWithWidget *self = user_data;
  GtkTreeSelection *selection;

  if (path_is_heading (view, path))
    return;

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->program_list));

  refresh_and_emit_app_selected (self, selection);

  g_signal_emit (self, signals[SIGNAL_APPLICATION_ACTIVATED], 0,
		 self->priv->selected_app_info);
}

static void
item_forget_association_cb (GtkMenuItem *item,
			    gpointer user_data)
{
  GtkOpenWithWidget *self = user_data;
  GtkTreePath *path = NULL;
  GtkTreeIter iter;
  GtkTreeModel *model;
  GAppInfo *info;

  gtk_tree_view_get_cursor (GTK_TREE_VIEW (self->priv->program_list), &path, NULL);
  model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->program_list));

  if (path != NULL)
    {
      gtk_tree_model_get_iter (model, &iter, path);
      gtk_tree_model_get (model, &iter,
			  COLUMN_APP_INFO, &info,
			  -1);

      if (info != NULL && g_app_info_can_remove_supports_type (info))
	g_app_info_remove_supports_type (info, self->priv->content_type, NULL);
    }

  _gtk_open_with_widget_refilter (self);
}

static GtkWidget *
gtk_open_with_widget_build_popup_menu (GtkOpenWithWidget *self)
{
  GtkWidget *menu;
  GtkWidget *item;

  menu = gtk_menu_new ();

  item = gtk_menu_item_new_with_label (_("Forget association"));
  g_signal_connect (item, "activate",
		    G_CALLBACK (item_forget_association_cb), self);
  gtk_widget_show (item);

  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);

  return menu;
}

static gboolean
should_show_menu (GtkOpenWithWidget *self,
		  GdkEventButton *event)
{
  GtkTreeIter iter;
  GtkTreePath *path;
  GtkTreeModel *model;
  gboolean recommended, retval;
  GAppInfo *info;

  gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self->priv->program_list),
				 event->x, event->y,
				 &path, NULL, NULL, NULL);

  model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->program_list));
  gtk_tree_model_get_iter (model, &iter, path);

  gtk_tree_model_get (model, &iter,
		      COLUMN_RECOMMENDED, &recommended,
		      COLUMN_APP_INFO, &info,
		      -1);

  retval = recommended && (info != NULL);

  gtk_tree_path_free (path);

  if (info != NULL)
    g_object_unref (info);

  return retval;
}

static void
do_popup_menu (GtkOpenWithWidget *self,
	       GdkEventButton *event)
{
  GtkWidget *menu;

  if (!should_show_menu (self, event))
    return;

  menu = gtk_open_with_widget_build_popup_menu (self);
  gtk_menu_attach_to_widget (GTK_MENU (menu), self->priv->program_list, NULL);
  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
		  event->button, event->time);
}

static gboolean
program_list_button_press_event_cb (GtkWidget *treeview,
				    GdkEventButton *event,
				    gpointer user_data)
{
  GtkOpenWithWidget *self = user_data;

  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
    do_popup_menu (self, event);

  return FALSE;
}

static gboolean
gtk_open_with_search_equal_func (GtkTreeModel *model,
				 int column,
				 const char *key,
				 GtkTreeIter *iter,
				 gpointer user_data)
{
  char *normalized_key;
  char *name, *normalized_name;
  char *path, *normalized_path;
  char *basename, *normalized_basename;
  gboolean ret;

  if (key != NULL)
    {
      normalized_key = g_utf8_casefold (key, -1);
      g_assert (normalized_key != NULL);

      ret = TRUE;

      gtk_tree_model_get (model, iter,
			  COLUMN_NAME, &name,
			  COLUMN_EXEC, &path,
			  -1);

      if (name != NULL)
	{
	  normalized_name = g_utf8_casefold (name, -1);
	  g_assert (normalized_name != NULL);

	  if (strncmp (normalized_name, normalized_key, strlen (normalized_key)) == 0) {
	    ret = FALSE;
	  }

	  g_free (normalized_name);
	}

      if (ret && path != NULL)
	{
	  normalized_path = g_utf8_casefold (path, -1);
	  g_assert (normalized_path != NULL);

	  basename = g_path_get_basename (path);
	  g_assert (basename != NULL);

	  normalized_basename = g_utf8_casefold (basename, -1);
	  g_assert (normalized_basename != NULL);

	  if (strncmp (normalized_path, normalized_key, strlen (normalized_key)) == 0 ||
	      strncmp (normalized_basename, normalized_key, strlen (normalized_key)) == 0) {
	    ret = FALSE;
	  }

	  g_free (basename);
	  g_free (normalized_basename);
	  g_free (normalized_path);
	}

      g_free (name);
      g_free (path);
      g_free (normalized_key);

      return ret;
    }
  else
    {
      return TRUE;
    }
}

static gint
gtk_open_with_sort_func (GtkTreeModel *model,
			 GtkTreeIter *a,
			 GtkTreeIter *b,
			 gpointer user_data)
{
  gboolean a_recommended, b_recommended;
  gboolean a_heading, b_heading;
  gchar *a_name, *b_name, *a_casefold, *b_casefold;
  gint retval;

  /* this returns:
   * - <0 if a should show before b
   * - =0 if a is the same as b
   * - >0 if a should show after b
   */

  gtk_tree_model_get (model, a,
		      COLUMN_NAME, &a_name,
		      COLUMN_RECOMMENDED, &a_recommended,
		      COLUMN_HEADING, &a_heading,
		      -1);

  gtk_tree_model_get (model, b,
		      COLUMN_NAME, &b_name,
		      COLUMN_RECOMMENDED, &b_recommended,
		      COLUMN_HEADING, &b_heading,
		      -1);

  /* the recommended one always wins */
  if (a_recommended && !b_recommended)
    {
      retval = -1;
      goto out;
    }

  if (b_recommended && !a_recommended)
    {
      retval = 1;
      goto out;
    }

  /* they're both recommended or not, so if one is a heading, wins */
  if (a_heading)
    {
      return -1;
      goto out;
    }

  if (b_heading)
    {
      return 1;
      goto out;
    }

  a_casefold = a_name != NULL ?
    g_utf8_casefold (a_name, -1) : NULL;
  b_casefold = b_name != NULL ?
    g_utf8_casefold (b_name, -1) : NULL;

  retval = g_strcmp0 (a_casefold, b_casefold);

  g_free (a_casefold);
  g_free (b_casefold);

 out:
  g_free (a_name);
  g_free (b_name);

  return retval;
}

static void
heading_cell_renderer_func (GtkTreeViewColumn *column,
			    GtkCellRenderer *cell,
			    GtkTreeModel *model,
			    GtkTreeIter *iter,
			    gpointer _user_data)
{
  gboolean heading;

  gtk_tree_model_get (model, iter,
		      COLUMN_HEADING, &heading,
		      -1);

  g_object_set  (cell,
		 "visible", heading,
		 NULL);
}

static void
padding_cell_renderer_func (GtkTreeViewColumn *column,
			    GtkCellRenderer *cell,
			    GtkTreeModel *model,
			    GtkTreeIter *iter,
			    gpointer user_data)
{
  gboolean heading;

  gtk_tree_model_get (model, iter,
		      COLUMN_HEADING, &heading,
		      -1);
  if (heading)
    g_object_set (cell,
		  "visible", FALSE,
		  "xpad", 0,
		  "ypad", 0,
		  NULL);
  else
    g_object_set (cell,
		  "visible", TRUE,
		  "xpad", 3,
		  "ypad", 3,
		  NULL);
}

static gboolean
gtk_open_with_selection_func (GtkTreeSelection *selection,
			      GtkTreeModel *model,
			      GtkTreePath *path,
			      gboolean path_currently_selected,
			      gpointer user_data)
{
  GtkTreeIter iter;
  gboolean heading;

  gtk_tree_model_get_iter (model, &iter, path);
  gtk_tree_model_get (model, &iter,
		      COLUMN_HEADING, &heading,
		      -1);

  return !heading;
}

static gint
compare_apps_func (gconstpointer a,
		   gconstpointer b)
{
  return !g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b));
}

static void
gtk_open_with_widget_real_add_items (GtkOpenWithWidget *self)
{
  GList *all_applications = NULL, *content_type_apps = NULL, *l;
  gchar *app_string;
  GIcon *icon;
  gboolean heading_added, unref_icon;
  gboolean show_recommended, show_headings, show_all;

  if (self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_RECOMMENDED)
    {
      show_all = FALSE;
      show_headings = FALSE;
      show_recommended = TRUE;
    }
  else if (self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_ALL)
    {
      show_all = TRUE;
      show_headings = FALSE;
      show_recommended = FALSE;
    }
  else
    {
      show_all = self->priv->show_more_clicked;
      show_headings = TRUE;
      show_recommended = TRUE;
    }

  if (show_recommended)
    content_type_apps = g_app_info_get_all_for_type (self->priv->content_type);

  if (show_all)
    all_applications = g_app_info_get_all ();

  heading_added = FALSE;
  
  for (l = content_type_apps; l != NULL; l = l->next)
    {
      GAppInfo *app = l->data;
      GtkTreeIter iter;

      if (!g_app_info_supports_uris (app) &&
	  !g_app_info_supports_files (app))
	continue;

      if (!heading_added && show_headings)
	{
	  gtk_list_store_append (self->priv->program_list_store, &iter);
	  gtk_list_store_set (self->priv->program_list_store, &iter,
			      COLUMN_HEADING_TEXT, _("Recommended Applications"),
			      COLUMN_HEADING, TRUE,
			      COLUMN_RECOMMENDED, TRUE,
			      -1);

	  heading_added = TRUE;
	}

      app_string = g_strdup_printf ("<b>%s</b>\n<i>%s</i>",
				    g_app_info_get_display_name (app) != NULL ?
				    g_app_info_get_display_name (app) : "",
				    g_app_info_get_description (app) != NULL ?
				    g_app_info_get_description (app) : "");

      icon = g_app_info_get_icon (app);
      if (icon == NULL)
	{
	  icon = g_themed_icon_new ("application-x-executable");
	  unref_icon = TRUE;
	}

      gtk_list_store_append (self->priv->program_list_store, &iter);
      gtk_list_store_set (self->priv->program_list_store, &iter,
			  COLUMN_APP_INFO, app,
			  COLUMN_GICON, icon,
			  COLUMN_NAME, g_app_info_get_display_name (app),
			  COLUMN_DESC, app_string,
			  COLUMN_EXEC, g_app_info_get_executable (app),
			  COLUMN_HEADING, FALSE,
			  COLUMN_RECOMMENDED, TRUE,
			  -1);

      g_free (app_string);
      if (unref_icon)
	g_object_unref (icon);

      unref_icon = FALSE;
    }

  heading_added = FALSE;

  for (l = all_applications; l != NULL && show_all; l = l->next)
    {
      GAppInfo *app = l->data;
      GtkTreeIter iter;

      if (!g_app_info_supports_uris (app) &&
	  !g_app_info_supports_files (app))
	continue;

      if (content_type_apps != NULL &&
	  g_list_find_custom (content_type_apps, app,
			      (GCompareFunc) compare_apps_func))
	continue;

      if (!heading_added && show_headings)
	{
	  gtk_list_store_append (self->priv->program_list_store, &iter);
	  gtk_list_store_set (self->priv->program_list_store, &iter,
			      COLUMN_HEADING_TEXT, _("Other Applications"),
			      COLUMN_HEADING, TRUE,
			      COLUMN_RECOMMENDED, FALSE,
			      -1);

	  heading_added = TRUE;
	}

      app_string = g_strdup_printf ("<b>%s</b>\n<i>%s</i>",
				    g_app_info_get_display_name (app) != NULL ?
				    g_app_info_get_display_name (app) : "",
				    g_app_info_get_description (app) != NULL ?
				    g_app_info_get_description (app) : "");

      icon = g_app_info_get_icon (app);
      if (icon == NULL)
	{
	  icon = g_themed_icon_new ("application-x-executable");
	  unref_icon = TRUE;
	}

      gtk_list_store_append (self->priv->program_list_store, &iter);
      gtk_list_store_set (self->priv->program_list_store, &iter,
			  COLUMN_APP_INFO, app,
			  COLUMN_GICON, icon,
			  COLUMN_NAME, g_app_info_get_display_name (app),
			  COLUMN_DESC, app_string,
			  COLUMN_EXEC, g_app_info_get_executable (app),
			  COLUMN_HEADING, FALSE,
			  COLUMN_RECOMMENDED, FALSE,
			  -1);

      g_free (app_string);
      if (unref_icon)
	g_object_unref (icon);

      unref_icon = FALSE;
    }

  if (content_type_apps != NULL)
    g_list_free_full (content_type_apps, g_object_unref);

  if (all_applications != NULL)
    g_list_free_full (all_applications, g_object_unref);
}

static void
gtk_open_with_widget_add_items (GtkOpenWithWidget *self)
{
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkTreeModel *sort;

  /* create list store */
  self->priv->program_list_store = gtk_list_store_new (NUM_COLUMNS,
						       G_TYPE_APP_INFO,
						       G_TYPE_ICON,
						       G_TYPE_STRING,
						       G_TYPE_STRING,
						       G_TYPE_STRING,
						       G_TYPE_BOOLEAN,
						       G_TYPE_STRING,
						       G_TYPE_BOOLEAN);
  sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (self->priv->program_list_store));

  /* populate the widget */
  gtk_open_with_widget_real_add_items (self);

  gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->program_list), 
			   GTK_TREE_MODEL (sort));
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort),
					COLUMN_NAME,
					GTK_SORT_ASCENDING);
  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort),
				   COLUMN_NAME,
				   gtk_open_with_sort_func,
				   self, NULL);
  gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (self->priv->program_list),
  				       gtk_open_with_search_equal_func,
  				       NULL, NULL);

  column = gtk_tree_view_column_new ();

  /* initial padding */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  g_object_set (renderer,
		"xpad", (self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_HEADINGS) ? 6 : 0,
		NULL);
  self->priv->padding_renderer = renderer;

  /* heading text renderer */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_attributes (column, renderer,
				       "text", COLUMN_HEADING_TEXT,
				       NULL);
  g_object_set (renderer,
		"weight", PANGO_WEIGHT_BOLD,
		"weight-set", TRUE,
		"ypad", 6,
		"xpad", 0,
		NULL);
  gtk_tree_view_column_set_cell_data_func (column, renderer,
					   heading_cell_renderer_func,
					   NULL, NULL);

  /* padding renderer for non-heading cells */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_cell_data_func (column, renderer,
					   padding_cell_renderer_func,
					   NULL, NULL);

  /* app icon renderer */
  renderer = gtk_cell_renderer_pixbuf_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_attributes (column, renderer,
				       "gicon", COLUMN_GICON,
				       NULL);
  g_object_set (renderer,
		"stock-size", GTK_ICON_SIZE_DIALOG,
		NULL);

  /* app name renderer */
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, TRUE);
  gtk_tree_view_column_set_attributes (column, renderer,
				       "markup", COLUMN_DESC,
				       NULL);
  g_object_set (renderer,
		"ellipsize", PANGO_ELLIPSIZE_END,
		"ellipsize-set", TRUE,
		NULL);
  
  gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
  gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->program_list), column);
}

static void
gtk_open_with_widget_set_property (GObject *object,
				   guint property_id,
				   const GValue *value,
				   GParamSpec *pspec)
{
  GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);

  switch (property_id)
    {
    case PROP_CONTENT_TYPE:
      self->priv->content_type = g_value_dup_string (value);
      break;
    case PROP_SHOW_MODE:
      gtk_open_with_widget_set_show_mode (self,
					  g_value_get_enum (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_open_with_widget_get_property (GObject *object,
				   guint property_id,
				   GValue *value,
				   GParamSpec *pspec)
{
  GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);

  switch (property_id)
    {
    case PROP_CONTENT_TYPE:
      g_value_set_string (value, self->priv->content_type);
      break;
    case PROP_SHOW_MODE:
      g_value_set_enum (value, self->priv->show_mode);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
show_more_button_clicked_cb (GtkButton *button,
			     gpointer user_data)
{
  GtkOpenWithWidget *self = user_data;

  self->priv->show_more_clicked = TRUE;
  gtk_widget_hide (GTK_WIDGET (button));

  _gtk_open_with_widget_refilter (self);
}

static void
gtk_open_with_widget_ensure_show_more_button (GtkOpenWithWidget *self)
{
  if (self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_HEADINGS)
    {
      if (!self->priv->show_more_clicked)
	gtk_widget_show (self->priv->show_more);
    }
  else
    {
      gtk_widget_hide (self->priv->show_more);
    }
}

static void
gtk_open_with_widget_constructed (GObject *object)
{
  GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);

  g_assert (self->priv->content_type != NULL);

  if (G_OBJECT_CLASS (gtk_open_with_widget_parent_class)->constructed != NULL)
    G_OBJECT_CLASS (gtk_open_with_widget_parent_class)->constructed (object);

  gtk_open_with_widget_ensure_show_more_button (self);
  gtk_open_with_widget_add_items (self);
}

static void
gtk_open_with_widget_finalize (GObject *object)
{
  GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);

  g_free (self->priv->content_type);

  G_OBJECT_CLASS (gtk_open_with_widget_parent_class)->finalize (object);
}

static void
gtk_open_with_widget_dispose (GObject *object)
{
  GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);

  if (self->priv->selected_app_info != NULL)
    {
      g_object_unref (self->priv->selected_app_info);
      self->priv->selected_app_info = NULL;
    }

  G_OBJECT_CLASS (gtk_open_with_widget_parent_class)->dispose (object);
}

static void
gtk_open_with_widget_class_init (GtkOpenWithWidgetClass *klass)
{
  GObjectClass *gobject_class;
  GParamSpec *pspec;

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->dispose = gtk_open_with_widget_dispose;
  gobject_class->finalize = gtk_open_with_widget_finalize;
  gobject_class->set_property = gtk_open_with_widget_set_property;
  gobject_class->get_property = gtk_open_with_widget_get_property;
  gobject_class->constructed = gtk_open_with_widget_constructed;

  g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");

  /**
   * GtkOpenWithWidget::show-mode:
   *
   * The #GtkOpenWithWidgetShowMode for this widget.
   **/
  pspec =
    g_param_spec_enum ("show-mode",
		       P_("The widget show mode"),
		       P_("The show mode for this widget"),
		       GTK_TYPE_OPEN_WITH_WIDGET_SHOW_MODE,
		       GTK_OPEN_WITH_WIDGET_SHOW_MODE_HEADINGS,
		       G_PARAM_CONSTRUCT | G_PARAM_READWRITE |
		       G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (gobject_class, PROP_SHOW_MODE, pspec);

  signals[SIGNAL_APPLICATION_SELECTED] =
    g_signal_new ("application-selected",
		  GTK_TYPE_OPEN_WITH_WIDGET,
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GtkOpenWithWidgetClass, application_selected),
		  NULL, NULL,
		  _gtk_marshal_VOID__OBJECT,
		  G_TYPE_NONE,
		  1, G_TYPE_APP_INFO);

  signals[SIGNAL_APPLICATION_ACTIVATED] =
    g_signal_new ("application-activated",
		  GTK_TYPE_OPEN_WITH_WIDGET,
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GtkOpenWithWidgetClass, application_activated),
		  NULL, NULL,
		  _gtk_marshal_VOID__OBJECT,
		  G_TYPE_NONE,
		  1, G_TYPE_APP_INFO);

  g_type_class_add_private (klass, sizeof (GtkOpenWithWidgetPrivate));
}

static void
gtk_open_with_widget_init (GtkOpenWithWidget *self)
{
  GtkWidget *scrolled_window, *button, *w;
  GtkTreeSelection *selection;

  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_OPEN_WITH_WIDGET,
					    GtkOpenWithWidgetPrivate);
  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);

  gtk_container_set_border_width (GTK_CONTAINER (self), 5);

  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_set_size_request (scrolled_window, 400, 300);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
				       GTK_SHADOW_IN);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
				  GTK_POLICY_NEVER,
				  GTK_POLICY_AUTOMATIC);
  gtk_widget_show (scrolled_window);

  self->priv->program_list = gtk_tree_view_new ();
  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->priv->program_list),
				     FALSE);
  gtk_container_add (GTK_CONTAINER (scrolled_window), self->priv->program_list);
  gtk_box_pack_start (GTK_BOX (self), scrolled_window, TRUE, TRUE, 0);
  gtk_widget_show (self->priv->program_list);

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->program_list));
  gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
  gtk_tree_selection_set_select_function (selection, gtk_open_with_selection_func,
					  self, NULL);
  g_signal_connect_swapped (selection, "changed",
			    G_CALLBACK (refresh_and_emit_app_selected),
			    self);
  g_signal_connect (self->priv->program_list, "row-activated",
		    G_CALLBACK (program_list_selection_activated),
		    self);
  g_signal_connect (self->priv->program_list, "button-press-event",
		    G_CALLBACK (program_list_button_press_event_cb),
		    self);

  button = gtk_button_new_with_label (_("Show more applications..."));
  w = gtk_image_new_from_stock (GTK_STOCK_ADD,
				GTK_ICON_SIZE_BUTTON);

  gtk_button_set_image (GTK_BUTTON (button), w);
  gtk_box_pack_start (GTK_BOX (self), button, TRUE, TRUE, 6);

  g_signal_connect (button, "clicked",
		    G_CALLBACK (show_more_button_clicked_cb), self);

  self->priv->show_more = button;
}

static GAppInfo *
gtk_open_with_widget_get_app_info (GtkOpenWith *object)
{
  GtkOpenWithWidget *self = GTK_OPEN_WITH_WIDGET (object);

  if (self->priv->selected_app_info == NULL)
    return NULL;

  return g_object_ref (self->priv->selected_app_info);
}

static void
gtk_open_with_widget_iface_init (GtkOpenWithIface *iface)
{
  iface->get_app_info = gtk_open_with_widget_get_app_info;
}

void
_gtk_open_with_widget_refilter (GtkOpenWithWidget *self)
{

  gtk_open_with_widget_ensure_show_more_button (self);

  if (self->priv->program_list_store != NULL)
    {
      gtk_list_store_clear (self->priv->program_list_store);

      /* don't add additional xpad if we don't have headings */
      g_object_set (self->priv->padding_renderer,
		    "visible", self->priv->show_mode == GTK_OPEN_WITH_WIDGET_SHOW_MODE_HEADINGS,
		    NULL);

      gtk_open_with_widget_real_add_items (self);
    }
}

GtkWidget *
gtk_open_with_widget_new (const gchar *content_type)
{
  return g_object_new (GTK_TYPE_OPEN_WITH_WIDGET,
		       "content-type", content_type,
		       NULL);
}

/**
 * gtk_open_with_widget_set_show_mode:
 * @self: a #GtkOpenWithWidget
 * @show_mode: the new show mode for this widget
 *
 * Sets the mode for the widget to show the list of applications.
 * See #GtkOpenWithWidgetShowMode for more details.
 *
 * Since: 3.0
 **/
void
gtk_open_with_widget_set_show_mode (GtkOpenWithWidget *self,
				    GtkOpenWithWidgetShowMode show_mode)
{
  g_return_if_fail (GTK_IS_OPEN_WITH_WIDGET (self));

  if (self->priv->show_mode != show_mode)
    {
      self->priv->show_mode = show_mode;
      g_object_notify (G_OBJECT (self), "show-mode");

      self->priv->show_more_clicked = FALSE;
      _gtk_open_with_widget_refilter (self);
    }
}

/**
 * gtk_open_with_widget_get_show_mode:
 * @self: a #GtkOpenWithWidget
 *
 * Returns the current mode for the widget to show the list of applications.
 * See #GtkOpenWithWidgetShowMode for mode details.
 *
 * Returns: a #GtkOpenWithWidgetShowMode
 *
 * Since: 3.0
 **/
GtkOpenWithWidgetShowMode
gtk_open_with_widget_get_show_mode (GtkOpenWithWidget *self)
{
  g_return_val_if_fail (GTK_IS_OPEN_WITH_WIDGET (self), FALSE);

  return self->priv->show_mode;
}