view autoprofile/gtk_widget.c @ 1028:314cfd774bc4

s/purple.guifications.org/plugins.guifications.org/
author Paul Aurich <paul@darkrain42.org>
date Thu, 06 Aug 2009 12:30:12 -0700
parents 9e2f8ef9e772
children 888daeb5b79d
line wrap: on
line source

/*--------------------------------------------------------------------------*
 * AUTOPROFILE                                                              *
 *                                                                          *
 * A Purple away message and profile manager that supports dynamic text       *
 *                                                                          *
 * AutoProfile is the legal property of its developers.  Please refer to    *
 * the COPYRIGHT file distributed with this source distribution.            *
 *                                                                          *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
 *--------------------------------------------------------------------------*/

#include "widget.h"
#include "request.h"
#include "autoprofile.h"

#include "gtkprefs.h"
#include "gtkimhtml.h"

#define AP_RESPONSE_CHOOSE 98125

static GtkWidget *dialog_box = NULL;
static GtkWidget *dialog_box_contents = NULL;
static GtkWidget *dialog_box_preview = NULL;
static struct widget *dialog_box_widget = NULL;
static GStaticMutex preview_mutex = G_STATIC_MUTEX_INIT;

static GtkWidget *component_dialog = NULL;
static GtkWidget *choose_button = NULL;

static GtkWidget *widget_dialog = NULL;
static GtkWidget *delete_button = NULL;
static GtkWidget *rename_button = NULL;
static GtkListStore *tree_list = NULL; 

static GHashTable *pref_names = NULL;

static void component_dialog_show ();

void ap_widget_prefs_updated (struct widget *w) {
  gchar *output;
        
  if (dialog_box_preview == NULL) return;
  if (w != dialog_box_widget) return;

  // TODO: Investigate how laggy this is, possibly add a timeout
  output = w->component->generate (w);
  g_static_mutex_lock (&preview_mutex);
  gtk_imhtml_clear (GTK_IMHTML(dialog_box_preview));
  gtk_imhtml_append_text (GTK_IMHTML(dialog_box_preview), output,
    GTK_IMHTML_NO_SCROLL);
  g_static_mutex_unlock (&preview_mutex);
  free (output);
}

static void update_widget_list (GtkListStore *ls) {
  GtkTreeIter iter;
  GList *widgets, *widgets_start;
  struct widget *w;
  GString *s;

  s = g_string_new ("");

  gtk_list_store_clear (ls);

  widgets_start = widgets = ap_widget_get_widgets ();

  for (widgets = widgets_start; widgets != NULL; widgets = widgets->next) {
    gtk_list_store_append (ls, &iter);
    w = (struct widget *) widgets->data;
    g_string_printf (s, "<b>%s</b>", w->alias);

    gtk_list_store_set (ls, &iter,
      0, s->str,
      1, w,
      -1);
  }
  g_list_free (widgets_start);
  g_string_free (s, TRUE);
}

static void refresh_cb (GtkWidget *widget, gpointer data) {
  struct widget *w;

  w = (struct widget *) data;
  ap_widget_prefs_updated (w);
}

/* Widget configuration menu */
static GtkWidget *get_widget_configuration (struct widget *w) {
  GtkWidget *config, *hbox, *vbox, *sw;
  GtkWidget *label, *button;
  GtkWidget *menu;
  GString *s;
  gchar *output;
  
  config = gtk_vbox_new (FALSE, 0);
 
  /* Title/Description */
  hbox = gtk_hbox_new (FALSE, 8);
  gtk_container_set_border_width (GTK_CONTAINER(hbox), 6);
  gtk_box_pack_start (GTK_BOX(config), hbox, FALSE, FALSE, 0);

  s = g_string_new ("");
  g_string_printf (s, "<b>%s:</b> %s", w->component->name, 
    w->component->description);
  label = gtk_label_new ("");
  gtk_label_set_markup (GTK_LABEL(label), s->str);
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
  g_string_free (s, TRUE); 

  /* Separator */
  gtk_box_pack_start (GTK_BOX (config), gtk_hseparator_new (),
    FALSE, FALSE, 0);
 
  /* Preview window */
  vbox = gtk_vbox_new (FALSE, 6);
  gtk_container_set_border_width (GTK_CONTAINER(vbox), 6);
  gtk_box_pack_start (GTK_BOX(config), vbox, FALSE, FALSE, 0);

  hbox = gtk_hbox_new (FALSE, 8);
  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

  label = gtk_label_new ("");
  gtk_label_set_markup (GTK_LABEL(label), _("<b>Preview</b>"));
  gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);

  button = gtk_button_new_with_label (_("Refresh"));
  gtk_box_pack_end (GTK_BOX(hbox), button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (refresh_cb), w);

  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw),
    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), 
    GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX(vbox), sw, TRUE, TRUE, 0);

  dialog_box_preview = gtk_imhtml_new (NULL, NULL);
  gtk_container_add (GTK_CONTAINER(sw), dialog_box_preview);
  pidgin_setup_imhtml (dialog_box_preview);
  output = w->component->generate (w);
  gtk_imhtml_append_text (GTK_IMHTML(dialog_box_preview),
    output, GTK_IMHTML_NO_SCROLL);
  free (output);
  dialog_box_widget = w;

  /* Separator */
  gtk_box_pack_start (GTK_BOX (config), gtk_hseparator_new (),
    FALSE, FALSE, 0);

  /* Configuration stuff */
  vbox = gtk_vbox_new (FALSE, 8);
  gtk_container_set_border_width (GTK_CONTAINER(vbox), 6);
  gtk_box_pack_start (GTK_BOX(config), vbox, TRUE, TRUE, 0);

  label = gtk_label_new ("");
  gtk_label_set_markup (GTK_LABEL(label), _("<b>Configuration</b>"));
  gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 0);
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);

  if (w->component->pref_menu == NULL ||
      (menu = (w->component->pref_menu) (w)) == NULL) {
    label = gtk_label_new (_("No options available for this component"));
    gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
  } else {
    gtk_box_pack_start (GTK_BOX (vbox), menu, TRUE, TRUE, 0);
  }

  return config;
}

/* Info message */
static GtkWidget *get_info_message () {
  GtkWidget *page;
  GtkWidget *aboutwin;
  GtkWidget *text;

  /* Make the box */
  page = gtk_vbox_new (FALSE, 8);
  gtk_container_set_border_width (GTK_CONTAINER (page), 12);

  /* Window with info */
  aboutwin = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(aboutwin),
    GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(aboutwin), 
    GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX(page), aboutwin, TRUE, TRUE, 0);

  text = gtk_imhtml_new (NULL, NULL);
  gtk_container_add (GTK_CONTAINER(aboutwin), text);
  pidgin_setup_imhtml (text);

  /* Info text */
  gtk_imhtml_append_text (GTK_IMHTML(text),
    _("<b><u>Basic info</u></b><br>"), GTK_IMHTML_NO_SCROLL);

  gtk_imhtml_append_text (GTK_IMHTML(text),
    _("A <b>widget</b> is a little piece/snippet of automatically "
      "generated text.  There are all sorts of widgets; each type has "
      "different content (i.e. a random quote, text from a blog, the "
      "song currently playing, etc).<br><br>"), GTK_IMHTML_NO_SCROLL);

  gtk_imhtml_append_text (GTK_IMHTML(text),
    _("To use a widget, simply drag it from the list on the left and "
      "drop it into a profile or status message.  <i>It's that easy!</i>"
      "<br><br>"), GTK_IMHTML_NO_SCROLL);

  gtk_imhtml_append_text (GTK_IMHTML(text),
    _("<b>To edit your profile:</b> "
      "Use the \"Info/profile\" tab in this window.<br>"), 
    GTK_IMHTML_NO_SCROLL);

  gtk_imhtml_append_text (GTK_IMHTML(text),
    _("<b>To edit your available/away/status message:</b> "
      "Use the regular Purple interface built into the bottom of the buddy "
      "list.<br><br>"), GTK_IMHTML_NO_SCROLL);

  gtk_imhtml_append_text (GTK_IMHTML(text),
    _("<b><u>Advanced Tips</u></b><br>"), GTK_IMHTML_NO_SCROLL);

  gtk_imhtml_append_text (GTK_IMHTML(text),
    _("You can insert a widget into a profile or status by typing its name.  "
      "To do this, just type \"[widget-name]\" wherever you want to "
      "place a widget (names of widgets are listed on the left).  <br><br>"
      "<b>You type:</b> The song I am playing now is [iTunesInfo].<br>"
      "<b>AutoProfile result:</b> The song I am playing now is "
      "The Beatles - Yellow Submarine.<br><br>"), GTK_IMHTML_NO_SCROLL);

  return page;
}

/* Dialog window actions */
static void widget_popup_rename_cb (
  struct widget *w, const char *new_text) {

  GtkTreeIter iter;
  GValue val;
  struct widget *cur_widget;
  GString *s;

  gtk_tree_model_get_iter_first (GTK_TREE_MODEL(tree_list), &iter);  

  while (TRUE) {
    val.g_type = 0;
    gtk_tree_model_get_value (GTK_TREE_MODEL(tree_list), &iter, 1, &val);
    cur_widget = g_value_get_pointer(&val);

    if (cur_widget == w) break;

    if (!gtk_tree_model_iter_next (GTK_TREE_MODEL(tree_list), &iter)) {
      purple_notify_error (NULL, NULL,
        N_("Unable to change name"), 
        N_("The specified widget no longer exists."));
      return;
    }
  }

  if (ap_widget_rename (w, new_text)) {
    s = g_string_new ("");
    g_string_printf (s, "<b>%s</b>", w->alias);
    // Set value in ls
    gtk_list_store_set (tree_list, &iter,
      0, s->str,
      1, w,
      -1);
    g_string_free (s, TRUE);
  } else {
    purple_notify_error (NULL, NULL,
      N_("Unable to change name"), 
      N_("The widget name you have specified is already in use."));
  }
}

static void delete_cb (GtkWidget *button, GtkTreeSelection *sel)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  GValue val;
  struct widget *w;

  gtk_tree_selection_get_selected (sel, &model, &iter);
  val.g_type = 0;
  gtk_tree_model_get_value (model, &iter, 1, &val);
  w = g_value_get_pointer(&val);
  ap_widget_delete (w);
  gtk_list_store_remove (GTK_LIST_STORE(model), &iter);
}

static void rename_cb (GtkWidget *button, GtkTreeSelection *sel)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  GValue val;
  struct widget *w;
        
  gtk_tree_selection_get_selected (sel, &model, &iter);
  val.g_type = 0;
  gtk_tree_model_get_value (model, &iter, 1, &val);
  w = g_value_get_pointer(&val);
	
  purple_request_input(NULL, 
    _("Rename Widget"), NULL,
    _("Enter a new name for this widget."), w->alias, 
    FALSE, FALSE, NULL,
    _("Rename"), G_CALLBACK(widget_popup_rename_cb),
    _("Cancel"), NULL, NULL, NULL, NULL, w);
}

static void add_cb (GtkWidget *button, GtkTreeSelection *sel)
{
  component_dialog_show ();
}
        
void ap_widget_gtk_start () {
  pref_names = g_hash_table_new (g_str_hash, g_str_equal);
}

void ap_widget_gtk_finish () {
  done_with_widget_list ();
  g_hash_table_destroy (pref_names);
  pref_names = NULL;
}

static void widget_sel_cb (GtkTreeSelection *sel, GtkTreeModel *model) {
  GtkTreeIter iter;
  struct widget *w;
  GValue val;

  gtk_widget_destroy (dialog_box_contents);
  
  if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
    gtk_widget_set_sensitive(rename_button, FALSE);
    gtk_widget_set_sensitive(delete_button, FALSE);
    dialog_box_contents = get_info_message ();
    dialog_box_preview = NULL;
    dialog_box_widget = NULL;
  } else {
    gtk_widget_set_sensitive(rename_button, TRUE);
    gtk_widget_set_sensitive(delete_button, TRUE);

    val.g_type = 0;
    gtk_tree_model_get_value (GTK_TREE_MODEL(tree_list), &iter, 1, &val);
    w = g_value_get_pointer(&val);

    dialog_box_contents = get_widget_configuration (w);
  }

  gtk_box_pack_start (GTK_BOX(dialog_box), dialog_box_contents,
    TRUE, TRUE, 0);
  gtk_widget_show_all (dialog_box);
}

GtkWidget *ap_widget_get_config_page ()
{
  GtkTreeSelection *sel;
  GtkWidget *vbox;
  GtkWidget *add_button;
  
  /* Arrange main parts of window */
  dialog_box = gtk_hbox_new (FALSE, 0);
  
  vbox = gtk_vbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX(dialog_box), vbox, FALSE, FALSE, 0);
  
  get_widget_list (vbox, &sel);
  g_signal_connect (G_OBJECT (sel), "changed", G_CALLBACK (widget_sel_cb), 
    NULL);

  add_button = gtk_button_new_with_label (_("New Widget"));
  g_signal_connect (G_OBJECT(add_button), "clicked", 
                    G_CALLBACK(add_cb), sel);
  gtk_box_pack_start (GTK_BOX(vbox), add_button, FALSE, FALSE, 0);
  
  rename_button = gtk_button_new_with_label (_("Rename"));  
  gtk_widget_set_sensitive(rename_button, FALSE);
  g_signal_connect (G_OBJECT(rename_button), "clicked", 
                    G_CALLBACK(rename_cb), sel);
  gtk_box_pack_start (GTK_BOX(vbox), rename_button, FALSE, FALSE, 0); 

  delete_button = gtk_button_new_with_label (_("Delete"));
  gtk_widget_set_sensitive(delete_button, FALSE); 
  g_signal_connect (G_OBJECT(delete_button), "clicked", 
                    G_CALLBACK(delete_cb), sel);
  gtk_box_pack_start (GTK_BOX(vbox), delete_button, FALSE, FALSE, 0); 

  dialog_box_contents = get_info_message ();
  gtk_box_pack_start (GTK_BOX(dialog_box), dialog_box_contents,
    TRUE, TRUE, 0);

  return dialog_box;
}

/* DND */
static void
drag_data_get_cb(GtkWidget *widget, GdkDragContext *ctx,
  GtkSelectionData *data, guint info, guint time,
  gpointer user_data)
{
  GtkListStore *ls = (GtkListStore *) user_data;
        
  if (ls == NULL) return;
  
  if (data->target == gdk_atom_intern ("STRING", FALSE)) {
    GtkTreeRowReference *ref;
    GtkTreePath *source_row;
    GtkTreeIter iter;
    GString *s;
    struct widget *w;
    GValue val = {0};

    ref = g_object_get_data (G_OBJECT(ctx), "gtk-tree-view-source-row");
    source_row = gtk_tree_row_reference_get_path (ref);

    if (source_row == NULL) return;

    gtk_tree_model_get_iter(GTK_TREE_MODEL(ls), &iter, source_row);
    gtk_tree_model_get_value(GTK_TREE_MODEL(ls), &iter,
      1, &val);

    w = g_value_get_pointer (&val);
    s = g_string_new ("");
    g_string_printf (s, "[%s]", w->alias);
    gtk_selection_data_set (data, gdk_atom_intern ("STRING", FALSE),
      8, (guchar *)s->str, strlen(s->str)+1);

    g_string_free (s, TRUE);
    gtk_tree_path_free (source_row);
  } 
}

void done_with_widget_list () {
  if (tree_list) {
    g_object_unref (tree_list);
    tree_list = NULL;
  }

  widget_dialog = NULL;
  delete_button = NULL;
  dialog_box = NULL;
  dialog_box_contents = NULL;
  dialog_box_preview = NULL;
  dialog_box_widget = NULL;
  if (component_dialog != NULL) {
    gtk_widget_destroy (component_dialog);
    component_dialog = NULL;
    choose_button = NULL;
  }
}

GtkWidget *get_widget_list (GtkWidget *box, GtkTreeSelection **sel) 
{
  GtkWidget *sw;
  GtkWidget *event_view;
  GtkCellRenderer *rend;
  GtkTreeViewColumn *col;
  GtkTargetEntry gte [] = {{"STRING", 0, GTK_IMHTML_DRAG_STRING}};
 
  if (tree_list == NULL) {
    tree_list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(tree_list),
      0, GTK_SORT_ASCENDING);
    update_widget_list (tree_list);
    g_object_ref (G_OBJECT(tree_list));
  }
  
  /* List of widgets */
  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), 
    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), 
    GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX(box), sw, TRUE, TRUE, 0);

  event_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_list));
  *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view));

  rend = gtk_cell_renderer_text_new ();
  col = gtk_tree_view_column_new_with_attributes (_("Widget"), rend, 
    "markup", 0, NULL);

  gtk_tree_view_append_column (GTK_TREE_VIEW (event_view), col);
  gtk_tree_view_column_set_sort_column_id (col, 0);
  gtk_container_add (GTK_CONTAINER(sw), event_view);

  /* Drag and Drop */
  gtk_tree_view_enable_model_drag_source(
    GTK_TREE_VIEW(event_view), GDK_BUTTON1_MASK, gte,
    1, GDK_ACTION_COPY);
  g_signal_connect(G_OBJECT(event_view), "drag-data-get",
    G_CALLBACK(drag_data_get_cb), tree_list);

  return event_view;
}

/*********************************************************
  Component selection window
**********************************************************/

static void add_component (struct component *c) {
  struct widget *w;
  GtkTreeIter iter;
  GString *s;

  w = ap_widget_create (c);

  if (w == NULL) return;

  s = g_string_new ("");

  gtk_list_store_append (tree_list, &iter);
  g_string_printf (s, "<b>%s</b>", w->alias);

  gtk_list_store_set (tree_list, &iter,
    0, s->str,
    1, w,
    -1);
  g_string_free (s, TRUE);
}

static void component_row_activate_cb (GtkTreeView *view, GtkTreePath *path,
  GtkTreeViewColumn *column, gpointer null) 
{
  GtkTreeSelection *sel;
  GtkTreeIter iter;
  struct component *c;
  GtkTreeModel *model;

  sel = gtk_tree_view_get_selection (view);

  if (gtk_tree_selection_get_selected (sel, &model, &iter)) {
    gtk_tree_model_get (model, &iter, 1, &c, -1);
    add_component (c);
  }

  gtk_widget_destroy (component_dialog);
  component_dialog = NULL;
  choose_button = NULL; 
}

static void component_response_cb(GtkWidget *d, int response, 
  GtkTreeSelection *sel)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  GValue val;
  struct component *c;

  switch (response) {
    case AP_RESPONSE_CHOOSE:
      gtk_tree_selection_get_selected (sel, &model, &iter);
      val.g_type = 0;
      gtk_tree_model_get_value (model, &iter, 1, &val);
      c = g_value_get_pointer(&val);
      add_component (c);
    case GTK_RESPONSE_CLOSE:
    case GTK_RESPONSE_CANCEL:
    case GTK_RESPONSE_DELETE_EVENT:
      gtk_widget_destroy(d);
      component_dialog = NULL;
      choose_button = NULL;
      break;
  }
}

static void component_sel_cb (GtkTreeSelection *sel, GtkTreeModel *model) {
  GtkTreeIter iter;

  if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
    gtk_widget_set_sensitive(choose_button, FALSE);
  } else {
    gtk_widget_set_sensitive(choose_button, TRUE);
  }
}

static void update_component_list (GtkListStore *ls) {
  GtkTreeIter iter;
  GList *components;
  struct component *c;
  GString *s;
  gchar *name, *description;

  gtk_list_store_clear (ls);

  s = g_string_new ("");

  for (components = ap_component_get_components ();
       components != NULL;
       components = components->next) {
    gtk_list_store_append (ls, &iter);
    c = (struct component *) components->data;

    name = g_markup_escape_text (c->name, -1);
    description = g_markup_escape_text (c->description, -1);

    g_string_printf (s, "<b>%s</b>\n%s", name, description);

    gtk_list_store_set (ls, &iter,
      0, s->str,
      1, c,
      -1);
    free (name);
    free (description);
  }

  g_string_free (s, TRUE);
}

static void component_dialog_show ()
{
  GtkWidget *sw;
  GtkWidget *event_view;
  GtkListStore *ls;
  GtkCellRenderer *rendt;
  GtkTreeViewColumn *col;
  GtkTreeSelection *sel;

  if (component_dialog != NULL) {
    gtk_window_present(GTK_WINDOW(component_dialog));
    return;
  }

  component_dialog = gtk_dialog_new_with_buttons (_("Select a widget type"),
    NULL,
    GTK_DIALOG_NO_SEPARATOR,
    NULL);

  choose_button = gtk_dialog_add_button (GTK_DIALOG(component_dialog),
    _("Create widget"), AP_RESPONSE_CHOOSE);
  gtk_dialog_add_button (GTK_DIALOG(component_dialog),
    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
  gtk_widget_set_sensitive (choose_button, FALSE);

  sw = gtk_scrolled_window_new (NULL,NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), 
    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), 
    GTK_SHADOW_IN);

  gtk_box_pack_start (GTK_BOX(GTK_DIALOG(component_dialog)->vbox), sw, 
    TRUE, TRUE, 0);

  ls = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(ls),
    0, GTK_SORT_ASCENDING);

  update_component_list (ls);

  event_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(ls));

  g_signal_connect(G_OBJECT(event_view), "row-activated",
    G_CALLBACK(component_row_activate_cb), event_view);

  sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view));

  rendt = gtk_cell_renderer_text_new ();
  col = gtk_tree_view_column_new_with_attributes (_("Widget type"),
    rendt,
    "markup", 0,
    NULL);

#if GTK_CHECK_VERSION(2,6,0)
  gtk_tree_view_column_set_expand (col, TRUE);
  g_object_set(rendt, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
#endif
  gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
  gtk_tree_view_column_set_sort_column_id (col, 0);
  g_object_unref (G_OBJECT(ls));
  gtk_container_add (GTK_CONTAINER(sw), event_view);

  g_signal_connect (G_OBJECT (sel), "changed", 
    G_CALLBACK (component_sel_cb), NULL);
  g_signal_connect(G_OBJECT(component_dialog), "response", 
    G_CALLBACK(component_response_cb), sel);
  gtk_window_set_default_size(GTK_WINDOW(component_dialog), 550, 430);
  gtk_widget_show_all(component_dialog);
}

/* Preferences stuff */
static void pref_callback (const char *name, PurplePrefType type,
  gconstpointer val, gpointer data) {
  struct widget *w = (struct widget *) data;
  ap_widget_prefs_updated (w);
}

static const gchar *get_const_pref (struct widget *w, const char *key) {
  gchar *pref, *result;
  // This is here to prevent memory leaks

  pref = ap_prefs_get_pref_name (w, key);
  if (pref_names == NULL) {
    pref_names = g_hash_table_new (g_str_hash, g_str_equal);
  }

  result = g_hash_table_lookup (pref_names, pref);

  if (!result) {
    g_hash_table_insert (pref_names, pref, pref);
    return pref;
  } else {
    free (pref);
    return result;
  }
}

GtkWidget *ap_prefs_checkbox (struct widget *w, const char *title, 
  const char *key, GtkWidget *page) 
{
  GtkWidget *result;
  const gchar *pref;

  pref = get_const_pref (w, key);
  result = pidgin_prefs_checkbox (title, pref, page);
  purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    pref_callback, w);

  return result;
}

GtkWidget *ap_prefs_dropdown_from_list (struct widget *w, GtkWidget *page,
  const gchar *title, PurplePrefType type, const char *key, GList *menuitems) 
{
  GtkWidget *result;
  const gchar *pref;

  pref = get_const_pref (w, key);
  result = pidgin_prefs_dropdown_from_list (
    page, title, type, pref, menuitems);
  purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    pref_callback, w);

  return result;
}

GtkWidget *ap_prefs_labeled_entry (struct widget *w, GtkWidget *page, 
  const gchar *title, const char *key, GtkSizeGroup *sg) {
  GtkWidget *result; 
  const gchar *pref;

  pref = get_const_pref (w, key);
  result = pidgin_prefs_labeled_entry (page, title, pref, sg);
  purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    pref_callback, w);

  return result;
}

GtkWidget *ap_prefs_labeled_spin_button (struct widget *w, 
  GtkWidget *page, const gchar *title, const char *key, int min, 
  int max, GtkSizeGroup *sg) 
{
  GtkWidget *result; 
  const gchar *pref;

  pref = get_const_pref (w, key);
  result = pidgin_prefs_labeled_spin_button (page, title, pref,
    min, max, sg);
  purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    pref_callback, w);

  return result;
}